@xapy/orderbook 0.1.24 → 0.1.26

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 (48) hide show
  1. package/README.md +2 -1
  2. package/dist/exchanges/binance/index.d.cts +1 -1
  3. package/dist/exchanges/binance/index.d.ts +1 -1
  4. package/dist/exchanges/bingx/index.d.cts +1 -1
  5. package/dist/exchanges/bingx/index.d.ts +1 -1
  6. package/dist/exchanges/bitget/index.d.cts +1 -1
  7. package/dist/exchanges/bitget/index.d.ts +1 -1
  8. package/dist/exchanges/bybit/index.d.cts +1 -1
  9. package/dist/exchanges/bybit/index.d.ts +1 -1
  10. package/dist/exchanges/coinex/index.d.cts +1 -1
  11. package/dist/exchanges/coinex/index.d.ts +1 -1
  12. package/dist/exchanges/deribit/index.d.cts +1 -1
  13. package/dist/exchanges/deribit/index.d.ts +1 -1
  14. package/dist/exchanges/{huobi → edgex}/index.cjs +181 -122
  15. package/dist/exchanges/edgex/index.cjs.map +1 -0
  16. package/dist/exchanges/edgex/index.d.cts +62 -0
  17. package/dist/exchanges/edgex/index.d.ts +62 -0
  18. package/dist/exchanges/{huobi → edgex}/index.js +177 -120
  19. package/dist/exchanges/edgex/index.js.map +1 -0
  20. package/dist/exchanges/gate/index.d.cts +1 -1
  21. package/dist/exchanges/gate/index.d.ts +1 -1
  22. package/dist/exchanges/grvt/index.d.cts +1 -1
  23. package/dist/exchanges/grvt/index.d.ts +1 -1
  24. package/dist/exchanges/hyperliquid/index.d.cts +1 -1
  25. package/dist/exchanges/hyperliquid/index.d.ts +1 -1
  26. package/dist/exchanges/kucoin/index.d.cts +1 -1
  27. package/dist/exchanges/kucoin/index.d.ts +1 -1
  28. package/dist/exchanges/lighter/index.cjs +664 -0
  29. package/dist/exchanges/lighter/index.cjs.map +1 -0
  30. package/dist/exchanges/lighter/index.d.cts +58 -0
  31. package/dist/exchanges/lighter/index.d.ts +58 -0
  32. package/dist/exchanges/lighter/index.js +658 -0
  33. package/dist/exchanges/lighter/index.js.map +1 -0
  34. package/dist/exchanges/okx/index.d.cts +1 -1
  35. package/dist/exchanges/okx/index.d.ts +1 -1
  36. package/dist/index.cjs +519 -171
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +4 -3
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.js +518 -171
  41. package/dist/index.js.map +1 -1
  42. package/dist/{stream-Con47OAp.d.cts → stream-YYPvjMV4.d.cts} +1 -1
  43. package/dist/{stream-Con47OAp.d.ts → stream-YYPvjMV4.d.ts} +1 -1
  44. package/package.json +24 -12
  45. package/dist/exchanges/huobi/index.cjs.map +0 -1
  46. package/dist/exchanges/huobi/index.d.cts +0 -37
  47. package/dist/exchanges/huobi/index.d.ts +0 -37
  48. package/dist/exchanges/huobi/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1438,167 +1438,8 @@ var BingxClient = class {
1438
1438
  }
1439
1439
  };
1440
1440
 
