koala73/worldmonitor · error

CoinGecko returned no stablecoin data

Error message

CoinGecko returned no stablecoin data

What it means

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.

Solutions

  1. Log the raw body when validation fails to see whether it's an error object (rate limit) or truly empty.
  2. Rely on the built-in CoinPaprika fallback; if it also fails, check CoinGecko API status and key/quota.
  3. Retry with backoff — rate limits reset within a minute on the free tier.
  4. Verify the stablecoins query params (platforms, limit) still match the current CoinGecko API.

Example fix

// before
const data = await resp.json();
if (!Array.isArray(data) || data.length === 0) {
  throw new Error('CoinGecko returned no stablecoin data');
}
// after
const data = await resp.json();
if (!Array.isArray(data) || data.length === 0) {
  console.warn('CoinGecko body:', JSON.stringify(data).slice(0, 200));
  throw new Error('CoinGecko returned no stablecoin data');
}
Defensive patterns

Strategy: fallback

Validate before calling

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');

Type guard

function isStablecoinList(data) { return Array.isArray(data) && data.length > 0 && data.every(d => typeof d?.id === 'string'); }

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22). Data as JSON: /api/errors/23018e0816f7ec79. Report an issue: GitHub.

Appendix: source

Thrown at scripts/seed-stablecoin-markets.mjs:46

// The fallback runs COINPAPRIKA_IDS in rounds of COINPAPRIKA_CONCURRENCY, each
// round bounded by one request timeout. Both values are passed to the helper
// explicitly so this derivation describes the call as made, and mapping a new
// stablecoin widens the budget instead of silently breaking the invariant.
const COINPAPRIKA_CONCURRENCY = 4;
export const COINPAPRIKA_WORST_CASE_MS = Math.ceil(COINPAPRIKA_IDS.length / COINPAPRIKA_CONCURRENCY) * REQUEST_TIMEOUT_MS;

async function fetchFromCoinGecko() {
  const { baseUrl, headers } = coingeckoEndpoint();
  const url = `${baseUrl}/coins/markets?vs_currency=usd&ids=${STABLECOIN_IDS}&order=market_cap_desc&sparkline=false&price_change_percentage=7d`;

  const resp = await fetchCoinGeckoWithRetryBudget(url, {
    headers,
    requestTimeoutMs: REQUEST_TIMEOUT_MS,
    budgetMs: COINGECKO_RETRY_BUDGET_MS,
  });
  const data = await resp.json();
  if (!Array.isArray(data) || data.length === 0) {
    throw new Error('CoinGecko returned no stablecoin data');
  }
  return data;
}

async function fetchFromCoinPaprika() {
  console.log('  [CoinPaprika] Falling back to CoinPaprika...');
  if (COINPAPRIKA_IDS.length === 0) throw new Error('No CoinPaprika ID mapping for stablecoins');

  const tickers = await fetchCoinPaprikaTickersById(COINPAPRIKA_IDS, {
    concurrency: COINPAPRIKA_CONCURRENCY,
    timeoutMs: REQUEST_TIMEOUT_MS,
  });
  const reverseMap = new Map(Object.entries(COINPAPRIKA_ID_MAP).map(([g, p]) => [p, g]));
  return tickers
    .map((t) => ({
      id: reverseMap.get(t.id) || t.id,
      current_price: t.quotes.USD.price,
      price_change_percentage_24h: t.quotes.USD.percent_change_24h,

View on GitHub (pinned to e586b8b4b8)