@xapy/orderbook 0.1.3 → 0.1.5

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 (69) hide show
  1. package/dist/exchanges/binance/index.cjs +605 -0
  2. package/dist/exchanges/binance/index.cjs.map +1 -0
  3. package/dist/exchanges/binance/index.d.cts +60 -0
  4. package/dist/exchanges/binance/index.d.ts +60 -0
  5. package/dist/exchanges/binance/index.js +601 -0
  6. package/dist/exchanges/binance/index.js.map +1 -0
  7. package/dist/exchanges/bingx/index.cjs +104 -24
  8. package/dist/exchanges/bingx/index.cjs.map +1 -1
  9. package/dist/exchanges/bingx/index.d.cts +10 -3
  10. package/dist/exchanges/bingx/index.d.ts +10 -3
  11. package/dist/exchanges/bingx/index.js +104 -24
  12. package/dist/exchanges/bingx/index.js.map +1 -1
  13. package/dist/exchanges/bitget/index.cjs +2 -1
  14. package/dist/exchanges/bitget/index.cjs.map +1 -1
  15. package/dist/exchanges/bitget/index.d.cts +1 -1
  16. package/dist/exchanges/bitget/index.d.ts +1 -1
  17. package/dist/exchanges/bitget/index.js +2 -1
  18. package/dist/exchanges/bitget/index.js.map +1 -1
  19. package/dist/exchanges/bybit/index.cjs +2 -1
  20. package/dist/exchanges/bybit/index.cjs.map +1 -1
  21. package/dist/exchanges/bybit/index.d.cts +1 -1
  22. package/dist/exchanges/bybit/index.d.ts +1 -1
  23. package/dist/exchanges/bybit/index.js +2 -1
  24. package/dist/exchanges/bybit/index.js.map +1 -1
  25. package/dist/exchanges/coinex/index.cjs +2 -1
  26. package/dist/exchanges/coinex/index.cjs.map +1 -1
  27. package/dist/exchanges/coinex/index.d.cts +1 -1
  28. package/dist/exchanges/coinex/index.d.ts +1 -1
  29. package/dist/exchanges/coinex/index.js +2 -1
  30. package/dist/exchanges/coinex/index.js.map +1 -1
  31. package/dist/exchanges/gate/index.cjs +2 -1
  32. package/dist/exchanges/gate/index.cjs.map +1 -1
  33. package/dist/exchanges/gate/index.d.cts +1 -1
  34. package/dist/exchanges/gate/index.d.ts +1 -1
  35. package/dist/exchanges/gate/index.js +2 -1
  36. package/dist/exchanges/gate/index.js.map +1 -1
  37. package/dist/exchanges/huobi/index.cjs +2 -1
  38. package/dist/exchanges/huobi/index.cjs.map +1 -1
  39. package/dist/exchanges/huobi/index.d.cts +1 -1
  40. package/dist/exchanges/huobi/index.d.ts +1 -1
  41. package/dist/exchanges/huobi/index.js +2 -1
  42. package/dist/exchanges/huobi/index.js.map +1 -1
  43. package/dist/exchanges/hyperliquid/index.cjs +536 -0
  44. package/dist/exchanges/hyperliquid/index.cjs.map +1 -0
  45. package/dist/exchanges/hyperliquid/index.d.cts +45 -0
  46. package/dist/exchanges/hyperliquid/index.d.ts +45 -0
  47. package/dist/exchanges/hyperliquid/index.js +532 -0
  48. package/dist/exchanges/hyperliquid/index.js.map +1 -0
  49. package/dist/exchanges/kucoin/index.cjs +614 -0
  50. package/dist/exchanges/kucoin/index.cjs.map +1 -0
  51. package/dist/exchanges/kucoin/index.d.cts +60 -0
  52. package/dist/exchanges/kucoin/index.d.ts +60 -0
  53. package/dist/exchanges/kucoin/index.js +609 -0
  54. package/dist/exchanges/kucoin/index.js.map +1 -0
  55. package/dist/exchanges/okx/index.cjs +2 -1
  56. package/dist/exchanges/okx/index.cjs.map +1 -1
  57. package/dist/exchanges/okx/index.d.cts +1 -1
  58. package/dist/exchanges/okx/index.d.ts +1 -1
  59. package/dist/exchanges/okx/index.js +2 -1
  60. package/dist/exchanges/okx/index.js.map +1 -1
  61. package/dist/index.cjs +729 -24
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/index.d.cts +5 -2
  64. package/dist/index.d.ts +5 -2
  65. package/dist/index.js +727 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/{stream-BKpWmRYN.d.cts → stream-C73HPYqT.d.cts} +1 -1
  68. package/dist/{stream-BKpWmRYN.d.ts → stream-C73HPYqT.d.ts} +1 -1
  69. package/package.json +34 -1
