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