Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
# `provider` — KrakenProvider class
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`KrakenProvider` is the universal `CurrencyProvider` implementation
for Kraken's free `/0/public/Ticker` endpoint. It fetches real-time
last-trade-closed prices for a fixed set of high-volume crypto pairs and
returns them as reciprocal-of-USD rates, so a downstream `CurrencyConverter`
can translate `USD → BTC` (and friends) without callers ever seeing
Kraken's non-standard pair names.
The class is the canonical sibling-package surface defined by the slim
`@suaveplan/currency-providers` core: it wraps a single upstream endpoint,
maps every documented failure mode to a typed `CurrencyProviderError`, and
exposes nothing that consumers can't already obtain from the shared core
helpers.
## Features
- No API key required — Kraken's public endpoint is free and rate-limited
per IP
- 12 high-volume symbols out of the box (BTC, ETH, SOL, USDT, DOT, ADA,
LINK, AVAX, ATOM, LTC, DOGE, XRP); extensible via constructor
`extraSymbols`
- Reciprocal-of-USD rate model — `1 USD = (1 / krakenLastTradePrice)` of the
target crypto, matching the shape every `CurrencyConverter` consumer expects
- Uses the slim core's `httpRequest()` helper so HTTP failures, timeouts,
and caller abort-signal propagation are handled uniformly
- Maps the four wire-level failure modes to typed `CurrencyProviderError`:
`HTTP_ERROR` (non-2xx), `UPSTREAM_ERROR` (Kraken `error[]` array),
`PARSE_ERROR` (malformed JSON / shape mismatch), `UNKNOWN_CURRENCY` (all
requested targets unmapped)
- Universal — no `node:`/`bun:`/DOM imports; runs unchanged under
Node ≥ 18, Bun, modern browsers (subject to Kraken's CORS), Web Workers
- Optional injectable `FetchLike` (defaults to `globalThis.fetch`) for
recording tests, proxying, or staging URLs
## Usage: Basic
```ts
import { KrakenProvider } from "@suaveplan/currency-provider-kraken/provider/provider";
const provider = new KrakenProvider();
const result = await provider.fetchRates({ base: "USD" });
console.log(result.rates.BTC); // e.g. 0.0000149 (≈ 1 / 67000)
console.log(result.rates.ETH); // e.g. 0.0002857 (≈ 1 / 3500)
```
## Usage: Advanced
```ts
import { CurrencyConverter } from "@suaveplan/currency";
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
import { KrakenProvider } from "@suaveplan/currency-provider-kraken/provider/provider";
// Use the slim core's registry — wire Kraken plus any other sibling
// providers your app supports. The Kraken instance extends coverage with
// a newly-listed pair via `extraSymbols`, and reuses a custom fetch
// closure that adds a proxy header.
const registry = new CurrencyProviderRegistry();
registry.register(
new KrakenProvider({
baseUrl: "https://kraken.proxy.example.com/0/public",
fetch: async (url, init) =>
fetch(url, {
...init,
headers: {
...(init?.headers ?? {}),
"x-proxy-token": process.env.PROXY_TOKEN ?? "",
},
}),
extraSymbols: { MATIC: "MATICUSD" },
}),
);
const converter = new CurrencyConverter({
provider: registry.get("kraken"),
});
const btcRate = await converter.getRate("USD", "BTC");
const maticRate = await converter.getRate("USD", "MATIC");
```
## API Reference
| Export | Kind | Description |
|---|---|---|
| `DEFAULT_KRAKEN_BASE_URL` | `string` | `"https://api.kraken.com/0/public"` — overridable via `config.baseUrl`. |
| `KrakenProviderConfig` | `interface` extends `NoKeyConfig` | Optional `baseUrl`, optional `fetch` (`FetchLike`), optional `extraSymbols` (`Record<string, string>`). |
| `KrakenProvider` | `class implements CurrencyProvider` | Fixed `name = "kraken"`, `supportsCrypto = true`, `supportsFiat = false`. Constructor takes an optional `KrakenProviderConfig`. |
| `KrakenProvider.fetchRates(options?)` | `(RateFetchOptions?) => Promise<RateFetchResult>` | Issues one `GET /Ticker?pair=<csv>` via the injected fetch. Returns `{ base, rates, timestamp }`. Throws `CurrencyProviderError` on every documented failure path. |
### Error codes raised
| Code | When |
|---|---|
| `CURRENCY_PROVIDER.HTTP_ERROR` | Non-2xx response (raised by the shared `httpRequest`). Carries `status`. |
| `CURRENCY_PROVIDER.UPSTREAM_ERROR` | Kraken response carries a non-empty `error[]` array. Carries `upstreamMessage`. |
| `CURRENCY_PROVIDER.PARSE_ERROR` | Body fails JSON-parse, OR the parsed body does not match the `{ error: string[], result: object }` shape. Carries `cause` for the JSON-parse case. |
| `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` | Caller supplied `options.targets` but none of the requested symbols resolved to a mapped Kraken pair. |
## Implementation Notes
- The pair list sent in the `pair=` query is the value list of the merged
symbol map — Kraken supports a comma-separated batch so the whole supported
set is fetched in a single round trip. This is more efficient than one
request per symbol AND matches what Kraken's documentation recommends.
- The rate model is `1 / Number.parseFloat(ticker.c[0])` where `c[0]` is
Kraken's "last trade closed" price. Entries whose price does not parse
to a positive finite number are skipped (not raised) — Kraken occasionally
returns `"0.00"` for freshly-listed pairs. If the entire mapped set drops,
`options.targets` filtering will subsequently raise `UNKNOWN_CURRENCY`
on the caller's behalf.
- `options.signal` is forwarded into `httpRequest()` so the caller's
`AbortController` composes with any future timeout the helper applies.
- The provider does **not** retry, cache, or rate-limit. Those concerns
belong in wrapper providers (the slim core's `## Out of scope` defers
them) — keeping this class focused on Kraken's wire protocol keeps the
failure surface auditable.
- All HTTP error mapping flows through the shared `httpRequest()` helper
exported from `@suaveplan/currency-providers/http/http`, so the response
is never read on a non-2xx path — Kraken's body on a 5xx is typically
HTML and would otherwise raise a confusing `PARSE_ERROR`.
- Tier purity: no `node:` imports, no DOM imports, no `setTimeout`. The
helper uses `AbortSignal.timeout` for timeouts (universal in Node 17.3+,
Bun, modern browsers).