@nemesis-oss/binance-sdk 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shubham Taywade
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # binance-sdk
2
2
 
3
- TypeScript SDK for Binance — **Spot + USD-M Futures**, REST + WebSocket, public market data
4
- and authenticated trading, with zod-validated typed responses.
3
+ TypeScript SDK for Binance — **Spot, USD-M Futures, COIN-M Futures, Margin, Wallet, and
4
+ Sub-account**, REST + WebSocket, public market data and authenticated trading, with
5
+ zod-validated typed responses throughout.
5
6
 
6
7
  Canonical Binance client for the `trading-workspace` `sdk/` directory (mirrors `sdk/dhanhq-ts`'s
7
- role for DhanHQ). Feature-parity with `binance-client-js` (REST + WS), with typed schemas.
8
+ role for DhanHQ). Feature-parity with `binance-client-js` (REST + WS), with typed schemas, plus
9
+ production infrastructure (header-based rate-limit tracking, server time sync, HMAC/Ed25519/RSA
10
+ signing) and client-side safety guardrails for autonomous/LLM-driven callers.
8
11
 
9
12
  ## Install
10
13
 
@@ -23,6 +26,11 @@ const client = new BinanceClient({
23
26
  testnet: true, // or demo: true; defaults to live
24
27
  });
25
28
 
29
+ // v3: lazy product namespaces over one shared CoreContext — nothing is built
30
+ // until first access, and `client.core` exposes the shared runtime.
31
+ client.core.credentials.canSign; // true
32
+ client.core.http('fapi'); // cached per-host transport
33
+
26
34
  // Public market data (no keys needed)
27
35
  const klines = await client.spot.market.klines('SOLUSDT', '15m', { limit: 500 });
28
36
  const funding = await client.futures.data.fundingRateHistory('ETHUSDT', { limit: 100 });
@@ -91,6 +99,34 @@ client.closeUserStream();
91
99
  await client.startSpotUserStream();
92
100
  client.spot.wsUser.on('executionReport', (event) => console.log(event.s));
93
101
  client.closeSpotUserStream();