1441
- // src/exchanges/huobi/symbols.ts
1442
- var QUOTES3 = ["USDT", "USDC", "USD", "BTC", "ETH"];
1443
- function toHuobiSymbol(symbol, market) {
1444
- const [base, quote] = symbol.split("/");
1445
- if (!base || !quote) {
1446
- throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
1447
- }
1448
- return market === "spot" ? `${base}${quote}`.toLowerCase() : `${base}-${quote}`.toUpperCase();
1449
- }
1450
- function fromHuobiSymbol(s, market) {
1451
- if (market === "spot") {
1452
- const upper = s.toUpperCase();
1453
- for (const q of QUOTES3) {
1454
- if (upper.endsWith(q)) return `${upper.slice(0, -q.length)}/${q}`;
1455
- }
1456
- return upper;
1457
- }
1458
- return s.toUpperCase().replace("-", "/");
1459
- }
1460
-
1461
- // src/exchanges/huobi/rest.ts
1462
- var SPOT_BASE = "https://api.huobi.pro";
1463
- var PERP_BASE = "https://api.hbdm.com";
1464
- async function fetchHuobiOrderbook(opts) {
1465
- const symbol = toHuobiSymbol(opts.symbol, opts.market);
1466
- const step = opts.step ?? "step0";
1467
- const url = opts.market === "spot" ? `${SPOT_BASE}/market/depth?symbol=${symbol}&type=${step}` : `${PERP_BASE}/linear-swap-ex/market/depth?contract_code=${symbol}&type=${step}`;
1468
- const res = await httpJson({
1469
- url,
1470
- timeoutMs: opts.timeoutMs,
1471
- transformUrl: opts.transformUrl
1472
- });
1473
- if (res.status !== "ok" || !res.tick) {
1474
- throw new ExchangeError(
1475
- "huobi",
1476
- res["err-msg"] ?? res["err-code"] ?? "missing tick"
1477
- );
1478
- }
1479
- const ts = res.tick.ts;
1480
- return {
1481
- exchange: "huobi",
1482
- symbol: fromHuobiSymbol(symbol, opts.market),
1483
- market: opts.market,
1484
- bids: res.tick.bids.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1485
- asks: res.tick.asks.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1486
- timestamp: ts,
1487
- sequence: res.tick.version ?? ts
1488
- };
1489
- }
1490
-
1491
- // src/exchanges/huobi/ws.ts
1492
- var WS_SPOT4 = "wss://api.huobi.pro/ws";
1493
- var WS_PERP = "wss://api.hbdm.com/linear-swap-ws";
1494
- function streamHuobiOrderbook(opts) {
1495
- const huobiSymbol = toHuobiSymbol(opts.symbol, opts.market);
1496
- const step = opts.step ?? "step0";
1497
- const channel = `market.${huobiSymbol}.depth.${step}`;
1498
- const merger = new BookMerger();
1499
- let ws = null;
1500
- const stream = new OrderbookStream(() => ws?.close());
1501
- const subscribePayload = JSON.stringify({
1502
- sub: channel,
1503
- id: `sub-${Date.now()}`
1504
- });
1505
- const handlePayload = (text) => {
1506
- let msg;
1507
- try {
1508
- msg = JSON.parse(text);
1509
- } catch {
1510
- return;
1511
- }
1512
- if (typeof msg.ping === "number") {
1513
- ws?.send(JSON.stringify({ pong: msg.ping }));
1514
- return;
1515
- }
1516
- if (msg.status === "ok" && msg.subbed) return;
1517
- if (msg.ch !== channel || !msg.tick) return;
1518
- const ts = msg.tick.ts;
1519
- const sequence = msg.tick.version ?? ts;
1520
- const event = {
1521
- kind: "snapshot",
1522
- bids: msg.tick.bids.map(([p, s]) => ({
1523
- price: Number(p),
1524
- size: Number(s)
1525
- })),
1526
- asks: msg.tick.asks.map(([p, s]) => ({
1527
- price: Number(p),
1528
- size: Number(s)
1529
- })),
1530
- sequence,
1531
- timestamp: ts
1532
- };
1533
- const r = merger.apply(event);
1534
- if (!r.ok) return;
1535
- const book = {
1536
- exchange: "huobi",
1537
- symbol: fromHuobiSymbol(huobiSymbol, opts.market),
1538
- market: opts.market,
1539
- bids: r.bids,
1540
- asks: r.asks,
1541
- timestamp: r.timestamp,
1542
- sequence: r.sequence
1543
- };
1544
- stream.emit("update", book);
1545
- };
1546
- const onMessage = async (raw) => {
1547
- if (typeof raw === "string") {
1548
- handlePayload(raw);
1549
- return;
1550
- }
1551
- const inflate = await getGzipInflate();
1552
- handlePayload(inflate(raw));
1553
- };
1554
- ws = new WSClient({
1555
- url: opts.market === "spot" ? WS_SPOT4 : WS_PERP,
1556
- onOpen: (sock) => {
1557
- merger.reset();
1558
- sock.send(subscribePayload);
1559
- },
1560
- onMessage
1561
- // Huobi heartbeat is server-initiated; we reply inline above. No client ping.
1562
- });
1563
- ws.on("open", () => stream.emit("connected"));
1564
- ws.on("close", (r) => stream.emit("disconnected", r));
1565
- ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
1566
- ws.on("error", (e) => stream.emit("error", e));
1567
- ws.connect().catch((e) => stream.emit("error", e));
1568
- return stream;
1569
- }
1570
-
1571
- // src/exchanges/huobi/index.ts
1572
- var HuobiClient = class {
1573
- exchange = "huobi";
1574
- transformUrl;
1575
- timeoutMs;
1576
- constructor(opts = {}) {
1577
- this.transformUrl = opts.transformUrl;
1578
- this.timeoutMs = opts.timeoutMs;
1579
- }
1580
- fetchOrderbook(symbol, _opts = {}) {
1581
- const { pair, market } = parseSymbol(symbol);
1582
- return fetchHuobiOrderbook({
1583
- symbol: pair,
1584
- market,
1585
- timeoutMs: this.timeoutMs,
1586
- transformUrl: this.transformUrl
1587
- });
1588
- }
1589
- streamOrderbook(symbol, _opts = {}) {
1590
- const { pair, market } = parseSymbol(symbol);
1591
- return streamHuobiOrderbook({
1592
- symbol: pair,
1593
- market,
1594
- transformUrl: this.transformUrl,
1595
- timeoutMs: this.timeoutMs
1596
- });
1597
- }
1598
- };
1599
-
1600
1441
  // src/exchanges/coinex/symbols.ts
