Files
suaveplan/openspec/archive/2026-05-17-add-currency-provider-coingecko/proposal.md
2026-05-27 23:29:53 +01:00

109 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Add @suaveplan/currency-provider-coingecko
**Package**: `@suaveplan/currency-provider-coingecko`
**Status**: PROPOSED
**Type**: new package (sibling-plugin landing from the
2026-05-17 `slim-currency-providers` refactor wave)
- **Spec delta**: ./specs/currency-provider-coingecko/spec.md
## Why
`@suaveplan/currency-providers` was slimmed on 2026-05-17 to a thin foundation
shipping the `CurrencyProvider` contract, `CurrencyProviderRegistry`,
`defineCurrencyProvider` factory, shared `httpRequest` + `composeAbortSignal`
HTTP helper, the `ApiKeyConfig` / `NoKeyConfig` config shapes, and the typed
`CurrencyProviderError` + `CURRENCY_PROVIDER_CODES` registry. The nine
concrete providers (ECB, Fixer, OXR, Binance, CoinGecko, CoinMarketCap,
CryptoCompare, Kraken, Virtual) moved to sibling
`@suaveplan/currency-provider-<name>` packages so consumers install only what
they wire in and each provider iterates on its own version cadence.
This change lands the CoinGecko sibling. CoinGecko exposes a free public
endpoint (no key, rate-limited at 10–30 calls/min) and a paid Pro tier that
accepts the API key via an `x-cg-pro-api-key` header. Both shapes share the
same request URL pattern (`/simple/price?ids=...&vs_currencies=...`) and the
same response shape, so the key is genuinely optional rather than mutually
exclusive — consumers upgrade by passing `apiKey` without otherwise changing
their wiring.
## What changes
### ADDED
- `CoinGeckoProvider` class implementing
`CurrencyProvider from @suaveplan/currency` — `name = "coingecko"`,
`supportsCrypto = true`, `supportsFiat = true`. Constructor accepts an
optional config object of shape
`NoKeyConfig & { readonly apiKey?: string }`; the `apiKey` field is
truly optional (free tier requires nothing).
- `fetchRates(options?)` issues a single request through the shared
`httpRequest()` helper, attaches the `x-cg-pro-api-key` header iff
`apiKey` was supplied, parses the JSON body, inverts the upstream
price ("1 crypto = X fiat") into the converter's rate convention
("1 base = X crypto"), and filters to `options.targets` when given.
- `getCoinGeckoId(symbol)` lookup helper exposing the curated symbol →
CoinGecko-ID mapping (BTC, ETH, SOL, USDT, USDC, BNB, XRP, ADA, DOGE,
DOT, AVAX, MATIC, LINK, UNI, ATOM, LTC). Case-insensitive; returns
`undefined` for unknown symbols.
- Failure paths raise `CurrencyProviderError`:
- `HTTP_ERROR` — non-2xx response (delegated to `httpRequest()`).
- `PARSE_ERROR` — response body is not valid JSON.
- `UPSTREAM_ERROR` — JSON parses but is not the expected
`Record<string, Record<string, number>>` shape.
- `UNKNOWN_CURRENCY` — caller-supplied `options.targets` includes a
symbol with no CoinGecko-ID mapping; raised proactively before the
HTTP call so the diagnostic surfaces the bad symbol rather than a
silent empty `rates` field.
## Source mapping
| Migrated module | Upstream source |
|---|---|
| `CoinGeckoProvider`, `getCoinGeckoId` | `../suaveplan/packages/finance/currency-providers/src/crypto/coingecko.ts` |
Upstream used a bare `fetch` call, bare `Error`, and no key support. This
migration upgrades all three: routes through the shared `httpRequest`
helper (so abort-signal composition + per-call timeouts + typed
`HTTP_ERROR` come for free), raises typed `CurrencyProviderError`s with
stable codes (so consumers can dispatch on `err.code`), and adds the
optional `apiKey` field with `x-cg-pro-api-key` header attachment for
the Pro tier.
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/currency-provider-coingecko` to
enumerate. As this is a new package, the dependent list is empty at
landing.
- No current dependents (new package; verified by
`bunx turbo ls --filter=...@suaveplan/currency-provider-coingecko`).
The package consumes (does not modify):
- `@suaveplan/currency` — `CurrencyProvider`, `RateFetchOptions`,
`RateFetchResult` contract types only. [x] no action needed (contract
consumer, not modifier).
- `@suaveplan/currency-providers` — `httpRequest`, `composeAbortSignal`,
`CurrencyProviderError`, `CURRENCY_PROVIDER_CODES`, `NoKeyConfig`.
[x] no action needed (slim core is the surface this package is built
against).
- `@suaveplan/types` — `FetchLike`. [x] no action needed.
- `@suaveplan/error` — transitively via `CurrencyProviderError`'s base
class. [x] no action needed.
## Out of scope
- Historical-rate queries (`/coins/{id}/history`), trending coins, market
cap data — `CurrencyProvider` only contracts for spot rates.
- WebSocket / push subscriptions — CoinGecko does not offer a public
WebSocket; sibling providers that do (Binance, Kraken) own that surface.
- Auto-discovery of CoinGecko IDs beyond the curated 16-entry map —
consumers needing a different symbol register it via their own wrapper
or open a follow-up change extending the map.
- Rate-limit awareness, retry, OTel instrumentation — same deferral as
the slim core and every other sibling provider; once those wrappers
land they apply uniformly via the registry.