const { $, showView, showFlash } = require("./helpers"); const { getTopTokens } = require("../../shared/tokenList"); const { state, saveState } = require("../../shared/state"); const { lookupTokenInfo } = require("../../shared/balances"); const { isScamAddress } = require("../../shared/scamlist"); const { log } = require("../../shared/log"); let ctx; function isTracked(address) { const lower = address.toLowerCase(); return state.trackedTokens.some((t) => t.address.toLowerCase() === lower); } function tokenLabel(t) { return t.name ? t.name + " (" + t.symbol + ")" : t.symbol; } function renderTop10() { const el = $("settings-addtoken-top10"); el.innerHTML = getTopTokens(10) .map((t) => { const tracked = isTracked(t.address); const cls = tracked ? "border border-border px-1 text-xs opacity-40 cursor-default" : "border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"; return ( `` ); }) .join(""); el.querySelectorAll(".settings-addtoken-quick:not([disabled])").forEach( (btn) => { btn.addEventListener("click", async () => { const token = { address: btn.dataset.address, symbol: btn.dataset.symbol, decimals: parseInt(btn.dataset.decimals, 10), name: btn.dataset.name || btn.dataset.symbol, }; state.trackedTokens.push(token); await saveState(); showFlash("Added " + token.symbol); renderTop10(); renderDropdown(); ctx.doRefreshAndRender(); }); }, ); } function renderDropdown() { const sel = $("settings-addtoken-select"); const tokens = getTopTokens(100); let html = ''; for (const t of tokens) { const tracked = isTracked(t.address); const label = tokenLabel(t) + (tracked ? " (tracked)" : ""); html += ``; } sel.innerHTML = html; } function show() { $("settings-addtoken-address").value = ""; $("settings-addtoken-info").textContent = ""; $("settings-addtoken-info").style.visibility = "hidden"; renderTop10(); renderDropdown(); showView("settings-addtoken"); } function init(_ctx) { ctx = _ctx; $("btn-settings-addtoken-back").addEventListener("click", () => { ctx.showSettingsView(); }); $("btn-settings-addtoken-select").addEventListener("click", async () => { const sel = $("settings-addtoken-select"); const opt = sel.options[sel.selectedIndex]; if (!opt || !opt.value) { showFlash("Please select a token."); return; } if (isTracked(opt.value)) { showFlash("Already tracked."); return; } const token = { address: opt.value, symbol: opt.dataset.symbol, decimals: parseInt(opt.dataset.decimals, 10), name: opt.dataset.name || opt.dataset.symbol, }; state.trackedTokens.push(token); await saveState(); showFlash("Added " + token.symbol); renderTop10(); renderDropdown(); ctx.doRefreshAndRender(); }); $("btn-settings-addtoken-manual").addEventListener("click", async () => { const addr = $("settings-addtoken-address").value.trim(); if (!addr || !addr.startsWith("0x")) { showFlash( "Please enter a valid contract address starting with 0x.", ); return; } if (isTracked(addr)) { showFlash("Already tracked."); return; } if (isScamAddress(addr)) { showFlash("This address is on a known scam/fraud list."); return; } const infoEl = $("settings-addtoken-info"); infoEl.textContent = "Looking up token..."; infoEl.style.visibility = "visible"; log.debugf("Looking up token contract", addr); try { const info = await lookupTokenInfo(addr, state.rpcUrl); log.infof("Adding token", info.symbol, addr); state.trackedTokens.push({ address: addr, symbol: info.symbol, decimals: info.decimals, name: info.name, }); await saveState(); showFlash("Added " + info.symbol); $("settings-addtoken-address").value = ""; infoEl.textContent = ""; infoEl.style.visibility = "hidden"; renderTop10(); renderDropdown(); ctx.doRefreshAndRender(); } catch (e) { const detail = e.shortMessage || e.message || String(e); log.errorf("Token lookup failed for", addr, detail); showFlash(detail); infoEl.textContent = ""; infoEl.style.visibility = "hidden"; } }); } module.exports = { init, show };