# Add @suaveplan/currency-providers — concrete CurrencyProvider plugins **Package**: `@suaveplan/currency-providers` **Status**: PROPOSED **Type**: migration (upstream `../suaveplan/packages/finance/currency-providers`) - **Design**: ./design.md - **Spec delta**: ./specs/currency-providers/spec.md ## Why `STATUS.md` lists `@suaveplan/currency-providers` as Pending. With `@suaveplan/currency` shipped on genesis (2026-05-17), the provider plugins are the next link in the commerce chain: they are the concrete `CurrencyProvider` implementations that real applications wire into `CurrencyConverter`. Every fiat conversion (ECB / Fixer / Open Exchange Rates), every crypto quote (CoinGecko / CoinMarketCap / Binance / Kraken / CryptoCompare), and every operator-defined virtual-currency (game gold, gems, etc.) depends on this package. Without it, `@suaveplan/currency` is an empty engine; with it, the commerce stack has rate data flowing end-to-end. The migration preserves the upstream public surface verbatim — same provider class names, same config shapes (`ApiKeyConfig` / `NoKeyConfig`), same `fetchRates` semantics — and lifts the package to the genesis quality bar: every plain `throw new Error(...)` becomes a typed `CurrencyProviderError` carrying a stable `CURRENCY_PROVIDER.*` code; HTTP is performed through an injectable `FetchLike` (default `globalThis.fetch`) so tests are real fakes not mocks; every provider is universal-tier with zero browser/Node APIs; folder layout matches the §10 no-prefix rule (`providers/ecb/ecb.ts`, not `providers/ecb-provider/ecb-provider.ts`); every implementation file has a sibling `.md`; every public module has a subpath export. The enterprise audit (`../suaveplan/enterprise/currency-providers.md`) lists nine "feature-rich enhancements" — rate-limited wrappers, historical-rate endpoints, WebSocket streaming, on-chain oracles, aggregator providers, health monitors, multi-base normalization, deterministic replay, SLO tracking. Those land in follow-up changes once their dependency packages (`@suaveplan/retry`, `@suaveplan/rate-limiter`, `@suaveplan/events`, `@suaveplan/cryptocurrency`'s chain registry) are wired through this surface; the migration baseline matches the upstream behaviour bit-for-bit, fills the three test-coverage gaps the audit flagged (Fixer / OXR / CoinMarketCap had no tests upstream), and unblocks every downstream commerce consumer. ## What changes - ADDED: `EcbProvider` — ECB daily XML feed, no API key, EUR-base with automatic conversion to any requested base. - ADDED: `FixerProvider` — Fixer.io API, requires `apiKey`, EUR-base on free tier. - ADDED: `OpenExchangeRatesProvider` — OXR API, requires `apiKey`, USD-base on free tier, supports `targets` filtering. - ADDED: `BinanceProvider` — Binance spot ticker, no API key, USDT-base by default, real-time prices. - ADDED: `CoinGeckoProvider` + `getCoinGeckoId` helper — CoinGecko `simple/price` endpoint, no API key, 16-symbol built-in registry. - ADDED: `CoinMarketCapProvider` — CMC `cryptocurrency/quotes/latest`, requires `apiKey`, supports configurable target symbols. - ADDED: `CryptoCompareProvider` — CryptoCompare `pricemulti`, no API key, 15-symbol default list. - ADDED: `KrakenProvider` — Kraken `Ticker`, no API key, maps Kraken's non-standard pair names back to canonical symbols. - ADDED: `VirtualCurrencyProvider` — operator-defined game / virtual currencies with runtime `addCurrency` / `updateRate` / `removeCurrency` / `getRate` / `getCurrencyCodes`, anchored against a fiat base, registers every currency with `@suaveplan/money`'s currency registry. - ADDED: `ApiKeyConfig` / `NoKeyConfig` / `HttpFetchOptions` config types (named interfaces, no inline shapes per AGENTS.md §11). - ADDED: `FetchLike` injection on every provider — every `fetchRates` implementation accepts an optional `fetch` in its constructor config, defaulting to `globalThis.fetch`. Tests inject a custom `FetchLike` instead of mocking globals. - ADDED: `CURRENCY_PROVIDER_CODES` registry via `defineCodes("CURRENCY_PROVIDER", [...])` — codes `HTTP_ERROR`, `PARSE_ERROR`, `UPSTREAM_ERROR`, `UNSUPPORTED_BASE`, `MISSING_CONFIG`, `UNKNOWN_CURRENCY`. - ADDED: `CurrencyProviderError extends SuaveplanError` carrying optional `providerName` / `status` / `upstreamMessage` discriminants. ## Out of scope - Rate-limit-aware `RateLimitedProvider` wrapper — pending `@suaveplan/rate-limiter` (exists upstream, not yet migrated to genesis). - Retry + backoff wrapper — pending `@suaveplan/retry` audit for genesis parity (the upstream is migrated but its integration shape with these providers is a follow-up). - Historical-rate endpoints (`fetchHistoricalRates(date)`) — additive surface that requires a coordinated `CurrencyProvider` interface extension in `@suaveplan/currency`. - WebSocket streaming adapters (Binance / Kraken) — additive surface; spec lives in a separate change. - On-chain oracle adapter (Chainlink / Pyth) — pending `@suaveplan/cryptocurrency` chain registry. - `AggregatorProvider` (volume-weighted multi-source average) — depends on the historical-rate surface and rate-limit wrapper. - Provider health-monitor background job — depends on `@suaveplan/job-scheduler` integration. - OpenTelemetry per-request spans / counters — pending coordinated observability surface in `@suaveplan/currency`. - Stablecoin redirect routing — additive optimisation, no public-surface change. ## Source mapping This change derives from `../suaveplan/enterprise/currency-providers.md`. | Requirement | Source lines | Notes | |---|---|---| | 9 providers (3 fiat + 5 crypto + 1 virtual) | L17-L31 | verbatim | | `ApiKeyConfig` / `NoKeyConfig` / `HttpFetchOptions` | L17-L19, L30 | verbatim | | `getCoinGeckoId` helper | L17, L25 | verbatim | | `VirtualCurrencyProvider` runtime mutators | L29, L70-L86 of upstream `virtual.ts` | verbatim | | Test coverage parity for Fixer / OXR / CoinMarketCap | L50-L51 | upstream had 6/9 covered; genesis ships 9/9 | | Typed error hierarchy (replace plain `Error`) | L52-L53 | enhanced: `CurrencyProviderError` + 6 codes | | Injectable `FetchLike` per provider | L40-L41, L66-L67 | enhanced: enables real-fake tests, removes `vi.stubGlobal` | | Universal-tier purity | L60-L69 | verbatim (genesis cross-cutting baseline) | | Subpath exports per provider | upstream `package.json` `exports` | verbatim | | Sibling `.md` per file | L61-L63 | verbatim (genesis cross-cutting) | | 100% Istanbul coverage | L61, L101 | verbatim (genesis cross-cutting) | ### Requirements with no source line | Requirement | Justification | |---|---| | Folder-naming rule §10 (`providers/ecb/ecb.ts`) | Genesis-wide structural rule from `openspec/AGENTS.md §10`; upstream used flat `fiat/ecb.ts` / `crypto/binance.ts` which collides once a 10th provider arrives. | | `@suaveplan/testing/runner` import for tests | Genesis-wide CI gate from `openspec/AGENTS.md §7` / CLAUDE.md rule 11. | | Zero `export *` in feature barrels | Genesis-wide rule §6a / CLAUDE.md rule 10. | | `defineCodes` registry under `@suaveplan/error` | Genesis-wide foundation pattern; upstream threw plain `Error`. | ## Verification approach Every requirement in the spec delta has a `**Verified by:**` line naming concrete `it(...)` titles in these files: - `src/providers/ecb/ecb.test.ts` — ECB XML parsing, base conversion, filtering, error handling, custom `baseUrl`, `FetchLike` injection. - `src/providers/fixer/fixer.test.ts` — Fixer API call, success-payload handling, error-payload handling, params construction, custom base / targets. - `src/providers/open-exchange-rates/open-exchange-rates.test.ts` — OXR endpoint construction, `targets` filtering, timestamp conversion. - `src/providers/binance/binance.test.ts` — symbol-suffix matching, zero-price skip, target filtering, custom `baseUrl`. - `src/providers/coingecko/coingecko.test.ts` — `getCoinGeckoId` case-insensitivity, ID→symbol mapping, non-USD base. - `src/providers/coinmarketcap/coinmarketcap.test.ts` — API-key header, quotes parsing, target list construction. - `src/providers/cryptocompare/cryptocompare.test.ts` — invert-price, zero-price skip, default targets. - `src/providers/kraken/kraken.test.ts` — pair→symbol reverse mapping, Kraken error array, target filtering. - `src/providers/virtual/virtual.test.ts` — `addCurrency` / `updateRate` / `removeCurrency` / `getCurrencyCodes` / `getRate`, virtual-base conversion. - `src/error-codes/error-codes.test.ts` — every `CURRENCY_PROVIDER.*` code reachable; `SuaveplanError` contract preserved. ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/currency-providers` to enumerate. This is a brand-new package on genesis, so no current dependents exist: - No dependents (verified by `bunx turbo ls --filter=...@suaveplan/currency-providers`). Downstream packages that will consume this once they migrate (`@suaveplan/payment`, `@suaveplan/booking`, `api-plugin-virtual-currency`, etc.) are out of scope here. ## Migration impact New package — additive only. No existing consumers; nothing breaks.