8.3 KiB
Capability Spec Delta: currency-provider-kraken (add-currency-provider-kraken)
Package: @suaveplan/currency-provider-kraken
Status: PROPOSED
Every requirement below carries a
**Verified by:**line at authoring time.
ADDED Requirements
Requirement: KrakenProvider implements the CurrencyProvider contract
The package SHALL export a KrakenProvider class implementing the
CurrencyProvider contract from @suaveplan/currency. Instances MUST
expose name = "kraken", supportsCrypto = true, supportsFiat = false,
and a fetchRates(options?) method returning a Promise<RateFetchResult>.
Scenario: identity metadata is fixed
- WHEN a new
KrakenProvider()is constructed - THEN
provider.name === "kraken",provider.supportsCrypto === true, andprovider.supportsFiat === false
Verified by: src/provider/provider.test.ts::"exposes the canonical kraken identity"
Requirement: Constructor accepts NoKeyConfig with optional extras
The constructor SHALL accept an optional KrakenProviderConfig extending
the slim core's NoKeyConfig (optional baseUrl, optional injectable
FetchLike). It MUST additionally accept an optional extraSymbols record
that is merged on top of KRAKEN_SYMBOL_MAP so callers can extend coverage
without forking the package. The constructor SHALL NOT require an API key.
Scenario: defaults are used when no config is supplied
- WHEN
new KrakenProvider()is constructed andfetchRates()is called - THEN the request URL begins with
DEFAULT_KRAKEN_BASE_URLand the injectedglobalThis.fetchis used
Scenario: custom baseUrl and fetch are honoured
- WHEN
new KrakenProvider({ baseUrl: "https://custom.kraken", fetch: recordingFetch })is constructed andfetchRates()is called - THEN
recordingFetchis invoked exactly once with a URL beginning with"https://custom.kraken"
Scenario: extraSymbols extends the symbol map
- WHEN
new KrakenProvider({ extraSymbols: { MATIC: "MATICUSD" } })is constructed and the Kraken response carriesMATICUSD - THEN the returned
ratesmap contains aMATICentry
Verified by: src/provider/provider.test.ts::"defaults to globalThis.fetch and DEFAULT_KRAKEN_BASE_URL" AND src/provider/provider.test.ts::"honours custom baseUrl and fetch" AND src/provider/provider.test.ts::"extends the symbol map via extraSymbols"
Requirement: Uses the shared httpRequest helper
The provider SHALL issue its request through httpRequest() from
@suaveplan/currency-providers/http/http so that abort-signal composition,
timeout derivation, and non-2xx → HTTP_ERROR mapping are uniform across
every sibling provider. It SHALL forward options.signal to the helper.
Scenario: non-2xx response raises HTTP_ERROR
- WHEN the injected fetch responds with status 503
- THEN
fetchRates()rejects with aCurrencyProviderErrorwhosecode === CURRENCY_PROVIDER_CODES.HTTP_ERROR,providerName === "kraken", andstatus === 503
Scenario: caller signal aborts the request
- WHEN
options.signalis an already-abortedAbortSignal - THEN
fetchRates({ signal })rejects with an abort error (the injected fetch sees an aborted signal)
Verified by: src/provider/provider.test.ts::"raises HTTP_ERROR on non-2xx response" AND src/provider/provider.test.ts::"forwards options.signal to the underlying fetch"
Requirement: Maps Kraken error array to UPSTREAM_ERROR
When the Kraken response body's error field is a non-empty string array,
the provider SHALL raise CurrencyProviderError(UPSTREAM_ERROR) with
providerName: "kraken" and upstreamMessage set to the first element of
the array. The error message string SHALL include the first upstream
message so existing Error.message consumers continue to match it.
Scenario: non-empty error array maps to UPSTREAM_ERROR
- WHEN the response body is
{ error: ["EGeneral:Too many requests"], result: {} } - THEN
fetchRates()rejects with aCurrencyProviderErrorwhosecode === CURRENCY_PROVIDER_CODES.UPSTREAM_ERROR,upstreamMessage === "EGeneral:Too many requests", andmessagecontains"Too many requests"
Verified by: src/provider/provider.test.ts::"maps a non-empty error array to UPSTREAM_ERROR"
Requirement: Maps unparsable bodies to PARSE_ERROR
When the response body is not valid JSON, or when the parsed body's
result field is missing / not an object, the provider SHALL raise
CurrencyProviderError(PARSE_ERROR) with providerName: "kraken".
Scenario: malformed JSON raises PARSE_ERROR
- WHEN the response body is
"not-json" - THEN
fetchRates()rejects with aCurrencyProviderErrorwhosecode === CURRENCY_PROVIDER_CODES.PARSE_ERROR
Scenario: missing result field raises PARSE_ERROR
- WHEN the response body is
{ "error": [] }(noresult) - THEN
fetchRates()rejects with aCurrencyProviderErrorwhosecode === CURRENCY_PROVIDER_CODES.PARSE_ERROR
Verified by: src/provider/provider.test.ts::"raises PARSE_ERROR on malformed JSON" AND src/provider/provider.test.ts::"raises PARSE_ERROR when result is missing"
Requirement: Maps ticker prices to reciprocal-of-USD rates
For every entry in data.result whose Kraken pair matches a mapped symbol,
the provider SHALL set rates[symbol] = 1 / Number.parseFloat(ticker.c[0])
when that float is positive and finite. Entries whose price does not parse
to a positive finite number SHALL be skipped (not raised) — Kraken
occasionally returns "0.00" for freshly-listed pairs.
Scenario: positive prices are reciprocated
- WHEN the response carries
XXBTZUSD: { c: ["67000.00", "0.1"] }andXETHZUSD: { c: ["3500.00", "1.0"] } - THEN the returned rates satisfy
rates.BTC ≈ 1/67000andrates.ETH ≈ 1/3500
Scenario: zero / non-finite prices are skipped, not raised
- WHEN the response carries
XXBTZUSD: { c: ["0.00", "0"] }alongside a validXETHZUSDentry - THEN the returned rates contain
ETHbut notBTC, and no error is raised
Verified by: src/provider/provider.test.ts::"maps positive prices to reciprocal-of-USD rates" AND src/provider/provider.test.ts::"skips zero or non-finite prices without raising"
Requirement: Filters by options.targets and raises on all-unknown targets
When options.targets is supplied, the provider SHALL return only rates for
the requested target symbols. If after filtering the resulting rate map is
empty AND options.targets was non-empty, the provider SHALL raise
CurrencyProviderError(UNKNOWN_CURRENCY) with providerName: "kraken" so
callers see the failure rather than a silent empty rate set.
Scenario: subset filtering returns only requested targets
- WHEN the response carries BTC + ETH + SOL and
fetchRates({ targets: ["BTC"] })is called - THEN the returned
rateshas exactly one key —"BTC"
Scenario: all-unknown targets raise UNKNOWN_CURRENCY
- WHEN
fetchRates({ targets: ["FAKE1", "FAKE2"] })is called against a healthy ticker response - THEN
fetchRatesrejects with aCurrencyProviderErrorwhosecode === CURRENCY_PROVIDER_CODES.UNKNOWN_CURRENCYandproviderName === "kraken"
Verified by: src/provider/provider.test.ts::"filters by options.targets" AND src/provider/provider.test.ts::"raises UNKNOWN_CURRENCY when every requested target is unmapped"
Requirement: Exposes the symbol-mapping surface
The package SHALL export KRAKEN_SYMBOL_MAP (the canonical symbol → Kraken
pair record), KRAKEN_PAIR_TO_SYMBOL (the reverse map, pair → symbol), and
DEFAULT_KRAKEN_BASE_URL. The forward map SHALL be Object.freeze-d.
Scenario: symbol map is exported, frozen, and complete
- WHEN the package is imported
- THEN
KRAKEN_SYMBOL_MAPis exported,Object.isFrozen(KRAKEN_SYMBOL_MAP)istrue, and it contains entries forBTC,ETH,SOL,USDT,DOT,ADA,LINK,AVAX,ATOM,LTC,DOGE,XRP
Scenario: reverse map is consistent with the forward map
- WHEN any entry
KRAKEN_SYMBOL_MAP[symbol] === pairis examined - THEN
KRAKEN_PAIR_TO_SYMBOL[pair] === symbol
Verified by: src/symbols/symbols.test.ts::"exports a frozen 12-entry symbol map" AND src/symbols/symbols.test.ts::"reverse map is consistent with the forward map"