Skip to content

Fall back to Chainlink feeds for BTC/ETH when CoinGecko refuses - #112

Merged
leonardoaraujosantos merged 1 commit into
mainfrom
fix/crypto-price-fallback
Sep 29, 2026
Merged

leonardoaraujosantos merged 1 commit into
mainfrom
fix/crypto-price-fallback

Conversation

@leonardoaraujosantos

Copy link
Copy Markdown
Contributor

Outage

Since 2026-09-29 CoinGecko's keyless https://api.coingecko.com/api/v3/simple/price answers HTTP 403 with a CloudFront HTML page ("The request could not be satisfied … Request blocked") to every caller: the production server, a dev laptop and GitHub runners. /api/v3/ping still answers 200. CoinGecko was the only BTC/ETH source, so market_crypto:crypto_price, BTC/ETH price alerts, and ether's USD value in wallet answers (and in portfolios when the Aave oracle does not answer) were all down.

Fix

  • Chainlink on-chain fallback: BTC/USD 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c and ETH/USD 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 on Ethereum mainnet, verified on chain (description() = "BTC / USD" / "ETH / USD", decimals() = 8). Read through the existing Infura node as one constant Multicall3 eth_call (decimals() + latestRoundData() for both feeds). Quote time = updatedAt; older than the 1 h heartbeat + 5 min is marked stale in the time line, and a stale reading is no reading for an alert. Source label "Chainlink"; figures rounded to the cent.
  • Order: CoinGecko first only with the new optional COINGECKO_API_KEY (SecretStr, Demo plan, sent as x-cg-demo-api-key, redacted from logs); otherwise, or on any CoinGecko failure (HTTP error, 403/429, non-JSON, missing price), Chainlink. Without INFURA_KEY CoinGecko is asked as before.
  • Same chain everywhere: price tool, price alerts (creation and sweep) and wallet/portfolio ether pricing; the portfolio footer names the fallback source. Clock and transport come from Edges. If every source fails, the existing "no current figure" behaviour holds.
  • Egress: all reads through Edges.http_transport; the feed read is constant and reaches only mainnet.infura.io, which the crypto server's declared target now lists. Closed vocabulary unchanged.
  • COINGECKO_API_KEY declared for bot in docker-compose.yml; README, docs/deploy-coolify.md, docs/operations.md updated. OpenSpec: new fix-crypto-price-fallback change; add-alert-kinds scenario adjusted.

Tests

  • Unit (tests/unit/test_crypto_price_fallback.py): CloudFront 403 regression, fallback on 429/500/HTML/missing price/timeout, keyless skips CoinGecko, key header only when set and never in the URL, neither key logged, both sources down, constant feed request, decimals scaling, round validation, stale boundary and wording, alerts (Chainlink source, stale → failed read), wallet pricing, declared hosts.
  • E2E: "qual o preço do bitcoin?" answered from Chainlink in Portuguese with CoinGecko returning 403; a BTC price alert offered and fired from Chainlink; portfolio ether priced by Chainlink when the oracle is down.
  • Live (tests/integration/test_chainlink_live.py, skips without INFURA_KEY): feed description/decimals, fresh BTC/ETH readings, price tool answering from Chainlink.

CoinGecko's keyless /api/v3/simple/price now answers every caller with a
CloudFront 403 HTML page (/ping still answers 200), which took down the
crypto_price tool, BTC/ETH price alerts and ether's USD value in wallet and
portfolio answers.

Read the Chainlink BTC/USD (0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c)
and ETH/USD (0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419) mainnet feeds
through the existing Infura node: one constant Multicall3 eth_call of
decimals() and latestRoundData() for both feeds. Quote time is updatedAt;
a reading past the one-hour heartbeat plus five minutes is marked stale,
and price alerts treat it as no reading.

CoinGecko is asked first only when the new optional COINGECKO_API_KEY is
set (Demo plan, x-cg-demo-api-key header, redacted from logs); otherwise,
and on any CoinGecko failure, Chainlink answers. Without an Infura key
CoinGecko is asked as before. The price tool, price alerts and wallet/
portfolio ether pricing share the order, the clock and the edges'
transport. When every source fails nothing is substituted.
@leonardoaraujosantos
leonardoaraujosantos merged commit 393f853 into main Sep 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant