@xapy/orderbook 0.1.21 → 0.1.23

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.
Files changed (80) hide show
  1. package/README.md +14 -9
  2. package/dist/exchanges/binance/index.cjs +21 -6
  3. package/dist/exchanges/binance/index.cjs.map +1 -1
  4. package/dist/exchanges/binance/index.d.cts +1 -1
  5. package/dist/exchanges/binance/index.d.ts +1 -1
  6. package/dist/exchanges/binance/index.js +21 -6
  7. package/dist/exchanges/binance/index.js.map +1 -1
  8. package/dist/exchanges/bingx/index.cjs +38 -11
  9. package/dist/exchanges/bingx/index.cjs.map +1 -1
  10. package/dist/exchanges/bingx/index.d.cts +1 -1
  11. package/dist/exchanges/bingx/index.d.ts +1 -1
  12. package/dist/exchanges/bingx/index.js +38 -11
  13. package/dist/exchanges/bingx/index.js.map +1 -1
  14. package/dist/exchanges/bitget/index.cjs +21 -6
  15. package/dist/exchanges/bitget/index.cjs.map +1 -1
  16. package/dist/exchanges/bitget/index.d.cts +1 -1
  17. package/dist/exchanges/bitget/index.d.ts +1 -1
  18. package/dist/exchanges/bitget/index.js +21 -6
  19. package/dist/exchanges/bitget/index.js.map +1 -1
  20. package/dist/exchanges/bybit/index.cjs +21 -6
  21. package/dist/exchanges/bybit/index.cjs.map +1 -1
  22. package/dist/exchanges/bybit/index.d.cts +1 -1
  23. package/dist/exchanges/bybit/index.d.ts +1 -1
  24. package/dist/exchanges/bybit/index.js +21 -6
  25. package/dist/exchanges/bybit/index.js.map +1 -1
  26. package/dist/exchanges/coinex/index.cjs +160 -63
  27. package/dist/exchanges/coinex/index.cjs.map +1 -1
  28. package/dist/exchanges/coinex/index.d.cts +11 -17
  29. package/dist/exchanges/coinex/index.d.ts +11 -17
  30. package/dist/exchanges/coinex/index.js +160 -63
  31. package/dist/exchanges/coinex/index.js.map +1 -1
  32. package/dist/exchanges/{bitmart → deribit}/index.cjs +174 -148
  33. package/dist/exchanges/deribit/index.cjs.map +1 -0
  34. package/dist/exchanges/deribit/index.d.cts +48 -0
  35. package/dist/exchanges/deribit/index.d.ts +48 -0
  36. package/dist/exchanges/{bitmart → deribit}/index.js +172 -146
  37. package/dist/exchanges/deribit/index.js.map +1 -0
  38. package/dist/exchanges/gate/index.cjs +21 -6
  39. package/dist/exchanges/gate/index.cjs.map +1 -1
  40. package/dist/exchanges/gate/index.d.cts +1 -1
  41. package/dist/exchanges/gate/index.d.ts +1 -1
  42. package/dist/exchanges/gate/index.js +21 -6
  43. package/dist/exchanges/gate/index.js.map +1 -1
  44. package/dist/exchanges/huobi/index.cjs +21 -6
  45. package/dist/exchanges/huobi/index.cjs.map +1 -1
  46. package/dist/exchanges/huobi/index.d.cts +1 -1
  47. package/dist/exchanges/huobi/index.d.ts +1 -1
  48. package/dist/exchanges/huobi/index.js +21 -6
  49. package/dist/exchanges/huobi/index.js.map +1 -1
  50. package/dist/exchanges/hyperliquid/index.cjs +21 -6
  51. package/dist/exchanges/hyperliquid/index.cjs.map +1 -1
  52. package/dist/exchanges/hyperliquid/index.d.cts +1 -1
  53. package/dist/exchanges/hyperliquid/index.d.ts +1 -1
  54. package/dist/exchanges/hyperliquid/index.js +21 -6
  55. package/dist/exchanges/hyperliquid/index.js.map +1 -1
  56. package/dist/exchanges/kucoin/index.cjs +21 -6
  57. package/dist/exchanges/kucoin/index.cjs.map +1 -1
  58. package/dist/exchanges/kucoin/index.d.cts +1 -1
  59. package/dist/exchanges/kucoin/index.d.ts +1 -1
  60. package/dist/exchanges/kucoin/index.js +21 -6
  61. package/dist/exchanges/kucoin/index.js.map +1 -1
  62. package/dist/exchanges/okx/index.cjs +21 -6
  63. package/dist/exchanges/okx/index.cjs.map +1 -1
  64. package/dist/exchanges/okx/index.d.cts +1 -1
  65. package/dist/exchanges/okx/index.d.ts +1 -1
  66. package/dist/exchanges/okx/index.js +21 -6
  67. package/dist/exchanges/okx/index.js.map +1 -1
  68. package/dist/index.cjs +304 -184
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.d.cts +3 -3
  71. package/dist/index.d.ts +3 -3
  72. package/dist/index.js +304 -184
  73. package/dist/index.js.map +1 -1
  74. package/dist/{stream-L9MJjYbE.d.cts → stream-CO3LD9jf.d.cts} +1 -1
  75. package/dist/{stream-L9MJjYbE.d.ts → stream-CO3LD9jf.d.ts} +1 -1
  76. package/package.json +7 -6
  77. package/dist/exchanges/bitmart/index.cjs.map +0 -1
  78. package/dist/exchanges/bitmart/index.d.cts +0 -68
  79. package/dist/exchanges/bitmart/index.d.ts +0 -68
  80. package/dist/exchanges/bitmart/index.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -325,6 +325,21 @@ async function fetchBybitOrderbook(opts) {
325
325
  var hasNativeWebSocket = typeof globalThis.WebSocket !== "undefined";
326
326
 
327
327
  // src/transport/ws.ts
328
+ function toError(value, fallback = "unknown error") {
329
+ if (value instanceof Error) {
330
+ return value.message ? value : new Error(`${value.name}: ${fallback}`);
331
+ }
332
+ if (typeof value === "string" && value) return new Error(value);
333
+ return new Error(fallback);
334
+ }
335
+ function normalizeWsError(ev) {
336
+ const detail = ev?.message || (ev?.error instanceof Error ? ev.error.message : void 0) || (typeof ev?.error === "string" ? ev.error : void 0);
337
+ return new Error(detail ? `WebSocket error: ${detail}` : "WebSocket error");
338
+ }
339
+ function describeClose(ev) {
340
+ if (ev?.reason) return ev.reason;
341
+ return ev?.code === void 0 ? "closed" : `closed (code ${ev.code})`;
342
+ }
328
343
  var cachedCtor = null;
329
344
  async function getWebSocketCtor() {
330
345
  if (cachedCtor) return cachedCtor;
@@ -372,7 +387,7 @@ var WSClient = class extends TypedEmitter {
372
387
  close: () => this.close()
373
388
  });
374
389
  } catch (err) {
375
- this.emit("error", err);
390
+ this.emit("error", toError(err, "onOpen handler failed"));
376
391
  }
377
392
  };
378
393
  ws.onmessage = (e) => {
@@ -380,21 +395,21 @@ var WSClient = class extends TypedEmitter {
380
395
  try {
381
396
  result = this.cfg.onMessage(e.data);
382
397
  } catch (err) {
383
- this.emit("error", err);
398
+ this.emit("error", toError(err, "onMessage handler failed"));
384
399
  return;
385
400
  }
386
401
  if (result && typeof result.catch === "function") {
387
402
  result.catch(
388
- (err) => this.emit("error", err)
403
+ (err) => this.emit("error", toError(err, "onMessage handler failed"))
389
404
  );
390
405
  }
391
406
  };
392
407
  ws.onerror = (e) => {
393
- this.emit("error", new Error(e?.message ?? "WebSocket error"));
408
+ this.emit("error", normalizeWsError(e));
394
409
  };
395
410
  ws.onclose = (e) => {
396
411
  this.stopPing();
397
- const reason = e?.reason ?? "closed";
412
+ const reason = describeClose(e);
398
413
  this.emit("close", reason);
399
414
  if (!this.closed) this.scheduleReconnect();
400
415
  };
@@ -434,7 +449,7 @@ var WSClient = class extends TypedEmitter {
434
449
  }
435
450
  this.emit("reconnecting", next.attempt, next.wait);
436
451
  setTimeout(() => {
437
- this.connect().catch((e) => this.emit("error", e));
452
+ this.connect().catch((e) => this.emit("error", toError(e, "reconnect failed")));
438
453
  }, next.wait);
439
454
  }
440
455
  };
@@ -1234,7 +1249,7 @@ function streamBingxOrderbook(opts) {
1234
1249
  const wsSymbol = opts.symbol.toUpperCase().replace("/", "-");
1235
1250
  const level = opts.depth ?? 20;
1236
1251
  const interval = opts.interval ?? "500ms";
1237
- const depthDataType = `${wsSymbol}@depth${level}@${interval}`;
1252
+ const depthDataType = isSpot ? `${wsSymbol}@depth${level}` : `${wsSymbol}@depth${level}@${interval}`;
1238
1253
  const tickerDataType = `${wsSymbol}@bookTicker`;
1239
1254
  const merger = new BookMerger();
1240
1255
  let ws = null;
@@ -1277,8 +1292,8 @@ function streamBingxOrderbook(opts) {
1277
1292
  };
1278
1293
  stream.emit("update", book);
1279
1294
  };
