fix(crypto-quotes): use CoinPaprika as primary, CoinGecko as fallback#3086
Conversation
Railway bundle log 2026-04-14 07:17:10 UTC showed seed-bundle-market-backup finishing with failed:1. Crypto-Quotes hit CoinGecko 429s on every retry: [Crypto-Quotes] CoinGecko 429 — waiting 10s (1/5) ... (5 attempts, 10/20/30/40/50s back-off) [Crypto-Quotes] Crypto-Quotes failed after 120.0s: timeout Root cause: CoinGecko 5-step retry budget (10+20+30+40+50 = 150s) exceeds the bundle 120s section timeout, so the existing CoinPaprika fallback never runs — the child process is killed mid-retry. Fix: swap source order. CoinPaprika is now primary; CoinGecko is retained as fallback for sparkline_in_7d data (CoinPaprika does not provide sparklines). Probed CoinPaprika live: all 10 mapped crypto IDs present in /v1/tickers, no auth required. Trade-off: when CoinPaprika is healthy, sparkline arrays will be empty. Acceptable — the panel already handles undefined sparklines, and the alternative (no quotes at all because CoinGecko is rate-limited) is worse. Tests: crypto-config.test.mjs 6/6, typecheck + typecheck:api clean.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…snapshots Codex review on PR #3086 caught: validate() only required >=1 quote with positive price. With the new CoinPaprika-primary path, a single dropped or renamed ticker would silently publish a 9/10 snapshot. Health stays green while one tracked asset disappears from the panel — exactly the silent data-loss class we want to avoid on a fixed-cardinality top-10 feed. Tightened validate() to require: - quotes.length === CRYPTO_IDS.length (full cardinality) - every quote has Number.isFinite(price) && price > 0 - every configured symbol is present in the response (defends against duplicate IDs masquerading as full coverage) When the validator rejects, runSeed() takes the skipped path: existing TTL is extended, seed-meta is bumped with count=0, and the Railway log will scream which symbol is missing on the next cycle so the broken CoinPaprika mapping is caught immediately. Tests: crypto-config.test.mjs 6/6, typecheck clean.
Greptile SummaryThis PR fixes a chronic Two minor items worth addressing before or after merging:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Bundle as Railway Bundle (120s timeout)
participant FQ as fetchCryptoQuotes()
participant CP as fetchFromCoinPaprika()
participant CG as fetchFromCoinGecko()
participant Redis as Redis (market:crypto:v1)
Bundle->>FQ: invoke
FQ->>CP: try (PRIMARY)
CP->>CP: "GET /v1/tickers?quotes=USD (15s timeout)"
alt CoinPaprika succeeds
CP-->>FQ: "normalized data (sparkline=[])"
FQ->>Redis: SET market:crypto:v1
FQ-->>Bundle: success ✅
else CoinPaprika fails
CP-->>FQ: throw Error
FQ->>CG: catch → fallback
CG->>CG: "fetchWithRateLimitRetry (maxAttempts=5, up to 150s)"
alt CoinGecko succeeds
CG-->>FQ: data with sparklines
FQ->>Redis: SET market:crypto:v1
FQ-->>Bundle: success ✅
else CoinGecko 429s / both fail
CG-->>FQ: throw Error
FQ-->>Bundle: failed:1 ❌ (same as before)
end
end
|
| data.quotes.some((q) => q.price > 0) | ||
| ); | ||
| if (!Array.isArray(data?.quotes)) return false; | ||
| if (data.quotes.length !== CRYPTO_IDS.length) return false; |
There was a problem hiding this comment.
sourceVersion is written to seed-meta:market:crypto:v1 in Redis for health monitoring (per AGENTS.md). With CoinPaprika now as the primary source, the value 'coingecko-markets' no longer reflects the live data path, which could mislead dashboards or on-call runbooks that inspect this field.
| if (data.quotes.length !== CRYPTO_IDS.length) return false; | |
| sourceVersion: 'coinpaprika-tickers', |
…rceVersion P2-1: CoinGecko fallback was still wired with maxAttempts=5 (10+20+30+40+50 = 150s budget), so when CoinPaprika fails the fallback path could itself overrun the bundle's 120s section timeout — recreating the exact failure mode this PR fixes. Capped at maxAttempts=2 (10+20=30s) so the fallback always finishes well within the bundle window. P2-2: sourceVersion in seed-meta was still 'coingecko-markets' even though CoinPaprika is now primary. Changed to 'coinpaprika-tickers+coingecko-fallback' so health dashboards and on-call runbooks see the real data path. Tests: crypto-config.test.mjs 6/6, typecheck clean.
Why this PR?
Railway `seed-bundle-market-backup` log 2026-04-14 07:17:10 UTC reported `failed:1` because the Crypto-Quotes section hit chronic CoinGecko 429s and exhausted the bundle's 120s section timeout inside the retry loop — the existing CoinPaprika fallback never got a chance to run.
```
[Crypto-Quotes] CoinGecko 429 — waiting 10s (attempt 1/5)
[Crypto-Quotes] CoinGecko 429 — waiting 20s (attempt 2/5)
[Crypto-Quotes] CoinGecko 429 — waiting 30s (attempt 3/5)
[Crypto-Quotes] CoinGecko 429 — waiting 40s (attempt 4/5)
[Crypto-Quotes] CoinGecko 429 — waiting 50s (attempt 5/5)
[Crypto-Quotes] Crypto-Quotes failed after 120.0s: timeout
[Bundle:market-backup] Finished in 124.3s, ran:1 skipped:6 failed:1
```
Math: CoinGecko's 5-step back-off budget = 10+20+30+40+50 = 150s. Bundle section timeout = 120s. The child process is killed mid-retry, so the `catch` block that would have called `fetchFromCoinPaprika()` never executes.
Fix
Swap the source order: CoinPaprika is now PRIMARY; CoinGecko is retained as FALLBACK for its `sparkline_in_7d` data (CoinPaprika does not provide sparklines).
```js
async function fetchCryptoQuotes() {
let data;
try {
data = await fetchFromCoinPaprika();
} catch (err) {
console.warn(` [CoinPaprika] Failed: ${err.message} — falling back to CoinGecko`);
data = await fetchFromCoinGecko();
}
}
```
Live probe of CoinPaprika confirms coverage: `/v1/tickers?quotes=USD` returns 2000 entries, all 10 mapped IDs present, no auth required, no documented per-IP rate limit problems.
Trade-off
When CoinPaprika is healthy, sparkline arrays will be empty (CoinPaprika doesn't expose 7d price history in `/tickers`). Acceptable — the panel already handles undefined sparklines, and the alternative (no quotes at all because CoinGecko is rate-limited) is strictly worse.
Files
Testing
Post-Deploy Monitoring & Validation
Related