Files
2026-05-27 23:29:53 +01:00

7.5 KiB

Add @suaveplan/currency-provider-binance — Binance public-ticker provider

Package: @suaveplan/currency-provider-binance Status: ARCHIVED Type: feature (sibling provider in the slim currency-providers wave)

  • Design: ./design.md
  • Spec delta: ./specs/currency-provider-binance/spec.md

Why

The 2026-05-17 slim refactor of @suaveplan/currency-providers removed nine concrete provider classes from the umbrella package and committed to landing each as its own @suaveplan/currency-provider-<name> sibling. BinanceProvider is the Binance leg of that wave. It is the key-less crypto-rates provider in the line-up — alongside CoinGecko, CryptoCompare, and Kraken — and the only one that ships sub-second trading-desk pricing. Without it the slim core has no working crypto provider out of the box; downstream agents would have to hand-roll one, which is exactly the bug the slim refactor exists to fix.

This change ports the Binance implementation that previously lived at packages/finance/currency-providers/src/crypto/binance.ts upstream into the genesis monorepo as a standalone package, with three quality improvements over the upstream form:

  1. HTTP transport is delegated to the slim core's httpRequest helper, so HTTP_ERROR carries the typed CurrencyProviderError shape instead of a plain Error and abort/timeout composition is uniform across every sibling.
  2. Failure paths are split into the four typed codes the slim spec defines (HTTP_ERROR, PARSE_ERROR, UPSTREAM_ERROR, UNKNOWN_CURRENCY) — the upstream form raised a single plain Error on the HTTP path and silently dropped malformed payloads or missing targets.
  3. Payload shape is structurally validated (array of { symbol: string; price: string }); upstream took the wire shape on faith and would TypeError on a Cloudflare error page.

What changes

ADDED

  • BinanceProvider — class implementing the CurrencyProvider contract on top of the Binance public spot ticker (GET /api/v3/ticker/price).
  • BINANCE_PROVIDER_NAME ("binance") and BINANCE_DEFAULT_BASE_URL ("https://api.binance.com/api/v3") constants so consumers can wire the registry and overrides without string literals.

NOT INCLUDED (out of scope — same deferrals as the slim core)

  • WebSocket streaming (Binance /ws/!ticker@arr) — REST-only for v1
  • Historical klines / candlesticks
  • Authenticated endpoints (account, orders) — non-goal for a CurrencyProvider
  • Per-call rate-limit accounting — deferred to the future RateLimitedProvider wrapper from the slim core

Source mapping

Module Upstream source
BinanceProvider ../suaveplan/packages/finance/currency-providers/src/crypto/binance.ts
Sibling-shape contract openspec/archive/2026-05-17-slim-currency-providers/design.md
Slim core surface consumed packages/finance/currency-providers/src/{http,error-codes,types}/

The new genesis-original wrappers (httpRequest delegation, typed PARSE_ERROR / UPSTREAM_ERROR / UNKNOWN_CURRENCY paths, structural payload validation) are listed in the design rationale; they have no upstream analogue because upstream raised plain Error and ignored payload shape.

Parity Checklist

Capability Upstream Migrated Notes
Class-based BinanceProvider implements CurrencyProvider ✅ ✅ Identical constructor shape; takes NoKeyConfig now (super-set of upstream's optional { baseUrl }).
name === "binance", supportsCrypto === true, supportsFiat === false ✅ ✅ Verbatim. Exposed as BINANCE_PROVIDER_NAME for registry wiring.
Default base URL https://api.binance.com/api/v3 ✅ ✅ Exported as BINANCE_DEFAULT_BASE_URL constant.
Custom baseUrl override ✅ ✅ Via NoKeyConfig.baseUrl.
GET /ticker/price fetch ✅ ✅ Now via slim-core httpRequest.
Default base USDT ✅ ✅ Verbatim.
Suffix-match decoding (BTCUSDT → BTC when base is USDT) ✅ ✅ Plus a defensive length check that rejects symbols equal to the suffix.
Skip zero-price tickers ✅ ✅ Verbatim.
Skip negative-price / non-finite tickers 🚫 ⬆️ Upstream only checked price > 0 — fails open on NaN. Genesis form adds Number.isFinite.
Optional targets filter ✅ ⬆️ Upstream silently dropped missing targets; genesis raises UNKNOWN_CURRENCY so consumers learn about upstream listing changes immediately.
Plain Error on non-2xx HTTP ✅ 🚫 Replaced by typed CurrencyProviderError(HTTP_ERROR) raised by the slim core's httpRequest. Reason: the slim core's contract requires typed errors from every sibling.
Structural payload validation 🚫 ⬆️ Upstream cast the response shape without checking; genesis raises UPSTREAM_ERROR on malformed rows.
PARSE_ERROR on non-JSON body 🚫 ⬆️ Upstream would leak a runtime SyntaxError; genesis wraps response.json() in a typed catch.
Injectable FetchLike 🚫 ⬆️ Upstream used globalThis.fetch directly; genesis form takes a NoKeyConfig.fetch override for tests / proxies / edge runtimes.
Optional per-request timeout 🚫 ⬆️ New: timeoutMs flows through to httpRequest and composes with the caller's signal.
AbortSignal propagation from RateFetchOptions 🚫 ⬆️ Upstream did not honour options.signal; genesis forwards it through httpRequest.
Upstream test suite (vi.spyOn-based fetch mock) ✅ ⬆️ Rewritten to @suaveplan/testing/runner with a recording FetchLike closure — no vi.mock, no vi.spyOn.
Sibling .md per source file + ≥200-word README 🚫 ⬆️ Upstream binance.md was 46 lines; genesis form ships the §4 doc contract.

No 🚫 row is left without justification.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/currency-provider-binance to enumerate.

  • No current dependents (verified by bunx turbo ls --filter=...@suaveplan/currency-provider-binance — this is a new package and downstream commerce stacks consume it through the CurrencyProviderRegistry lookup, not by direct import).
  • @suaveplan/currency-providers — [ ] tests run / [x] docs reviewed / [x] no action needed (this package consumes the slim core; the slim core has no knowledge of any sibling and was not modified)
  • @suaveplan/currency — [ ] tests run / [x] docs reviewed / [x] no action needed (we depend on the CurrencyProvider contract — the contract is unchanged)

Migration

There is no migration path because this is a new package. Consumers that previously used the upstream BinanceProvider from the pre-slim-refactor @suaveplan/currency-providers (which has never shipped on genesis) replace:

// Before — upstream pre-slim-refactor form
import { BinanceProvider } from "@suaveplan/currency-providers";

with:

// After — genesis sibling form
import { BinanceProvider } from "@suaveplan/currency-provider-binance";

Constructor signatures are compatible: new BinanceProvider() and new BinanceProvider({ baseUrl: "…" }) both work unchanged. New capabilities (fetch, timeoutMs) are additive and optional.

Out of scope

  • The other eight sibling provider packages — each lands under its own OpenSpec change per AGENTS.md §0c.
  • Streaming / WebSocket / authenticated endpoints — REST-only for v1.
  • Retry orchestration, circuit breaker, rate-limit accounting — those ship as wrapper providers consuming the CurrencyProvider contract.