1280
- const handleDepth = (d) => {
1281
- const ts = d.T ?? d.ts ?? Date.now();
1295
+ const handleDepth = (d, frameTs) => {
1296
+ const ts = d.T ?? d.ts ?? frameTs ?? Date.now();
1282
1297
  const event = {
1283
1298
  kind: "snapshot",
1284
1299
  bids: d.bids.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
@@ -1320,10 +1335,22 @@ function streamBingxOrderbook(opts) {
1320
1335
  } catch {
1321
1336
  return;
1322
1337
  }
1323
- if (msg.id && !msg.data) return;
1338
+ if (msg.id && !msg.data) {
1339
+ if (msg.code) {
1340
+ stream.emit(
1341
+ "error",
1342
+ new ExchangeError(
1343
+ "bingx",
1344
+ `subscribe failed (${msg.id}): ${msg.code}${msg.msg ? ` ${msg.msg}` : ""}`
1345
+ )
1346
+ );
1347
+ }
1348
+ return;
1349
+ }
1324
1350
  if (!msg.dataType || !msg.data) return;
1351
+ const frameTs = msg.ts ?? msg.timestamp;
1325
1352
  if (msg.dataType === depthDataType) {
1326
- handleDepth(msg.data);
1353
+ handleDepth(msg.data, frameTs);
1327
1354
  } else if (msg.dataType === tickerDataType) {
1328
1355
  handleTicker(msg.data);
1329
1356
  }
@@ -1618,61 +1645,93 @@ async function fetchCoinexOrderbook(opts) {
1618
1645
  }
1619
1646
 
1620
1647
  // src/exchanges/coinex/ws.ts
1621
- var WS_SPOT5 = "wss://socket.coinex.com/";
1622
- var WS_FUTURES2 = "wss://perpetual.coinex.com/";
1648
+ var WS_SPOT5 = "wss://socket.coinex.com/v2/spot";
1649
+ var WS_FUTURES2 = "wss://socket.coinex.com/v2/futures";
1623
1650
  function streamCoinexOrderbook(opts) {
1624
1651
  const market = toCoinexSymbol(opts.symbol);
1625
1652
  const depth = opts.depth ?? 50;
1626
1653
  const merger = new BookMerger();
1627
- let seq = 0;
1628
1654
  let ws = null;
1629
1655
  const stream = new OrderbookStream(() => ws?.close());
1630
- const subscribePayload = JSON.stringify({
1656
+ const subscribeDepth = JSON.stringify({
1631
1657
  method: "depth.subscribe",
1632
- params: [market, depth, "0", true],
1658
+ params: {
1659
+ // [market, limit, interval, if_full] — `true` = every push is a full
1660
+ // snapshot; the merger revalidates monotonic ordering by updated_at.
1661
+ market_list: [[market, depth, "0", true]]
1662
+ },
1633
1663
  id: 1
1634
1664
  });
1635
- const pingPayload = () => JSON.stringify({ method: "server.ping", params: [], id: 11 });
1636
- const emit = (r) => {
1665
+ const subscribeBbo = JSON.stringify({
1666
+ method: "bbo.subscribe",
1667
+ params: { market_list: [market] },
1668
+ id: 2
1669
+ });
1670
+ let lastMerged = null;
1671
+ let lastTicker = null;
1672
+ const emitOverlayed = () => {
1673
+ if (!lastMerged) return;
1674
+ let bids = lastMerged.bids;
1675
+ let asks = lastMerged.asks;
1676
+ let timestamp = lastMerged.timestamp;
1677
+ let sequence = lastMerged.sequence;
1678
+ if (lastTicker) {
1679
+ bids = overlayBid2(bids, lastTicker.bid);
1680
+ asks = overlayAsk2(asks, lastTicker.ask);
1681
+ if (lastTicker.ts > timestamp) {
1682
+ timestamp = lastTicker.ts;
1683
+ sequence = Math.max(sequence, lastTicker.ts);
1684
+ }
1685
+ }
1637
1686
  const book = {
1638
1687
  exchange: "coinex",
1639
1688
  symbol: fromCoinexSymbol(market),
1640
1689
  market: opts.market,
1690
+ bids,
1691
+ asks,
1692
+ timestamp,
1693
+ sequence
1694
+ };
1695
+ stream.emit("update", book);
1696
+ };
1697
+ const handleDepth = (d) => {
1698
+ if (d.market !== market) return;
1699
+ const inner = d.depth;
1700
+ const r = merger.apply({
1701
+ kind: "snapshot",
1702
+ bids: inner.bids.map(([p, s]) => ({
1703
+ price: Number(p),
1704
+ size: Number(s)
1705
+ })),
1706
+ asks: inner.asks.map(([p, s]) => ({
1707
+ price: Number(p),
1708
+ size: Number(s)
1709
+ })),
1710
+ sequence: inner.updated_at,
1711
+ timestamp: inner.updated_at
1712
+ });
1713
+ if (!r.ok) return;
1714
+ lastMerged = {
1641
1715
  bids: r.bids,
1642
1716
  asks: r.asks,
1643
1717
  timestamp: r.timestamp,
1644
1718
  sequence: r.sequence
1645
1719
  };
1646
- stream.emit("update", book);
1720
+ emitOverlayed();
1647
1721
  };
1648
- const handleDepthPush = (push) => {
1649
- const [clean, payload, mkt] = push;
1650
- if (mkt !== market) return;
1651
- const bids = parseLevels(payload.bids);
1652
- const asks = parseLevels(payload.asks);
1653
- const event = clean ? {
1654
- kind: "snapshot",
1655
- bids,
1656
- asks,
1657
- sequence: ++seq,
1658
- timestamp: payload.time
1659
- } : {
1660
- kind: "delta",
1661
- bids,
1662
- asks,
1663
- sequence: ++seq,
1664
- timestamp: payload.time
1722
+ const handleBbo = (b) => {
1723
+ if (b.market !== market) return;
1724
+ const bidPrice = Number(b.best_bid_price);
1725
+ const askPrice = Number(b.best_ask_price);
1726
+ const bidSize = Number(b.best_bid_size);
1727
+ const askSize = Number(b.best_ask_size);
1728
+ if (!Number.isFinite(bidPrice) || !Number.isFinite(askPrice)) return;
1729
+ lastTicker = {
1730
+ bid: { price: bidPrice, size: bidSize },
1731
+ ask: { price: askPrice, size: askSize },
1732
+ ts: b.updated_at
1665
1733
  };
1666
- const r = merger.apply(event);
1667
- if (r.ok) {
1668
- emit(r);
1669
- return;
1670
- }
1671
- if (r.reason === "no-snapshot") return;
1672
- if (r.reason === "gap") {
1673
- merger.reset();
1674
- seq = 0;
1675
- }
1734
+ emitOverlayed();
1676
1735
  };
1677
1736
  const handlePayload = (text) => {
1678
1737
  let msg;
@@ -1682,31 +1741,32 @@ function streamCoinexOrderbook(opts) {
1682
1741
  return;
1683
1742
  }
1684
1743
  if (msg.id != null && !msg.method) return;
1685
- if (msg.error) {
1686
- stream.emit(
1687
- "error",
1688
- new Error(`coinex: ${msg.error.code} ${msg.error.message}`)
1689
- );
1690
- return;
1691
- }
1692
- if (msg.method === "depth.update" && Array.isArray(msg.params)) {
1693
- handleDepthPush(msg.params);
1744
+ if (!msg.data || !msg.method) return;
1745
+ if (msg.method === "depth.update") {
1746
+ handleDepth(msg.data);
1747
+ } else if (msg.method === "bbo.update") {
1748
+ handleBbo(msg.data);
1694
1749
  }
1695
1750
  };
1696
- const onMessage = (raw) => {
1697
- if (typeof raw !== "string") return;
1698
- handlePayload(raw);
1751
+ const onMessage = async (raw) => {
1752
+ if (typeof raw === "string") {
1753
+ handlePayload(raw);
1754
+ return;
1755
+ }
1756
+ const inflate = await getGzipInflate();
1757
+ handlePayload(inflate(raw));
1699
1758
  };
1700
1759
  ws = new WSClient({
1701
1760
  url: opts.market === "spot" ? WS_SPOT5 : WS_FUTURES2,
1702
1761
  onOpen: (sock) => {
1703
1762
  merger.reset();
1704
- seq = 0;
1705
- sock.send(subscribePayload);
1763
+ lastMerged = null;
1764
+ lastTicker = null;
1765
+ sock.send(subscribeDepth);
1766
+ sock.send(subscribeBbo);
1706
1767
  },
1707
- onMessage,
1708
- pingIntervalMs: 25e3,
1709
- pingPayload
1768
+ onMessage
1769
+ // Native WS ping/pong frames keep the connection alive — no app-level ping.
1710
1770
  });
1711
1771
  ws.on("open", () => stream.emit("connected"));
1712
1772
  ws.on("close", (r) => stream.emit("disconnected", r));
@@ -1715,11 +1775,31 @@ function streamCoinexOrderbook(opts) {
1715
1775
  ws.connect().catch((e) => stream.emit("error", e));
1716
1776
  return stream;
1717
1777
  }
1718
- function parseLevels(rows) {
1719
- if (!rows) return [];
1720
- const out = [];
1721
- for (const [p, s] of rows) out.push({ price: Number(p), size: Number(s) });
1722
- return out;
1778
+ function overlayAsk2(asks, top) {
1779
+ if (!(top.size > 0)) return asks;
1780
+ let i = 0;
1781
+ for (; i < asks.length; i++) {
1782
+ const level = asks[i];
1783
+ if (!level || level.price >= top.price) break;
1784
+ }
1785
+ const at = asks[i];
1786
+ if (at && at.price === top.price) {
1787
+ return [{ price: top.price, size: top.size }, ...asks.slice(i + 1)];
1788
+ }
1789
+ return [{ price: top.price, size: top.size }, ...asks.slice(i)];
1790
+ }
1791
+ function overlayBid2(bids, top) {
1792
+ if (!(top.size > 0)) return bids;
1793
+ let i = 0;
1794
+ for (; i < bids.length; i++) {
1795
+ const level = bids[i];
1796
+ if (!level || level.price <= top.price) break;
1797
+ }
1798
+ const at = bids[i];
1799
+ if (at && at.price === top.price) {
1800
+ return [{ price: top.price, size: top.size }, ...bids.slice(i + 1)];
1801
+ }
1802
+ return [{ price: top.price, size: top.size }, ...bids.slice(i)];
1723
1803
  }
1724
1804
 
1725
1805
  // src/exchanges/coinex/index.ts
@@ -2417,8 +2497,8 @@ async function fetchKucoinOrderbook(opts) {
2417
2497
  exchange: "kucoin",
2418
2498
  symbol: fromKucoinSpotSymbol(symbol2),
2419
2499
  market: "spot",
2420
- bids: parseLevels2(res2.data.bids),
2421
- asks: parseLevels2(res2.data.asks),
2500
+ bids: parseLevels(res2.data.bids),
2501
+ asks: parseLevels(res2.data.asks),
2422
2502
  timestamp: res2.data.time,
2423
2503
  sequence: Number(res2.data.sequence)
2424
2504
  };
@@ -2437,13 +2517,13 @@ async function fetchKucoinOrderbook(opts) {
2437
2517
  exchange: "kucoin",
2438
2518
  symbol: fromKucoinFuturesSymbol(symbol),
2439
2519
  market: "perpetual",
2440
- bids: parseLevels2(res.data.bids),
2441
- asks: parseLevels2(res.data.asks),
2520
+ bids: parseLevels(res.data.bids),
2521
+ asks: parseLevels(res.data.asks),
2442
2522
  timestamp: res.data.ts,
2443
2523
  sequence: res.data.sequence
2444
2524
  };
2445
2525
  }
2446
- function parseLevels2(rows) {
2526
+ function parseLevels(rows) {
2447
2527
  return rows.map(([p, s]) => ({ price: Number(p), size: Number(s) }));
2448
2528
  }
2449
2529
 
@@ -2569,98 +2649,88 @@ var KucoinClient = class {
2569
2649
  }
2570
2650
  };
2571
2651
 
2572
- // src/exchanges/bitmart/symbols.ts
2573
- var QUOTES6 = ["USDT", "USDC", "USD"];
2574
- function toBitmartSymbol(symbol) {
2652
+ // src/exchanges/deribit/symbols.ts
2653
+ function toDeribitSymbol(symbol, market) {
2575
2654
  const [base, quote] = symbol.split("/");
2576
2655
  if (!base || !quote) {
2577
2656
  throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
2578
2657
  }
2579
- return `${base}${quote}`.toUpperCase();
2580
- }
2581
- function fromBitmartSymbol(s) {
2582
- const upper = s.toUpperCase();
2583
- for (const q of QUOTES6) {
2584
- if (upper.endsWith(q) && upper.length > q.length) {
2585
- return `${upper.slice(0, -q.length)}/${q}`;
2586
- }
2658
+ const pair = `${base}_${quote}`.toUpperCase();
2659
+ if (market !== "perpetual") return pair;
2660
+ if (quote.toUpperCase() !== "USDC") {
2661
+ throw new Error(
2662
+ `deribit linear perpetuals are USDC-margined; got "${quote}" (inverse contracts like BTC-PERPETUAL are unsupported)`
2663
+ );
2587
2664
  }
2588
- return upper;
2665
+ return `${pair}-PERPETUAL`;
2666
+ }
2667
+ function fromDeribitSymbol(instrument) {
2668
+ let s = instrument.toUpperCase();
2669
+ if (s.endsWith("-PERPETUAL")) s = s.slice(0, -"-PERPETUAL".length);
2670
+ const [base, quote] = s.split("_");
2671
+ return base && quote ? `${base}/${quote}` : s;
2589
2672
  }
2590
2673
 
2591
- // src/exchanges/bitmart/rest.ts
2592
- var BASE8 = "https://api-cloud-v2.bitmart.com";
2593
- async function fetchBitmartOrderbook(opts) {
2594
- if (opts.market !== "perpetual") {
2595
- throw new ExchangeError(
2596
- "bitmart",
2597
- "spot is not supported \u2014 only USDT-M perpetual"
2598
- );
2599
- }
2600
- const symbol = toBitmartSymbol(opts.symbol);
2601
- const url = `${BASE8}/contract/public/depth?symbol=${symbol}`;
2674
+ // src/exchanges/deribit/rest.ts
2675
+ var BASE8 = "https://www.deribit.com";
2676
+ var ALLOWED_DEPTHS = [1, 5, 10, 20, 50, 100, 1e3, 1e4];
2677
+ function clampDepth(depth) {
2678
+ return ALLOWED_DEPTHS.find((d) => d >= depth) ?? ALLOWED_DEPTHS[ALLOWED_DEPTHS.length - 1];
2679
+ }
2680
+ async function fetchDeribitOrderbook(opts) {
2681
+ const instrument = toDeribitSymbol(opts.symbol, opts.market);
2682
+ const depth = clampDepth(opts.depth ?? 50);
2683
+ const url = `${BASE8}/api/v2/public/get_order_book?instrument_name=${instrument}&depth=${depth}`;
2602
2684
  const res = await httpJson({
2603
2685
  url,
2604
2686
  timeoutMs: opts.timeoutMs,
2605
2687
  transformUrl: opts.transformUrl
2606
2688
  });
2607
- if (res.code !== 1e3) {
2608
- throw new ExchangeError("bitmart", `${res.code}: ${res.message ?? ""}`);
2689
+ if (res.error) {
2690
+ throw new ExchangeError(
2691
+ "deribit",
2692
+ `${res.error.code}: ${res.error.message}`
2693
+ );
2609
2694
  }
2695
+ const r = res.result;
2696
+ if (!r) throw new ExchangeError("deribit", "empty orderbook response");
2610
2697
  return {
2611
- exchange: "bitmart",
2612
- symbol: fromBitmartSymbol(res.data.symbol),
2613
- market: "perpetual",
2614
- bids: parseLevels3(res.data.bids),
2615
- asks: parseLevels3(res.data.asks),
2616
- timestamp: res.data.timestamp,
2617
- sequence: res.data.timestamp
2698
+ exchange: "deribit",
2699
+ symbol: fromDeribitSymbol(r.instrument_name ?? instrument),
2700
+ market: opts.market,
2701
+ bids: r.bids.map(([price, size]) => ({ price, size })),
2702
+ asks: r.asks.map(([price, size]) => ({ price, size })),
2703
+ timestamp: r.timestamp,
2704
+ sequence: r.change_id
2618
2705
  };
2619
2706
  }
2620
- function parseLevels3(rows) {
2621
- const out = [];
2622
- for (const row of rows) {
2623
- out.push({ price: Number(row[0]), size: Number(row[1]) });
2624
- }
2625
- return out;
2626
- }
2627
2707
 
2628
- // src/exchanges/bitmart/ws.ts
2629
- var WS_FUTURES4 = "wss://openapi-ws-v2.bitmart.com/api?protocol=1.1";
2630
- function streamBitmartOrderbook(opts) {
2631
- if (opts.market !== "perpetual") {
2632
- throw new ExchangeError(
2633
- "bitmart",
2634
- "spot is not supported \u2014 only USDT-M perpetual"
2635
- );
2636
- }
2637
- const symbol = toBitmartSymbol(opts.symbol);
2638
- const level = snapLevel(opts.depth);
2639
- const interval = opts.interval ?? "200ms";
2640
- const channelArg = `futures/depthAll${level}:${symbol}@${interval}`;
2708
+ // src/exchanges/deribit/ws.ts
2709
+ var WS_PUBLIC3 = "wss://www.deribit.com/ws/api/v2";
2710
+ var HEARTBEAT_SECONDS = 30;
2711
+ function streamDeribitOrderbook(opts) {
2712
+ const instrument = toDeribitSymbol(opts.symbol, opts.market);
2713
+ const channel = `book.${instrument}.${opts.interval ?? "100ms"}`;
2641
2714
  const merger = new BookMerger();
2642
2715
  let ws = null;
2716
+ let sock = null;
2717
+ let recovering = false;
2643
2718
  const stream = new OrderbookStream(() => ws?.close());
2644
- const subscribePayload = JSON.stringify({
2645
- action: "subscribe",
2646
- args: [channelArg]
2647
- });
2648
- const pingPayload = () => JSON.stringify({ action: "ping" });
2649
- const handleFrame = (frame) => {
2650
- if (frame.symbol !== symbol) return;
2651
- const event = {
2652
- kind: "snapshot",
2653
- bids: parseLevels4(frame.bids),
2654
- asks: parseLevels4(frame.asks),
2655
- sequence: frame.ms_t,
2656
- timestamp: frame.ms_t
2657
- };
2658
- const r = merger.apply(event);
2659
- if (!r.ok) return;
2719
+ let nextId = 1;
2720
+ const subscribeIds = /* @__PURE__ */ new Set();
2721
+ const rpc = (method, params) => {
2722
+ const id = nextId++;
2723
+ sock?.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
2724
+ return id;
2725
+ };
2726
+ const subscribe = () => {
2727
+ subscribeIds.add(rpc("public/subscribe", { channels: [channel] }));
2728
+ };
2729
+ const emit = (r) => {
2660
2730
  const book = {
2661
- exchange: "bitmart",
2662
- symbol: fromBitmartSymbol(symbol),
2663
- market: "perpetual",
2731
+ exchange: "deribit",
2732
+ symbol: fromDeribitSymbol(instrument),
2733
+ market: opts.market,
2664
2734
  bids: r.bids,
2665
2735
  asks: r.asks,
2666
2736
  timestamp: r.timestamp,
@@ -2668,41 +2738,101 @@ function streamBitmartOrderbook(opts) {
2668
2738
  };
2669
2739
  stream.emit("update", book);
2670
2740
  };
2671
- const handlePayload = (text) => {
2741
+ const resync = () => {
2742
+ if (recovering) return;
2743
+ recovering = true;
2744
+ merger.reset();
2745
+ rpc("public/unsubscribe", { channels: [channel] });
2746
+ subscribe();
2747
+ };
2748
+ const toLevels = (entries) => entries.map(([action, price, amount]) => ({
2749
+ price,
2750
+ size: action === "delete" ? 0 : amount
2751
+ }));
2752
+ const buildEvent2 = (d) => {
2753
+ const bids = toLevels(d.bids);
2754
+ const asks = toLevels(d.asks);
2755
+ if (d.type === "snapshot") {
2756
+ return {
2757
+ kind: "snapshot",
2758
+ bids,
2759
+ asks,
2760
+ sequence: d.change_id,
2761
+ timestamp: d.timestamp
2762
+ };
2763
+ }
2764
+ return {
2765
+ kind: "delta",
2766
+ bids,
2767
+ asks,
2768
+ sequence: d.change_id,
2769
+ prevSequence: d.prev_change_id,
2770
+ timestamp: d.timestamp
2771
+ };
2772
+ };
2773
+ const handleData = (d) => {
2774
+ if (d.type === "snapshot") recovering = false;
2775
+ else if (recovering) return;
2776
+ const r = merger.apply(buildEvent2(d));
2777
+ if (r.ok) {
2778
+ emit(r);
2779
+ } else if (r.reason === "gap" || r.reason === "no-snapshot") {
2780
+ resync();
2781
+ }
2782
+ };
2783
+ const onMessage = (raw) => {
2784
+ if (typeof raw !== "string") return;
2672
2785
  let msg;
2673
2786
  try {
2674
- msg = JSON.parse(text);
2787
+ msg = JSON.parse(raw);
2675
2788
  } catch {
2676
2789
  return;
2677
2790
  }
2678
- if (msg.action === "subscribe" || msg.action === "pong") return;
2679
- if (msg.errorCode) {
2680
- stream.emit(
2681
- "error",
2682
- new Error(`bitmart: ${msg.errorCode} ${msg.errorMessage ?? ""}`)
2683
- );
2791
+ if (msg.method === "heartbeat") {
2792
+ if (msg.params?.type === "test_request") rpc("public/test", {});
2684
2793
  return;
2685
2794
  }
2686
- if (msg.group !== channelArg || !msg.data) return;
2687
- handleFrame(msg.data);
2688
- };
2689
- const onMessage = async (raw) => {
2690
- if (typeof raw === "string") {
2691
- handlePayload(raw);
2795
+ if (msg.method === "subscription") {
2796
+ if (msg.params?.channel !== channel || !msg.params.data) return;
2797
+ handleData(msg.params.data);
2692
2798
  return;
2693
2799
  }
2694
- const inflate = await getGzipInflate();
2695
- handlePayload(inflate(raw));
2800
+ if (msg.id !== void 0) {
2801
+ if (msg.error) {
2802
+ stream.emit(
2803
+ "error",
2804
+ new ExchangeError(
2805
+ "deribit",
2806
+ `${msg.error.code}: ${msg.error.message}`
2807
+ )
2808
+ );
2809
+ return;
2810
+ }
2811
+ if (subscribeIds.delete(msg.id)) {
2812
+ const channels = Array.isArray(msg.result) ? msg.result : [];
2813
+ if (!channels.includes(channel)) {
2814
+ stream.emit(
2815
+ "error",
2816
+ new ExchangeError(
2817
+ "deribit",
2818
+ `subscribe rejected for "${channel}" \u2014 unknown instrument or interval`
2819
+ )
2820
+ );
2821
+ }
2822
+ }
2823
+ }
2696
2824
  };
2697
2825
  ws = new WSClient({
2698
- url: WS_FUTURES4,
2699
- onOpen: (sock) => {
2826
+ url: WS_PUBLIC3,
2827
+ onOpen: (handle) => {
2828
+ sock = handle;
2700
2829
  merger.reset();
2701
- sock.send(subscribePayload);
2830
+ recovering = false;
2831
+ subscribeIds.clear();
2832
+ rpc("public/set_heartbeat", { interval: HEARTBEAT_SECONDS });
2833
+ subscribe();
2702
2834
  },
2703
- onMessage,
2704
- pingIntervalMs: 15e3,
2705
- pingPayload
2835
+ onMessage
2706
2836
  });
2707
2837
  ws.on("open", () => stream.emit("connected"));
2708
2838
  ws.on("close", (r) => stream.emit("disconnected", r));
@@ -2711,21 +2841,10 @@ function streamBitmartOrderbook(opts) {
2711
2841
  ws.connect().catch((e) => stream.emit("error", e));
2712
2842
  return stream;
2713
2843
  }
2714
- function snapLevel(depth) {
2715
- if (depth === 5) return 5;
2716
- if (depth === 20) return 20;
2717
- return 50;
2718
- }
2719
- function parseLevels4(rows) {
2720
- if (!rows) return [];
2721
- const out = [];
2722
- for (const r of rows) out.push({ price: Number(r.price), size: Number(r.vol) });
2723
- return out;
2724
- }
2725
2844
 
2726
- // src/exchanges/bitmart/index.ts
2727
- var BitmartClient = class {
2728
- exchange = "bitmart";
2845
+ // src/exchanges/deribit/index.ts
2846
+ var DeribitClient = class {
2847
+ exchange = "deribit";
2729
2848
  transformUrl;
2730
2849
  timeoutMs;
2731
2850
  constructor(opts = {}) {
@@ -2734,7 +2853,7 @@ var BitmartClient = class {
2734
2853
  }
2735
2854
  fetchOrderbook(symbol, opts = {}) {
2736
2855
  const { pair, market } = parseSymbol(symbol);
2737
- return fetchBitmartOrderbook({
2856
+ return fetchDeribitOrderbook({
2738
2857
  symbol: pair,
2739
2858
  market,
2740
2859
  depth: opts.depth,
@@ -2742,11 +2861,12 @@ var BitmartClient = class {
2742
2861
  transformUrl: this.transformUrl
2743
2862
  });
2744
2863
  }
2745
- streamOrderbook(symbol, _opts = {}) {
2864
+ streamOrderbook(symbol, opts = {}) {
2746
2865
  const { pair, market } = parseSymbol(symbol);
2747
- return streamBitmartOrderbook({
2866
+ return streamDeribitOrderbook({
2748
2867
  symbol: pair,
2749
2868
  market,
2869
+ depth: opts.depth,
2750
2870
  transformUrl: this.transformUrl,
2751
2871
  timeoutMs: this.timeoutMs
2752
2872
  });
@@ -2757,10 +2877,10 @@ exports.Backoff = Backoff;
2757
2877
  exports.BinanceClient = BinanceClient;
2758
2878
  exports.BingxClient = BingxClient;
2759
2879
  exports.BitgetClient = BitgetClient;
2760
- exports.BitmartClient = BitmartClient;
2761
2880
  exports.BookMerger = BookMerger;
2762
2881
  exports.BybitClient = BybitClient;
2763
2882
  exports.CoinexClient = CoinexClient;
2883
+ exports.DeribitClient = DeribitClient;
2764
2884
  exports.ExchangeError = ExchangeError;
2765
2885
  exports.GateClient = GateClient;
2766
2886
  exports.HuobiClient = HuobiClient;