diff --git a/modules/rwallet/lib/price-feed.ts b/modules/rwallet/lib/price-feed.ts index df1a8b1..86b9574 100644 --- a/modules/rwallet/lib/price-feed.ts +++ b/modules/rwallet/lib/price-feed.ts @@ -34,6 +34,7 @@ const NATIVE_COIN_ID: Record = { interface CacheEntry { prices: Map; // address (lowercase) → USD price nativePrice: number; + cgAvailable: boolean; // true if CoinGecko successfully returned token data ts: number; } @@ -74,32 +75,27 @@ export async function getNativePrice(chainId: string): Promise { return data?.[coinId]?.usd ?? 0; } -/** Fetch token prices for contract addresses on a chain (1 per request for free tier) */ +/** Fetch token prices for a batch of contract addresses on a chain */ export async function getTokenPrices( chainId: string, addresses: string[], -): Promise> { +): Promise<{ prices: Map; available: boolean }> { const platform = CHAIN_PLATFORM[chainId]; - if (!platform || addresses.length === 0) return new Map(); + if (!platform || addresses.length === 0) return { prices: new Map(), available: false }; const lower = [...new Set(addresses.map((a) => a.toLowerCase()))]; - const result = new Map(); + const prices = new Map(); - // CoinGecko free tier: 1 contract address per request, ~30 req/min - // Process in batches of 3 with a short delay between batches - const BATCH = 3; - for (let i = 0; i < lower.length; i += BATCH) { - const batch = lower.slice(i, i + BATCH); - const fetches = batch.map(async (addr) => { - const data = await cgFetch( - `https://api.coingecko.com/api/v3/simple/token_price/${platform}?contract_addresses=${addr}&vs_currencies=usd`, - ); - if (data?.[addr]?.usd) result.set(addr, data[addr].usd); - }); - await Promise.allSettled(fetches); - if (i + BATCH < lower.length) await new Promise((r) => setTimeout(r, 1500)); + const data = await cgFetch( + `https://api.coingecko.com/api/v3/simple/token_price/${platform}?contract_addresses=${lower.join(",")}&vs_currencies=usd`, + ); + if (data && !data.error_code) { + for (const addr of lower) { + if (data[addr]?.usd) prices.set(addr, data[addr].usd); + } + return { prices, available: true }; } - return result; + return { prices, available: false }; } /** Fetch and cache all prices for a chain (native + tokens) */ @@ -117,13 +113,14 @@ async function fetchChainPrices( const promise = (async (): Promise => { try { - const [nativePrice, tokenPrices] = await Promise.all([ + const [nativePrice, tokenResult] = await Promise.all([ getNativePrice(chainId), getTokenPrices(chainId, tokenAddresses), ]); const entry: CacheEntry = { - prices: tokenPrices, + prices: tokenResult.prices, nativePrice, + cgAvailable: tokenResult.available, ts: Date.now(), }; cache.set(chainId, entry); @@ -202,7 +199,8 @@ export async function enrichWithPrices( }; }); - if (options?.filterSpam) { + // Only filter spam when CoinGecko data is available to verify against + if (options?.filterSpam && priceData.cgAvailable) { return enriched.filter((b) => { // Native tokens always pass if (!b.tokenAddress || b.tokenAddress === "0x0000000000000000000000000000000000000000") return true;