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