diff --git a/src/popup/index.js b/src/popup/index.js index 7f04280..db8765c 100644 --- a/src/popup/index.js +++ b/src/popup/index.js @@ -6,6 +6,7 @@ const { JsonRpcProvider, formatEther, } = require("ethers"); +const { getTopTokenPrices } = require("../shared/tokens"); const DEBUG = true; const DEBUG_MNEMONIC = @@ -119,6 +120,31 @@ function addressFromPrivateKey(key) { return w.address; } +// -- price fetching -- +// { "ETH": 1234.56, "LINK": 8.60, ... } +const prices = {}; + +async function refreshPrices() { + try { + const fetched = await getTopTokenPrices(25); + Object.assign(prices, fetched); + } catch (e) { + // prices stay empty on error + } +} + +function formatUsd(amount) { + if (amount === null || amount === undefined || isNaN(amount)) return ""; + if (amount < 0.01) return "< $0.01"; + return ( + "$" + + amount.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }) + ); +} + // -- balance fetching -- function getProvider() { return new JsonRpcProvider(state.rpcUrl); @@ -188,9 +214,15 @@ function renderWalletList() { if (addr.ensName) { html += `
${addr.ensName}
`; } + html += `
${addr.address}
`; html += `
`; - html += `${addr.address}`; - html += `${addr.balance} ETH`; + html += `${addr.balance} ETH`; + const ethUsd = prices.ETH + ? parseFloat(addr.balance) * prices.ETH + : null; + if (ethUsd !== null) { + html += `${formatUsd(ethUsd)}`; + } html += `
`; html += ``; }); @@ -235,7 +267,9 @@ function showAddressDetail() { $("address-full").textContent = addr.address; $("address-copied-msg").textContent = ""; $("address-eth-balance").textContent = addr.balance; - $("address-usd-value").textContent = ""; + const ethUsd = prices.ETH ? parseFloat(addr.balance) * prices.ETH : null; + $("address-usd-value").textContent = + ethUsd !== null ? formatUsd(ethUsd) : ""; const ensEl = $("address-ens"); if (addr.ensName) { ensEl.textContent = addr.ensName; @@ -331,10 +365,9 @@ async function init() { } else { renderWalletList(); showView("main"); - // Fetch balances in the background, re-render when done - refreshBalances().then(() => { - renderWalletList(); - }); + // Fetch prices and balances in parallel, re-render as each completes + refreshPrices().then(() => renderWalletList()); + refreshBalances().then(() => renderWalletList()); } // -- Welcome -- diff --git a/src/shared/tokens.js b/src/shared/tokens.js new file mode 100644 index 0000000..76e6b27 --- /dev/null +++ b/src/shared/tokens.js @@ -0,0 +1,820 @@ +// Default ERC-20 token list for Ethereum mainnet, ordered by market cap. +// Users can add custom tokens by contract address. This list provides +// convenient defaults so users don't have to look up addresses manually. +// +// Source: Etherscan, CoinGecko, CoinMarketCap as of 2026-02-25. +// Ordered by approximate market cap descending. + +const COINDESK_API = "https://data-api.coindesk.com/index/cc/v1/latest/tick"; + +const DEFAULT_TOKENS = [ + // 1-10 + { + address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + symbol: "USDT", + decimals: 6, + }, + { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + decimals: 6, + }, + { + address: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52", + symbol: "BNB", + decimals: 18, + }, + { + address: "0x514910771AF9Ca656af840dff83E8264EcF986CA", + symbol: "LINK", + decimals: 18, + }, + { + address: "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE", + symbol: "SHIB", + decimals: 18, + }, + { + address: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + symbol: "stETH", + decimals: 18, + }, + { + address: "0x2AF5D2aD76741191D15Dfe7bF6aC92d4Bd912Ca3", + symbol: "LEO", + decimals: 18, + }, + { + address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + symbol: "WETH", + decimals: 18, + }, + { + address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + symbol: "WBTC", + decimals: 8, + }, + { + address: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + symbol: "UNI", + decimals: 18, + }, + // 11-20 + { + address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", + symbol: "DAI", + decimals: 18, + }, + { + address: "0x6De037ef9aD2725EB40118Bb1702EBb27e4Aeb24", + symbol: "RNDR", + decimals: 18, + }, + { + address: "0x6982508145454Ce325dDbE47a25d4ec3d2311933", + symbol: "PEPE", + decimals: 18, + }, + { + address: "0xaea46A60368A7bD060eec7DF8CBa43b7EF41Ad85", + symbol: "FET", + decimals: 18, + }, + { + address: "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", + symbol: "AAVE", + decimals: 18, + }, + { + address: "0x75231F58b43240C9718Dd58B4967c5114342a86c", + symbol: "OKB", + decimals: 18, + }, + { + address: "0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1", + symbol: "ARB", + decimals: 18, + }, + { + address: "0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6", + symbol: "POL", + decimals: 18, + }, + { + address: "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", + symbol: "MKR", + decimals: 18, + }, + { + address: "0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF", + symbol: "IMX", + decimals: 18, + }, + // 21-30 + { + address: "0xe28b3B32B6c345A34Ff64674606124Dd5Aceca30", + symbol: "INJ", + decimals: 18, + }, + { + address: "0xc944E90C64B2c07662A292be6244BDf05Cda44a7", + symbol: "GRT", + decimals: 18, + }, + { + address: "0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3", + symbol: "ONDO", + decimals: 18, + }, + { + address: "0x4a220E6096B25EADb88358cb44068A3248254675", + symbol: "QNT", + decimals: 18, + }, + { + address: "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32", + symbol: "LDO", + decimals: 18, + }, + { + address: "0x3883f5e181fccaF8410FA61e12b59BAd963fb645", + symbol: "THETA", + decimals: 18, + }, + { + address: "0x4e15361fd6b4BB609Fa63C81A2be19d873717870", + symbol: "FTM", + decimals: 18, + }, + { + address: "0xec53bF9167f50cDEB3Ae105f56099aaaB9061F83", + symbol: "EIGEN", + decimals: 18, + }, + { + address: "0x808507121B80c02388fAd14726482e061B8da827", + symbol: "PENDLE", + decimals: 18, + }, + { + address: "0x163f8C2467924be0ae7B5347228CABF260318753", + symbol: "WLD", + decimals: 18, + }, + // 31-40 + { + address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + symbol: "wstETH", + decimals: 18, + }, + { + address: "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", + symbol: "weETH", + decimals: 18, + }, + { + address: "0x57e114B691Db790C35207b2e685D4A43181e6061", + symbol: "ENA", + decimals: 18, + }, + { + address: "0x5283D291DBCF85356A21bA090E6db59121208b44", + symbol: "BLUR", + decimals: 18, + }, + { + address: "0xBB0E17EF65F82Ab018d8EDd776e8DD940327B28b", + symbol: "AXS", + decimals: 18, + }, + { + address: "0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F", + symbol: "SNX", + decimals: 18, + }, + { + address: "0xD533a949740bb3306d119CC777fa900bA034cd52", + symbol: "CRV", + decimals: 18, + }, + { + address: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72", + symbol: "ENS", + decimals: 18, + }, + { + address: "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + symbol: "BAT", + decimals: 18, + }, + { + address: "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD", + symbol: "LRC", + decimals: 18, + }, + // 41-50 + { + address: "0x3506424F91fD33084466F402d5D97f05F8e3b4AF", + symbol: "CHZ", + decimals: 18, + }, + { + address: "0xc00e94Cb662C3520282E6f5717214004A7f26888", + symbol: "COMP", + decimals: 18, + }, + { + address: "0x4d224452801ACEd8B2F0aebE155379bb5D594381", + symbol: "APE", + decimals: 18, + }, + { + address: "0xae78736Cd615f374D3085123A210448E74Fc6393", + symbol: "rETH", + decimals: 18, + }, + { + address: "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942", + symbol: "MANA", + decimals: 18, + }, + { + address: "0x3845badAde8e6dFF049820680d1F14bD3903a5d0", + symbol: "SAND", + decimals: 18, + }, + { + address: "0x111111111117dC0aa78b770fA6A738034120C302", + symbol: "1INCH", + decimals: 18, + }, + { + address: "0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e", + symbol: "YFI", + decimals: 18, + }, + { + address: "0x15D4c048F83bd7e37d49eA4C83a07267Ec4203dA", + symbol: "GALA", + decimals: 8, + }, + { + address: "0x853d955aCEf822Db058eb8505911ED77F175b99e", + symbol: "FRAX", + decimals: 18, + }, + // 51-60 + { + address: "0xE41d2489571d322189246DaFA5ebDe1F4699F498", + symbol: "ZRX", + decimals: 18, + }, + { + address: "0x6810e776880C02933D47DB1b9fc05908e5386b96", + symbol: "GNO", + decimals: 18, + }, + { + address: "0x56072C95FAA701256059aa122697B133aDEd9279", + symbol: "SKY", + decimals: 18, + }, + { + address: "0x9994E35Db50125E0DF82e4c2dde62496CE330999", + symbol: "MORPHO", + decimals: 18, + }, + { + address: "0x69af81e73A73B40adF4f3d4223Cd9b1ECE623074", + symbol: "MASK", + decimals: 18, + }, + { + address: "0xD33526068D116cE69F19A9ee46F0bd304F21A51f", + symbol: "RPL", + decimals: 18, + }, + { + address: "0x6B3595068778DD592e39A122f4f5a5cF09C90fE2", + symbol: "SUSHI", + decimals: 18, + }, + { + address: "0xba100000625a3754423978a60c9317c58a424e3D", + symbol: "BAL", + decimals: 18, + }, + { + address: "0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B", + symbol: "CVX", + decimals: 18, + }, + { + address: "0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b", + symbol: "CRO", + decimals: 8, + }, + // 61-70 + { + address: "0x4691937a7508860F876c9c0a2a617E7d9E945D4B", + symbol: "WOO", + decimals: 18, + }, + { + address: "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07", + symbol: "OMG", + decimals: 18, + }, + { + address: "0xdd974D5C2e2928deA5F71b9825b8b646686BD200", + symbol: "KNC", + decimals: 18, + }, + { + address: "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671", + symbol: "NMR", + decimals: 18, + }, + { + address: "0x58b6A8A3302369DAEc383334672404Ee733aB239", + symbol: "LPT", + decimals: 18, + }, + { + address: "0x767FE9EDC9E0dF98E07454847909338D505F78a4", + symbol: "ILV", + decimals: 18, + }, + { + address: "0x7DD9c5Cba05E151C895FDe1CF355C9A1D5DA6429", + symbol: "GLM", + decimals: 18, + }, + { + address: "0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D", + symbol: "LQTY", + decimals: 18, + }, + { + address: "0x320623b8E4fF03373931769A31Fc52A4E78B5d70", + symbol: "RSR", + decimals: 18, + }, + { + address: "0x8290333ceF9e6D528dD5618Fb97a76f268f3EDD4", + symbol: "ANKR", + decimals: 18, + }, + // 71-80 + { + address: "0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828", + symbol: "UMA", + decimals: 18, + }, + { + address: "0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0", + symbol: "FXS", + decimals: 18, + }, + { + address: "0xDE30da39c46104798bB5aA3fe8B9e0e1F348163F", + symbol: "GTC", + decimals: 18, + }, + { + address: "0x408e41876cCCDC0F92210600ef50372656052a38", + symbol: "REN", + decimals: 18, + }, + { + address: "0x3472A5A71965499acd81997a54BBA8D852C6E53d", + symbol: "BADGER", + decimals: 18, + }, + { + address: "0xbC396689893D065F41bc2C6EcbEE5e0085233447", + symbol: "PERP", + decimals: 18, + }, + { + address: "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2", + symbol: "HOT", + decimals: 18, + }, + { + address: "0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6", + symbol: "STG", + decimals: 18, + }, + { + address: "0x6985884C4392D348587B19cb9eAAf157F13271cd", + symbol: "ZRO", + decimals: 18, + }, + { + address: "0xFD09911130e6930Bf87F2B0554c44F400bD80D3e", + symbol: "ETHFI", + decimals: 18, + }, + // 81-90 + { + address: "0x5aFE3855358E112B5647B952709E6165e1c1eEEe", + symbol: "SAFE", + decimals: 18, + }, + { + address: "0x0b38210ea11411557c13457D4dA7dC6ea731B88a", + symbol: "API3", + decimals: 18, + }, + { + address: "0x1494CA1F11D487c2bBe4543E90080AeBa4BA3C2b", + symbol: "DPI", + decimals: 18, + }, + { + address: "0x7a58c0Be72BE218B41C608b7Fe7C5bB630736C71", + symbol: "PEOPLE", + decimals: 18, + }, + { + address: "0xCdF7028ceAB81fA0C6971208e83fa7872994beE5", + symbol: "T", + decimals: 18, + }, + { + address: "0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998", + symbol: "AUDIO", + decimals: 18, + }, + { + address: "0x4F9254C83EB525f9FCf346490bbb3ed28a81C667", + symbol: "CELR", + decimals: 18, + }, + { + address: "0x0391D2021f89DC339F60Fff84546EA23E337750f", + symbol: "BOND", + decimals: 18, + }, + { + address: "0x43Dfc4159D86F3A37A5A4B3D4580b888ad7d4DDd", + symbol: "DODO", + decimals: 18, + }, + { + address: "0x3F382DbD960E3a9bbCeaE22651E88158d2791550", + symbol: "GHST", + decimals: 18, + }, + // 91-100 + { + address: "0xf4d2888d29D722226FafA5d9B24F9164c092421E", + symbol: "LOOKS", + decimals: 18, + }, + { + address: "0x32353A6C91143bfd6C7d363B546e62a9A2489A20", + symbol: "AGLD", + decimals: 18, + }, + { + address: "0x090185f2135308BaD17527004364eBcC2D37e5F6", + symbol: "SPELL", + decimals: 18, + }, + { + address: "0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC", + symbol: "HOP", + decimals: 18, + }, + { + address: "0x0F2D719407FdBeFF09D87557AbB7232601FD9F29", + symbol: "SYN", + decimals: 18, + }, + { + address: "0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb", + symbol: "INST", + decimals: 18, + }, + { + address: "0xcAfE001067cDEF266AfB7Eb5A286dCFD277f3dE5", + symbol: "PSP", + decimals: 18, + }, + { + address: "0x31c8EAcBFFdD875c74b94b077895Bd78CF1E64A3", + symbol: "RAD", + decimals: 18, + }, + { + address: "0xDBDB4d16EdA451D0503b854CF79D55697F90c8DF", + symbol: "ALCX", + decimals: 18, + }, + { + address: "0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39", + symbol: "HEX", + decimals: 8, + }, + // 101-110 (stablecoins continued) + { + address: "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8", + symbol: "PYUSD", + decimals: 6, + }, + { + address: "0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f", + symbol: "GHO", + decimals: 18, + }, + { + address: "0xdC035D45d973E3EC169d2276DDab16f1e407384F", + symbol: "USDS", + decimals: 18, + }, + { + address: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3", + symbol: "USDe", + decimals: 18, + }, + { + address: "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0", + symbol: "LUSD", + decimals: 18, + }, + { + address: "0x8E870D67F660D95d5be530380D0eC0bd388289E1", + symbol: "USDP", + decimals: 18, + }, + { + address: "0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd", + symbol: "GUSD", + decimals: 2, + }, + { + address: "0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E", + symbol: "crvUSD", + decimals: 18, + }, + { + address: "0x99D8a9C45b2ecA8864373A26D1459e3Dff1e17F3", + symbol: "MIM", + decimals: 18, + }, + { + address: "0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919", + symbol: "RAI", + decimals: 18, + }, + // 111-120 (wrapped / liquid staking continued) + { + address: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", + symbol: "cbBTC", + decimals: 8, + }, + { + address: "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + symbol: "cbETH", + decimals: 18, + }, + { + address: "0xa2E3356610840701BDf5611a53974510Ae27E2e1", + symbol: "wBETH", + decimals: 18, + }, + { + address: "0xbf5495Efe5DB9ce00f80364C8B423567e58d2110", + symbol: "ezETH", + decimals: 18, + }, + { + address: "0xD9A442856C234a39a81a089C06451EBAa4306a72", + symbol: "pufETH", + decimals: 18, + }, + { + address: "0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7", + symbol: "rsETH", + decimals: 18, + }, + { + address: "0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38", + symbol: "osETH", + decimals: 18, + }, + { + address: "0x5E8422345238F34275888049021821E8E08CAa1f", + symbol: "frxETH", + decimals: 18, + }, + { + address: "0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa", + symbol: "mETH", + decimals: 18, + }, + { + address: "0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa", + symbol: "tBTC", + decimals: 18, + }, + // 121-130 + { + address: "0x4da27a545c0c5B758a6BA100e3a049001de870f5", + symbol: "stkAAVE", + decimals: 18, + }, + { + address: "0x62B9c7356A2Dc64a1969e19C23e4f579F9810Aa7", + symbol: "cvxCRV", + decimals: 18, + }, + { + address: "0x0d438F3b5175Bebc262bF23753C1E53d03432bDE", + symbol: "wNXM", + decimals: 18, + }, + { + address: "0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44", + symbol: "KP3R", + decimals: 18, + }, + { + address: "0x0cEC1A9154Ff802e7934Fc916Ed7Ca50bDE6844e", + symbol: "POOL", + decimals: 18, + }, + { + address: "0xba5BDe662c17e2aDFF1075610382B9B691296350", + symbol: "RARE", + decimals: 18, + }, + { + address: "0x44108f0223A3C3028F5Fe7AEC7f9bb2E66beF82F", + symbol: "ACX", + decimals: 18, + }, + { + address: "0x33349B282065b0284d756F0577FB39c158F935e6", + symbol: "MPL", + decimals: 18, + }, + { + address: "0x6243d8CEA23066d098a15582d81a598b4e8391F4", + symbol: "FLX", + decimals: 18, + }, + { + address: "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C", + symbol: "TORN", + decimals: 18, + }, + // 131-140 + { + address: "0xaaeE1A9723aaDB7afA2810263653A34bA2C21C7a", + symbol: "MOG", + decimals: 18, + }, + { + address: "0xb131f4A55907B10d1F0A50d8ab8FA09EC342cd74", + symbol: "MEME", + decimals: 18, + }, + { + address: "0x761D38e5ddf6ccf6Cf7c55759d5210750B5D60F3", + symbol: "ELON", + decimals: 18, + }, + { + address: "0x8f8221aFbB33998d8584A2B05749bA73c37a938a", + symbol: "REQ", + decimals: 18, + }, + { + address: "0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9", + symbol: "SXP", + decimals: 18, + }, + { + address: "0x5732046A883704404F284Ce41FfADd5b007FD668", + symbol: "BLZ", + decimals: 18, + }, + { + address: "0xDDB3422497E61e13543BeA06989C0789117555c5", + symbol: "COTI", + decimals: 18, + }, + { + address: "0x9E32b13ce7f2e80A01932B42553652E053D6ed8e", + symbol: "Metis", + decimals: 18, + }, + { + address: "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c", + symbol: "EUROC", + decimals: 6, + }, + { + address: "0xC581b735A1688071A1746c968e0798D642EDE491", + symbol: "EURT", + decimals: 6, + }, + // 141-150 + { + address: "0x6e1A19F235bE7ED8E3369eF73b196C07257494DE", + symbol: "FIL", + decimals: 18, + }, + { + address: "0xC669928185DbCE49d2230CC9B0979BE6DC797957", + symbol: "BTT", + decimals: 18, + }, + { + address: "0x6fB3e0A217407EFFf7Ca062D46c26E5d60a14d69", + symbol: "IOTX", + decimals: 18, + }, + { + address: "0x41e5560054824eA6B0732E656E3Ad64E20e94E45", + symbol: "CVC", + decimals: 8, + }, + { + address: "0x27054b13b1B798B345b591a4d22e6562d47eA75a", + symbol: "AST", + decimals: 4, + }, + { + address: "0x2e9d63788249371f1DFC918a52f8d799F4a38C94", + symbol: "TOKE", + decimals: 18, + }, + { + address: "0x9813037ee2218799597d83D4a5B6F3b6778218d9", + symbol: "BONE", + decimals: 18, + }, + { + address: "0xCa14007Eff0dB1f8135f4C25B34De49AB0d42766", + symbol: "STRK", + decimals: 18, + }, + { + address: "0x0C10bF8FcB7Bf5412187A595ab97a3609160b5c6", + symbol: "USDD", + decimals: 18, + }, + { + address: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0", + symbol: "MATIC", + decimals: 18, + }, +]; + +// Deduplicate by address (case-insensitive), preserving order +const seen = new Set(); +const TOKENS = DEFAULT_TOKENS.filter((t) => { + const key = t.address.toLowerCase(); + if (seen.has(key)) return false; + seen.add(key); + return true; +}); + +// Return the symbols of the top N tokens (by position in the list). +function getTopTokenSymbols(n) { + return TOKENS.slice(0, n).map((t) => t.symbol); +} + +// Fetch USD prices for the top N tokens from CoinDesk. +// Also always includes ETH. Returns { ETH: 1234.56, LINK: 8.60, ... }. +// n must be <= 30 (API and sanity limit). +async function getTopTokenPrices(n) { + if (n > 30) { + throw new Error("getTopTokenPrices: n must be <= 30, got " + n); + } + const symbols = ["ETH", ...getTopTokenSymbols(n)]; + // Deduplicate (ETH might be in the token list as WETH but we want ETH-USD) + const unique = [...new Set(symbols)]; + const instruments = unique.map((s) => s + "-USD").join(","); + const url = + COINDESK_API + + "?market=cadli&instruments=" + + instruments + + "&apply_mapping=true"; + const resp = await fetch(url); + const json = await resp.json(); + const prices = {}; + for (const [instrument, data] of Object.entries(json.Data || {})) { + const symbol = instrument.split("-")[0]; + if (data.VALUE !== undefined) { + prices[symbol] = data.VALUE; + } + } + return prices; +} + +module.exports = { TOKENS, getTopTokenSymbols, getTopTokenPrices };