# CoinGeckoProvider _(no submodules)_ ## Purpose CoinGecko sibling for `@suaveplan/currency-providers`. Implements the `CurrencyProvider` contract from `@suaveplan/currency` against the public `/simple/price` endpoint, with optional Pro-tier `apiKey` support that attaches the `x-cg-pro-api-key` header to every outbound request. The free public tier (no key, 10–30 calls/min) and the Pro tier share the same URL shape, so upgrading from free to Pro is a single-field config change with no other wiring impact. ## Features - `CoinGeckoProvider implements CurrencyProvider` — `name = "coingecko"`, `supportsCrypto = true`, `supportsFiat = true`. - Optional `apiKey` config (`x-cg-pro-api-key` header attachment). - Optional `baseUrl` and `fetch` config (default `https://api.coingecko.com/api/v3` and `globalThis.fetch`). - Caller `AbortSignal` propagation via the shared `httpRequest()` helper (composes with timeouts from the helper). - Proactive `UNKNOWN_CURRENCY` gate — unknown `options.targets` reject before the HTTP call is issued. - Typed `CurrencyProviderError` on every failure path: `HTTP_ERROR`, `PARSE_ERROR`, `UPSTREAM_ERROR`, `UNKNOWN_CURRENCY`. ## Usage: Basic ```ts import { CoinGeckoProvider } from "@suaveplan/currency-provider-coingecko/coingecko/coingecko"; const provider = new CoinGeckoProvider(); const result = await provider.fetchRates({ base: "USD" }); // result.rates.BTC = 0.0000149… (1 USD ≈ 0.0000149 BTC at ~$67k/BTC) ``` ## Usage: Advanced ```ts import { CurrencyConverter } from "@suaveplan/currency"; import { CurrencyProviderRegistry } from "@suaveplan/currency-providers"; import { CoinGeckoProvider } from "@suaveplan/currency-provider-coingecko/coingecko/coingecko"; const registry = new CurrencyProviderRegistry(); registry.register( new CoinGeckoProvider({ apiKey: process.env.COINGECKO_PRO_KEY, baseUrl: "https://pro-api.coingecko.com/api/v3", }), ); const converter = new CurrencyConverter({ provider: registry.get("coingecko"), staleDurationMs: 60_000, // 1 minute — crypto rates move fast }); // Targets are validated against the curated COIN_IDS map BEFORE the // HTTP call — passing an unknown target raises UNKNOWN_CURRENCY // without making a network round trip. const controller = new AbortController(); const rates = await registry.get("coingecko").fetchRates({ base: "EUR", targets: ["BTC", "ETH"], signal: controller.signal, }); ``` ## API Reference ### `class CoinGeckoProvider implements CurrencyProvider` - `constructor(config?: CoinGeckoConfig)` — config is fully optional. - `readonly name: "coingecko"` - `readonly supportsCrypto: true` - `readonly supportsFiat: true` - `fetchRates(options?: RateFetchOptions): Promise` - Issues `GET ${baseUrl}/simple/price?ids=&vs_currencies=`. - Forwards `options.signal` to the shared `httpRequest()` helper. - Pre-validates `options.targets` against `COIN_IDS`; an unknown entry raises `CurrencyProviderError(UNKNOWN_CURRENCY)` before any network activity. - Inverts upstream "1 crypto = X fiat" prices into the converter's "1 base = X crypto" convention. - Throws `CurrencyProviderError` with `code` ∈ {`HTTP_ERROR`, `PARSE_ERROR`, `UPSTREAM_ERROR`, `UNKNOWN_CURRENCY`} carrying `providerName === "coingecko"`. ### `interface CoinGeckoConfig extends NoKeyConfig` - `readonly baseUrl?: string` — overrides `https://api.coingecko.com/api/v3`. - `readonly fetch?: FetchLike` — overrides `globalThis.fetch`. - `readonly apiKey?: string` — Pro-tier key, attached as `x-cg-pro-api-key` header when present. ## Implementation Notes - Pre-validating `options.targets` against `COIN_IDS` (not against the upstream response) means the rejection message names the bad symbol rather than swallowing it with a missing-key in the result map. This is the difference between "your `ZZZ` symbol is not in our curated set" (actionable) and "your rate result has no `ZZZ` field" (silent defect). - The shape guard (`isResponseShape`) accepts any value matching `Record>` rather than asserting the exact `bitcoin`/`ethereum` keys CoinGecko currently returns. The upstream may add new coins or rename old ones, and a strict assertion would treat that as a wire-format break instead of a benign superset. - Headers are constructed once at instantiation time and frozen so the per-request `init` spread is the only mutable copy — defends against accidental key leakage into a downstream debugger.