// Fiat prices for every Aegis-supported coin — CoinGecko's free /simple/price // endpoint, one request covers the lot. Opt-in via Settings so a // privacy-conscious user isn't quietly telling CoinGecko when Aegis is open. // // Cache is in-memory (returned by fullState() → panel). The addon polls // every 5 min while enabled; each fetch is cheap (~200 B response) and // the free tier tolerates one call/5 min per client easily. // // Trade-off named in the settings copy: CoinGecko sees the browser's IP // + a User-Agent every poll. Not seed-linked, not address-linked, but a // data point. Off by default. const COIN_GECKO_IDS = { bch: "bitcoin-cash", btc: "bitcoin", trx: "tron", eth: "ethereum", sol: "solana", sc: "siacoin", dgb: "digibyte", }; const ENDPOINT = "https://api.coingecko.com/api/v3/simple/price"; const POLL_MS = 5 * 60 * 1000; module.exports = function makePriceFeed({ log = () => {}, onChange = () => {} } = {}) { const state = { enabled: false, prices: {}, // { : usd (number) } fetchedAt: null, error: null, loading: false, }; let timer = null; async function fetchOnce() { if (!state.enabled) return; state.loading = true; state.error = null; onChange(); try { const ids = Object.values(COIN_GECKO_IDS).join(","); const url = `${ENDPOINT}?ids=${encodeURIComponent(ids)}&vs_currencies=usd`; const r = await fetch(url); if (!r.ok) throw new Error(`CoinGecko HTTP ${r.status}`); const body = await r.json(); const next = {}; for (const [chain, cgId] of Object.entries(COIN_GECKO_IDS)) { const usd = body?.[cgId]?.usd; if (typeof usd === "number") next[chain] = usd; } state.prices = next; state.fetchedAt = Date.now(); state.error = null; } catch (e) { state.error = e?.message || String(e); log("price fetch failed:", state.error); } finally { state.loading = false; onChange(); } } function schedule() { clearTimeout(timer); if (!state.enabled) return; timer = setTimeout(async () => { await fetchOnce(); schedule(); }, POLL_MS); } return { // Snapshot for the panel: only what the UI needs. snapshot() { return { enabled: state.enabled, prices: state.prices, fetchedAt: state.fetchedAt, error: state.error, loading: state.loading, }; }, // Turn the feed on/off. Enabling triggers an immediate fetch so the // panel doesn't wait 5 minutes for the first price. async setEnabled(on) { const changed = !!on !== state.enabled; state.enabled = !!on; if (!state.enabled) { state.prices = {}; state.fetchedAt = null; state.error = null; clearTimeout(timer); if (changed) onChange(); return; } onChange(); await fetchOnce(); schedule(); }, // Force-refresh — bound to a manual "refresh" button in the panel. refresh() { return fetchOnce(); }, dispose() { clearTimeout(timer); state.enabled = false; }, }; };