theseus/bundled-addons/aegis/lib/prices.js

99 lines
3 KiB
JavaScript
Raw Normal View History

feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect Aegis Wallet 0.4.4 → 0.6.1: - Vault lifecycle from the wallet gate. The locked / not-yet-created states now show a master-password form (with optional BIP39 mnemonic on setup) instead of redirecting users to Settings › Passwords. New api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by the existing "vault-derive" capability. api.openSettings(section) also added; settings.html honours a #section hash on open. - Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44 path or a WIF; the cashaddr is derived in the add-on, the signer material goes to a separate wallet-imports.enc via api.vault.imports {list, add, remove, signer}. Argus password-vault gains createImports / unlockImports / saveImports with its own KDF salt so the imports key is disjoint from the passwords key. lib/chain-bch-imported.js is a single-address Electrum adapter; spend support is deferred to M.1b. - Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted in add-on storage. Fiat lines under balances, in the wallet picker, and a portfolio total when 2+ wallets are open. Settings tab is now reachable while the vault is locked so the toggle is always available. - WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js). @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay on the right side of LGPL §4d. Sign requests go through approvalModal and are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS. - DGB adapter load is now soft-fail: when Aegis runs from userData/addons the bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of taking the whole add-on down.
2026-09-09 10:33:21 +02:00
// 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: {}, // { <chain>: 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; },
};
};