first commit
This commit is contained in:
79
docs/money/finance/currency-provider-coingecko/README.md
Normal file
79
docs/money/finance/currency-provider-coingecko/README.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# @suaveplan/currency-provider-coingecko - Universal CoinGecko sibling for @suaveplan/currency-providers. Exposes CoinGeckoProvider (free public tier + optional Pro-tier x-cg-pro-api-key) and the getCoinGeckoId symbol lookup; routes every request through the shared httpRequest helper and raises typed CurrencyProviderError on HTTP_ERROR / PARSE_ERROR / UPSTREAM_ERROR / UNKNOWN_CURRENCY.
|
||||
|
||||
Universal CoinGecko sibling for
|
||||
[`@suaveplan/currency-providers`](../currency-providers/README.md). Implements
|
||||
the `CurrencyProvider` contract from `@suaveplan/currency` against the
|
||||
public `/simple/price` endpoint, with optional Pro-tier `apiKey` support
|
||||
attached as `x-cg-pro-api-key` on every outbound request. Both free and
|
||||
Pro tiers share the same URL shape, so upgrading is a single-field
|
||||
config change.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [coin-ids](./src/coin-ids/coin-ids.md) — Owns the curated mapping from uppercase ticker symbol (`BTC`, `ETH`, …) to the opaque CoinGecko coin id (`bitcoin`, `et…
|
||||
- [coin-ids](./src/coin-ids/coin-ids.md) — Owns the curated mapping from uppercase ticker symbol (`BTC`, `ETH`, …) to the opaque CoinGecko coin id (`bitcoin`, `et…
|
||||
- [coingecko](./src/coingecko/coingecko.md) — CoinGecko sibling for `@suaveplan/currency-providers`. Implements the `CurrencyProvider` contract from `@suaveplan/curr…
|
||||
- [coingecko](./src/coingecko/coingecko.md) — CoinGecko sibling for `@suaveplan/currency-providers`. Implements the `CurrencyProvider` contract from `@suaveplan/curr…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/currency-provider-coingecko
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
||||
import { CoinGeckoProvider } from "@suaveplan/currency-provider-coingecko";
|
||||
|
||||
// Free public tier — no API key required.
|
||||
const free = new CoinGeckoProvider();
|
||||
|
||||
// Pro tier — pass apiKey; the same provider attaches x-cg-pro-api-key.
|
||||
const pro = new CoinGeckoProvider({
|
||||
apiKey: process.env.COINGECKO_PRO_KEY,
|
||||
baseUrl: "https://pro-api.coingecko.com/api/v3",
|
||||
});
|
||||
|
||||
const registry = new CurrencyProviderRegistry();
|
||||
registry.register(pro);
|
||||
|
||||
const converter = new CurrencyConverter({
|
||||
provider: registry.get("coingecko"),
|
||||
staleDurationMs: 60_000,
|
||||
});
|
||||
|
||||
const rates = await registry
|
||||
.get("coingecko")
|
||||
.fetchRates({ base: "USD", targets: ["BTC", "ETH"] });
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- `CoinGeckoProvider implements CurrencyProvider` — drops directly into
|
||||
any `@suaveplan/currency` converter or `CurrencyProviderRegistry`.
|
||||
- Truly optional `apiKey` — free tier requires no config; Pro tier
|
||||
attaches `x-cg-pro-api-key` automatically.
|
||||
- 16 curated cryptocurrencies (BTC, ETH, SOL, USDT, USDC, BNB, XRP, ADA,
|
||||
DOGE, DOT, AVAX, MATIC, LINK, UNI, ATOM, LTC).
|
||||
- Routes every request through the shared `httpRequest()` helper from
|
||||
`@suaveplan/currency-providers` for abort-signal composition,
|
||||
per-call timeouts, and typed HTTP failures.
|
||||
- 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`.
|
||||
- Universal — zero browser / Node-only APIs. Works in Node 18+, Bun,
|
||||
modern browsers, and Web Workers.
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/coingecko/coingecko.md`](./src/coingecko/coingecko.md) —
|
||||
`CoinGeckoProvider` class + `CoinGeckoConfig` type.
|
||||
- [`src/coin-ids/coin-ids.md`](./src/coin-ids/coin-ids.md) — `COIN_IDS`
|
||||
curated map + `getCoinGeckoId(symbol)` lookup.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# coin-ids
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Owns the curated mapping from uppercase ticker symbol (`BTC`, `ETH`, …) to
|
||||
the opaque CoinGecko coin id (`bitcoin`, `ethereum`, …) that the
|
||||
`/simple/price` endpoint requires. Without this translation step the
|
||||
provider would have to either fall back to CoinGecko's heavyweight
|
||||
`/coins/list` discovery endpoint (~10k entries, defeats tree-shaking) or
|
||||
ask every caller to learn CoinGecko's id convention — neither is
|
||||
acceptable for a slim sibling.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen `COIN_IDS` map covering the top 16 cryptos by market cap at
|
||||
landing (BTC, ETH, SOL, USDT, USDC, BNB, XRP, ADA, DOGE, DOT, AVAX,
|
||||
MATIC, LINK, UNI, ATOM, LTC).
|
||||
- Case-insensitive `getCoinGeckoId(symbol)` lookup returning `string | undefined`.
|
||||
- Zero runtime dependencies; pure data + a one-line lookup function.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { getCoinGeckoId } from "@suaveplan/currency-provider-coingecko/coin-ids/coin-ids";
|
||||
|
||||
getCoinGeckoId("BTC"); // "bitcoin"
|
||||
getCoinGeckoId("eth"); // "ethereum" — case-insensitive
|
||||
getCoinGeckoId("ZZZ"); // undefined — unknown symbol
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
The provider uses `getCoinGeckoId` as the proactive `UNKNOWN_CURRENCY`
|
||||
gate. Consumers can mirror the same pattern when composing their own
|
||||
filters before hitting the registry:
|
||||
|
||||
```ts
|
||||
import {
|
||||
COIN_IDS,
|
||||
getCoinGeckoId,
|
||||
} from "@suaveplan/currency-provider-coingecko/coin-ids/coin-ids";
|
||||
|
||||
function partitionSupported(symbols: readonly string[]): {
|
||||
supported: string[];
|
||||
unknown: string[];
|
||||
} {
|
||||
const supported: string[] = [];
|
||||
const unknown: string[] = [];
|
||||
for (const symbol of symbols) {
|
||||
if (getCoinGeckoId(symbol) !== undefined) {
|
||||
supported.push(symbol);
|
||||
} else {
|
||||
unknown.push(symbol);
|
||||
}
|
||||
}
|
||||
return { supported, unknown };
|
||||
}
|
||||
|
||||
// Iterate the full map when generating a UI dropdown:
|
||||
const everyKnown = Object.keys(COIN_IDS);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `COIN_IDS: Readonly<Record<string, string>>`
|
||||
|
||||
Frozen mapping from uppercase ticker (key) to CoinGecko coin id (value).
|
||||
Keys are always uppercase; mutation throws because the object is frozen.
|
||||
|
||||
### `getCoinGeckoId(symbol: string): string | undefined`
|
||||
|
||||
Returns the CoinGecko coin id for the given symbol. The input is
|
||||
upper-cased before the lookup, so `"btc"` and `"BTC"` both resolve to
|
||||
`"bitcoin"`. Returns `undefined` for any symbol not present in
|
||||
`COIN_IDS`. Does not throw.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The map is intentionally hand-curated rather than fetched at runtime: a
|
||||
sibling that issues a discovery call before every batch would (a) add a
|
||||
network round trip every consumer pays for once, (b) bloat the
|
||||
in-memory cache, and (c) couple this package's reliability to a second
|
||||
upstream endpoint whose response shape can drift. Adding a new symbol is
|
||||
a one-line PR; the trade-off is heavily skewed toward "type it 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