102
+
103
+ // COIN-M Futures (inverse contracts, margined in the base asset)
104
+ const coinmBalance = await client.coinm.account.balance();
105
+ await client.coinm.trading.createOrder({
106
+ symbol: 'BTCUSD_PERP',
107
+ side: 'BUY',
108
+ type: 'LIMIT',
109
+ price: 60000,
110
+ quantity: 1, // contracts, not base-asset quantity
111
+ timeInForce: 'GTC',
112
+ });
113
+ client.coinm.ws.subscribe([client.coinm.ws.kline('BTCUSD_PERP', '1m')]);
114
+ await client.startCoinMUserStream();
115
+
116
+ // Margin (cross + isolated)
117
+ const marginAccount = await client.margin.account.crossAccount();
118
+ await client.margin.account.borrow('USDT', 100);
119
+ await client.margin.trading.createOrder({ symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', price: 60000, quantity: 0.001 });
120
+
121
+ // Wallet
122
+ await client.wallet.universalTransfer({ type: 'MAIN_UMFUTURE', asset: 'USDT', amount: 500 });
123
+ const deposits = await client.wallet.depositHistory({ coin: 'USDT' });
124
+
125
+ // Sub-accounts (master account only)
126
+ const subAccounts = await client.subaccount.list();
127
+
128
+ // Server time sync — mitigates -1021 (timestamp outside recvWindow) from local clock drift
129
+ await client.syncTime();
94
130
  ```
95
131
 
96
132
  ## API Surface
@@ -105,6 +141,49 @@ client.closeSpotUserStream();
105
141
  - `ws` — market WebSocket streams (kline, trade, aggTrade, depth incl. diff-depth, ticker incl.
106
142
  rolling-window, bookTicker, miniTicker, avgPrice + all-market/arr variants)
107
143
  - `wsUser` — spot user data stream (executionReport, outboundAccountPosition, balanceUpdate, listStatus)
144
+ - `wsApi` — Spot WebSocket API (`wss://ws-api.binance.com/ws-api/v3`): signed trading
145
+ (`order.place/test/cancel/cancelReplace/status`, `openOrders.status/cancelAll`,
146
+ `orderList.place/cancel/status`, `openOrderLists.status`), account (`account.status/commission`,
147
+ `allOrders`, `allOrderLists`, `myTrades`), `userDataStream.start/ping/stop`, and public market
148
+ data (`ping`, `time`, `exchangeInfo`, `depth`, `trades.recent/historical/aggregate`, `klines`,
149
+ `uiKlines`, `avgPrice`, `ticker.24hr/tradingDay/price/book`) — its own method names, distinct
150
+ from the futures WS API below.
151
+
152
+ ### COIN-M Futures (`client.coinm`)
153
+
154
+ Inverse contracts (e.g. `BTCUSD_PERP`) margined in the base asset rather than USDT — same shape
155
+ as `client.futures`, against `dapi.binance.com`.
156
+
157
+ - `market` — klines (incl. continuous/index/mark-price variants), funding-rate history, open
158
+ interest + history, premium index, plus the standard public REST inherited from spot/futures
159
+ - `account` — balance, account info, position risk, income history, user trades, leverage
160
+ brackets, commission rate, position mode
161
+ - `trading` — order lifecycle (create/test/get/cancel/cancelAll/all), leverage/margin-type
162
+ changes, position margin
163
+ - `userStream` — listenKey lifecycle (create / keep-alive / close)
164
+ - `ws` — market WebSocket streams, same naming scheme as `client.futures.ws`
165
+ - `wsUser` — user data stream (ACCOUNT_UPDATE, ORDER_TRADE_UPDATE, MARGIN_CALL)
166
+
167
+ ### Margin (`client.margin`)
168
+
169
+ Cross and isolated margin trading.
170
+
171
+ - `account` — cross/isolated account info, max borrowable/transferable, `borrow`/`repay` (via
172
+ the unified `borrow-repay` endpoint), cross/isolated transfers, interest history,
173
+ force-liquidation history, price index
174
+ - `trading` — order lifecycle (create/get/cancel/cancelAll/all), myTrades
175
+
176
+ ### Wallet (`client.wallet`)
177
+
178
+ - Universal transfer (+ history) between Spot/Margin/USD-M/COIN-M/Funding
179
+ - Deposit history, deposit address, withdraw history, withdraw submission
180
+ - Funding wallet / user asset queries, account snapshot, API key permissions, asset detail
181
+ - Dust log + dust-to-BNB conversion, trade fees
182
+
183
+ ### Sub-account (`client.subaccount`, master account only)
184
+
185
+ List/status, spot/futures/margin summaries, universal transfer (+ history), virtual sub-account
186
+ creation, futures/margin enablement, deposit address/history.
108
187
 
109
188
  ### Futures (`client.futures`)
110
189
 
@@ -134,13 +213,180 @@ client.closeSpotUserStream();
134
213
 
135
214
  ### Client options
136
215
 
137
- `apiKey`, `apiSecret`, `testnet`, `demo`, `recvWindow`, `apiBase`, `wsBase`, `wsUserBase`,
138
- `wsApiBase`, `timeoutMs`, `maxRetries`, `retryBaseDelayMs`, `retryMaxDelayMs`.
216
+ `apiKey`, `apiSecret`, `privateKey` + `signatureAlgorithm` (Ed25519/RSA, instead of HMAC),
217
+ `testnet`, `demo`, `recvWindow`, `apiBase`, `wsBase`, `wsUserBase`, `wsApiBase`, `dapiBase`,
218
+ `wsSpotApiBase`, `wsDapiBase`, `timeoutMs`, `maxRetries`, `retryBaseDelayMs`, `retryMaxDelayMs`,
219
+ `retryPolicy` (`strict` default / `legacy`), `idempotentPaths`, `rateLimitWeightPerMinute`,
220
+ `rateLimitSafetyMargin`, `httpsAgent`, `proxy`, `events` (observability bus),
221
+ `safety` (see [Agent safety](#agent-safety)).
222
+
223
+ Product aliases: `client.futures.usdm` and `client.futures.coinm` point at the USDⓈ-M and
224
+ COIN-M surfaces; `client.products` gives the product-oriented view
225
+ (`spot` / `usdm` / `coinm` / `margin` / `wallet` / `subaccount`).
226
+
227
+ ### Reliability
228
+
229
+ - **Rate-limit tracking**: `RateLimitTracker` parses `X-MBX-USED-WEIGHT-*` /
230
+ `X-MBX-ORDER-COUNT-*` response headers and delays queued requests once usage crosses a
231
+ configurable safety margin (`rateLimitWeightPerMinute` / `rateLimitSafetyMargin`), to preempt
232
+ `-1003` IP bans rather than react to them. Inspect the live per-host snapshot via
233
+ `client.getRateLimitUsage()`.
234
+ - **Server time sync**: `client.syncTime()` fetches each REST host's server time and applies the
235
+ offset to every signed request's `timestamp`, mitigating `-1021` errors from local clock drift.
236
+ - **Endpoint-aware retries (strict by default)**: exponential backoff with jitter on `429`/`418`
237
+ (rejected before processing — safe for any method) and `5xx`/network errors on idempotent
238
+ methods (GET/DELETE, whitelisted paths such as `order/test`). A `POST` order placement that
239
+ times out is **not** blindly retried — the request may already be live on the exchange.
240
+ Retried signed requests are re-signed with a fresh timestamp. Set `retryPolicy: 'legacy'` to
241
+ restore retry-everything behavior.
242
+ - **WebSocket lifecycle**: every WS client is an explicit state machine
243
+ (`IDLE → CONNECTING → OPEN → RECONNECTING → CLOSING → CLOSED`, via `ws.getState()` and
244
+ `ws.state` events) with race-free reconnection (a retired socket can never trigger a duplicate
245
+ connection) and **proactive 24-hour rotation**: Binance kills stream connections at 24h, so the
246
+ SDK opens a replacement at T-23h and swaps traffic with no data gap.
247
+ - **Subscription acknowledgement**: `await ws.subscribe([...])` resolves only after the server
248
+ confirms the subscription, and every (re)connect verifies the live subscription set with
249
+ `LIST_SUBSCRIPTIONS` and repairs drift automatically.
250
+ - **Exact numbers**: all WS frames are parsed with large-integer preservation (identifiers above
251
+ 2^53 surface as decimal strings instead of silently-corrupted floats — also available as
252
+ `parseJsonLossless` for your own payloads), and money math in the execution/risk/state layers
253
+ runs on a BigInt-backed `Decimal` (`0.1 + 0.2 === 0.3`, exactly).
254
+
255
+ ### Execution (idempotent placement + reconciliation)
256
+
257
+ `client.futures.execution` and `client.spot.execution` are the safe way to place orders — one
258
+ execution semantics across USDⓈ-M, spot and the paper simulator (see
259
+ [Multi-backend execution](#multi-backend-execution-live--paper) below):
260
+
261
+ ```typescript
262
+ const execution = await client.futures.execution.placeOrder({
263
+ symbol: 'BTCUSDT',
264
+ side: 'BUY',
265
+ type: 'MARKET',
266
+ quantity: 0.001,
267
+ intentId: 'my-strategy-signal-42', // idempotency key: replays return the original execution
268
+ });
269
+ // {
270
+ // intentId, clientOrderId, exchangeOrderId, status, executedQuantity: '0.001',
271
+ // averagePrice: '…', fills: [...], reconciliationState: 'acked' | 'reconciled' | …
272
+ // }
273
+ ```
274
+
275
+ - Every order carries a deterministic `newClientOrderId` derived from the intent — the exchange
276
+ itself becomes the idempotency guard; duplicate intents return the recorded execution.
277
+ - On a transport failure the outcome is **reconciled** by polling
278
+ `GET /fapi/v1/order?origClientOrderId=…`: found → the execution is recovered and returned;
279
+ `-2013` (order does not exist) → one safe resubmission with the same clientOrderId; still
280
+ ambiguous → `ExecutionUnknownError` carrying the intent for manual follow-up — never a guess.
281
+ - With a user-data stream attached (`execution.setUserStream(wsUser)`), execution reports
282
+ (`ORDER_TRADE_UPDATE` on futures, `executionReport` on spot) stream live fills into the ledger.
283
+ - `cancelOrder` reconciles the same way, and `execution.reconcile(intentId)` forces a refresh.
284
+
285
+ ### Multi-backend execution (live + paper)
286
+
287
+ Product specifics live behind `ExecutionAdapter` implementations
288
+ (`FuturesExecutionAdapter`, `SpotExecutionAdapter`, `PaperExecutionAdapter`), so paper trading
289
+ is a first-class execution backend — the same envelope, idempotency and reconciliation
290
+ semantics, zero exchange traffic:
291
+
292
+ ```typescript
293
+ // Paper manager: identical API to client.futures.execution
294
+ const paper = client.createPaperExecutionManager({ initialBalance: 50_000 });
295
+ const fill = await paper.placeOrder({
296
+ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: 0.01, intentId: 'strategy-1',
297
+ });
298
+ // fill.status === 'FILLED', fill.fills[0].price — exact decimal strings
299
+
300
+ // Gateway: route live/paper through one interface, independent ledgers
301
+ const gateway = client.createExecutionGateway({ defaultBackend: 'paper' });
302
+ await gateway.placeOrder({ ... }); // paper (default)
303
+ await gateway.placeOrder({ ...order }, { backend: 'live' }); // one live order
304
+ gateway.paperEngine.getAccountInfo(); // simulation state
305
+ ```
306
+
307
+ Standalone single-product clients for focused integrations. v2 factories
308
+ still ship for back-compat; the v3 first-class `ProductClient` classes
309
+ (`SpotClient`, `USDMClient`, `CoinMClient`) own their own `CoreContext`,
310
+ the v3 execution platform (Spot / USDⓈ-M) and the v3 state engine
311
+ (Spot / USDⓈ-M):
312
+
313
+ ```typescript
314
+ import { createSpotClient, createUSDMClient } from '@nemesis-oss/binance-sdk';
315
+ const spot = createSpotClient({ apiKey, apiSecret }); // v2 factory
316
+ await spot.syncTime();
317
+ await spot.market.depth('BTCUSDT', 20);
318
+ spot.close();
319
+
320
+ // v3 first-class product client — owns CoreContext, owns the platform
321
+ import { CoreContext, SpotClient, USDMClient, CoinMClient } from '@nemesis-oss/binance-sdk';
322
+ const core = new CoreContext({ apiKey, apiSecret });
323
+ const usdm = new USDMClient(core);
324
+ await usdm.executionPlatform.startUserSession(); // managed listen-key + 30-min keep-alive
325
+ const book = await usdm.books.watch('BTCUSDT'); // local L2 book, synced
326
+ usdm.close();
327
+ ```
328
+
329
+ ### Local order books
330
+
331
+ ```typescript
332
+ const books = client.createFuturesOrderBookEngine();
333
+ const btc = await books.subscribe('BTCUSDT');
334
+ btc.metrics(); // bestBid / bestAsk / midPrice / spread / spreadBps / microprice /
335
+ // imbalance / bidDepth / askDepth / lastUpdateId / synced
336
+ btc.vwap('bids', 5); // size-weighted VWAP of the top 5 levels (exact decimal string)
337
+ ```
338
+
339
+ The engine seeds each book from a REST snapshot, applies `@depth@100ms` diffs in sequence,
340
+ detects gaps (`pu` / sequence discontinuity) and self-heals with a fresh snapshot.
341
+
342
+ ### Agent safety
343
+
344
+ `BinanceClientOptions.safety` configures a `RiskGateway` (a superset of the v2.0
345
+ `TradingPolicy`), evaluated on every mutating request before it leaves the process — for
346
+ autonomous/LLM-driven callers where a misread prompt shouldn't be able to spend the account.
347
+ Shared across every product namespace, and fed live state by `client.futures.execution`.
348
+
349
+ ```typescript
350
+ const client = new BinanceClient({
351
+ apiKey, apiSecret,
352
+ safety: {
353
+ dryRun: true, // throws DryRunError instead of sending the request
354
+ allowedSymbols: ['BTCUSDT', 'ETHUSDT'],
355
+ maxNotionalPerOrder: 100, // an order whose notional can't be verified is refused
356
+ maxLeverage: 20, // leverage changes above this are refused
357
+ maxOpenNotional: 5_000, // aggregate tracked open-order notional budget
358
+ maxDailyLoss: 1_000, // realized-loss kill switch (UTC day window)
359
+ maxConsecutiveFailures: 5, // exchange-rejection streak circuit breaker
360
+ allowWithdrawals: false, // default once `safety` is set at all
361
+ },
362
+ });
363
+ client.getRiskStatus(); // { circuitBreaker, openNotional: '500', dailyPnl: '-12.5', ... }
364
+ ```
365
+
366
+ - `dryRun` — intercepts `POST`/`PUT`/`DELETE` and throws `DryRunError` (carrying the request that
367
+ would have been sent, via `.describe()`) instead of sending it. It never returns a synthetic
368
+ success payload — a fabricated `orderId` would lead a caller to believe an order exists.
369
+ - `readOnly` — refuses mutating requests outright.
370
+ - `allowedSymbols` — rejects orders naming a symbol outside the list.
371
+ - `maxNotionalPerOrder` — caps a single order's notional (from `price*quantity` or
372
+ `quoteOrderQty`); an order whose notional can't be determined (e.g. a MARKET order with no
373
+ price) is refused rather than waved through.
374
+ - `maxLeverage` / `maxOpenNotional` / `maxDailyLoss` / `maxConsecutiveFailures` — layered
375
+ account-state limits. The circuit breaker trips on the daily loss limit or a rejection streak,
376
+ refuses every mutating request while tripped, and rolls over at the UTC day boundary (or via
377
+ `resetBreaker()` after human review).
378
+ - `allowWithdrawals` / `allowTransfers` / `blockedPaths` — withdrawals are denied by default the
379
+ moment any `safety` config is set; transfers and arbitrary endpoints can be pinned off too.
380
+
381
+ Omitting `safety` entirely leaves behavior exactly as without a policy.
139
382
 
140
383
  ### Errors
141
384
 
142
- `BinanceError` base, `BinanceAuthError`, `BinanceApiError` (code + status), `RateLimitError`,
143
- `NetworkError`.
385
+ `BinanceError` base, `BinanceAuthError`, `BinanceApiError` (`code`, `status`, `endpoint`,
386
+ `method`, response `headers`, plus `isRateLimitError()`/`isTimestampError()`/
387
+ `isInsufficientBalance()`), `RateLimitError` (`retryAfterMs`), `NetworkError`,
388
+ `PolicyViolationError` (a `TradingPolicy` rule blocked the request), `DryRunError` (dry-run
389
+ suppressed the request).
144
390
 
145
391
  ### Paper trading
146
392
 
@@ -148,13 +394,141 @@ Simulates fills against live public prices — nothing is sent to the exchange.
148
394
  at the requested leverage and released pro-rata as a position is reduced.
149
395
 
150
396
  ```typescript
151
- import { PaperTradingEngine } from '@nemesis-oss/binance-sdk';
397
+ import { PaperTradingEngine, SlippageModel, BinanceUsdmFeeModel } from '@nemesis-oss/binance-sdk';
152
398
 
399
+ // Legacy behaviour (instant fills, zero fees) is the default:
153
400
  const engine = new PaperTradingEngine({ initialBalance: 10_000 });
154
401
  await engine.placeOrder({ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: 0.05, leverage: 5 });
155
402
  await engine.updatePositions(); // re-mark against live prices
156
403
  const close = await engine.placeOrder({ symbol: 'BTCUSDT', side: 'SELL', type: 'MARKET', quantity: 0.05 });
157
404
  console.log(close.realizedPnl, engine.getAccountInfo().balance);
405
+
406
+ // Or approximate reality in layers:
407
+ const realistic = new PaperTradingEngine({
408
+ initialBalance: 10_000,
409
+ executionModel: new SlippageModel(10), // 10 bps market impact
410
+ feeModel: new BinanceUsdmFeeModel(), // 5 bps taker / 2 bps maker
411
+ });
412
+ ```
413
+
414
+ Execution models compose: `SlippageModel`, `PartialFillModel`, `OrderBookModel` (VWAP walks
415
+ against a real/injected book), `LatencyModel`, `CompositeModel`. Fee models implement
416
+ `FeeModel` (`TakerMakerFeeModel` / `BinanceUsdmFeeModel`).
417
+
418
+ ### Observability
419
+
420
+ `client.events` is a structured, JSON-serializable event bus wired through every subsystem:
421
+
422
+ ```typescript
423
+ client.events.on('execution.', (event) => console.log(event.scope, event.name, event.payload));
424
+ client.events.on('risk.denied', (event) => audit(event.payload));
425
+ client.events.on('ws.', (event) => console.log(event.name, event.payload));
426
+ // http.request.start / http.request.end / http.request.retry / http.request.error
427
+ // — each carrying contract metadata: product, operation, security, declaredWeight
428
+ // ws.connecting / ws.open / ws.state / ws.reconnecting / ws.rotated / ws.resynced / ws.stale
429
+ // execution.submitted / execution.acked / execution.reconciled / execution.retry / risk.*
430
+ // orderBook.synced / orderBook.desync
431
+ ```
432
+
433
+ Events carry `ts`/`seq`/`scope`/`name`/`payload`; subscribe with exact names, trailing-dot
434
+ prefixes (`'ws.'`) or `'*'`. Pass your own bus via `new BinanceClient({ events })`, or forward
435
+ everything to a logger with `forwardEventsToLogger(bus, logger)`.
436
+
437
+ ### Endpoint maps & agent-native docs
438
+
439
+ - `llms.txt` / `llms-full.txt` — machine-oriented SDK index and flattened endpoint list
440
+ (Binance Agent-Native convention).
441
+ - `docs/endpoint-map/*.md` — per-product tables of every implemented endpoint
442
+ (operation → method → path → auth → SDK surface).
443
+ - **`contracts/` (v3)** — machine-readable inventory of the SDK surface:
444
+ `endpoint-catalog.json` (every REST operation: product, method, path, security, weight,
445
+ implementer), `security-catalog.json` (auth matrix per product),
446
+ `websocket-catalog.json` (WS families, stream ceilings, renewal/liveness policy),
447
+ `catalog.json` (index).
448
+ Generated with `npm run contracts:generate` and CI-validated against the registry —
449
+ the input layer for generated bindings, tool metadata and coverage accounting.
450
+ - `ENDPOINT_REGISTRY` / `listEndpoints({ product, method, authentication })` in code.
451
+ - **Contract layer**: `getContract(product, operation)` / `findContract(path, method)` /
452
+ `contractFor(baseURL, method, path)` — registry entries with normalized security schemes
453
+ (`none`/`apiKey`/`signature`) and declared request weights; `describeContract()` renders a
454
+ one-line description for logs and agents.
455
+ - Regenerate everything from the registry with `npm run docs:generate` (the generator
456
+ cross-checks the registry against the actual `http.<verb>()` calls in `src/resources`)
457
+ and `npm run contracts:generate`.
458
+ - `docs/architecture/v3-foundation.md` — the v3 platform architecture and migration plan.
459
+ - `docs/architecture/v3-websocket.md` — the v3 WebSocket platform design (milestone 2).
460
+ - `docs/architecture/v3-execution.md` — the v3 execution platform design (milestone 3).
461
+ - `docs/architecture/v3-state.md` — the v3 state engine + paper backend design (milestone 4).
462
+
463
+ ## v3 Platform (stable since 3.0.0)
464
+
465
+ `3.0.0` ships the platform rewrite: the foundation (see
466
+ `docs/architecture/v3-foundation.md`), the WebSocket platform (see
467
+ `docs/architecture/v3-websocket.md`), the execution platform (see
468
+ `docs/architecture/v3-execution.md`), the state engine + paper execution
469
+ backend (see `docs/architecture/v3-state.md`), the first-class
470
+ Spot / USDⓈ-M / COIN-M product clients, and the execution audit sink;
471
+ every v2.x surface keeps
472
+ working unchanged.
473
+
474
+ ```typescript
475
+ import { BinanceClient, CoreContext, USDMClient, Credentials } from '@nemesis-oss/binance-sdk';
476
+
477
+ // Standalone product client over a caller-owned shared runtime
478
+ const core = new CoreContext({ apiKey, apiSecret });
479
+ const usdm = new USDMClient(core);
480
+ await core.syncTime(); // one clock sync, every host
481
+ const fill = await usdm.execution.placeOrder({
482
+ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: '0.01',
483
+ });
484
+ usdm.close(); // product-scoped cleanup
485
+
486
+ // Two products, one runtime: shared transports, shared event bus
487
+ const other = new USDMClient(core); // same weight budget, same events
488
+
489
+ // The multi-product facade is lazy now — namespaces build on first access
490
+ // (stable identity) and share client.core:
491
+ const client = new BinanceClient({ apiKey, apiSecret });
492
+ client.core.credentials.describe(); // 'hmac key ****xxxx'
493
+ client.futures.execution === client.futures.execution; // true — cached
494
+
495
+ // --- WebSocket platform (milestone 2): client.ws ---
496
+ const sub = await client.ws.usdm.subscribe('btcusdt@aggTrade');
497
+ sub.on('message', (tick) => console.log(tick.p)); // parsed payload
498
+ await sub.waitForReady();
499
+ client.ws.stats(); // pools, renewal deadlines, WS API state
500
+ await client.ws.api.usdm.accountStatus(); // persistent multiplexed WS API
501
+ sub.close(); // refcounted UNSUBSCRIBE
502
+
503
+ // --- Execution platform (milestone 3): usdm.executionPlatform ---
504
+ const session = await usdm.executionPlatform.startUserSession();
505
+ // listen key created + WS connected + 30-min keep-alive + auto key rotation;
506
+ // the product's execution manager now tracks live fills from the stream:
507
+
508
+ const execution = await usdm.execution.placeOrder({
509
+ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: '0.001',
510
+ });
511
+ const record = usdm.executionPlatform.orders.get(execution.clientOrderId);
512
+ // → live OrderRecord: status, exact decimal quantities, per-trade fills
513
+ await usdm.executionPlatform.orders.waitForTerminal(execution.clientOrderId);
514
+ usdm.executionPlatform.positions.get('BTCUSDT'); // from ACCOUNT_UPDATE
515
+ usdm.executionPlatform.classify(err).safety;
516
+ // → 'safe' | 'idempotent' | 'reconciliation-required' | 'never-retry'
517
+
518
+ // --- State engine + paper backend (milestone 4) ---
519
+ const book = await usdm.books.watch('BTCUSDT'); // local L2 book, synced
520
+ book.bestBid; // exact decimal strings
521
+ book.metrics(); // spread, microprice, imbalance
522
+ await usdm.executionPlatform.reconcile(); // REST fold: startup gap-fill
523
+
524
+ import { createPaperExecutionPlatform } from '@nemesis-oss/binance-sdk';
525
+ const paper = createPaperExecutionPlatform(client.core, { initialBalance: 25_000 });
526
+ await paper.platform.startUserSession(); // local session, zero network
527
+ const fill = await paper.execution.placeOrder({
528
+ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: '0.01',
529
+ });
530
+ paper.platform.orders.get(fill.clientOrderId); // same OrderRecord as live
531
+ paper.platform.positions.get('BTCUSDT'); // same PositionRecord as live
158
532
  ```
159
533
 
160
534
  ## LLM Tools & MCP
@@ -169,11 +543,31 @@ const { openai, anthropic, mcp } = toolkitToFormats(tk); // tool schemas per for
169
543
  ```
170
544
 
171
545
  - **Tool groups**: `market`, `account`, `trading` (USD-M futures), `spot` (Spot), `ws`
172
- (streams + WS API), `derived` (composites like size/close/bracket), `paper`.
546
+ (streams + WS API), `derived` (composites like size/close/bracket), `paper`, and
547
+ `execution` (v2.3).
548
+ - **Execution tools (v2.3)** — order tools routed through the `ExecutionGateway`, so agents
549
+ get idempotency, reconciliation and backend routing for free:
550
+
551
+ ```ts
552
+ // Run the whole toolkit against the simulator — no API-key trading risk
553
+ const tk = createFuturesToolkit(client, { executionBackend: 'paper', paper: { initialBalance: 50_000 } });
554
+
555
+ // Same schema, one extra param per call: backend: 'live' | 'paper' (optional)
556
+ const fill = await call('execution_place_order', {
557
+ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: 0.01,
558
+ intentId: 'strategy-1', // idempotency: retries return the original execution
559
+ });
560
+ // execution_get_order finds intents across both ledgers; execution_status returns
561
+ // default backend + risk-gateway state + paper account; execution_cancel_order
562
+ // resolves already-gone orders to a terminal CANCELED execution (never a thrown -2011).
563
+ ```
564
+
173
565
  - **MCP server**: `npx binance-sdk-mcp` (stdio) auto-registers every tool plus reference
174
566
  resources (`binance://futures/symbols`, `binance://futures/premium-index`, `binance://spot/symbols`).
175
567
  For local development against source instead of the published package, point your MCP host at
176
568
  `mcp-config/local-dev.json` (runs `npx tsx src/mcp/index.ts` directly).
569
+ `createBinanceMcpServer(client, { executionBackend: 'paper' })` builds a paper-mode server —
570
+ every order tool routes through the simulator, safe to expose to untrusted hosts.
177
571
  - **Agent skills**: Markdown skills under `skills/` — futures trading / market-data / algo /
178
572
  portfolio-margin, plus spot trading / market-data — for Skills-Hub-style agents.
179
573
 
@@ -193,6 +587,11 @@ npm test # vitest, HTTP/WS mocked
193
587
  npm run typecheck
194
588
  npm run build # tsup -> dist/ (ESM + CJS + .d.ts)
195
589
  npm run smoke # hits live public Binance endpoints, no keys needed
590
+ npm run smoke:testnet # exercises COIN-M/Margin/Wallet/Sub-account/Spot-WS-API against
591
+ # Binance testnet (see script header for the required env vars);
592
+ # unauthenticated sections still run without keys
196
593
  ```
197
594
 
198
- CI runs typecheck, build and tests on Node 18/20/22; pushing a `v*` tag publishes to npm.
595
+ CI runs typecheck, build and tests on Node LTS and latest. Publishing to npm happens by
596
+ cutting a GitHub Release (`.github/workflows/release.yml` runs on `release: published`), a
597
+ separate, deliberate step — not on every push or tag.