Context
Sister issue to #3712. The dfx-wallet app also calls CoinGecko directly to validate which on-chain tokens are listed on CoinGecko — used as a hard filter on dynamically-discovered tokens (only CoinGecko-listed tokens are rendered):
Payload is ~2.6 MB / ~17 000 coins (Nov 2026). The wallet caches it client-side for 24 h, but every cold start hits CoinGecko directly — fingerprintable, and rate-limited under load.
Proposal
Add a public, unauthenticated endpoint mirroring the upstream shape:
GET /v1/pricing/coins-list?include_platform=true
Response identical to CoinGecko's coins/list (so the wallet only swaps the base URL):
[
{ "id": "bitcoin", "symbol": "btc", "name": "Bitcoin", "platforms": {} },
{ "id": "tether", "symbol": "usdt","name": "Tether", "platforms": { "ethereum": "0xdac17f95...", "polygon-pos": "0xc2132...", ... } },
...
]
Implementation notes
- Live next to
PricingController (src/subdomains/supporting/pricing/pricing.controller.ts).
- Reuse
CoinGeckoService / CoinGeckoClient — client.coinList({ include_platform: true }) returns the right shape.
- Server-side cache is essential here: response is ~2.6 MB and changes slowly. Cache for 24 h (matches what the wallet does locally and CoinGecko's own update cadence).
- Serve gzipped — the payload compresses ~10×.
- The
pricing-proxy service on dfxprd/dfxdev already has CoinGecko caching and would also be a fine backing implementation.
Out of scope
- Filtering down the payload (e.g. only chains the wallet cares about). Keeping the upstream shape minimizes wallet diff and keeps the endpoint reusable.
Follow-up (wallet side)
Once live, the wallet swaps COINGECKO_LIST_URL in coingecko-coins-list.ts to \${DFX_API_URL}/v1/pricing/coins-list?include_platform=true and drops the direct CoinGecko call.
Context
Sister issue to #3712. The
dfx-walletapp also calls CoinGecko directly to validate which on-chain tokens are listed on CoinGecko — used as a hard filter on dynamically-discovered tokens (only CoinGecko-listed tokens are rendered):https://api.coingecko.com/api/v3/coins/list?include_platform=truesrc/services/pricing/coingecko-coins-list.ts:18(COINGECKO_LIST_URL)Payload is ~2.6 MB / ~17 000 coins (Nov 2026). The wallet caches it client-side for 24 h, but every cold start hits CoinGecko directly — fingerprintable, and rate-limited under load.
Proposal
Add a public, unauthenticated endpoint mirroring the upstream shape:
Response identical to CoinGecko's
coins/list(so the wallet only swaps the base URL):[ { "id": "bitcoin", "symbol": "btc", "name": "Bitcoin", "platforms": {} }, { "id": "tether", "symbol": "usdt","name": "Tether", "platforms": { "ethereum": "0xdac17f95...", "polygon-pos": "0xc2132...", ... } }, ... ]Implementation notes
PricingController(src/subdomains/supporting/pricing/pricing.controller.ts).CoinGeckoService/CoinGeckoClient—client.coinList({ include_platform: true })returns the right shape.pricing-proxyservice on dfxprd/dfxdev already has CoinGecko caching and would also be a fine backing implementation.Out of scope
Follow-up (wallet side)
Once live, the wallet swaps
COINGECKO_LIST_URLincoingecko-coins-list.tsto\${DFX_API_URL}/v1/pricing/coins-list?include_platform=trueand drops the direct CoinGecko call.