{"record":{"id":"23018e0816f7ec79","repo":"koala73/worldmonitor","slug":"coingecko-returned-no-stablecoin-data","errorCode":null,"errorMessage":"CoinGecko returned no stablecoin data","messagePattern":"CoinGecko returned no stablecoin data","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/seed-stablecoin-markets.mjs","lineNumber":46,"sourceCode":"// The fallback runs COINPAPRIKA_IDS in rounds of COINPAPRIKA_CONCURRENCY, each\n// round bounded by one request timeout. Both values are passed to the helper\n// explicitly so this derivation describes the call as made, and mapping a new\n// stablecoin widens the budget instead of silently breaking the invariant.\nconst COINPAPRIKA_CONCURRENCY = 4;\nexport const COINPAPRIKA_WORST_CASE_MS = Math.ceil(COINPAPRIKA_IDS.length / COINPAPRIKA_CONCURRENCY) * REQUEST_TIMEOUT_MS;\n\nasync function fetchFromCoinGecko() {\n  const { baseUrl, headers } = coingeckoEndpoint();\n  const url = `${baseUrl}/coins/markets?vs_currency=usd&ids=${STABLECOIN_IDS}&order=market_cap_desc&sparkline=false&price_change_percentage=7d`;\n\n  const resp = await fetchCoinGeckoWithRetryBudget(url, {\n    headers,\n    requestTimeoutMs: REQUEST_TIMEOUT_MS,\n    budgetMs: COINGECKO_RETRY_BUDGET_MS,\n  });\n  const data = await resp.json();\n  if (!Array.isArray(data) || data.length === 0) {\n    throw new Error('CoinGecko returned no stablecoin data');\n  }\n  return data;\n}\n\nasync function fetchFromCoinPaprika() {\n  console.log('  [CoinPaprika] Falling back to CoinPaprika...');\n  if (COINPAPRIKA_IDS.length === 0) throw new Error('No CoinPaprika ID mapping for stablecoins');\n\n  const tickers = await fetchCoinPaprikaTickersById(COINPAPRIKA_IDS, {\n    concurrency: COINPAPRIKA_CONCURRENCY,\n    timeoutMs: REQUEST_TIMEOUT_MS,\n  });\n  const reverseMap = new Map(Object.entries(COINPAPRIKA_ID_MAP).map(([g, p]) => [p, g]));\n  return tickers\n    .map((t) => ({\n      id: reverseMap.get(t.id) || t.id,\n      current_price: t.quotes.USD.price,\n      price_change_percentage_24h: t.quotes.USD.percent_change_24h,","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/scripts/seed-stablecoin-markets.mjs#L28-L64","documentation":"Thrown by fetchFromCoinGecko in the stablecoin markets seeder when the CoinGecko stablecoins endpoint returns JSON that is not a non-empty array. The script treats an empty or wrongly-shaped body as 'no data' and aborts this source so the CoinPaprika fallback can run.","triggerScenarios":"resp.json() parses successfully but the body is not an Array or is an empty array — e.g. CoinGecko returns an error object ({status: {...}}) for rate limiting, or an empty list for the requested parameters.","commonSituations":"CoinGecko free-tier rate limit (429 with JSON error body, not an array); API plan changes altering the response envelope; an incorrectly built query returning zero stablecoins; CoinGecko incident returning {} bodies.","solutions":["Log the raw body when validation fails to see whether it's an error object (rate limit) or truly empty.","Rely on the built-in CoinPaprika fallback; if it also fails, check CoinGecko API status and key/quota.","Retry with backoff — rate limits reset within a minute on the free tier.","Verify the stablecoins query params (platforms, limit) still match the current CoinGecko API."],"exampleFix":"// before\nconst data = await resp.json();\nif (!Array.isArray(data) || data.length === 0) {\n  throw new Error('CoinGecko returned no stablecoin data');\n}\n// after\nconst data = await resp.json();\nif (!Array.isArray(data) || data.length === 0) {\n  console.warn('CoinGecko body:', JSON.stringify(data).slice(0, 200));\n  throw new Error('CoinGecko returned no stablecoin data');\n}","handlingStrategy":"fallback","validationCode":"const body = await (await fetch(coingeckoUrl, { headers })).json(); if (!Array.isArray(body) || body.length === 0) console.warn('CoinGecko non-array/empty body; fallback will be used');","typeGuard":"function isStablecoinList(data) { return Array.isArray(data) && data.length > 0 && data.every(d => typeof d?.id === 'string'); }","tryCatchPattern":"try { await fetchFromCoinGecko(); } catch (e) { if (e.message === 'CoinGecko returned no stablecoin data') { console.warn('CoinGecko empty/blocked; using CoinPaprika fallback'); await fetchFromCoinPaprika(); } else throw e; }","preventionTips":["Log the raw response body on failure to distinguish rate-limit errors from empty data","Respect CoinGecko rate limits (backoff, API key on paid tier)","Keep the CoinPaprika fallback healthy by testing it independently","Pin and review query params against the current CoinGecko API docs"],"tags":["api","coingecko","empty-response","crypto"],"backgroundTag":"empty-api-response","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}