{"record":{"id":"888ed4d0d47ce1cb","repo":"jamiepine/voicebox","slug":"jupiter-http-res-status","errorCode":null,"errorMessage":"Jupiter HTTP ${res.status}","messagePattern":"Jupiter HTTP (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"landing/src/lib/token-stats.ts","lineNumber":390,"sourceCode":"      capped = true;\n      break;\n    }\n  }\n\n  const top = [...balances.entries()]\n    .sort((a, b) => b[1] - a[1])\n    .slice(0, TOP_HOLDERS)\n    .map(([owner, raw]) => ({ owner, amount: raw / 10 ** decimals }));\n\n  return { count: balances.size, capped, top };\n}\n\n// ── Price (Jupiter, no key required) ─────────────────────────────────────────\nasync function getJupiterPrice(mint: string): Promise<number | null> {\n  const res = await fetch(`https://lite-api.jup.ag/price/v3?ids=${mint}`, {\n    next: { revalidate: REVALIDATE_S },\n  });\n  if (!res.ok) throw new Error(`Jupiter HTTP ${res.status}`);\n  const json = (await res.json()) as Record<string, { usdPrice?: number } | undefined>;\n  const price = json[mint]?.usdPrice;\n  return typeof price === 'number' ? price : null;\n}\n\n// ── Creator rewards (pump.fun swap-api) ──────────────────────────────────────\n// Lifetime creator fees earned by the creator wallet, in SOL. The swap-api\n// returns a daily series with a running `cumulativeCreatorFeeSOL`; the latest\n// (max) bucket is the lifetime total. The per-coin endpoint is unreliable, so\n// we use the per-creator one.\ninterface CreatorFeeBucket {\n  cumulativeCreatorFeeSOL: string;\n}\nasync function getCreatorRewardsSol(): Promise<number | null> {\n  const res = await fetch(\n    `https://swap-api.pump.fun/v1/creators/${TOKEN_CREATOR_ADDRESS}/fees?interval=1d`,\n    { next: { revalidate: REVALIDATE_S }, headers: { 'User-Agent': 'voicebox.sh' } },\n  );","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/landing/src/lib/token-stats.ts#L372-L408","documentation":"Thrown by getJupiterPrice() when Jupiter's lite price API (lite-api.jup.ag/price/v3) returns a non-2xx status while fetching the USD price for the $VOICEBOX mint. It is a server-side Next.js fetch with ISR revalidation (REVALIDATE_S = TOKEN_STATS_CACHE_MS/1000). The token-stats module's header comment declares 'Never throws' — this throw must be isolated by the caller so a price failure degrades only the price metric to null and lands in `warnings`.","triggerScenarios":"GET https://lite-api.jup.ag/price/v3?ids=<TOKEN_CONTRACT_ADDRESS> responds 429 (rate limit), 404/400 (unknown or gapped mint), or 5xx (Jupiter outage). Any DNS/TCP failure before the status check surfaces as a fetch rejection rather than this message.","commonSituations":"Jupiter lite-api is unauthenticated and rate-limits aggressively under load; a viral token page being crawled can trip 429s. A freshly deployed or migrated mint may 404 until Jupiter indexes it. Stale REVALIDATE_S values that are too short amplify rate-limit pressure.","solutions":["Wrap the getJupiterPrice() call in try/catch at the call site and degrade the price to null, matching the module's documented contract.","Confirm TOKEN_CONTRACT_ADDRESS in landing/src/lib/constants.ts matches the live Solana mint.","If 429s persist, lengthen TOKEN_STATS_CACHE_MS to reduce request frequency.","Add one retry with backoff for transient 5xx before degrading to null."],"exampleFix":"// before\nconst price = await getJupiterPrice(MINT);\n// after\nlet price: number | null = null;\ntry {\n  price = await getJupiterPrice(MINT);\n} catch (e) {\n  warnings.push(`price: ${(e as Error).message}`);\n}","handlingStrategy":"fallback","validationCode":"function isRevalidateFresh(lastFetchMs: number, cacheMs: number): boolean {\n  return Date.now() - lastFetchMs < cacheMs;\n}\n// Only call getJupiterPrice when the cache is stale, reducing 429 risk.","typeGuard":"function isJupiterPricePayload(\n  json: unknown,\n  mint: string,\n): json is Record<string, { usdPrice?: number } | undefined> {\n  return typeof json === 'object' && json !== null && mint in json;\n}","tryCatchPattern":"let price: number | null = null;\ntry {\n  price = await getJupiterPrice(MINT);\n} catch (e) {\n  warnings.push(`price: ${(e as Error).message}`);\n  price = null; // degrade one metric, keep rendering\n}","preventionTips":["Honor the module's 'never throws' contract: every sub-fetch is wrapped and degrades a single metric to null.","Tune REVALIDATE_S against the upstream rate limit; longer cache = fewer hits.","Log failures into `warnings` so degraded metrics are visible, not silent."],"tags":["network","external-api","jupiter","solana","typescript","nextjs","isr"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}