Back to OScope
oscope/public/js/trackers.js
Code
61 lines (58 loc) · 2.82 KB
// Tracked wallets: names you give to addresses. Their trades arrive live on any page.
import { store, emitter, toast, h, sol, usd, short } from './util.js';
import { live, net } from './net.js';
export const trackers = Object.assign(emitter(), {
list: store.get('trackers', []), // [{ w, name, emoji, alerts, added }]
feed: [], // newest first, this session
byW() { return new Map(this.list.map(t => [t.w, t])); },
save() { store.set('trackers', this.list); net.track(this.list.map(t => t.w)); this.emit('change'); },
add(w, name, emoji = '') {
w = w.trim();
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(w)) { toast('That is not a Solana address', 'warn'); return false; }
if (this.list.some(t => t.w === w)) { toast('Already tracked', 'info'); return false; }
this.list.unshift({ w, name: (name || '').trim().slice(0, 32) || short(w), emoji: emoji.slice(0, 4), alerts: true, added: Date.now() });
this.save();
return true;
},
remove(w) { this.list = this.list.filter(t => t.w !== w); this.save(); },
update(w, patch) { const t = this.list.find(x => x.w === w); if (t) Object.assign(t, patch); this.save(); },
name(w) { return this.list.find(t => t.w === w); },
importJson(text) {
let arr;
try { arr = JSON.parse(text); } catch { toast('Not valid JSON', 'warn'); return 0; }
if (!Array.isArray(arr)) arr = Object.entries(arr).map(([w, v]) => ({ w, name: typeof v === 'string' ? v : v?.name }));
let n = 0;
for (const x of arr) {
const w = typeof x === 'string' ? x : x?.w || x?.wallet || x?.address || x?.trackedWalletAddress;
const name = typeof x === 'object' ? x?.name || x?.label : '';
if (w && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(w) && !this.list.some(t => t.w === w)) {
this.list.push({ w, name: (name || short(w)).slice(0, 32), emoji: x?.emoji || '', alerts: true, added: Date.now() }); n++;
}
}
this.save();
return n;
},
});
live.on('wt', m => {
const by = trackers.byW();
const alertsOn = store.get('settings', {}).trackerToasts !== false;
for (const it of m.list) {
const t = by.get(it.w);
if (!t) continue;
it.who = t;
trackers.feed.unshift(it);
if (alertsOn && t.alerts && document.visibilityState === 'visible') {
const node = h('a.tt', { href: `/coin/${it.m}`, 'data-link': '' },
h('span.tt-who', null, (t.emoji ? t.emoji + ' ' : '') + t.name),
h('span.' + (it.buy ? 'up' : 'down'), null, it.buy ? ' bought ' : ' sold '),
h('b', null, sol(it.sol) + ' SOL'),
h('span.mut', null, ' of '),
h('b', null, it.s || short(it.m)),
h('span.mut', null, ' at ' + usd(it.mc * live.sol)));
toast('', it.buy ? 'buy' : 'sell', 4500, node);
}
}
if (trackers.feed.length > 500) trackers.feed.length = 500;
trackers.emit('feed', m.list);
});
net.track(trackers.list.map(t => t.w));