1601
- var QUOTES4 = ["USDT", "USDC", "USD"];
1442
+ var QUOTES3 = ["USDT", "USDC", "USD"];
1602
1443
  function toCoinexSymbol(symbol) {
1603
1444
  const [base, quote] = symbol.split("/");
1604
1445
  if (!base || !quote) {
@@ -1608,7 +1449,7 @@ function toCoinexSymbol(symbol) {
1608
1449
  }
1609
1450
  function fromCoinexSymbol(s) {
1610
1451
  const upper = s.toUpperCase();
1611
- for (const q of QUOTES4) {
1452
+ for (const q of QUOTES3) {
1612
1453
  if (upper.endsWith(q)) {
1613
1454
  return `${upper.slice(0, -q.length)}/${q}`;
1614
1455
  }
@@ -1643,7 +1484,7 @@ async function fetchCoinexOrderbook(opts) {
1643
1484
  }
1644
1485
 
1645
1486
  // src/exchanges/coinex/ws.ts
1646
- var WS_SPOT5 = "wss://socket.coinex.com/v2/spot";
1487
+ var WS_SPOT4 = "wss://socket.coinex.com/v2/spot";
1647
1488
  var WS_FUTURES2 = "wss://socket.coinex.com/v2/futures";
1648
1489
  function streamCoinexOrderbook(opts) {
1649
1490
  const market = toCoinexSymbol(opts.symbol);
@@ -1755,7 +1596,7 @@ function streamCoinexOrderbook(opts) {
1755
1596
  handlePayload(inflate(raw));
1756
1597
  };
1757
1598
  ws = new WSClient({
1758
- url: opts.market === "spot" ? WS_SPOT5 : WS_FUTURES2,
1599
+ url: opts.market === "spot" ? WS_SPOT4 : WS_FUTURES2,
1759
1600
  onOpen: (sock) => {
1760
1601
  merger.reset();
1761
1602
  lastMerged = null;
@@ -2026,7 +1867,7 @@ var OkxClient = class {
2026
1867
  };
2027
1868
 
2028
1869
  // src/exchanges/binance/symbols.ts
2029
- var QUOTES5 = [
1870
+ var QUOTES4 = [
2030
1871
  "USDT",
2031
1872
  "USDC",
2032
1873
  "FDUSD",
@@ -2046,7 +1887,7 @@ function toBinanceSymbol(symbol) {
2046
1887
  }
2047
1888
  function fromBinanceSymbol(s) {
2048
1889
  const upper = s.toUpperCase();
2049
- for (const q of QUOTES5) {
1890
+ for (const q of QUOTES4) {
2050
1891
  if (upper.endsWith(q) && upper.length > q.length) {
2051
1892
  return `${upper.slice(0, -q.length)}/${q}`;
2052
1893
  }
@@ -2078,13 +1919,13 @@ async function fetchBinanceOrderbook(opts) {
2078
1919
  }
2079
1920
 
2080
1921
  // src/exchanges/binance/ws.ts
2081
- var WS_SPOT6 = "wss://stream.binance.com:9443/ws";
1922
+ var WS_SPOT5 = "wss://stream.binance.com:9443/ws";
2082
1923
  var WS_FUTURES3 = "wss://fstream.binance.com/ws";
2083
1924
  function streamBinanceOrderbook(opts) {
2084
1925
  const symbol = toBinanceSymbol(opts.symbol);
2085
1926
  const isSpot = opts.market === "spot";
2086
1927
  const interval = opts.interval ?? "100ms";
2087
- const wsBase = isSpot ? WS_SPOT6 : WS_FUTURES3;
1928
+ const wsBase = isSpot ? WS_SPOT5 : WS_FUTURES3;
2088
1929
  const wsUrl = `${wsBase}/${symbol.toLowerCase()}@depth@${interval}`;
2089
1930
  const merger = new BookMerger();
2090
1931
  let snapshotId = -1;
@@ -2743,13 +2584,13 @@ function streamDeribitOrderbook(opts) {
2743
2584
  rpc("public/unsubscribe", { channels: [channel] });
2744
2585
  subscribe();
2745
2586
  };
2746
- const toLevels2 = (entries) => entries.map(([action, price, amount]) => ({
2587
+ const toLevels4 = (entries) => entries.map(([action, price, amount]) => ({
2747
2588
  price,
2748
2589
  size: action === "delete" ? 0 : amount
2749
2590
  }));
2750
2591
  const buildEvent2 = (d) => {
2751
- const bids = toLevels2(d.bids);
2752
- const asks = toLevels2(d.asks);
2592
+ const bids = toLevels4(d.bids);
2593
+ const asks = toLevels4(d.asks);
2753
2594
  if (d.type === "snapshot") {
2754
2595
  return {
2755
2596
  kind: "snapshot",
@@ -3097,6 +2938,512 @@ var GrvtClient = class {
3097
2938
  }
3098
2939
  };
3099
2940
 
3100
- export { Backoff, BinanceClient, BingxClient, BitgetClient, BookMerger, BybitClient, CoinexClient, DeribitClient, ExchangeError, GateClient, GrvtClient, HuobiClient, HyperliquidClient, KucoinClient, OkxClient, OrderbookStream, RateLimitError, SequenceGapError, parseSymbol };
2941
+ // src/exchanges/lighter/markets.ts
2942
+ var LIGHTER_REST = "https://mainnet.zklighter.elliot.ai";
2943
+ var cache = null;
2944
+ var inflight = null;
2945
+ var key = (name, market) => `${market}:${name.toUpperCase()}`;
2946
+ async function fetchMarkets(opts) {
2947
+ const res = await httpJson({
2948
+ url: `${LIGHTER_REST}/api/v1/orderBooks`,
2949
+ timeoutMs: opts.timeoutMs,
2950
+ transformUrl: opts.transformUrl
2951
+ });
2952
+ const rows = res.order_books ?? [];
2953
+ if (rows.length === 0) {
2954
+ throw new ExchangeError(
2955
+ "lighter",
2956
+ res.message ? `${res.code}: ${res.message}` : "empty orderBooks response"
2957
+ );
2958
+ }
2959
+ const map = /* @__PURE__ */ new Map();
2960
+ for (const row of rows) {
2961
+ map.set(key(row.symbol, row.market_type === "spot" ? "spot" : "perpetual"), row);
2962
+ }
2963
+ return map;
2964
+ }
2965
+ function loadMarkets(opts) {
2966
+ inflight ??= fetchMarkets(opts).then(
2967
+ (m) => {
2968
+ cache = m;
2969
+ inflight = null;
2970
+ return m;
2971
+ },
2972
+ (err) => {
2973
+ inflight = null;
2974
+ throw err;
2975
+ }
2976
+ );
2977
+ return inflight;
2978
+ }
2979
+ async function resolveLighterMarket(name, market, opts) {
2980
+ const k = key(name, market);
2981
+ const hit = cache?.get(k);
2982
+ const meta = hit ?? (await loadMarkets(opts)).get(k);
2983
+ if (!meta) {
2984
+ throw new ExchangeError(
2985
+ "lighter",
2986
+ `unknown ${market} market "${name}" \u2014 not listed on lighter`
2987
+ );
2988
+ }
2989
+ if (meta.status && meta.status !== "active") {
2990
+ throw new ExchangeError(
2991
+ "lighter",
2992
+ `market "${name}" is ${meta.status} \u2014 no book is published for it`
2993
+ );
2994
+ }
2995
+ return meta;
2996
+ }
2997
+
2998
+ // src/exchanges/lighter/symbols.ts
2999
+ function toLighterSymbol(symbol, market) {
3000
+ const [base, quote] = symbol.split("/");
3001
+ if (!base || !quote) {
3002
+ throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
3003
+ }
3004
+ if (quote.toUpperCase() !== "USDC") {
3005
+ throw new Error(`lighter only supports USDC-quoted pairs; got "${quote}"`);
3006
+ }
3007
+ return market === "perpetual" ? base.toUpperCase() : `${base.toUpperCase()}/${quote.toUpperCase()}`;
3008
+ }
3009
+ function fromLighterSymbol(name) {
3010
+ return name.includes("/") ? name.toUpperCase() : `${name.toUpperCase()}/USDC`;
3011
+ }
3012
+
3013
+ // src/exchanges/lighter/rest.ts
3014
+ var MAX_ORDERS = 250;
3015
+ function usToMs(us) {
3016
+ return Number.isFinite(us) && us > 0 ? Math.floor(us / 1e3) : Date.now();
3017
+ }
3018
+ function aggregate(orders, side, capped) {
3019
+ const byPrice = /* @__PURE__ */ new Map();
3020
+ for (const o of orders ?? []) {
3021
+ const price = Number(o.price);
3022
+ const size = Number(o.remaining_base_amount);
3023
+ if (!Number.isFinite(price) || !Number.isFinite(size)) continue;
3024
+ byPrice.set(price, (byPrice.get(price) ?? 0) + size);
3025
+ }
3026
+ const levels = [];
3027
+ for (const [price, size] of byPrice) levels.push({ price, size });
3028
+ levels.sort((a, b) => side === "bid" ? b.price - a.price : a.price - b.price);
3029
+ if (capped) levels.pop();
3030
+ return levels;
3031
+ }
3032
+ async function fetchLighterOrderbook(opts) {
3033
+ const name = toLighterSymbol(opts.symbol, opts.market);
3034
+ const meta = await resolveLighterMarket(name, opts.market, opts);
3035
+ const res = await httpJson({
3036
+ url: `${LIGHTER_REST}/api/v1/orderBookOrders?market_id=${meta.market_id}&limit=${MAX_ORDERS}`,
3037
+ timeoutMs: opts.timeoutMs,
3038
+ transformUrl: opts.transformUrl
3039
+ });
3040
+ if (!res.asks && !res.bids) {
3041
+ throw new ExchangeError(
3042
+ "lighter",
3043
+ res.message ? `${res.code}: ${res.message}` : "empty orderbook response"
3044
+ );
3045
+ }
3046
+ const depth = opts.depth ?? 50;
3047
+ const bids = aggregate(res.bids, "bid", res.total_bids === MAX_ORDERS);
3048
+ const asks = aggregate(res.asks, "ask", res.total_asks === MAX_ORDERS);
3049
+ const timestamp = Date.now();
3050
+ return {
3051
+ exchange: "lighter",
3052
+ symbol: fromLighterSymbol(name),
3053
+ market: opts.market,
3054
+ bids: bids.slice(0, depth),
3055
+ asks: asks.slice(0, depth),
3056
+ timestamp,
3057
+ // The L3 endpoint publishes no sequence of its own — the WS `nonce` chain
3058
+ // is where ordering is enforced.
3059
+ sequence: timestamp
3060
+ };
3061
+ }
3062
+
3063
+ // src/exchanges/lighter/ws.ts
3064
+ var WS_URL2 = "wss://mainnet.zklighter.elliot.ai/stream";
3065
+ var PING_INTERVAL_MS = 6e4;
3066
+ var toLevels2 = (rows) => (rows ?? []).map((l) => ({ price: Number(l.price), size: Number(l.size) }));
3067
+ function streamLighterOrderbook(opts) {
3068
+ const name = toLighterSymbol(opts.symbol, opts.market);
3069
+ const symbol = fromLighterSymbol(name);
3070
+ const marketId = resolveLighterMarket(name, opts.market, opts).then(
3071
+ (m) => m.market_id
3072
+ );
3073
+ marketId.catch(() => {
3074
+ });
3075
+ const merger = new BookMerger();
3076
+ let ws = null;
3077
+ let sock = null;
3078
+ let channel = null;
3079
+ let recovering = false;
3080
+ const stream = new OrderbookStream(() => ws?.close());
3081
+ const send = (type) => {
3082
+ if (channel) sock?.send(JSON.stringify({ type, channel }));
3083
+ };
3084
+ const emit = (r) => {
3085
+ const book = {
3086
+ exchange: "lighter",
3087
+ symbol,
3088
+ market: opts.market,
3089
+ bids: r.bids,
3090
+ asks: r.asks,
3091
+ timestamp: r.timestamp,
3092
+ sequence: r.sequence
3093
+ };
3094
+ stream.emit("update", book);
3095
+ };
3096
+ const resync = () => {
3097
+ if (recovering) return;
3098
+ recovering = true;
3099
+ merger.reset();
3100
+ send("unsubscribe");
3101
+ send("subscribe");
3102
+ };
3103
+ const buildEvent2 = (msg, snapshot) => {
3104
+ const ob = msg.order_book ?? {};
3105
+ const bids = toLevels2(ob.bids);
3106
+ const asks = toLevels2(ob.asks);
3107
+ const timestamp = msg.timestamp ?? usToMs(ob.last_updated_at);
3108
+ const sequence = Number(ob.nonce ?? 0);
3109
+ return snapshot ? { kind: "snapshot", bids, asks, sequence, timestamp } : {
3110
+ kind: "delta",
3111
+ bids,
3112
+ asks,
3113
+ sequence,
3114
+ prevSequence: Number(ob.begin_nonce ?? 0),
3115
+ timestamp
3116
+ };
3117
+ };
3118
+ const handleBook = (msg, snapshot) => {
3119
+ if (snapshot) recovering = false;
3120
+ else if (recovering) return;
3121
+ const r = merger.apply(buildEvent2(msg, snapshot));
3122
+ if (r.ok) {
3123
+ emit(r);
3124
+ } else if (r.reason === "gap" || r.reason === "no-snapshot") {
3125
+ resync();
3126
+ }
3127
+ };
3128
+ const onMessage = (raw) => {
3129
+ if (typeof raw !== "string") return;
3130
+ let msg;
3131
+ try {
3132
+ msg = JSON.parse(raw);
3133
+ } catch {
3134
+ return;
3135
+ }
3136
+ if (msg.error) {
3137
+ stream.emit(
3138
+ "error",
3139
+ new ExchangeError("lighter", `${msg.error.code}: ${msg.error.message}`)
3140
+ );
3141
+ return;
3142
+ }
3143
+ if (!msg.order_book) return;
3144
+ if (msg.channel && channel && msg.channel !== channel.replace("/", ":")) {
3145
+ return;
3146
+ }
3147
+ if (msg.type === "subscribed/order_book") handleBook(msg, true);
3148
+ else if (msg.type === "update/order_book") handleBook(msg, false);
3149
+ };
3150
+ ws = new WSClient({
3151
+ url: WS_URL2,
3152
+ onOpen: async (handle) => {
3153
+ sock = handle;
3154
+ merger.reset();
3155
+ recovering = false;
3156
+ channel = `order_book/${await marketId}`;
3157
+ send("subscribe");
3158
+ },
3159
+ onMessage,
3160
+ pingIntervalMs: PING_INTERVAL_MS,
3161
+ pingPayload: () => JSON.stringify({ type: "ping" })
3162
+ });
3163
+ ws.on("open", () => stream.emit("connected"));
3164
+ ws.on("close", (r) => stream.emit("disconnected", r));
3165
+ ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
3166
+ ws.on("error", (e) => stream.emit("error", e));
3167
+ ws.connect().catch((e) => stream.emit("error", e));
3168
+ return stream;
3169
+ }
3170
+
3171
+ // src/exchanges/lighter/index.ts
3172
+ var LighterClient = class {
3173
+ exchange = "lighter";
3174
+ transformUrl;
3175
+ timeoutMs;
3176
+ constructor(opts = {}) {
3177
+ this.transformUrl = opts.transformUrl;
3178
+ this.timeoutMs = opts.timeoutMs;
3179
+ }
3180
+ fetchOrderbook(symbol, opts = {}) {
3181
+ const { pair, market } = parseSymbol(symbol);
3182
+ return fetchLighterOrderbook({
3183
+ symbol: pair,
3184
+ market,
3185
+ depth: opts.depth,
3186
+ timeoutMs: this.timeoutMs,
3187
+ transformUrl: this.transformUrl
3188
+ });
3189
+ }
3190
+ streamOrderbook(symbol, opts = {}) {
3191
+ const { pair, market } = parseSymbol(symbol);
3192
+ return streamLighterOrderbook({
3193
+ symbol: pair,
3194
+ market,
3195
+ depth: opts.depth,
3196
+ transformUrl: this.transformUrl,
3197
+ timeoutMs: this.timeoutMs
3198
+ });
3199
+ }
3200
+ };
3201
+
3202
+ // src/exchanges/edgex/markets.ts
3203
+ var EDGEX_REST = "https://edgex-prod-v2.edgex.exchange";
3204
+ var EDGEX_WS = "wss://edgex-quote-prod-v2.edgex.exchange";
3205
+ var cache2 = null;
3206
+ var inflight2 = null;
3207
+ async function fetchContracts(opts) {
3208
+ const res = await httpJson({
3209
+ url: `${EDGEX_REST}/api/v2/public/meta/getMetaData`,
3210
+ timeoutMs: opts.timeoutMs,
3211
+ transformUrl: opts.transformUrl
3212
+ });
3213
+ const rows = res.data?.contractList ?? [];
3214
+ if (rows.length === 0) {
3215
+ throw new ExchangeError(
3216
+ "edgex",
3217
+ res.msg ? `${res.code}: ${res.msg}` : "empty metadata response"
3218
+ );
3219
+ }
3220
+ const map = /* @__PURE__ */ new Map();
3221
+ for (const row of rows) map.set(row.contractName.toUpperCase(), row);
3222
+ return map;
3223
+ }
3224
+ function loadContracts(opts) {
3225
+ inflight2 ??= fetchContracts(opts).then(
3226
+ (m) => {
3227
+ cache2 = m;
3228
+ inflight2 = null;
3229
+ return m;
3230
+ },
3231
+ (err) => {
3232
+ inflight2 = null;
3233
+ throw err;
3234
+ }
3235
+ );
3236
+ return inflight2;
3237
+ }
3238
+ async function resolveEdgexContract(contractName, opts) {
3239
+ const k = contractName.toUpperCase();
3240
+ const hit = cache2?.get(k);
3241
+ const contract = hit ?? (await loadContracts(opts)).get(k);
3242
+ if (!contract) {
3243
+ throw new ExchangeError(
3244
+ "edgex",
3245
+ `unknown contract "${contractName}" \u2014 not listed on edgex`
3246
+ );
3247
+ }
3248
+ return contract;
3249
+ }
3250
+
3251
+ // src/exchanges/edgex/symbols.ts
3252
+ function toEdgexSymbol(symbol, market) {
3253
+ const [base, quote] = symbol.split("/");
3254
+ if (!base || !quote) {
3255
+ throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
3256
+ }
3257
+ if (market !== "perpetual") {
3258
+ throw new Error(
3259
+ `edgex only lists perpetuals; use "${base.toUpperCase()}/${quote.toUpperCase()}:${quote.toUpperCase()}"`
3260
+ );
3261
+ }
3262
+ if (quote.toUpperCase() !== "USDC") {
3263
+ throw new Error(`edgex only supports USDC-quoted pairs; got "${quote}"`);
3264
+ }
3265
+ return `${base.toUpperCase()}${quote.toUpperCase()}`;
3266
+ }
3267
+ function fromEdgexSymbol(contractName) {
3268
+ const name = contractName.toUpperCase();
3269
+ return name.endsWith("USDC") ? `${name.slice(0, -4)}/USDC` : name;
3270
+ }
3271
+
3272
+ // src/exchanges/edgex/rest.ts
3273
+ var ALLOWED_LEVELS = [15, 200];
3274
+ function snapLevel(depth) {
3275
+ return ALLOWED_LEVELS.find((l) => l >= depth) ?? ALLOWED_LEVELS[ALLOWED_LEVELS.length - 1];
3276
+ }
3277
+ var toLevels3 = (rows) => (rows ?? []).map((l) => ({ price: Number(l.price), size: Number(l.size) }));
3278
+ async function fetchEdgexOrderbook(opts) {
3279
+ const contractName = toEdgexSymbol(opts.symbol, opts.market);
3280
+ const contract = await resolveEdgexContract(contractName, opts);
3281
+ const level = snapLevel(opts.depth ?? 50);
3282
+ const res = await httpJson({
3283
+ url: `${EDGEX_REST}/api/v2/public/quote/getDepth?contractId=${contract.contractId}&level=${level}`,
3284
+ timeoutMs: opts.timeoutMs,
3285
+ transformUrl: opts.transformUrl
3286
+ });
3287
+ const depth = res.data?.[0];
3288
+ if (!depth) {
3289
+ throw new ExchangeError(
3290
+ "edgex",
3291
+ res.msg ? `${res.code}: ${res.msg}` : "empty orderbook response"
3292
+ );
3293
+ }
3294
+ const timestamp = Date.now();
3295
+ return {
3296
+ exchange: "edgex",
3297
+ symbol: fromEdgexSymbol(depth.contractName ?? contractName),
3298
+ market: opts.market,
3299
+ bids: toLevels3(depth.bids),
3300
+ asks: toLevels3(depth.asks),
3301
+ timestamp,
3302
+ sequence: Number(depth.endVersion ?? 0)
3303
+ };
3304
+ }
3305
+
3306
+ // src/exchanges/edgex/ws.ts
3307
+ var WS_URL3 = `${EDGEX_WS}/api/v1/public/ws`;
3308
+ function streamEdgexOrderbook(opts) {
3309
+ const contractName = toEdgexSymbol(opts.symbol, opts.market);
3310
+ const symbol = fromEdgexSymbol(contractName);
3311
+ const level = snapLevel(opts.depth ?? 50);
3312
+ const contractId = resolveEdgexContract(contractName, opts).then(
3313
+ (c) => c.contractId
3314
+ );
3315
+ contractId.catch(() => {
3316
+ });
3317
+ const merger = new BookMerger();
3318
+ let ws = null;
3319
+ let sock = null;
3320
+ let channel = null;
3321
+ let recovering = false;
3322
+ const stream = new OrderbookStream(() => ws?.close());
3323
+ const send = (type) => {
3324
+ if (channel) sock?.send(JSON.stringify({ type, channel }));
3325
+ };
3326
+ const emit = (r) => {
3327
+ const book = {
3328
+ exchange: "edgex",
3329
+ symbol,
3330
+ market: opts.market,
3331
+ bids: r.bids,
3332
+ asks: r.asks,
3333
+ timestamp: r.timestamp,
3334
+ sequence: r.sequence
3335
+ };
3336
+ stream.emit("update", book);
3337
+ };
3338
+ const resync = () => {
3339
+ if (recovering) return;
3340
+ recovering = true;
3341
+ merger.reset();
3342
+ send("unsubscribe");
3343
+ send("subscribe");
3344
+ };
3345
+ const buildEvent2 = (d, snapshot) => {
3346
+ const bids = toLevels3(d.bids);
3347
+ const asks = toLevels3(d.asks);
3348
+ const timestamp = Date.now();
3349
+ const sequence = Number(d.endVersion ?? 0);
3350
+ return snapshot ? { kind: "snapshot", bids, asks, sequence, timestamp } : {
3351
+ kind: "delta",
3352
+ bids,
3353
+ asks,
3354
+ sequence,
3355
+ prevSequence: Number(d.startVersion ?? 0),
3356
+ timestamp
3357
+ };
3358
+ };
3359
+ const handleDepth = (d, snapshot) => {
3360
+ if (snapshot) recovering = false;
3361
+ else if (recovering) return;
3362
+ const r = merger.apply(buildEvent2(d, snapshot));
3363
+ if (r.ok) {
3364
+ emit(r);
3365
+ } else if (r.reason === "gap" || r.reason === "no-snapshot") {
3366
+ resync();
3367
+ }
3368
+ };
3369
+ const onMessage = (raw) => {
3370
+ if (typeof raw !== "string") return;
3371
+ let msg;
3372
+ try {
3373
+ msg = JSON.parse(raw);
3374
+ } catch {
3375
+ return;
3376
+ }
3377
+ if (msg.type === "ping") {
3378
+ sock?.send(JSON.stringify({ type: "pong", time: msg.time }));
3379
+ return;
3380
+ }
3381
+ if (msg.type === "error") {
3382
+ const { code, msg: detail } = msg.content ?? {};
3383
+ stream.emit(
3384
+ "error",
3385
+ new ExchangeError(
3386
+ "edgex",
3387
+ code ? `${code}: ${detail ?? ""}`.trim() : "subscribe rejected"
3388
+ )
3389
+ );
3390
+ return;
3391
+ }
3392
+ if (msg.type !== "quote-event") return;
3393
+ if (channel && (msg.channel ?? msg.content?.channel) !== channel) return;
3394
+ const snapshot = msg.content?.dataType?.toLowerCase() === "snapshot";
3395
+ for (const d of msg.content?.data ?? []) handleDepth(d, snapshot);
3396
+ };
3397
+ ws = new WSClient({
3398
+ url: WS_URL3,
3399
+ onOpen: async (handle) => {
3400
+ sock = handle;
3401
+ merger.reset();
3402
+ recovering = false;
3403
+ channel = `depth.${await contractId}.${level}`;
3404
+ send("subscribe");
3405
+ },
3406
+ onMessage
3407
+ });
3408
+ ws.on("open", () => stream.emit("connected"));
3409
+ ws.on("close", (r) => stream.emit("disconnected", r));
3410
+ ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
3411
+ ws.on("error", (e) => stream.emit("error", e));
3412
+ ws.connect().catch((e) => stream.emit("error", e));
3413
+ return stream;
3414
+ }
3415
+
3416
+ // src/exchanges/edgex/index.ts
3417
+ var EdgexClient = class {
3418
+ exchange = "edgex";
3419
+ transformUrl;
3420
+ timeoutMs;
3421
+ constructor(opts = {}) {
3422
+ this.transformUrl = opts.transformUrl;
3423
+ this.timeoutMs = opts.timeoutMs;
3424
+ }
3425
+ fetchOrderbook(symbol, opts = {}) {
3426
+ const { pair, market } = parseSymbol(symbol);
3427
+ return fetchEdgexOrderbook({
3428
+ symbol: pair,
3429
+ market,
3430
+ depth: opts.depth,
3431
+ timeoutMs: this.timeoutMs,
3432
+ transformUrl: this.transformUrl
3433
+ });
3434
+ }
3435
+ streamOrderbook(symbol, opts = {}) {
3436
+ const { pair, market } = parseSymbol(symbol);
3437
+ return streamEdgexOrderbook({
3438
+ symbol: pair,
3439
+ market,
3440
+ depth: opts.depth,
3441
+ transformUrl: this.transformUrl,
3442
+ timeoutMs: this.timeoutMs
3443
+ });
3444
+ }
3445
+ };
3446
+
3447
+ export { Backoff, BinanceClient, BingxClient, BitgetClient, BookMerger, BybitClient, CoinexClient, DeribitClient, EdgexClient, ExchangeError, GateClient, GrvtClient, HyperliquidClient, KucoinClient, LighterClient, OkxClient, OrderbookStream, RateLimitError, SequenceGapError, parseSymbol };
3101
3448
  //# sourceMappingURL=index.js.map
3102
3449
  //# sourceMappingURL=index.js.map