@nemesis-oss/binance-sdk 2.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
@@ -26,6 +26,11 @@ const client = new BinanceClient({
26
26
  testnet: true, // or demo: true; defaults to live
27
27
  });
28
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
+
29
34
  // Public market data (no keys needed)
30
35
  const klines = await client.spot.market.klines('SOLUSDT', '15m', { limit: 500 });
31
36
  const funding = await client.futures.data.fundingRateHistory('ETHUSDT', { limit: 100 });
@@ -211,8 +216,13 @@ creation, futures/margin enablement, deposit address/history.
211
216
  `apiKey`, `apiSecret`, `privateKey` + `signatureAlgorithm` (Ed25519/RSA, instead of HMAC),
212
217
  `testnet`, `demo`, `recvWindow`, `apiBase`, `wsBase`, `wsUserBase`, `wsApiBase`, `dapiBase`,
213
218
  `wsSpotApiBase`, `wsDapiBase`, `timeoutMs`, `maxRetries`, `retryBaseDelayMs`, `retryMaxDelayMs`,
214
- `rateLimitWeightPerMinute`, `rateLimitSafetyMargin`, `httpsAgent`, `proxy`, `safety`
215
- (see [Agent safety](#agent-safety)).
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`).
216
226
 
217
227
  ### Reliability
218
228
 
@@ -223,14 +233,118 @@ creation, futures/margin enablement, deposit address/history.
223
233
  `client.getRateLimitUsage()`.
224
234
  - **Server time sync**: `client.syncTime()` fetches each REST host's server time and applies the
225
235
  offset to every signed request's `timestamp`, mitigating `-1021` errors from local clock drift.
226
- - **Retry**: exponential backoff with jitter on `429`/`418`/`5xx`, configurable via
227
- `maxRetries`/`retryBaseDelayMs`/`retryMaxDelayMs`.
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.
228
341
 
229
342
  ### Agent safety
230
343
 
231
- `BinanceClientOptions.safety` configures a `TradingPolicy`, evaluated on every mutating request
232
- before it leaves the process — for autonomous/LLM-driven callers where a misread prompt
233
- shouldn't be able to spend the account. Shared across every product namespace.
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`.
234
348
 
235
349
  ```typescript
236
350
  const client = new BinanceClient({
@@ -239,9 +353,14 @@ const client = new BinanceClient({
239
353
  dryRun: true, // throws DryRunError instead of sending the request
240
354
  allowedSymbols: ['BTCUSDT', 'ETHUSDT'],
241
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
242
360
  allowWithdrawals: false, // default once `safety` is set at all
243
361
  },
244
362
  });
363
+ client.getRiskStatus(); // { circuitBreaker, openNotional: '500', dailyPnl: '-12.5', ... }
245
364
  ```
246
365
 
247
366
  - `dryRun` — intercepts `POST`/`PUT`/`DELETE` and throws `DryRunError` (carrying the request that
@@ -252,6 +371,10 @@ const client = new BinanceClient({
252
371
  - `maxNotionalPerOrder` — caps a single order's notional (from `price*quantity` or
253
372
  `quoteOrderQty`); an order whose notional can't be determined (e.g. a MARKET order with no
254
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).
255
378
  - `allowWithdrawals` / `allowTransfers` / `blockedPaths` — withdrawals are denied by default the
256
379
  moment any `safety` config is set; transfers and arbitrary endpoints can be pinned off too.
257
380
 
@@ -271,13 +394,141 @@ Simulates fills against live public prices — nothing is sent to the exchange.
271
394
  at the requested leverage and released pro-rata as a position is reduced.
272
395
 
273
396
  ```typescript
274
- import { PaperTradingEngine } from '@nemesis-oss/binance-sdk';
397
+ import { PaperTradingEngine, SlippageModel, BinanceUsdmFeeModel } from '@nemesis-oss/binance-sdk';
275
398
 
399
+ // Legacy behaviour (instant fills, zero fees) is the default:
276
400
  const engine = new PaperTradingEngine({ initialBalance: 10_000 });
277
401
  await engine.placeOrder({ symbol: 'BTCUSDT', side: 'BUY', type: 'MARKET', quantity: 0.05, leverage: 5 });
278
402
  await engine.updatePositions(); // re-mark against live prices
279
403
  const close = await engine.placeOrder({ symbol: 'BTCUSDT', side: 'SELL', type: 'MARKET', quantity: 0.05 });
280
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
281
532
  ```
282
533
 
283
534
  ## LLM Tools & MCP
@@ -292,11 +543,31 @@ const { openai, anthropic, mcp } = toolkitToFormats(tk); // tool schemas per for
292
543
  ```
293
544
 
294
545
  - **Tool groups**: `market`, `account`, `trading` (USD-M futures), `spot` (Spot), `ws`
295
- (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
+
296
565
  - **MCP server**: `npx binance-sdk-mcp` (stdio) auto-registers every tool plus reference
297
566
  resources (`binance://futures/symbols`, `binance://futures/premium-index`, `binance://spot/symbols`).
298
567
  For local development against source instead of the published package, point your MCP host at
299
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.
300
571
  - **Agent skills**: Markdown skills under `skills/` — futures trading / market-data / algo /
301
572
  portfolio-margin, plus spot trading / market-data — for Skills-Hub-style agents.
302
573