fix(rwallet): skip spam filter when CoinGecko data unavailable

Revert per-address batching (rate limit cascade). Track cgAvailable flag
in cache — only apply spam filter when CoinGecko successfully returned data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-25 16:25:48 -07:00
parent 395623af66
commit 8ba14a0e15
1 changed files with 19 additions and 21 deletions

View File

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