first commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# CoinGeckoProvider
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<RateFetchResult>`
|
||||
- Issues `GET ${baseUrl}/simple/price?ids=<curated-ids>&vs_currencies=<base.toLowerCase()>`.
|
||||
- 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<string, Record<string, number>>` 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.
|
||||
Reference in New Issue
Block a user