// 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));