@xapy/orderbook 0.1.23 → 0.1.25
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 +3 -0
- 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/edgex/index.cjs +648 -0
- 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/edgex/index.js +642 -0
- 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.cjs +627 -0
- package/dist/exchanges/grvt/index.cjs.map +1 -0
- package/dist/exchanges/grvt/index.d.cts +47 -0
- package/dist/exchanges/grvt/index.d.ts +47 -0
- package/dist/exchanges/grvt/index.js +623 -0
- package/dist/exchanges/grvt/index.js.map +1 -0
- package/dist/exchanges/huobi/index.d.cts +1 -1
- package/dist/exchanges/huobi/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 +738 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +736 -4
- package/dist/index.js.map +1 -1
- package/dist/{stream-CO3LD9jf.d.cts → stream-CrvepB6m.d.cts} +1 -1
- package/dist/{stream-CO3LD9jf.d.ts → stream-CrvepB6m.d.ts} +1 -1
- package/package.json +35 -2
package/dist/index.cjs
CHANGED
|
@@ -2745,13 +2745,13 @@ function streamDeribitOrderbook(opts) {
|
|
|
2745
2745
|
rpc("public/unsubscribe", { channels: [channel] });
|
|
2746
2746
|
subscribe();
|
|
2747
2747
|
};
|
|
2748
|
-
const
|
|
2748
|
+
const toLevels4 = (entries) => entries.map(([action, price, amount]) => ({
|
|
2749
2749
|
price,
|
|
2750
2750
|
size: action === "delete" ? 0 : amount
|
|
2751
2751
|
}));
|
|
2752
2752
|
const buildEvent2 = (d) => {
|
|
2753
|
-
const bids =
|
|
2754
|
-
const asks =
|
|
2753
|
+
const bids = toLevels4(d.bids);
|
|
2754
|
+
const asks = toLevels4(d.asks);
|
|
2755
2755
|
if (d.type === "snapshot") {
|
|
2756
2756
|
return {
|
|
2757
2757
|
kind: "snapshot",
|
|
@@ -2873,6 +2873,738 @@ var DeribitClient = class {
|
|
|
2873
2873
|
}
|
|
2874
2874
|
};
|
|
2875
2875
|
|
|
2876
|
+
// src/exchanges/grvt/symbols.ts
|
|
2877
|
+
function toGrvtSymbol(symbol, market) {
|
|
2878
|
+
const [base, quote] = symbol.split("/");
|
|
2879
|
+
if (!base || !quote) {
|
|
2880
|
+
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
2881
|
+
}
|
|
2882
|
+
if (market !== "perpetual") {
|
|
2883
|
+
throw new Error(
|
|
2884
|
+
`grvt only lists perpetuals; use "${base.toUpperCase()}/${quote.toUpperCase()}:${quote.toUpperCase()}"`
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
return `${base.toUpperCase()}_${quote.toUpperCase()}_Perp`;
|
|
2888
|
+
}
|
|
2889
|
+
function fromGrvtSymbol(instrument) {
|
|
2890
|
+
const parts = instrument.split("_");
|
|
2891
|
+
const [base, quote] = parts;
|
|
2892
|
+
return base && quote ? `${base.toUpperCase()}/${quote.toUpperCase()}` : instrument;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
// src/exchanges/grvt/rest.ts
|
|
2896
|
+
var BASE9 = "https://market-data.grvt.io";
|
|
2897
|
+
var ALLOWED_DEPTHS2 = [10, 50, 100, 500];
|
|
2898
|
+
function snapDepth(depth) {
|
|
2899
|
+
return ALLOWED_DEPTHS2.find((d) => d >= depth) ?? ALLOWED_DEPTHS2[ALLOWED_DEPTHS2.length - 1];
|
|
2900
|
+
}
|
|
2901
|
+
function nsToMs(ns) {
|
|
2902
|
+
const n = Number(ns);
|
|
2903
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n / 1e6) : Date.now();
|
|
2904
|
+
}
|
|
2905
|
+
var toLevels = (rows) => (rows ?? []).map((l) => ({ price: Number(l.price), size: Number(l.size) }));
|
|
2906
|
+
async function fetchGrvtOrderbook(opts) {
|
|
2907
|
+
const instrument = toGrvtSymbol(opts.symbol, opts.market);
|
|
2908
|
+
const depth = snapDepth(opts.depth ?? 50);
|
|
2909
|
+
const res = await httpJson({
|
|
2910
|
+
url: `${BASE9}/full/v1/book`,
|
|
2911
|
+
method: "POST",
|
|
2912
|
+
headers: { "Content-Type": "application/json" },
|
|
2913
|
+
body: JSON.stringify({ instrument, depth }),
|
|
2914
|
+
timeoutMs: opts.timeoutMs,
|
|
2915
|
+
transformUrl: opts.transformUrl
|
|
2916
|
+
});
|
|
2917
|
+
if (!res.result) {
|
|
2918
|
+
throw new ExchangeError(
|
|
2919
|
+
"grvt",
|
|
2920
|
+
res.code ? `${res.code}: ${res.message}` : "empty orderbook response"
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
const ts = nsToMs(res.result.event_time);
|
|
2924
|
+
return {
|
|
2925
|
+
exchange: "grvt",
|
|
2926
|
+
symbol: fromGrvtSymbol(res.result.instrument ?? instrument),
|
|
2927
|
+
market: opts.market,
|
|
2928
|
+
bids: toLevels(res.result.bids),
|
|
2929
|
+
asks: toLevels(res.result.asks),
|
|
2930
|
+
timestamp: ts,
|
|
2931
|
+
sequence: ts
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// src/exchanges/grvt/ws.ts
|
|
2936
|
+
var WS_FULL = "wss://market-data.grvt.io/ws/full";
|
|
2937
|
+
var STREAM = "v1.book.d";
|
|
2938
|
+
function streamGrvtOrderbook(opts) {
|
|
2939
|
+
const instrument = toGrvtSymbol(opts.symbol, opts.market);
|
|
2940
|
+
const selector = `${instrument}@${opts.rate ?? 500}`;
|
|
2941
|
+
const merger = new BookMerger();
|
|
2942
|
+
let ws = null;
|
|
2943
|
+
let sock = null;
|
|
2944
|
+
let recovering = false;
|
|
2945
|
+
let awaitingFirstDelta = false;
|
|
2946
|
+
let snapshotSequence = 0;
|
|
2947
|
+
let ackSequence = null;
|
|
2948
|
+
const stream = new OrderbookStream(() => ws?.close());
|
|
2949
|
+
let nextId = 1;
|
|
2950
|
+
const subscribeIds = /* @__PURE__ */ new Set();
|
|
2951
|
+
const rpc = (method) => {
|
|
2952
|
+
const id = nextId++;
|
|
2953
|
+
sock?.send(
|
|
2954
|
+
JSON.stringify({
|
|
2955
|
+
jsonrpc: "2.0",
|
|
2956
|
+
method,
|
|
2957
|
+
params: { stream: STREAM, selectors: [selector] },
|
|
2958
|
+
id
|
|
2959
|
+
})
|
|
2960
|
+
);
|
|
2961
|
+
return id;
|
|
2962
|
+
};
|
|
2963
|
+
const subscribe = () => subscribeIds.add(rpc("subscribe"));
|
|
2964
|
+
const emit = (r) => {
|
|
2965
|
+
const book = {
|
|
2966
|
+
exchange: "grvt",
|
|
2967
|
+
symbol: fromGrvtSymbol(instrument),
|
|
2968
|
+
market: opts.market,
|
|
2969
|
+
bids: r.bids,
|
|
2970
|
+
asks: r.asks,
|
|
2971
|
+
timestamp: r.timestamp,
|
|
2972
|
+
sequence: r.sequence
|
|
2973
|
+
};
|
|
2974
|
+
stream.emit("update", book);
|
|
2975
|
+
};
|
|
2976
|
+
const resync = () => {
|
|
2977
|
+
if (recovering) return;
|
|
2978
|
+
recovering = true;
|
|
2979
|
+
merger.reset();
|
|
2980
|
+
rpc("unsubscribe");
|
|
2981
|
+
subscribe();
|
|
2982
|
+
};
|
|
2983
|
+
const buildEvent2 = (feed, seq) => {
|
|
2984
|
+
const bids = toLevels(feed.bids);
|
|
2985
|
+
const asks = toLevels(feed.asks);
|
|
2986
|
+
const timestamp = nsToMs(feed.event_time);
|
|
2987
|
+
if (seq === 0) {
|
|
2988
|
+
snapshotSequence = ackSequence ?? 0;
|
|
2989
|
+
awaitingFirstDelta = true;
|
|
2990
|
+
return {
|
|
2991
|
+
kind: "snapshot",
|
|
2992
|
+
bids,
|
|
2993
|
+
asks,
|
|
2994
|
+
sequence: snapshotSequence,
|
|
2995
|
+
timestamp
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
const prevSequence = awaitingFirstDelta ? snapshotSequence : seq - 1;
|
|
2999
|
+
awaitingFirstDelta = false;
|
|
3000
|
+
return { kind: "delta", bids, asks, sequence: seq, prevSequence, timestamp };
|
|
3001
|
+
};
|
|
3002
|
+
const handleFeed = (msg) => {
|
|
3003
|
+
if (!msg.feed) return;
|
|
3004
|
+
const seq = Number(msg.sequence_number ?? 0);
|
|
3005
|
+
if (!Number.isFinite(seq)) return;
|
|
3006
|
+
if (seq === 0) recovering = false;
|
|
3007
|
+
else if (recovering) return;
|
|
3008
|
+
const r = merger.apply(buildEvent2(msg.feed, seq));
|
|
3009
|
+
if (r.ok) {
|
|
3010
|
+
emit(r);
|
|
3011
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
3012
|
+
resync();
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
const onMessage = (raw) => {
|
|
3016
|
+
if (typeof raw !== "string") return;
|
|
3017
|
+
let msg;
|
|
3018
|
+
try {
|
|
3019
|
+
msg = JSON.parse(raw);
|
|
3020
|
+
} catch {
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
if (msg.feed) {
|
|
3024
|
+
if (msg.selector && msg.selector !== selector) return;
|
|
3025
|
+
handleFeed(msg);
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
if (msg.id === void 0) return;
|
|
3029
|
+
if (msg.error) {
|
|
3030
|
+
stream.emit(
|
|
3031
|
+
"error",
|
|
3032
|
+
new ExchangeError("grvt", `${msg.error.code}: ${msg.error.message}`)
|
|
3033
|
+
);
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
if (subscribeIds.delete(msg.id)) {
|
|
3037
|
+
const subs = msg.result?.subs ?? [];
|
|
3038
|
+
const latest = Number(msg.result?.latest_sequence_number?.[0]);
|
|
3039
|
+
ackSequence = Number.isFinite(latest) ? latest : null;
|
|
3040
|
+
if (!subs.includes(selector)) {
|
|
3041
|
+
stream.emit(
|
|
3042
|
+
"error",
|
|
3043
|
+
new ExchangeError(
|
|
3044
|
+
"grvt",
|
|
3045
|
+
`subscribe rejected for "${STREAM}" selector "${selector}"`
|
|
3046
|
+
)
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
ws = new WSClient({
|
|
3052
|
+
url: WS_FULL,
|
|
3053
|
+
onOpen: (handle) => {
|
|
3054
|
+
sock = handle;
|
|
3055
|
+
merger.reset();
|
|
3056
|
+
recovering = false;
|
|
3057
|
+
awaitingFirstDelta = false;
|
|
3058
|
+
subscribeIds.clear();
|
|
3059
|
+
subscribe();
|
|
3060
|
+
},
|
|
3061
|
+
onMessage
|
|
3062
|
+
});
|
|
3063
|
+
ws.on("open", () => stream.emit("connected"));
|
|
3064
|
+
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
3065
|
+
ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
|
|
3066
|
+
ws.on("error", (e) => stream.emit("error", e));
|
|
3067
|
+
ws.connect().catch((e) => stream.emit("error", e));
|
|
3068
|
+
return stream;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
// src/exchanges/grvt/index.ts
|
|
3072
|
+
var GrvtClient = class {
|
|
3073
|
+
exchange = "grvt";
|
|
3074
|
+
transformUrl;
|
|
3075
|
+
timeoutMs;
|
|
3076
|
+
constructor(opts = {}) {
|
|
3077
|
+
this.transformUrl = opts.transformUrl;
|
|
3078
|
+
this.timeoutMs = opts.timeoutMs;
|
|
3079
|
+
}
|
|
3080
|
+
fetchOrderbook(symbol, opts = {}) {
|
|
3081
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3082
|
+
return fetchGrvtOrderbook({
|
|
3083
|
+
symbol: pair,
|
|
3084
|
+
market,
|
|
3085
|
+
depth: opts.depth,
|
|
3086
|
+
timeoutMs: this.timeoutMs,
|
|
3087
|
+
transformUrl: this.transformUrl
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
streamOrderbook(symbol, opts = {}) {
|
|
3091
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3092
|
+
return streamGrvtOrderbook({
|
|
3093
|
+
symbol: pair,
|
|
3094
|
+
market,
|
|
3095
|
+
depth: opts.depth,
|
|
3096
|
+
transformUrl: this.transformUrl,
|
|
3097
|
+
timeoutMs: this.timeoutMs
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
|
|
3102
|
+
// src/exchanges/lighter/markets.ts
|
|
3103
|
+
var LIGHTER_REST = "https://mainnet.zklighter.elliot.ai";
|
|
3104
|
+
var cache = null;
|
|
3105
|
+
var inflight = null;
|
|
3106
|
+
var key = (name, market) => `${market}:${name.toUpperCase()}`;
|
|
3107
|
+
async function fetchMarkets(opts) {
|
|
3108
|
+
const res = await httpJson({
|
|
3109
|
+
url: `${LIGHTER_REST}/api/v1/orderBooks`,
|
|
3110
|
+
timeoutMs: opts.timeoutMs,
|
|
3111
|
+
transformUrl: opts.transformUrl
|
|
3112
|
+
});
|
|
3113
|
+
const rows = res.order_books ?? [];
|
|
3114
|
+
if (rows.length === 0) {
|
|
3115
|
+
throw new ExchangeError(
|
|
3116
|
+
"lighter",
|
|
3117
|
+
res.message ? `${res.code}: ${res.message}` : "empty orderBooks response"
|
|
3118
|
+
);
|
|
3119
|
+
}
|
|
3120
|
+
const map = /* @__PURE__ */ new Map();
|
|
3121
|
+
for (const row of rows) {
|
|
3122
|
+
map.set(key(row.symbol, row.market_type === "spot" ? "spot" : "perpetual"), row);
|
|
3123
|
+
}
|
|
3124
|
+
return map;
|
|
3125
|
+
}
|
|
3126
|
+
function loadMarkets(opts) {
|
|
3127
|
+
inflight ??= fetchMarkets(opts).then(
|
|
3128
|
+
(m) => {
|
|
3129
|
+
cache = m;
|
|
3130
|
+
inflight = null;
|
|
3131
|
+
return m;
|
|
3132
|
+
},
|
|
3133
|
+
(err) => {
|
|
3134
|
+
inflight = null;
|
|
3135
|
+
throw err;
|
|
3136
|
+
}
|
|
3137
|
+
);
|
|
3138
|
+
return inflight;
|
|
3139
|
+
}
|
|
3140
|
+
async function resolveLighterMarket(name, market, opts) {
|
|
3141
|
+
const k = key(name, market);
|
|
3142
|
+
const hit = cache?.get(k);
|
|
3143
|
+
const meta = hit ?? (await loadMarkets(opts)).get(k);
|
|
3144
|
+
if (!meta) {
|
|
3145
|
+
throw new ExchangeError(
|
|
3146
|
+
"lighter",
|
|
3147
|
+
`unknown ${market} market "${name}" \u2014 not listed on lighter`
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
if (meta.status && meta.status !== "active") {
|
|
3151
|
+
throw new ExchangeError(
|
|
3152
|
+
"lighter",
|
|
3153
|
+
`market "${name}" is ${meta.status} \u2014 no book is published for it`
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
return meta;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
// src/exchanges/lighter/symbols.ts
|
|
3160
|
+
function toLighterSymbol(symbol, market) {
|
|
3161
|
+
const [base, quote] = symbol.split("/");
|
|
3162
|
+
if (!base || !quote) {
|
|
3163
|
+
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
3164
|
+
}
|
|
3165
|
+
if (quote.toUpperCase() !== "USDC") {
|
|
3166
|
+
throw new Error(`lighter only supports USDC-quoted pairs; got "${quote}"`);
|
|
3167
|
+
}
|
|
3168
|
+
return market === "perpetual" ? base.toUpperCase() : `${base.toUpperCase()}/${quote.toUpperCase()}`;
|
|
3169
|
+
}
|
|
3170
|
+
function fromLighterSymbol(name) {
|
|
3171
|
+
return name.includes("/") ? name.toUpperCase() : `${name.toUpperCase()}/USDC`;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
// src/exchanges/lighter/rest.ts
|
|
3175
|
+
var MAX_ORDERS = 250;
|
|
3176
|
+
function usToMs(us) {
|
|
3177
|
+
return Number.isFinite(us) && us > 0 ? Math.floor(us / 1e3) : Date.now();
|
|
3178
|
+
}
|
|
3179
|
+
function aggregate(orders, side, capped) {
|
|
3180
|
+
const byPrice = /* @__PURE__ */ new Map();
|
|
3181
|
+
for (const o of orders ?? []) {
|
|
3182
|
+
const price = Number(o.price);
|
|
3183
|
+
const size = Number(o.remaining_base_amount);
|
|
3184
|
+
if (!Number.isFinite(price) || !Number.isFinite(size)) continue;
|
|
3185
|
+
byPrice.set(price, (byPrice.get(price) ?? 0) + size);
|
|
3186
|
+
}
|
|
3187
|
+
const levels = [];
|
|
3188
|
+
for (const [price, size] of byPrice) levels.push({ price, size });
|
|
3189
|
+
levels.sort((a, b) => side === "bid" ? b.price - a.price : a.price - b.price);
|
|
3190
|
+
if (capped) levels.pop();
|
|
3191
|
+
return levels;
|
|
3192
|
+
}
|
|
3193
|
+
async function fetchLighterOrderbook(opts) {
|
|
3194
|
+
const name = toLighterSymbol(opts.symbol, opts.market);
|
|
3195
|
+
const meta = await resolveLighterMarket(name, opts.market, opts);
|
|
3196
|
+
const res = await httpJson({
|
|
3197
|
+
url: `${LIGHTER_REST}/api/v1/orderBookOrders?market_id=${meta.market_id}&limit=${MAX_ORDERS}`,
|
|
3198
|
+
timeoutMs: opts.timeoutMs,
|
|
3199
|
+
transformUrl: opts.transformUrl
|
|
3200
|
+
});
|
|
3201
|
+
if (!res.asks && !res.bids) {
|
|
3202
|
+
throw new ExchangeError(
|
|
3203
|
+
"lighter",
|
|
3204
|
+
res.message ? `${res.code}: ${res.message}` : "empty orderbook response"
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
const depth = opts.depth ?? 50;
|
|
3208
|
+
const bids = aggregate(res.bids, "bid", res.total_bids === MAX_ORDERS);
|
|
3209
|
+
const asks = aggregate(res.asks, "ask", res.total_asks === MAX_ORDERS);
|
|
3210
|
+
const timestamp = Date.now();
|
|
3211
|
+
return {
|
|
3212
|
+
exchange: "lighter",
|
|
3213
|
+
symbol: fromLighterSymbol(name),
|
|
3214
|
+
market: opts.market,
|
|
3215
|
+
bids: bids.slice(0, depth),
|
|
3216
|
+
asks: asks.slice(0, depth),
|
|
3217
|
+
timestamp,
|
|
3218
|
+
// The L3 endpoint publishes no sequence of its own — the WS `nonce` chain
|
|
3219
|
+
// is where ordering is enforced.
|
|
3220
|
+
sequence: timestamp
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
// src/exchanges/lighter/ws.ts
|
|
3225
|
+
var WS_URL2 = "wss://mainnet.zklighter.elliot.ai/stream";
|
|
3226
|
+
var PING_INTERVAL_MS = 6e4;
|
|
3227
|
+
var toLevels2 = (rows) => (rows ?? []).map((l) => ({ price: Number(l.price), size: Number(l.size) }));
|
|
3228
|
+
function streamLighterOrderbook(opts) {
|
|
3229
|
+
const name = toLighterSymbol(opts.symbol, opts.market);
|
|
3230
|
+
const symbol = fromLighterSymbol(name);
|
|
3231
|
+
const marketId = resolveLighterMarket(name, opts.market, opts).then(
|
|
3232
|
+
(m) => m.market_id
|
|
3233
|
+
);
|
|
3234
|
+
marketId.catch(() => {
|
|
3235
|
+
});
|
|
3236
|
+
const merger = new BookMerger();
|
|
3237
|
+
let ws = null;
|
|
3238
|
+
let sock = null;
|
|
3239
|
+
let channel = null;
|
|
3240
|
+
let recovering = false;
|
|
3241
|
+
const stream = new OrderbookStream(() => ws?.close());
|
|
3242
|
+
const send = (type) => {
|
|
3243
|
+
if (channel) sock?.send(JSON.stringify({ type, channel }));
|
|
3244
|
+
};
|
|
3245
|
+
const emit = (r) => {
|
|
3246
|
+
const book = {
|
|
3247
|
+
exchange: "lighter",
|
|
3248
|
+
symbol,
|
|
3249
|
+
market: opts.market,
|
|
3250
|
+
bids: r.bids,
|
|
3251
|
+
asks: r.asks,
|
|
3252
|
+
timestamp: r.timestamp,
|
|
3253
|
+
sequence: r.sequence
|
|
3254
|
+
};
|
|
3255
|
+
stream.emit("update", book);
|
|
3256
|
+
};
|
|
3257
|
+
const resync = () => {
|
|
3258
|
+
if (recovering) return;
|
|
3259
|
+
recovering = true;
|
|
3260
|
+
merger.reset();
|
|
3261
|
+
send("unsubscribe");
|
|
3262
|
+
send("subscribe");
|
|
3263
|
+
};
|
|
3264
|
+
const buildEvent2 = (msg, snapshot) => {
|
|
3265
|
+
const ob = msg.order_book ?? {};
|
|
3266
|
+
const bids = toLevels2(ob.bids);
|
|
3267
|
+
const asks = toLevels2(ob.asks);
|
|
3268
|
+
const timestamp = msg.timestamp ?? usToMs(ob.last_updated_at);
|
|
3269
|
+
const sequence = Number(ob.nonce ?? 0);
|
|
3270
|
+
return snapshot ? { kind: "snapshot", bids, asks, sequence, timestamp } : {
|
|
3271
|
+
kind: "delta",
|
|
3272
|
+
bids,
|
|
3273
|
+
asks,
|
|
3274
|
+
sequence,
|
|
3275
|
+
prevSequence: Number(ob.begin_nonce ?? 0),
|
|
3276
|
+
timestamp
|
|
3277
|
+
};
|
|
3278
|
+
};
|
|
3279
|
+
const handleBook = (msg, snapshot) => {
|
|
3280
|
+
if (snapshot) recovering = false;
|
|
3281
|
+
else if (recovering) return;
|
|
3282
|
+
const r = merger.apply(buildEvent2(msg, snapshot));
|
|
3283
|
+
if (r.ok) {
|
|
3284
|
+
emit(r);
|
|
3285
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
3286
|
+
resync();
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
const onMessage = (raw) => {
|
|
3290
|
+
if (typeof raw !== "string") return;
|
|
3291
|
+
let msg;
|
|
3292
|
+
try {
|
|
3293
|
+
msg = JSON.parse(raw);
|
|
3294
|
+
} catch {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
if (msg.error) {
|
|
3298
|
+
stream.emit(
|
|
3299
|
+
"error",
|
|
3300
|
+
new ExchangeError("lighter", `${msg.error.code}: ${msg.error.message}`)
|
|
3301
|
+
);
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
if (!msg.order_book) return;
|
|
3305
|
+
if (msg.channel && channel && msg.channel !== channel.replace("/", ":")) {
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
if (msg.type === "subscribed/order_book") handleBook(msg, true);
|
|
3309
|
+
else if (msg.type === "update/order_book") handleBook(msg, false);
|
|
3310
|
+
};
|
|
3311
|
+
ws = new WSClient({
|
|
3312
|
+
url: WS_URL2,
|
|
3313
|
+
onOpen: async (handle) => {
|
|
3314
|
+
sock = handle;
|
|
3315
|
+
merger.reset();
|
|
3316
|
+
recovering = false;
|
|
3317
|
+
channel = `order_book/${await marketId}`;
|
|
3318
|
+
send("subscribe");
|
|
3319
|
+
},
|
|
3320
|
+
onMessage,
|
|
3321
|
+
pingIntervalMs: PING_INTERVAL_MS,
|
|
3322
|
+
pingPayload: () => JSON.stringify({ type: "ping" })
|
|
3323
|
+
});
|
|
3324
|
+
ws.on("open", () => stream.emit("connected"));
|
|
3325
|
+
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
3326
|
+
ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
|
|
3327
|
+
ws.on("error", (e) => stream.emit("error", e));
|
|
3328
|
+
ws.connect().catch((e) => stream.emit("error", e));
|
|
3329
|
+
return stream;
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
// src/exchanges/lighter/index.ts
|
|
3333
|
+
var LighterClient = class {
|
|
3334
|
+
exchange = "lighter";
|
|
3335
|
+
transformUrl;
|
|
3336
|
+
timeoutMs;
|
|
3337
|
+
constructor(opts = {}) {
|
|
3338
|
+
this.transformUrl = opts.transformUrl;
|
|
3339
|
+
this.timeoutMs = opts.timeoutMs;
|
|
3340
|
+
}
|
|
3341
|
+
fetchOrderbook(symbol, opts = {}) {
|
|
3342
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3343
|
+
return fetchLighterOrderbook({
|
|
3344
|
+
symbol: pair,
|
|
3345
|
+
market,
|
|
3346
|
+
depth: opts.depth,
|
|
3347
|
+
timeoutMs: this.timeoutMs,
|
|
3348
|
+
transformUrl: this.transformUrl
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
streamOrderbook(symbol, opts = {}) {
|
|
3352
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3353
|
+
return streamLighterOrderbook({
|
|
3354
|
+
symbol: pair,
|
|
3355
|
+
market,
|
|
3356
|
+
depth: opts.depth,
|
|
3357
|
+
transformUrl: this.transformUrl,
|
|
3358
|
+
timeoutMs: this.timeoutMs
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
|
|
3363
|
+
// src/exchanges/edgex/markets.ts
|
|
3364
|
+
var EDGEX_REST = "https://edgex-prod-v2.edgex.exchange";
|
|
3365
|
+
var EDGEX_WS = "wss://edgex-quote-prod-v2.edgex.exchange";
|
|
3366
|
+
var cache2 = null;
|
|
3367
|
+
var inflight2 = null;
|
|
3368
|
+
async function fetchContracts(opts) {
|
|
3369
|
+
const res = await httpJson({
|
|
3370
|
+
url: `${EDGEX_REST}/api/v2/public/meta/getMetaData`,
|
|
3371
|
+
timeoutMs: opts.timeoutMs,
|
|
3372
|
+
transformUrl: opts.transformUrl
|
|
3373
|
+
});
|
|
3374
|
+
const rows = res.data?.contractList ?? [];
|
|
3375
|
+
if (rows.length === 0) {
|
|
3376
|
+
throw new ExchangeError(
|
|
3377
|
+
"edgex",
|
|
3378
|
+
res.msg ? `${res.code}: ${res.msg}` : "empty metadata response"
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
const map = /* @__PURE__ */ new Map();
|
|
3382
|
+
for (const row of rows) map.set(row.contractName.toUpperCase(), row);
|
|
3383
|
+
return map;
|
|
3384
|
+
}
|
|
3385
|
+
function loadContracts(opts) {
|
|
3386
|
+
inflight2 ??= fetchContracts(opts).then(
|
|
3387
|
+
(m) => {
|
|
3388
|
+
cache2 = m;
|
|
3389
|
+
inflight2 = null;
|
|
3390
|
+
return m;
|
|
3391
|
+
},
|
|
3392
|
+
(err) => {
|
|
3393
|
+
inflight2 = null;
|
|
3394
|
+
throw err;
|
|
3395
|
+
}
|
|
3396
|
+
);
|
|
3397
|
+
return inflight2;
|
|
3398
|
+
}
|
|
3399
|
+
async function resolveEdgexContract(contractName, opts) {
|
|
3400
|
+
const k = contractName.toUpperCase();
|
|
3401
|
+
const hit = cache2?.get(k);
|
|
3402
|
+
const contract = hit ?? (await loadContracts(opts)).get(k);
|
|
3403
|
+
if (!contract) {
|
|
3404
|
+
throw new ExchangeError(
|
|
3405
|
+
"edgex",
|
|
3406
|
+
`unknown contract "${contractName}" \u2014 not listed on edgex`
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
return contract;
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// src/exchanges/edgex/symbols.ts
|
|
3413
|
+
function toEdgexSymbol(symbol, market) {
|
|
3414
|
+
const [base, quote] = symbol.split("/");
|
|
3415
|
+
if (!base || !quote) {
|
|
3416
|
+
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
3417
|
+
}
|
|
3418
|
+
if (market !== "perpetual") {
|
|
3419
|
+
throw new Error(
|
|
3420
|
+
`edgex only lists perpetuals; use "${base.toUpperCase()}/${quote.toUpperCase()}:${quote.toUpperCase()}"`
|
|
3421
|
+
);
|
|
3422
|
+
}
|
|
3423
|
+
if (quote.toUpperCase() !== "USDC") {
|
|
3424
|
+
throw new Error(`edgex only supports USDC-quoted pairs; got "${quote}"`);
|
|
3425
|
+
}
|
|
3426
|
+
return `${base.toUpperCase()}${quote.toUpperCase()}`;
|
|
3427
|
+
}
|
|
3428
|
+
function fromEdgexSymbol(contractName) {
|
|
3429
|
+
const name = contractName.toUpperCase();
|
|
3430
|
+
return name.endsWith("USDC") ? `${name.slice(0, -4)}/USDC` : name;
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
// src/exchanges/edgex/rest.ts
|
|
3434
|
+
var ALLOWED_LEVELS = [15, 200];
|
|
3435
|
+
function snapLevel(depth) {
|
|
3436
|
+
return ALLOWED_LEVELS.find((l) => l >= depth) ?? ALLOWED_LEVELS[ALLOWED_LEVELS.length - 1];
|
|
3437
|
+
}
|
|
3438
|
+
var toLevels3 = (rows) => (rows ?? []).map((l) => ({ price: Number(l.price), size: Number(l.size) }));
|
|
3439
|
+
async function fetchEdgexOrderbook(opts) {
|
|
3440
|
+
const contractName = toEdgexSymbol(opts.symbol, opts.market);
|
|
3441
|
+
const contract = await resolveEdgexContract(contractName, opts);
|
|
3442
|
+
const level = snapLevel(opts.depth ?? 50);
|
|
3443
|
+
const res = await httpJson({
|
|
3444
|
+
url: `${EDGEX_REST}/api/v2/public/quote/getDepth?contractId=${contract.contractId}&level=${level}`,
|
|
3445
|
+
timeoutMs: opts.timeoutMs,
|
|
3446
|
+
transformUrl: opts.transformUrl
|
|
3447
|
+
});
|
|
3448
|
+
const depth = res.data?.[0];
|
|
3449
|
+
if (!depth) {
|
|
3450
|
+
throw new ExchangeError(
|
|
3451
|
+
"edgex",
|
|
3452
|
+
res.msg ? `${res.code}: ${res.msg}` : "empty orderbook response"
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
const timestamp = Date.now();
|
|
3456
|
+
return {
|
|
3457
|
+
exchange: "edgex",
|
|
3458
|
+
symbol: fromEdgexSymbol(depth.contractName ?? contractName),
|
|
3459
|
+
market: opts.market,
|
|
3460
|
+
bids: toLevels3(depth.bids),
|
|
3461
|
+
asks: toLevels3(depth.asks),
|
|
3462
|
+
timestamp,
|
|
3463
|
+
sequence: Number(depth.endVersion ?? 0)
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
// src/exchanges/edgex/ws.ts
|
|
3468
|
+
var WS_URL3 = `${EDGEX_WS}/api/v1/public/ws`;
|
|
3469
|
+
function streamEdgexOrderbook(opts) {
|
|
3470
|
+
const contractName = toEdgexSymbol(opts.symbol, opts.market);
|
|
3471
|
+
const symbol = fromEdgexSymbol(contractName);
|
|
3472
|
+
const level = snapLevel(opts.depth ?? 50);
|
|
3473
|
+
const contractId = resolveEdgexContract(contractName, opts).then(
|
|
3474
|
+
(c) => c.contractId
|
|
3475
|
+
);
|
|
3476
|
+
contractId.catch(() => {
|
|
3477
|
+
});
|
|
3478
|
+
const merger = new BookMerger();
|
|
3479
|
+
let ws = null;
|
|
3480
|
+
let sock = null;
|
|
3481
|
+
let channel = null;
|
|
3482
|
+
let recovering = false;
|
|
3483
|
+
const stream = new OrderbookStream(() => ws?.close());
|
|
3484
|
+
const send = (type) => {
|
|
3485
|
+
if (channel) sock?.send(JSON.stringify({ type, channel }));
|
|
3486
|
+
};
|
|
3487
|
+
const emit = (r) => {
|
|
3488
|
+
const book = {
|
|
3489
|
+
exchange: "edgex",
|
|
3490
|
+
symbol,
|
|
3491
|
+
market: opts.market,
|
|
3492
|
+
bids: r.bids,
|
|
3493
|
+
asks: r.asks,
|
|
3494
|
+
timestamp: r.timestamp,
|
|
3495
|
+
sequence: r.sequence
|
|
3496
|
+
};
|
|
3497
|
+
stream.emit("update", book);
|
|
3498
|
+
};
|
|
3499
|
+
const resync = () => {
|
|
3500
|
+
if (recovering) return;
|
|
3501
|
+
recovering = true;
|
|
3502
|
+
merger.reset();
|
|
3503
|
+
send("unsubscribe");
|
|
3504
|
+
send("subscribe");
|
|
3505
|
+
};
|
|
3506
|
+
const buildEvent2 = (d, snapshot) => {
|
|
3507
|
+
const bids = toLevels3(d.bids);
|
|
3508
|
+
const asks = toLevels3(d.asks);
|
|
3509
|
+
const timestamp = Date.now();
|
|
3510
|
+
const sequence = Number(d.endVersion ?? 0);
|
|
3511
|
+
return snapshot ? { kind: "snapshot", bids, asks, sequence, timestamp } : {
|
|
3512
|
+
kind: "delta",
|
|
3513
|
+
bids,
|
|
3514
|
+
asks,
|
|
3515
|
+
sequence,
|
|
3516
|
+
prevSequence: Number(d.startVersion ?? 0),
|
|
3517
|
+
timestamp
|
|
3518
|
+
};
|
|
3519
|
+
};
|
|
3520
|
+
const handleDepth = (d, snapshot) => {
|
|
3521
|
+
if (snapshot) recovering = false;
|
|
3522
|
+
else if (recovering) return;
|
|
3523
|
+
const r = merger.apply(buildEvent2(d, snapshot));
|
|
3524
|
+
if (r.ok) {
|
|
3525
|
+
emit(r);
|
|
3526
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
3527
|
+
resync();
|
|
3528
|
+
}
|
|
3529
|
+
};
|
|
3530
|
+
const onMessage = (raw) => {
|
|
3531
|
+
if (typeof raw !== "string") return;
|
|
3532
|
+
let msg;
|
|
3533
|
+
try {
|
|
3534
|
+
msg = JSON.parse(raw);
|
|
3535
|
+
} catch {
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
if (msg.type === "ping") {
|
|
3539
|
+
sock?.send(JSON.stringify({ type: "pong", time: msg.time }));
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
if (msg.type === "error") {
|
|
3543
|
+
const { code, msg: detail } = msg.content ?? {};
|
|
3544
|
+
stream.emit(
|
|
3545
|
+
"error",
|
|
3546
|
+
new ExchangeError(
|
|
3547
|
+
"edgex",
|
|
3548
|
+
code ? `${code}: ${detail ?? ""}`.trim() : "subscribe rejected"
|
|
3549
|
+
)
|
|
3550
|
+
);
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
if (msg.type !== "quote-event") return;
|
|
3554
|
+
if (channel && (msg.channel ?? msg.content?.channel) !== channel) return;
|
|
3555
|
+
const snapshot = msg.content?.dataType?.toLowerCase() === "snapshot";
|
|
3556
|
+
for (const d of msg.content?.data ?? []) handleDepth(d, snapshot);
|
|
3557
|
+
};
|
|
3558
|
+
ws = new WSClient({
|
|
3559
|
+
url: WS_URL3,
|
|
3560
|
+
onOpen: async (handle) => {
|
|
3561
|
+
sock = handle;
|
|
3562
|
+
merger.reset();
|
|
3563
|
+
recovering = false;
|
|
3564
|
+
channel = `depth.${await contractId}.${level}`;
|
|
3565
|
+
send("subscribe");
|
|
3566
|
+
},
|
|
3567
|
+
onMessage
|
|
3568
|
+
});
|
|
3569
|
+
ws.on("open", () => stream.emit("connected"));
|
|
3570
|
+
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
3571
|
+
ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
|
|
3572
|
+
ws.on("error", (e) => stream.emit("error", e));
|
|
3573
|
+
ws.connect().catch((e) => stream.emit("error", e));
|
|
3574
|
+
return stream;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// src/exchanges/edgex/index.ts
|
|
3578
|
+
var EdgexClient = class {
|
|
3579
|
+
exchange = "edgex";
|
|
3580
|
+
transformUrl;
|
|
3581
|
+
timeoutMs;
|
|
3582
|
+
constructor(opts = {}) {
|
|
3583
|
+
this.transformUrl = opts.transformUrl;
|
|
3584
|
+
this.timeoutMs = opts.timeoutMs;
|
|
3585
|
+
}
|
|
3586
|
+
fetchOrderbook(symbol, opts = {}) {
|
|
3587
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3588
|
+
return fetchEdgexOrderbook({
|
|
3589
|
+
symbol: pair,
|
|
3590
|
+
market,
|
|
3591
|
+
depth: opts.depth,
|
|
3592
|
+
timeoutMs: this.timeoutMs,
|
|
3593
|
+
transformUrl: this.transformUrl
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
streamOrderbook(symbol, opts = {}) {
|
|
3597
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3598
|
+
return streamEdgexOrderbook({
|
|
3599
|
+
symbol: pair,
|
|
3600
|
+
market,
|
|
3601
|
+
depth: opts.depth,
|
|
3602
|
+
transformUrl: this.transformUrl,
|
|
3603
|
+
timeoutMs: this.timeoutMs
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
|
|
2876
3608
|
exports.Backoff = Backoff;
|
|
2877
3609
|
exports.BinanceClient = BinanceClient;
|
|
2878
3610
|
exports.BingxClient = BingxClient;
|
|
@@ -2881,11 +3613,14 @@ exports.BookMerger = BookMerger;
|
|
|
2881
3613
|
exports.BybitClient = BybitClient;
|
|
2882
3614
|
exports.CoinexClient = CoinexClient;
|
|
2883
3615
|
exports.DeribitClient = DeribitClient;
|
|
3616
|
+
exports.EdgexClient = EdgexClient;
|
|
2884
3617
|
exports.ExchangeError = ExchangeError;
|
|
2885
3618
|
exports.GateClient = GateClient;
|
|
3619
|
+
exports.GrvtClient = GrvtClient;
|
|
2886
3620
|
exports.HuobiClient = HuobiClient;
|
|
2887
3621
|
exports.HyperliquidClient = HyperliquidClient;
|
|
2888
3622
|
exports.KucoinClient = KucoinClient;
|
|
3623
|
+
exports.LighterClient = LighterClient;
|
|
2889
3624
|
exports.OkxClient = OkxClient;
|
|
2890
3625
|
exports.OrderbookStream = OrderbookStream;
|
|
2891
3626
|
exports.RateLimitError = RateLimitError;
|