Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# BinanceProvider
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`BinanceProvider` is the Binance sibling implementation of the
|
||||
`CurrencyProvider` contract from `@suaveplan/currency`. It fetches the
|
||||
public Binance spot ticker (`GET /api/v3/ticker/price`) — a free,
|
||||
key-less endpoint that returns the latest mid-price for every listed
|
||||
trading pair — and decodes it into a `RateFetchResult` ready for the
|
||||
`CurrencyConverter`'s cache. The provider is universal with a
|
||||
shape that mirrors every other `@suaveplan/currency-provider-<name>`
|
||||
sibling: a `NoKeyConfig`-shaped constructor with optional `baseUrl`
|
||||
override and optional injectable `FetchLike`, a `fetchRates` method that
|
||||
delegates HTTP transport to the slim core's `httpRequest` helper, and
|
||||
typed `CurrencyProviderError`s for every failure mode (no plain
|
||||
`Error`).
|
||||
|
||||
## Features
|
||||
|
||||
- Public Binance ticker endpoint — no API key, no OAuth, no signature
|
||||
- Default base `USDT`; any other suffix-style base supported by Binance
|
||||
(e.g. `EUR`, `BTC`, `ETH`) works by passing `base` to `fetchRates`
|
||||
- Optional `targets` filter raises `UNKNOWN_CURRENCY` for any target the
|
||||
upstream does not list against the requested base — surfaces missing
|
||||
pairs eagerly rather than silently dropping them
|
||||
- Suffix matching: a ticker `BTCUSDT` becomes `BTC` when base is `USDT`;
|
||||
symbols equal to the suffix (`USDTUSDT` quirks) are defensively skipped
|
||||
- Sanitised pricing: zero, negative, and non-finite prices are skipped
|
||||
- Rate inversion: every rate is stored as `1 / price` so the cache
|
||||
answers "1 unit of base = N units of crypto"
|
||||
- Injectable `FetchLike` for tests, proxies, and edge-runtime adapters
|
||||
- Optional `timeoutMs` flows through to `httpRequest` and derives an
|
||||
`AbortSignal.timeout(ms)` composed with the caller's `signal`
|
||||
- Typed errors via `CurrencyProviderError` + the 8-code
|
||||
`CURRENCY_PROVIDER_*` registry — `HTTP_ERROR` (non-2xx), `PARSE_ERROR`
|
||||
(non-JSON body), `UPSTREAM_ERROR` (unexpected shape),
|
||||
`UNKNOWN_CURRENCY` (target absent from upstream listing)
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { CurrencyConverter } from "@suaveplan/currency";
|
||||
import { BinanceProvider } from "@suaveplan/currency-provider-binance";
|
||||
|
||||
const provider = new BinanceProvider();
|
||||
const converter = new CurrencyConverter({
|
||||
provider,
|
||||
staleDurationMs: 30_000,
|
||||
});
|
||||
|
||||
const rate = await converter.getRate("USDT", "BTC");
|
||||
console.log(rate);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { CurrencyProviderRegistry } from "@suaveplan/currency-providers";
|
||||
import {
|
||||
BinanceProvider,
|
||||
BINANCE_PROVIDER_NAME,
|
||||
} from "@suaveplan/currency-provider-binance";
|
||||
import type { FetchLike } from "@suaveplan/types";
|
||||
|
||||
// Wire a constrained FetchLike (corporate proxy, retry harness, etc.).
|
||||
const proxiedFetch: FetchLike = async (url, init) => {
|
||||
return await fetch(`https://proxy.example.com/${String(url)}`, init);
|
||||
};
|
||||
|
||||
const binance = new BinanceProvider({
|
||||
fetch: proxiedFetch,
|
||||
baseUrl: "https://api.binance.com/api/v3",
|
||||
timeoutMs: 8_000,
|
||||
});
|
||||
|
||||
const registry = new CurrencyProviderRegistry();
|
||||
registry.register(binance);
|
||||
|
||||
// Fetch only the pairs we care about — UNKNOWN_CURRENCY surfaces any
|
||||
// upstream listing changes immediately.
|
||||
const result = await registry
|
||||
.get(BINANCE_PROVIDER_NAME)
|
||||
.fetchRates({ base: "USDT", targets: ["BTC", "ETH", "SOL"] });
|
||||
console.log(result.rates);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `BINANCE_PROVIDER_NAME: "binance"`
|
||||
|
||||
Stable provider identifier — surfaced through `provider.name` and used
|
||||
as the registry key.
|
||||
|
||||
### `BINANCE_DEFAULT_BASE_URL: "https://api.binance.com/api/v3"`
|
||||
|
||||
Default REST base URL. Overridable via the constructor `baseUrl` for
|
||||
staging or proxy deployments.
|
||||
|
||||
### `class BinanceProvider implements CurrencyProvider`
|
||||
|
||||
Constructor: `new BinanceProvider(config?: NoKeyConfig & { timeoutMs?: number })`.
|
||||
|
||||
- `config.baseUrl` — overrides `BINANCE_DEFAULT_BASE_URL`
|
||||
- `config.fetch` — optional injectable `FetchLike`; defaults to
|
||||
`globalThis.fetch`
|
||||
- `config.timeoutMs` — optional per-request timeout in milliseconds; when
|
||||
set, derives an `AbortSignal.timeout` composed with the caller's
|
||||
`signal`
|
||||
|
||||
Properties:
|
||||
|
||||
- `name === "binance"` (literal)
|
||||
- `supportsCrypto === true`
|
||||
- `supportsFiat === false`
|
||||
|
||||
Method: `fetchRates(options?: RateFetchOptions): Promise<RateFetchResult>`.
|
||||
|
||||
- `options.base` — base currency suffix (default `"USDT"`)
|
||||
- `options.targets` — optional list of target currencies; raises
|
||||
`UNKNOWN_CURRENCY` for any target absent from the upstream listing
|
||||
- `options.signal` — optional `AbortSignal` forwarded to `httpRequest`
|
||||
|
||||
Throws `CurrencyProviderError` with one of:
|
||||
|
||||
- `CURRENCY_PROVIDER.HTTP_ERROR` — non-2xx response from Binance
|
||||
- `CURRENCY_PROVIDER.PARSE_ERROR` — body is not valid JSON
|
||||
- `CURRENCY_PROVIDER.UPSTREAM_ERROR` — payload is not the expected
|
||||
`Array<{ symbol: string; price: string }>` shape
|
||||
- `CURRENCY_PROVIDER.UNKNOWN_CURRENCY` — a requested target is not
|
||||
listed against the requested base
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Suffix matching: `endsWith(base)` plus a length check rejects symbols
|
||||
whose entire name equals the suffix (`USDTUSDT`-style edge cases),
|
||||
which would otherwise decode to an empty `""` rate key
|
||||
- Pricing: `Number.parseFloat` plus `Number.isFinite && price > 0` is
|
||||
the canonical "is this a usable mid-price" filter — zero, negative,
|
||||
and `NaN` values are silently skipped (not surfaced as errors) because
|
||||
Binance occasionally returns those for delisted-but-still-published
|
||||
symbols
|
||||
- Rate inversion: storing `1 / price` keeps the `RateFetchResult.rates`
|
||||
map answering "1 unit of base = N units of asset", matching the
|
||||
convention used by the rest of `@suaveplan/currency`
|
||||
- Transport: HTTP is delegated entirely to the slim core's
|
||||
`httpRequest` so every sibling provider raises identical `HTTP_ERROR`
|
||||
payloads, supports the same abort/timeout composition, and benefits
|
||||
from any future improvements (retry, circuit breaker) made there
|
||||
- JSON parsing: `response.json()` is wrapped so non-JSON bodies become
|
||||
`PARSE_ERROR` rather than leaking a runtime `SyntaxError`; payload
|
||||
shape validation is a typed structural check (no runtime Zod cost in
|
||||
the universal core)
|
||||
- IP-based rate limiting still applies upstream; the provider is
|
||||
intentionally rate-limit-agnostic — wrap it in the future
|
||||
`RateLimitedProvider` when that ships
|
||||
Reference in New Issue
Block a user