package/dist/index.js CHANGED
@@ -356,7 +356,8 @@ var WSClient = class extends TypedEmitter {
356
356
  async connect() {
357
357
  if (this.closed) return;
358
358
  const WS = await getWebSocketCtor();
359
- const ws = new WS(this.cfg.url);
359
+ const url = typeof this.cfg.url === "function" ? await this.cfg.url() : this.cfg.url;
360
+ const ws = new WS(url);
360
361
  ws.binaryType = "arraybuffer";
361
362
  this.ws = ws;
362
363
  ws.onopen = async () => {
@@ -1231,29 +1232,50 @@ function streamBingxOrderbook(opts) {
1231
1232
  const wsSymbol = opts.symbol.toUpperCase().replace("/", "-");
1232
1233
  const level = opts.depth ?? 20;
1233
1234
  const interval = opts.interval ?? "500ms";
1234
- const dataType = `${wsSymbol}@depth${level}@${interval}`;
1235
+ const depthDataType = `${wsSymbol}@depth${level}@${interval}`;
1236
+ const tickerDataType = `${wsSymbol}@bookTicker`;
1235
1237
  const merger = new BookMerger();
1236
1238
  let ws = null;
1237
1239
  const stream = new OrderbookStream(() => ws?.close());
1238
- const subscribePayload = JSON.stringify({
1239
- id: `sub-${Date.now()}`,
1240
+ const subId = Date.now();
1241
+ const subscribeDepth = JSON.stringify({
1242
+ id: `sub-depth-${subId}`,
1240
1243
  reqType: "sub",
1241
- dataType
1244
+ dataType: depthDataType
1242
1245
  });
1243
- const handlePayload = (text) => {
1244
- if (text === "Ping" || text === "ping") {
1245
- ws?.send(text === "Ping" ? "Pong" : "pong");
1246
- return;
1247
- }
1248
- let msg;
1249
- try {
1250
- msg = JSON.parse(text);
1251
- } catch {
1252
- return;
1246
+ const subscribeTicker = JSON.stringify({
1247
+ id: `sub-ticker-${subId}`,
1248
+ reqType: "sub",
1249
+ dataType: tickerDataType
1250
+ });
1251
+ let lastMerged = null;
1252
+ let lastTicker = null;
1253
+ const emitOverlayed = () => {
1254
+ if (!lastMerged) return;
1255
+ let bids = lastMerged.bids;
1256
+ let asks = lastMerged.asks;
1257
+ let timestamp = lastMerged.timestamp;
1258
+ let sequence = lastMerged.sequence;
1259
+ if (lastTicker) {
1260
+ bids = overlayBid(bids, lastTicker.bid);
1261
+ asks = overlayAsk(asks, lastTicker.ask);
1262
+ if (lastTicker.ts > timestamp) {
1263
+ timestamp = lastTicker.ts;
1264
+ sequence = Math.max(sequence, lastTicker.ts);
1265
+ }
1253
1266
  }
1254
- if (msg.id && !msg.data) return;
1255
- if (msg.dataType !== dataType || !msg.data) return;
1256
- const d = msg.data;
1267
+ const book = {
1268
+ exchange: "bingx",
1269
+ symbol: opts.symbol,
1270
+ market: opts.market,
1271
+ bids,
1272
+ asks,
1273
+ timestamp,
1274
+ sequence
1275
+ };
1276
+ stream.emit("update", book);
1277
+ };
1278
+ const handleDepth = (d) => {
1257
1279
  const ts = d.T ?? d.ts ?? Date.now();
1258
1280
  const event = {
1259
1281
  kind: "snapshot",
@@ -1264,16 +1286,45 @@ function streamBingxOrderbook(opts) {
1264
1286
  };
1265
1287
  const r = merger.apply(event);
1266
1288
  if (!r.ok) return;
1267
- const book = {
1268
- exchange: "bingx",
1269
- symbol: opts.symbol,
1270
- market: opts.market,
1289
+ lastMerged = {
1271
1290
  bids: r.bids,
1272
1291
  asks: r.asks,
1273
1292
  timestamp: r.timestamp,
1274
1293
  sequence: r.sequence
1275
1294
  };
1276
- stream.emit("update", book);
1295
+ emitOverlayed();
1296
+ };
1297
+ const handleTicker = (t) => {
1298
+ const bidPrice = Number(t.b);
1299
+ const askPrice = Number(t.a);
1300
+ const bidSize = Number(t.B);
1301
+ const askSize = Number(t.A);
1302
+ if (!Number.isFinite(bidPrice) || !Number.isFinite(askPrice)) return;
1303
+ lastTicker = {
1304
+ bid: { price: bidPrice, size: bidSize },
1305
+ ask: { price: askPrice, size: askSize },
1306
+ ts: t.T ?? t.E ?? Date.now()
1307
+ };
1308
+ emitOverlayed();
1309
+ };
1310
+ const handlePayload = (text) => {
1311
+ if (text === "Ping" || text === "ping") {
1312
+ ws?.send(text === "Ping" ? "Pong" : "pong");
1313
+ return;
1314
+ }
1315
+ let msg;
1316
+ try {
1317
+ msg = JSON.parse(text);
1318
+ } catch {
1319
+ return;
1320
+ }
1321
+ if (msg.id && !msg.data) return;
1322
+ if (!msg.dataType || !msg.data) return;
1323
+ if (msg.dataType === depthDataType) {
1324
+ handleDepth(msg.data);
1325
+ } else if (msg.dataType === tickerDataType) {
1326
+ handleTicker(msg.data);
1327
+ }
1277
1328
  };
1278
1329
  const onMessage = async (raw) => {
1279
1330
  if (typeof raw === "string") {
@@ -1287,7 +1338,10 @@ function streamBingxOrderbook(opts) {
1287
1338
  url: isSpot ? WS_SPOT3 : WS_SWAP,
1288
1339
  onOpen: (sock) => {
1289
1340
  merger.reset();
1290
- sock.send(subscribePayload);
1341
+ lastMerged = null;
1342
+ lastTicker = null;
1343
+ sock.send(subscribeDepth);
1344
+ sock.send(subscribeTicker);
1291
1345
  },
1292
1346
  onMessage
1293
1347
  // bingx server is the one that sends Ping frames; we don't need client pings.
@@ -1299,6 +1353,32 @@ function streamBingxOrderbook(opts) {
1299
1353
  ws.connect().catch((e) => stream.emit("error", e));
1300
1354
  return stream;
1301
1355
  }
1356
+ function overlayAsk(asks, top) {
1357
+ if (!(top.size > 0)) return asks;
1358
+ let i = 0;
1359
+ for (; i < asks.length; i++) {
1360
+ const level = asks[i];
1361
+ if (!level || level.price >= top.price) break;
1362
+ }
1363
+ const at = asks[i];
1364
+ if (at && at.price === top.price) {
1365
+ return [{ price: top.price, size: top.size }, ...asks.slice(i + 1)];
1366
+ }
1367
+ return [{ price: top.price, size: top.size }, ...asks.slice(i)];
1368
+ }
1369
+ function overlayBid(bids, top) {
1370
+ if (!(top.size > 0)) return bids;
1371
+ let i = 0;
1372
+ for (; i < bids.length; i++) {
1373
+ const level = bids[i];
1374
+ if (!level || level.price <= top.price) break;
1375
+ }
1376
+ const at = bids[i];
1377
+ if (at && at.price === top.price) {
1378
+ return [{ price: top.price, size: top.size }, ...bids.slice(i + 1)];
1379
+ }
1380
+ return [{ price: top.price, size: top.size }, ...bids.slice(i)];
1381
+ }
1302
1382
 
1303
1383
  // src/exchanges/bingx/index.ts
1304
1384
  var BingxClient = class {
@@ -1833,6 +1913,628 @@ var OkxClient = class {
1833
1913
  }
1834
1914
  };
1835
1915
 
1836
- export { Backoff, BingxClient, BitgetClient, BookMerger, BybitClient, CoinexClient, ExchangeError, GateClient, HuobiClient, OkxClient, OrderbookStream, RateLimitError, SequenceGapError, parseSymbol };
1916
+ // src/exchanges/binance/symbols.ts
1917
+ var QUOTES5 = [
1918
+ "USDT",
1919
+ "USDC",
1920
+ "FDUSD",
1921
+ "BUSD",
1922
+ "TUSD",
1923
+ "USD",
1924
+ "BTC",
1925
+ "ETH",
1926
+ "BNB"
1927
+ ];
1928
+ function toBinanceSymbol(symbol) {
1929
+ const [base, quote] = symbol.split("/");
1930
+ if (!base || !quote) {
1931
+ throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
1932
+ }
1933
+ return `${base}${quote}`.toUpperCase();
1934
+ }
1935
+ function fromBinanceSymbol(s) {
1936
+ const upper = s.toUpperCase();
1937
+ for (const q of QUOTES5) {
1938
+ if (upper.endsWith(q) && upper.length > q.length) {
1939
+ return `${upper.slice(0, -q.length)}/${q}`;
1940
+ }
1941
+ }
1942
+ return upper;
1943
+ }
1944
+
1945
+ // src/exchanges/binance/rest.ts
1946
+ var BASE_SPOT = "https://api.binance.com";
1947
+ var BASE_FAPI = "https://fapi.binance.com";
1948
+ async function fetchBinanceOrderbook(opts) {
1949
+ const symbol = toBinanceSymbol(opts.symbol);
1950
+ const limit = opts.depth ?? 1e3;
1951
+ const url = opts.market === "spot" ? `${BASE_SPOT}/api/v3/depth?symbol=${symbol}&limit=${limit}` : `${BASE_FAPI}/fapi/v1/depth?symbol=${symbol}&limit=${limit}`;
1952
+ const data = await httpJson({
1953
+ url,
1954
+ timeoutMs: opts.timeoutMs,
1955
+ transformUrl: opts.transformUrl
1956
+ });
1957
+ return {
1958
+ exchange: "binance",
1959
+ symbol: fromBinanceSymbol(symbol),
1960
+ market: opts.market,
1961
+ bids: data.bids.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1962
+ asks: data.asks.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
1963
+ timestamp: data.E ?? data.T ?? Date.now(),
1964
+ sequence: data.lastUpdateId
1965
+ };
1966
+ }
1967
+
1968
+ // src/exchanges/binance/ws.ts
1969
+ var WS_SPOT6 = "wss://stream.binance.com:9443/ws";
1970
+ var WS_FUTURES3 = "wss://fstream.binance.com/ws";
1971
+ function streamBinanceOrderbook(opts) {
1972
+ const symbol = toBinanceSymbol(opts.symbol);
1973
+ const isSpot = opts.market === "spot";
1974
+ const interval = opts.interval ?? "100ms";
1975
+ const wsBase = isSpot ? WS_SPOT6 : WS_FUTURES3;
1976
+ const wsUrl = `${wsBase}/${symbol.toLowerCase()}@depth@${interval}`;
1977
+ const merger = new BookMerger();
1978
+ let snapshotId = -1;
1979
+ let lastU = -1;
1980
+ let snapshotInFlight = false;
1981
+ let snapshotDone = false;
1982
+ let buffered = [];
1983
+ let recovering = false;
1984
+ let ws = null;
1985
+ const stream = new OrderbookStream(() => ws?.close());
1986
+ const emit = (r) => {
1987
+ const book = {
1988
+ exchange: "binance",
1989
+ symbol: fromBinanceSymbol(symbol),
1990
+ market: opts.market,
1991
+ bids: r.bids,
1992
+ asks: r.asks,
1993
+ timestamp: r.timestamp,
1994
+ sequence: r.sequence
1995
+ };
1996
+ stream.emit("update", book);
1997
+ };
1998
+ const triggerSnapshot = () => {
1999
+ if (snapshotInFlight) return;
2000
+ snapshotInFlight = true;
2001
+ fetchBinanceOrderbook({
2002
+ symbol: opts.symbol,
2003
+ market: opts.market,
2004
+ depth: opts.depth ?? 1e3,
2005
+ timeoutMs: opts.timeoutMs,
2006
+ transformUrl: opts.transformUrl
2007
+ }).then((snap) => {
2008
+ snapshotId = snap.sequence;
2009
+ const r = merger.apply({
2010
+ kind: "snapshot",
2011
+ bids: snap.bids,
2012
+ asks: snap.asks,
2013
+ sequence: snap.sequence,
2014
+ timestamp: snap.timestamp
2015
+ });
2016
+ if (r.ok) emit(r);
2017
+ snapshotDone = true;
2018
+ lastU = -1;
2019
+ const pending2 = buffered;
2020
+ buffered = [];
2021
+ for (const ev of pending2) processDelta(ev);
2022
+ }).catch((err) => stream.emit("error", err)).finally(() => {
2023
+ snapshotInFlight = false;
2024
+ });
2025
+ };
2026
+ const resync = () => {
2027
+ if (recovering) return;
2028
+ recovering = true;
2029
+ merger.reset();
2030
+ snapshotId = -1;
2031
+ snapshotDone = false;
2032
+ lastU = -1;
2033
+ buffered = [];
2034
+ setTimeout(() => {
2035
+ recovering = false;
2036
+ triggerSnapshot();
2037
+ }, 100);
2038
+ };
2039
+ const processDelta = (ev) => {
2040
+ if (ev.u < snapshotId + 1) return;
2041
+ const bids = ev.b.map(([p, s]) => ({
2042
+ price: Number(p),
2043
+ size: Number(s)
2044
+ }));
2045
+ const asks = ev.a.map(([p, s]) => ({
2046
+ price: Number(p),
2047
+ size: Number(s)
2048
+ }));
2049
+ let prevSequence;
2050
+ if (lastU < 0) {
2051
+ const overlap = isSpot ? ev.U <= snapshotId + 1 : ev.U <= snapshotId;
2052
+ if (!overlap) {
2053
+ resync();
2054
+ return;
2055
+ }
2056
+ prevSequence = snapshotId;
2057
+ } else if (isSpot) {
2058
+ prevSequence = ev.U - 1;
2059
+ } else {
2060
+ if (ev.pu === void 0) {
2061
+ resync();
2062
+ return;
2063
+ }
2064
+ prevSequence = ev.pu;
2065
+ }
2066
+ const r = merger.apply({
2067
+ kind: "delta",
2068
+ bids,
2069
+ asks,
2070
+ sequence: ev.u,
2071
+ prevSequence,
2072
+ timestamp: ev.E
2073
+ });
2074
+ if (r.ok) {
2075
+ lastU = ev.u;
2076
+ emit(r);
2077
+ } else if (r.reason === "gap" || r.reason === "no-snapshot") {
2078
+ resync();
2079
+ }
2080
+ };
2081
+ const onMessage = (raw) => {
2082
+ if (typeof raw !== "string") return;
2083
+ let msg;
2084
+ try {
2085
+ msg = JSON.parse(raw);
2086
+ } catch {
2087
+ return;
2088
+ }
2089
+ if (msg.e !== "depthUpdate") return;
2090
+ if (!snapshotDone) {
2091
+ buffered.push(msg);
2092
+ if (!snapshotInFlight) triggerSnapshot();
2093
+ return;
2094
+ }
2095
+ processDelta(msg);
2096
+ };
2097
+ ws = new WSClient({
2098
+ url: wsUrl,
2099
+ onOpen: () => {
2100
+ merger.reset();
2101
+ snapshotId = -1;
2102
+ snapshotDone = false;
2103
+ lastU = -1;
2104
+ buffered = [];
2105
+ },
2106
+ onMessage
2107
+ // Binance server-initiated WS-level ping; auto-handled by the WS impl.
2108
+ });
2109
+ ws.on("open", () => stream.emit("connected"));
2110
+ ws.on("close", (r) => stream.emit("disconnected", r));
2111
+ ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
2112
+ ws.on("error", (e) => stream.emit("error", e));
2113
+ ws.connect().catch((e) => stream.emit("error", e));
2114
+ return stream;
2115
+ }
2116
+
2117
+ // src/exchanges/binance/index.ts
2118
+ var BinanceClient = class {
2119
+ exchange = "binance";
2120
+ transformUrl;
2121
+ timeoutMs;
2122
+ constructor(opts = {}) {
2123
+ this.transformUrl = opts.transformUrl;
2124
+ this.timeoutMs = opts.timeoutMs;
2125
+ }
2126
+ fetchOrderbook(symbol, opts = {}) {
2127
+ const { pair, market } = parseSymbol(symbol);
2128
+ return fetchBinanceOrderbook({
2129
+ symbol: pair,
2130
+ market,
2131
+ depth: opts.depth,
2132
+ timeoutMs: this.timeoutMs,
2133
+ transformUrl: this.transformUrl
2134
+ });
2135
+ }
2136
+ streamOrderbook(symbol, opts = {}) {
2137
+ const { pair, market } = parseSymbol(symbol);
2138
+ return streamBinanceOrderbook({
2139
+ symbol: pair,
2140
+ market,
2141
+ depth: opts.depth,
2142
+ transformUrl: this.transformUrl,
2143
+ timeoutMs: this.timeoutMs
2144
+ });
2145
+ }
2146
+ };
2147
+
2148
+ // src/exchanges/hyperliquid/symbols.ts
2149
+ function toHyperliquidSymbol(symbol) {
2150
+ const [base, quote] = symbol.split("/");
2151
+ if (!base || !quote) {
2152
+ throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
2153
+ }
2154
+ if (quote.toUpperCase() !== "USDC") {
2155
+ throw new Error(
2156
+ `hyperliquid only supports USDC-quoted pairs; got "${quote}"`
2157
+ );
2158
+ }
2159
+ return base.toUpperCase();
2160
+ }
2161
+ function fromHyperliquidSymbol(coin) {
2162
+ return `${coin.toUpperCase()}/USDC`;
2163
+ }
2164
+
2165
+ // src/exchanges/hyperliquid/rest.ts
2166
+ var BASE7 = "https://api.hyperliquid.xyz";
2167
+ async function fetchHyperliquidOrderbook(opts) {
2168
+ if (opts.market !== "perpetual") {
2169
+ throw new Error(
2170
+ "hyperliquid: only perpetual markets are supported (spot uses @N-indexed coins)"
2171
+ );
2172
+ }
2173
+ const coin = toHyperliquidSymbol(opts.symbol);
2174
+ const data = await httpJson({
2175
+ url: `${BASE7}/info`,
2176
+ method: "POST",
2177
+ headers: { "Content-Type": "application/json" },
2178
+ body: JSON.stringify({ type: "l2Book", coin }),
2179
+ timeoutMs: opts.timeoutMs,
2180
+ transformUrl: opts.transformUrl
2181
+ });
2182
+ return {
2183
+ exchange: "hyperliquid",
2184
+ symbol: fromHyperliquidSymbol(data.coin),
2185
+ market: "perpetual",
2186
+ bids: data.levels[0].map((l) => ({
2187
+ price: Number(l.px),
2188
+ size: Number(l.sz)
2189
+ })),
2190
+ asks: data.levels[1].map((l) => ({
2191
+ price: Number(l.px),
2192
+ size: Number(l.sz)
2193
+ })),
2194
+ timestamp: data.time,
2195
+ sequence: data.time
2196
+ };
2197
+ }
2198
+
2199
+ // src/exchanges/hyperliquid/ws.ts
2200
+ var WS_URL = "wss://api.hyperliquid.xyz/ws";
2201
+ function streamHyperliquidOrderbook(opts) {
2202
+ if (opts.market !== "perpetual") {
2203
+ const s = new OrderbookStream(() => void 0);
2204
+ queueMicrotask(
2205
+ () => s.emit(
2206
+ "error",
2207
+ new Error(
2208
+ "hyperliquid: only perpetual markets are supported (spot uses @N-indexed coins)"
2209
+ )
2210
+ )
2211
+ );
2212
+ return s;
2213
+ }
2214
+ const coin = toHyperliquidSymbol(opts.symbol);
2215
+ const merger = new BookMerger();
2216
+ let ws = null;
2217
+ const stream = new OrderbookStream(() => ws?.close());
2218
+ const subscribePayload = JSON.stringify({
2219
+ method: "subscribe",
2220
+ subscription: { type: "l2Book", coin }
2221
+ });
2222
+ const onMessage = (raw) => {
2223
+ if (typeof raw !== "string") return;
2224
+ let msg;
2225
+ try {
2226
+ msg = JSON.parse(raw);
2227
+ } catch {
2228
+ return;
2229
+ }
2230
+ if (msg.channel === "pong" || msg.channel === "subscriptionResponse") {
2231
+ return;
2232
+ }
2233
+ if (msg.channel !== "l2Book" || !msg.data) return;
2234
+ const book = msg.data;
2235
+ if (book.coin !== coin) return;
2236
+ const event = {
2237
+ kind: "snapshot",
2238
+ bids: book.levels[0].map((l) => ({
2239
+ price: Number(l.px),
2240
+ size: Number(l.sz)
2241
+ })),
2242
+ asks: book.levels[1].map((l) => ({
2243
+ price: Number(l.px),
2244
+ size: Number(l.sz)
2245
+ })),
2246
+ sequence: book.time,
2247
+ timestamp: book.time
2248
+ };
2249
+ const r = merger.apply(event);
2250
+ if (!r.ok) return;
2251
+ const out = {
2252
+ exchange: "hyperliquid",
2253
+ symbol: fromHyperliquidSymbol(coin),
2254
+ market: "perpetual",
2255
+ bids: r.bids,
2256
+ asks: r.asks,
2257
+ timestamp: r.timestamp,
2258
+ sequence: r.sequence
2259
+ };
2260
+ stream.emit("update", out);
2261
+ };
2262
+ ws = new WSClient({
2263
+ url: WS_URL,
2264
+ onOpen: (sock) => {
2265
+ merger.reset();
2266
+ sock.send(subscribePayload);
2267
+ },
2268
+ onMessage,
2269
+ pingIntervalMs: 5e4,
2270
+ pingPayload: () => JSON.stringify({ method: "ping" })
2271
+ });
2272
+ ws.on("open", () => stream.emit("connected"));
2273
+ ws.on("close", (r) => stream.emit("disconnected", r));
2274
+ ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
2275
+ ws.on("error", (e) => stream.emit("error", e));
2276
+ ws.connect().catch((e) => stream.emit("error", e));
2277
+ return stream;
2278
+ }
2279
+
2280
+ // src/exchanges/hyperliquid/index.ts
2281
+ var HyperliquidClient = class {
2282
+ exchange = "hyperliquid";
2283
+ transformUrl;
2284
+ timeoutMs;
2285
+ constructor(opts = {}) {
2286
+ this.transformUrl = opts.transformUrl;
2287
+ this.timeoutMs = opts.timeoutMs;
2288
+ }
2289
+ fetchOrderbook(symbol, opts = {}) {
2290
+ const { pair, market } = parseSymbol(symbol);
2291
+ return fetchHyperliquidOrderbook({
2292
+ symbol: pair,
2293
+ market,
2294
+ depth: opts.depth,
2295
+ timeoutMs: this.timeoutMs,
2296
+ transformUrl: this.transformUrl
2297
+ });
2298
+ }
2299
+ streamOrderbook(symbol, opts = {}) {
2300
+ const { pair, market } = parseSymbol(symbol);
2301
+ return streamHyperliquidOrderbook({
2302
+ symbol: pair,
2303
+ market,
2304
+ depth: opts.depth,
2305
+ transformUrl: this.transformUrl,
2306
+ timeoutMs: this.timeoutMs
2307
+ });
2308
+ }
2309
+ };
2310
+
2311
+ // src/exchanges/kucoin/symbols.ts
2312
+ var FUTURES_QUOTES = ["USDT", "USDC", "USD"];
2313
+ function toKucoinSpotSymbol(symbol) {
2314
+ const [base, quote] = symbol.split("/");
2315
+ if (!base || !quote) {
2316
+ throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
2317
+ }
2318
+ return `${base.toUpperCase()}-${quote.toUpperCase()}`;
2319
+ }
2320
+ function fromKucoinSpotSymbol(s) {
2321
+ return s.toUpperCase().replace("-", "/");
2322
+ }
2323
+ function toKucoinFuturesSymbol(symbol) {
2324
+ const [base, quote] = symbol.split("/");
2325
+ if (!base || !quote) {
2326
+ throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
2327
+ }
2328
+ const b = base.toUpperCase() === "BTC" ? "XBT" : base.toUpperCase();
2329
+ return `${b}${quote.toUpperCase()}M`;
2330
+ }
2331
+ function fromKucoinFuturesSymbol(s) {
2332
+ const upper = s.toUpperCase();
2333
+ const stripped = upper.endsWith("M") ? upper.slice(0, -1) : upper;
2334
+ for (const q of FUTURES_QUOTES) {
2335
+ if (stripped.endsWith(q) && stripped.length > q.length) {
2336
+ let base = stripped.slice(0, -q.length);
2337
+ if (base === "XBT") base = "BTC";
2338
+ return `${base}/${q}`;
2339
+ }
2340
+ }
2341
+ return upper;
2342
+ }
2343
+
2344
+ // src/exchanges/kucoin/rest.ts
2345
+ var BASE_SPOT2 = "https://api.kucoin.com";
2346
+ var BASE_FUTURES = "https://api-futures.kucoin.com";
2347
+ async function fetchKucoinBullet(opts) {
2348
+ const base = opts.market === "spot" ? BASE_SPOT2 : BASE_FUTURES;
2349
+ const res = await httpJson({
2350
+ url: `${base}/api/v1/bullet-public`,
2351
+ method: "POST",
2352
+ timeoutMs: opts.timeoutMs,
2353
+ transformUrl: opts.transformUrl
2354
+ });
2355
+ if (res.code !== "200000") {
2356
+ throw new ExchangeError("kucoin", `bullet failed: ${res.code}`);
2357
+ }
2358
+ const server = res.data.instanceServers[0];
2359
+ if (!server) {
2360
+ throw new ExchangeError("kucoin", "bullet returned no instance servers");
2361
+ }
2362
+ return {
2363
+ endpoint: server.endpoint,
2364
+ token: res.data.token,
2365
+ pingInterval: server.pingInterval,
2366
+ pingTimeout: server.pingTimeout
2367
+ };
2368
+ }
2369
+ async function fetchKucoinOrderbook(opts) {
2370
+ const tier = (opts.depth ?? 100) <= 20 ? 20 : 100;
2371
+ if (opts.market === "spot") {
2372
+ const symbol2 = toKucoinSpotSymbol(opts.symbol);
2373
+ const url2 = `${BASE_SPOT2}/api/v1/market/orderbook/level2_${tier}?symbol=${symbol2}`;
2374
+ const res2 = await httpJson({
2375
+ url: url2,
2376
+ timeoutMs: opts.timeoutMs,
2377
+ transformUrl: opts.transformUrl
2378
+ });
2379
+ if (res2.code !== "200000") {
2380
+ throw new ExchangeError("kucoin", `orderbook failed: ${res2.code}`);
2381
+ }
2382
+ return {
2383
+ exchange: "kucoin",
2384
+ symbol: fromKucoinSpotSymbol(symbol2),
2385
+ market: "spot",
2386
+ bids: parseLevels(res2.data.bids),
2387
+ asks: parseLevels(res2.data.asks),
2388
+ timestamp: res2.data.time,
2389
+ sequence: Number(res2.data.sequence)
2390
+ };
2391
+ }
2392
+ const symbol = toKucoinFuturesSymbol(opts.symbol);
2393
+ const url = `${BASE_FUTURES}/api/v1/level2/depth${tier}?symbol=${symbol}`;
2394
+ const res = await httpJson({
2395
+ url,
2396
+ timeoutMs: opts.timeoutMs,
2397
+ transformUrl: opts.transformUrl
2398
+ });
2399
+ if (res.code !== "200000") {
2400
+ throw new ExchangeError("kucoin", `orderbook failed: ${res.code}`);
2401
+ }
2402
+ return {
2403
+ exchange: "kucoin",
2404
+ symbol: fromKucoinFuturesSymbol(symbol),
2405
+ market: "perpetual",
2406
+ bids: parseLevels(res.data.bids),
2407
+ asks: parseLevels(res.data.asks),
2408
+ timestamp: res.data.ts,
2409
+ sequence: res.data.sequence
2410
+ };
2411
+ }
2412
+ function parseLevels(rows) {
2413
+ return rows.map(([p, s]) => ({ price: Number(p), size: Number(s) }));
2414
+ }
2415
+
2416
+ // src/exchanges/kucoin/ws.ts
2417
+ function streamKucoinOrderbook(opts) {
2418
+ const isSpot = opts.market === "spot";
2419
+ const symbol = isSpot ? toKucoinSpotSymbol(opts.symbol) : toKucoinFuturesSymbol(opts.symbol);
2420
+ const level = (opts.depth ?? 50) <= 5 ? 5 : 50;
2421
+ const topic = isSpot ? `/spotMarket/level2Depth${level}:${symbol}` : `/contractMarket/level2Depth${level}:${symbol}`;
2422
+ const merger = new BookMerger();
2423
+ let ws = null;
2424
+ const stream = new OrderbookStream(() => ws?.close());
2425
+ const urlProvider = async () => {
2426
+ const bullet = await fetchKucoinBullet({
2427
+ market: opts.market,
2428
+ timeoutMs: opts.timeoutMs,
2429
+ transformUrl: opts.transformUrl
2430
+ });
2431
+ const connectId = `xapy-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
2432
+ return `${bullet.endpoint}?token=${encodeURIComponent(bullet.token)}&connectId=${connectId}`;
2433
+ };
2434
+ const subscribePayload = () => JSON.stringify({
2435
+ id: `sub-${Date.now()}`,
2436
+ type: "subscribe",
2437
+ topic,
2438
+ privateChannel: false,
2439
+ response: true
2440
+ });
2441
+ const pingPayload = () => JSON.stringify({ id: `${Date.now()}`, type: "ping" });
2442
+ const handleDepth = (d) => {
2443
+ const ts = d.timestamp ?? d.ts ?? Date.now();
2444
+ const event = {
2445
+ kind: "snapshot",
2446
+ bids: (d.bids ?? []).map(([p, s]) => ({
2447
+ price: Number(p),
2448
+ size: Number(s)
2449
+ })),
2450
+ asks: (d.asks ?? []).map(([p, s]) => ({
2451
+ price: Number(p),
2452
+ size: Number(s)
2453
+ })),
2454
+ sequence: ts,
2455
+ timestamp: ts
2456
+ };
2457
+ const r = merger.apply(event);
2458
+ if (!r.ok) return;
2459
+ const book = {
2460
+ exchange: "kucoin",
2461
+ symbol: isSpot ? fromKucoinSpotSymbol(symbol) : fromKucoinFuturesSymbol(symbol),
2462
+ market: opts.market,
2463
+ bids: r.bids,
2464
+ asks: r.asks,
2465
+ timestamp: r.timestamp,
2466
+ sequence: r.sequence
2467
+ };
2468
+ stream.emit("update", book);
2469
+ };
2470
+ const onMessage = (raw) => {
2471
+ if (typeof raw !== "string") return;
2472
+ let msg;
2473
+ try {
2474
+ msg = JSON.parse(raw);
2475
+ } catch {
2476
+ return;
2477
+ }
2478
+ if (msg.type === "welcome" || msg.type === "ack" || msg.type === "pong") {
2479
+ return;
2480
+ }
2481
+ if (msg.type === "error") {
2482
+ stream.emit("error", new Error(`kucoin: ${JSON.stringify(msg)}`));
2483
+ return;
2484
+ }
2485
+ if (msg.type !== "message" || msg.topic !== topic || !msg.data) return;
2486
+ handleDepth(msg.data);
2487
+ };
2488
+ ws = new WSClient({
2489
+ url: urlProvider,
2490
+ onOpen: (sock) => {
2491
+ merger.reset();
2492
+ sock.send(subscribePayload());
2493
+ },
2494
+ onMessage,
2495
+ // KuCoin's documented bullet `pingInterval` is 18000ms — stay under it.
2496
+ pingIntervalMs: 15e3,
2497
+ pingPayload
2498
+ });
2499
+ ws.on("open", () => stream.emit("connected"));
2500
+ ws.on("close", (r) => stream.emit("disconnected", r));
2501
+ ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
2502
+ ws.on("error", (e) => stream.emit("error", e));
2503
+ ws.connect().catch((e) => stream.emit("error", e));
2504
+ return stream;
2505
+ }
2506
+
2507
+ // src/exchanges/kucoin/index.ts
2508
+ var KucoinClient = class {
2509
+ exchange = "kucoin";
2510
+ transformUrl;
2511
+ timeoutMs;
2512
+ constructor(opts = {}) {
2513
+ this.transformUrl = opts.transformUrl;
2514
+ this.timeoutMs = opts.timeoutMs;
2515
+ }
2516
+ fetchOrderbook(symbol, opts = {}) {
2517
+ const { pair, market } = parseSymbol(symbol);
2518
+ return fetchKucoinOrderbook({
2519
+ symbol: pair,
2520
+ market,
2521
+ depth: opts.depth,
2522
+ timeoutMs: this.timeoutMs,
2523
+ transformUrl: this.transformUrl
2524
+ });
2525
+ }
2526
+ streamOrderbook(symbol, opts = {}) {
2527
+ const { pair, market } = parseSymbol(symbol);
2528
+ return streamKucoinOrderbook({
2529
+ symbol: pair,
2530
+ market,
2531
+ depth: opts.depth,
2532
+ transformUrl: this.transformUrl,
2533
+ timeoutMs: this.timeoutMs
2534
+ });
2535
+ }
2536
+ };
2537
+
2538
+ export { Backoff, BinanceClient, BingxClient, BitgetClient, BookMerger, BybitClient, CoinexClient, ExchangeError, GateClient, HuobiClient, HyperliquidClient, KucoinClient, OkxClient, OrderbookStream, RateLimitError, SequenceGapError, parseSymbol };
1837
2539
  //# sourceMappingURL=index.js.map
1838
2540
  //# sourceMappingURL=index.js.map