@xapy/orderbook 0.1.28 → 0.1.30
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 +4 -0
- package/dist/exchanges/aster/index.cjs +610 -0
- package/dist/exchanges/aster/index.cjs.map +1 -0
- package/dist/exchanges/aster/index.d.cts +65 -0
- package/dist/exchanges/aster/index.d.ts +65 -0
- package/dist/exchanges/aster/index.js +602 -0
- package/dist/exchanges/aster/index.js.map +1 -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.d.cts +1 -1
- package/dist/exchanges/edgex/index.d.ts +1 -1
- package/dist/exchanges/gate/index.d.cts +1 -1
- package/dist/exchanges/gate/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.d.cts +1 -1
- package/dist/exchanges/lighter/index.d.ts +1 -1
- package/dist/exchanges/mexc/index.cjs +729 -0
- package/dist/exchanges/mexc/index.cjs.map +1 -0
- package/dist/exchanges/mexc/index.d.cts +57 -0
- package/dist/exchanges/mexc/index.d.ts +57 -0
- package/dist/exchanges/mexc/index.js +721 -0
- package/dist/exchanges/mexc/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/exchanges/whitebit/index.d.cts +1 -1
- package/dist/exchanges/whitebit/index.d.ts +1 -1
- package/dist/index.cjs +544 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +543 -1
- package/dist/index.js.map +1 -1
- package/dist/{stream-D8ErAue9.d.cts → stream-Dyq24thx.d.cts} +1 -1
- package/dist/{stream-D8ErAue9.d.ts → stream-Dyq24thx.d.ts} +1 -1
- package/package.json +23 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BookEvent, O as OrderbookLevel, M as Market } from './stream-
|
|
2
|
-
export { C as ClientOptions, E as ExchangeName, a as Orderbook, b as OrderbookOptions, c as OrderbookStream, S as Side, d as StreamEvents } from './stream-
|
|
1
|
+
import { B as BookEvent, O as OrderbookLevel, M as Market } from './stream-Dyq24thx.cjs';
|
|
2
|
+
export { C as ClientOptions, E as ExchangeName, a as Orderbook, b as OrderbookOptions, c as OrderbookStream, S as Side, d as StreamEvents } from './stream-Dyq24thx.cjs';
|
|
3
3
|
export { BybitClient } from './exchanges/bybit/index.cjs';
|
|
4
4
|
export { BitgetClient } from './exchanges/bitget/index.cjs';
|
|
5
5
|
export { GateClient } from './exchanges/gate/index.cjs';
|
|
@@ -13,6 +13,8 @@ export { DeribitClient } from './exchanges/deribit/index.cjs';
|
|
|
13
13
|
export { LighterClient } from './exchanges/lighter/index.cjs';
|
|
14
14
|
export { EdgexClient } from './exchanges/edgex/index.cjs';
|
|
15
15
|
export { WhitebitClient } from './exchanges/whitebit/index.cjs';
|
|
16
|
+
export { AsterClient } from './exchanges/aster/index.cjs';
|
|
17
|
+
export { MexcClient } from './exchanges/mexc/index.cjs';
|
|
16
18
|
|
|
17
19
|
declare class ExchangeError extends Error {
|
|
18
20
|
readonly exchange: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BookEvent, O as OrderbookLevel, M as Market } from './stream-
|
|
2
|
-
export { C as ClientOptions, E as ExchangeName, a as Orderbook, b as OrderbookOptions, c as OrderbookStream, S as Side, d as StreamEvents } from './stream-
|
|
1
|
+
import { B as BookEvent, O as OrderbookLevel, M as Market } from './stream-Dyq24thx.js';
|
|
2
|
+
export { C as ClientOptions, E as ExchangeName, a as Orderbook, b as OrderbookOptions, c as OrderbookStream, S as Side, d as StreamEvents } from './stream-Dyq24thx.js';
|
|
3
3
|
export { BybitClient } from './exchanges/bybit/index.js';
|
|
4
4
|
export { BitgetClient } from './exchanges/bitget/index.js';
|
|
5
5
|
export { GateClient } from './exchanges/gate/index.js';
|
|
@@ -13,6 +13,8 @@ export { DeribitClient } from './exchanges/deribit/index.js';
|
|
|
13
13
|
export { LighterClient } from './exchanges/lighter/index.js';
|
|
14
14
|
export { EdgexClient } from './exchanges/edgex/index.js';
|
|
15
15
|
export { WhitebitClient } from './exchanges/whitebit/index.js';
|
|
16
|
+
export { AsterClient } from './exchanges/aster/index.js';
|
|
17
|
+
export { MexcClient } from './exchanges/mexc/index.js';
|
|
16
18
|
|
|
17
19
|
declare class ExchangeError extends Error {
|
|
18
20
|
readonly exchange: string;
|
package/dist/index.js
CHANGED
|
@@ -3435,6 +3435,548 @@ var WhitebitClient = class {
|
|
|
3435
3435
|
}
|
|
3436
3436
|
};
|
|
3437
3437
|
|
|
3438
|
-
|
|
3438
|
+
// src/exchanges/aster/symbols.ts
|
|
3439
|
+
var QUOTES5 = ["USDT", "USD1", "FORM", "USDC", "BTC", "ETH", "U"];
|
|
3440
|
+
function toAsterSymbol(symbol) {
|
|
3441
|
+
const [base, quote] = symbol.split("/");
|
|
3442
|
+
if (!base || !quote) {
|
|
3443
|
+
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
3444
|
+
}
|
|
3445
|
+
return `${base}${quote}`.toUpperCase();
|
|
3446
|
+
}
|
|
3447
|
+
function fromAsterSymbol(s) {
|
|
3448
|
+
const upper = s.toUpperCase();
|
|
3449
|
+
for (const q of QUOTES5) {
|
|
3450
|
+
if (upper.endsWith(q) && upper.length > q.length) {
|
|
3451
|
+
return `${upper.slice(0, -q.length)}/${q}`;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
return upper;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
// src/exchanges/aster/rest.ts
|
|
3458
|
+
var ASTER_SPOT_REST = "https://sapi.asterdex.com";
|
|
3459
|
+
var ASTER_FUTURES_REST = "https://fapi.asterdex.com";
|
|
3460
|
+
async function fetchAsterOrderbook(opts) {
|
|
3461
|
+
const symbol = toAsterSymbol(opts.symbol);
|
|
3462
|
+
const limit = opts.depth ?? 1e3;
|
|
3463
|
+
const url = opts.market === "spot" ? `${ASTER_SPOT_REST}/api/v3/depth?symbol=${symbol}&limit=${limit}` : `${ASTER_FUTURES_REST}/fapi/v1/depth?symbol=${symbol}&limit=${limit}`;
|
|
3464
|
+
const data = await httpJson({
|
|
3465
|
+
url,
|
|
3466
|
+
timeoutMs: opts.timeoutMs,
|
|
3467
|
+
transformUrl: opts.transformUrl
|
|
3468
|
+
});
|
|
3469
|
+
return {
|
|
3470
|
+
exchange: "aster",
|
|
3471
|
+
symbol: fromAsterSymbol(data.symbol ?? symbol),
|
|
3472
|
+
market: opts.market,
|
|
3473
|
+
bids: data.bids.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
|
|
3474
|
+
asks: data.asks.map(([p, s]) => ({ price: Number(p), size: Number(s) })),
|
|
3475
|
+
timestamp: data.E ?? data.T ?? Date.now(),
|
|
3476
|
+
sequence: data.lastUpdateId
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// src/exchanges/aster/ws.ts
|
|
3481
|
+
var WS_SPOT6 = "wss://sstream.asterdex.com/ws";
|
|
3482
|
+
var WS_FUTURES4 = "wss://fstream.asterdex.com/ws";
|
|
3483
|
+
function streamAsterOrderbook(opts) {
|
|
3484
|
+
const symbol = toAsterSymbol(opts.symbol);
|
|
3485
|
+
const isSpot = opts.market === "spot";
|
|
3486
|
+
const wsBase = isSpot ? WS_SPOT6 : WS_FUTURES4;
|
|
3487
|
+
const wsUrl = `${wsBase}/${symbol.toLowerCase()}@depth@${opts.interval ?? "100ms"}`;
|
|
3488
|
+
const merger = new BookMerger();
|
|
3489
|
+
let snapshotId = -1;
|
|
3490
|
+
let lastU = -1;
|
|
3491
|
+
let snapshotInFlight = false;
|
|
3492
|
+
let snapshotDone = false;
|
|
3493
|
+
let buffered = [];
|
|
3494
|
+
let recovering = false;
|
|
3495
|
+
let ws = null;
|
|
3496
|
+
const stream = new OrderbookStream(() => ws?.close());
|
|
3497
|
+
const emit = (r) => {
|
|
3498
|
+
const book = {
|
|
3499
|
+
exchange: "aster",
|
|
3500
|
+
symbol: fromAsterSymbol(symbol),
|
|
3501
|
+
market: opts.market,
|
|
3502
|
+
bids: r.bids,
|
|
3503
|
+
asks: r.asks,
|
|
3504
|
+
timestamp: r.timestamp,
|
|
3505
|
+
sequence: r.sequence
|
|
3506
|
+
};
|
|
3507
|
+
stream.emit("update", book);
|
|
3508
|
+
};
|
|
3509
|
+
const triggerSnapshot = () => {
|
|
3510
|
+
if (snapshotInFlight) return;
|
|
3511
|
+
snapshotInFlight = true;
|
|
3512
|
+
fetchAsterOrderbook({
|
|
3513
|
+
symbol: opts.symbol,
|
|
3514
|
+
market: opts.market,
|
|
3515
|
+
depth: opts.depth ?? 1e3,
|
|
3516
|
+
timeoutMs: opts.timeoutMs,
|
|
3517
|
+
transformUrl: opts.transformUrl
|
|
3518
|
+
}).then((snap) => {
|
|
3519
|
+
snapshotId = snap.sequence;
|
|
3520
|
+
const r = merger.apply({
|
|
3521
|
+
kind: "snapshot",
|
|
3522
|
+
bids: snap.bids,
|
|
3523
|
+
asks: snap.asks,
|
|
3524
|
+
sequence: snap.sequence,
|
|
3525
|
+
timestamp: snap.timestamp
|
|
3526
|
+
});
|
|
3527
|
+
if (r.ok) emit(r);
|
|
3528
|
+
snapshotDone = true;
|
|
3529
|
+
lastU = -1;
|
|
3530
|
+
const pending2 = buffered;
|
|
3531
|
+
buffered = [];
|
|
3532
|
+
for (const ev of pending2) processDelta(ev);
|
|
3533
|
+
}).catch((err) => stream.emit("error", err)).finally(() => {
|
|
3534
|
+
snapshotInFlight = false;
|
|
3535
|
+
});
|
|
3536
|
+
};
|
|
3537
|
+
const resync = () => {
|
|
3538
|
+
if (recovering) return;
|
|
3539
|
+
recovering = true;
|
|
3540
|
+
merger.reset();
|
|
3541
|
+
snapshotId = -1;
|
|
3542
|
+
snapshotDone = false;
|
|
3543
|
+
lastU = -1;
|
|
3544
|
+
buffered = [];
|
|
3545
|
+
setTimeout(() => {
|
|
3546
|
+
recovering = false;
|
|
3547
|
+
triggerSnapshot();
|
|
3548
|
+
}, 100);
|
|
3549
|
+
};
|
|
3550
|
+
const processDelta = (ev) => {
|
|
3551
|
+
if (ev.u < snapshotId) return;
|
|
3552
|
+
const bids = ev.b.map(([p, s]) => ({
|
|
3553
|
+
price: Number(p),
|
|
3554
|
+
size: Number(s)
|
|
3555
|
+
}));
|
|
3556
|
+
const asks = ev.a.map(([p, s]) => ({
|
|
3557
|
+
price: Number(p),
|
|
3558
|
+
size: Number(s)
|
|
3559
|
+
}));
|
|
3560
|
+
let prevSequence;
|
|
3561
|
+
if (lastU < 0) {
|
|
3562
|
+
if (ev.U > snapshotId) {
|
|
3563
|
+
resync();
|
|
3564
|
+
return;
|
|
3565
|
+
}
|
|
3566
|
+
prevSequence = snapshotId;
|
|
3567
|
+
} else {
|
|
3568
|
+
if (ev.pu === void 0) {
|
|
3569
|
+
resync();
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
prevSequence = ev.pu;
|
|
3573
|
+
}
|
|
3574
|
+
const r = merger.apply({
|
|
3575
|
+
kind: "delta",
|
|
3576
|
+
bids,
|
|
3577
|
+
asks,
|
|
3578
|
+
sequence: ev.u,
|
|
3579
|
+
prevSequence,
|
|
3580
|
+
timestamp: ev.E
|
|
3581
|
+
});
|
|
3582
|
+
if (r.ok) {
|
|
3583
|
+
lastU = ev.u;
|
|
3584
|
+
emit(r);
|
|
3585
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
3586
|
+
resync();
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
const onMessage = (raw) => {
|
|
3590
|
+
if (typeof raw !== "string") return;
|
|
3591
|
+
let msg;
|
|
3592
|
+
try {
|
|
3593
|
+
msg = JSON.parse(raw);
|
|
3594
|
+
} catch {
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
if (msg.e !== "depthUpdate") return;
|
|
3598
|
+
if (!snapshotDone) {
|
|
3599
|
+
buffered.push(msg);
|
|
3600
|
+
if (!snapshotInFlight) triggerSnapshot();
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
processDelta(msg);
|
|
3604
|
+
};
|
|
3605
|
+
ws = new WSClient({
|
|
3606
|
+
url: wsUrl,
|
|
3607
|
+
onOpen: () => {
|
|
3608
|
+
merger.reset();
|
|
3609
|
+
snapshotId = -1;
|
|
3610
|
+
snapshotDone = false;
|
|
3611
|
+
lastU = -1;
|
|
3612
|
+
buffered = [];
|
|
3613
|
+
},
|
|
3614
|
+
onMessage
|
|
3615
|
+
// Server-initiated WS-level ping; auto-handled by the WS impl.
|
|
3616
|
+
});
|
|
3617
|
+
ws.on("open", () => stream.emit("connected"));
|
|
3618
|
+
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
3619
|
+
ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
|
|
3620
|
+
ws.on("error", (e) => stream.emit("error", e));
|
|
3621
|
+
ws.connect().catch((e) => stream.emit("error", e));
|
|
3622
|
+
return stream;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
// src/exchanges/aster/index.ts
|
|
3626
|
+
var AsterClient = class {
|
|
3627
|
+
exchange = "aster";
|
|
3628
|
+
transformUrl;
|
|
3629
|
+
timeoutMs;
|
|
3630
|
+
constructor(opts = {}) {
|
|
3631
|
+
this.transformUrl = opts.transformUrl;
|
|
3632
|
+
this.timeoutMs = opts.timeoutMs;
|
|
3633
|
+
}
|
|
3634
|
+
fetchOrderbook(symbol, opts = {}) {
|
|
3635
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3636
|
+
return fetchAsterOrderbook({
|
|
3637
|
+
symbol: pair,
|
|
3638
|
+
market,
|
|
3639
|
+
depth: opts.depth,
|
|
3640
|
+
timeoutMs: this.timeoutMs,
|
|
3641
|
+
transformUrl: this.transformUrl
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
streamOrderbook(symbol, opts = {}) {
|
|
3645
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3646
|
+
return streamAsterOrderbook({
|
|
3647
|
+
symbol: pair,
|
|
3648
|
+
market,
|
|
3649
|
+
depth: opts.depth,
|
|
3650
|
+
transformUrl: this.transformUrl,
|
|
3651
|
+
timeoutMs: this.timeoutMs
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
};
|
|
3655
|
+
|
|
3656
|
+
// src/exchanges/mexc/contracts.ts
|
|
3657
|
+
var MEXC_SPOT_REST = "https://api.mexc.com";
|
|
3658
|
+
var MEXC_FUTURES_REST = "https://contract.mexc.com";
|
|
3659
|
+
var cache2 = null;
|
|
3660
|
+
var inflight2 = null;
|
|
3661
|
+
async function fetchContracts2(opts) {
|
|
3662
|
+
const res = await httpJson({
|
|
3663
|
+
url: `${MEXC_FUTURES_REST}/api/v1/contract/detail`,
|
|
3664
|
+
timeoutMs: opts.timeoutMs,
|
|
3665
|
+
transformUrl: opts.transformUrl
|
|
3666
|
+
});
|
|
3667
|
+
const rows = res.data ?? [];
|
|
3668
|
+
if (rows.length === 0) {
|
|
3669
|
+
throw new ExchangeError(
|
|
3670
|
+
"mexc",
|
|
3671
|
+
res.message ? `${res.code}: ${res.message}` : "empty contract/detail response"
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
const map = /* @__PURE__ */ new Map();
|
|
3675
|
+
for (const row of rows) map.set(row.symbol.toUpperCase(), row);
|
|
3676
|
+
return map;
|
|
3677
|
+
}
|
|
3678
|
+
function loadContracts2(opts) {
|
|
3679
|
+
inflight2 ??= fetchContracts2(opts).then(
|
|
3680
|
+
(m) => {
|
|
3681
|
+
cache2 = m;
|
|
3682
|
+
inflight2 = null;
|
|
3683
|
+
return m;
|
|
3684
|
+
},
|
|
3685
|
+
(err) => {
|
|
3686
|
+
inflight2 = null;
|
|
3687
|
+
throw err;
|
|
3688
|
+
}
|
|
3689
|
+
);
|
|
3690
|
+
return inflight2;
|
|
3691
|
+
}
|
|
3692
|
+
async function resolveContractSize(symbol, opts) {
|
|
3693
|
+
const k = symbol.toUpperCase();
|
|
3694
|
+
const hit = cache2?.get(k);
|
|
3695
|
+
const contract = hit ?? (await loadContracts2(opts)).get(k);
|
|
3696
|
+
if (!contract) {
|
|
3697
|
+
throw new ExchangeError(
|
|
3698
|
+
"mexc",
|
|
3699
|
+
`unknown futures contract "${symbol}" \u2014 not listed on mexc`
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
const size = Number(contract.contractSize);
|
|
3703
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
3704
|
+
throw new ExchangeError(
|
|
3705
|
+
"mexc",
|
|
3706
|
+
`contract "${symbol}" has no usable contractSize (${contract.contractSize})`
|
|
3707
|
+
);
|
|
3708
|
+
}
|
|
3709
|
+
return size;
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
// src/exchanges/mexc/symbols.ts
|
|
3713
|
+
var QUOTES6 = ["USDT", "USDC", "TUSD", "USD", "BTC", "ETH"];
|
|
3714
|
+
function toMexcSymbol(symbol, market) {
|
|
3715
|
+
const [base, quote] = symbol.split("/");
|
|
3716
|
+
if (!base || !quote) {
|
|
3717
|
+
throw new Error(`invalid symbol "${symbol}" \u2014 expected "BASE/QUOTE"`);
|
|
3718
|
+
}
|
|
3719
|
+
return market === "perpetual" ? `${base.toUpperCase()}_${quote.toUpperCase()}` : `${base}${quote}`.toUpperCase();
|
|
3720
|
+
}
|
|
3721
|
+
function fromMexcSymbol(s, market) {
|
|
3722
|
+
const upper = s.toUpperCase();
|
|
3723
|
+
if (market === "perpetual") {
|
|
3724
|
+
const [base, quote] = upper.split("_");
|
|
3725
|
+
return base && quote ? `${base}/${quote}` : upper;
|
|
3726
|
+
}
|
|
3727
|
+
for (const q of QUOTES6) {
|
|
3728
|
+
if (upper.endsWith(q) && upper.length > q.length) {
|
|
3729
|
+
return `${upper.slice(0, -q.length)}/${q}`;
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
return upper;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
// src/exchanges/mexc/rest.ts
|
|
3736
|
+
var toSpotLevels = (rows) => (rows ?? []).map(([p, s]) => ({ price: Number(p), size: Number(s) }));
|
|
3737
|
+
var toFuturesLevels = (rows, contractSize) => (rows ?? []).map(([price, vol]) => ({
|
|
3738
|
+
price: Number(price),
|
|
3739
|
+
size: Number(vol) * contractSize
|
|
3740
|
+
}));
|
|
3741
|
+
async function fetchMexcOrderbook(opts) {
|
|
3742
|
+
const symbol = toMexcSymbol(opts.symbol, opts.market);
|
|
3743
|
+
const depth = opts.depth ?? 50;
|
|
3744
|
+
if (opts.market === "spot") {
|
|
3745
|
+
const data = await httpJson({
|
|
3746
|
+
url: `${MEXC_SPOT_REST}/api/v3/depth?symbol=${symbol}&limit=${depth}`,
|
|
3747
|
+
timeoutMs: opts.timeoutMs,
|
|
3748
|
+
transformUrl: opts.transformUrl
|
|
3749
|
+
});
|
|
3750
|
+
return {
|
|
3751
|
+
exchange: "mexc",
|
|
3752
|
+
symbol: fromMexcSymbol(symbol, opts.market),
|
|
3753
|
+
market: opts.market,
|
|
3754
|
+
bids: toSpotLevels(data.bids),
|
|
3755
|
+
asks: toSpotLevels(data.asks),
|
|
3756
|
+
// The spot depth endpoint returns no timestamp of its own.
|
|
3757
|
+
timestamp: Date.now(),
|
|
3758
|
+
sequence: data.lastUpdateId
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
const contractSize = await resolveContractSize(symbol, opts);
|
|
3762
|
+
const res = await httpJson({
|
|
3763
|
+
url: `${MEXC_FUTURES_REST}/api/v1/contract/depth/${symbol}?limit=${depth}`,
|
|
3764
|
+
timeoutMs: opts.timeoutMs,
|
|
3765
|
+
transformUrl: opts.transformUrl
|
|
3766
|
+
});
|
|
3767
|
+
if (!res.data) {
|
|
3768
|
+
throw new ExchangeError(
|
|
3769
|
+
"mexc",
|
|
3770
|
+
res.message ? `${res.code}: ${res.message}` : "empty orderbook response"
|
|
3771
|
+
);
|
|
3772
|
+
}
|
|
3773
|
+
return {
|
|
3774
|
+
exchange: "mexc",
|
|
3775
|
+
symbol: fromMexcSymbol(symbol, opts.market),
|
|
3776
|
+
market: opts.market,
|
|
3777
|
+
bids: toFuturesLevels(res.data.bids, contractSize),
|
|
3778
|
+
asks: toFuturesLevels(res.data.asks, contractSize),
|
|
3779
|
+
timestamp: res.data.timestamp ?? Date.now(),
|
|
3780
|
+
sequence: Number(res.data.version ?? 0)
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
// src/exchanges/mexc/ws.ts
|
|
3785
|
+
var WS_FUTURES5 = "wss://contract.mexc.com/edge";
|
|
3786
|
+
var PING_INTERVAL_MS3 = 2e4;
|
|
3787
|
+
function streamMexcOrderbook(opts) {
|
|
3788
|
+
if (opts.market !== "perpetual") {
|
|
3789
|
+
throw new Error(
|
|
3790
|
+
`mexc spot streaming requires the protobuf WS feed, which this adapter does not decode \u2014 use fetchOrderbook("${opts.symbol}") for a REST snapshot, or stream the perpetual ("${opts.symbol}:${opts.symbol.split("/")[1]}")`
|
|
3791
|
+
);
|
|
3792
|
+
}
|
|
3793
|
+
const symbol = toMexcSymbol(opts.symbol, opts.market);
|
|
3794
|
+
const unified = fromMexcSymbol(symbol, opts.market);
|
|
3795
|
+
const contractSize = resolveContractSize(symbol, opts);
|
|
3796
|
+
contractSize.catch(() => {
|
|
3797
|
+
});
|
|
3798
|
+
const merger = new BookMerger();
|
|
3799
|
+
let seedVersion = -1;
|
|
3800
|
+
let lastVersion = -1;
|
|
3801
|
+
let snapshotInFlight = false;
|
|
3802
|
+
let snapshotDone = false;
|
|
3803
|
+
let buffered = [];
|
|
3804
|
+
let recovering = false;
|
|
3805
|
+
let scale = 0;
|
|
3806
|
+
let ws = null;
|
|
3807
|
+
const stream = new OrderbookStream(() => ws?.close());
|
|
3808
|
+
const emit = (r) => {
|
|
3809
|
+
const book = {
|
|
3810
|
+
exchange: "mexc",
|
|
3811
|
+
symbol: unified,
|
|
3812
|
+
market: opts.market,
|
|
3813
|
+
bids: r.bids,
|
|
3814
|
+
asks: r.asks,
|
|
3815
|
+
timestamp: r.timestamp,
|
|
3816
|
+
sequence: r.sequence
|
|
3817
|
+
};
|
|
3818
|
+
stream.emit("update", book);
|
|
3819
|
+
};
|
|
3820
|
+
const triggerSnapshot = () => {
|
|
3821
|
+
if (snapshotInFlight) return;
|
|
3822
|
+
snapshotInFlight = true;
|
|
3823
|
+
contractSize.then(async (size) => {
|
|
3824
|
+
scale = size;
|
|
3825
|
+
return fetchMexcOrderbook({
|
|
3826
|
+
symbol: opts.symbol,
|
|
3827
|
+
market: opts.market,
|
|
3828
|
+
depth: opts.depth ?? 200,
|
|
3829
|
+
timeoutMs: opts.timeoutMs,
|
|
3830
|
+
transformUrl: opts.transformUrl
|
|
3831
|
+
});
|
|
3832
|
+
}).then((snap) => {
|
|
3833
|
+
seedVersion = snap.sequence;
|
|
3834
|
+
const r = merger.apply({
|
|
3835
|
+
kind: "snapshot",
|
|
3836
|
+
bids: snap.bids,
|
|
3837
|
+
asks: snap.asks,
|
|
3838
|
+
sequence: snap.sequence,
|
|
3839
|
+
timestamp: snap.timestamp
|
|
3840
|
+
});
|
|
3841
|
+
if (r.ok) emit(r);
|
|
3842
|
+
snapshotDone = true;
|
|
3843
|
+
lastVersion = -1;
|
|
3844
|
+
const pending2 = buffered;
|
|
3845
|
+
buffered = [];
|
|
3846
|
+
for (const d of pending2) processDelta(d);
|
|
3847
|
+
}).catch((err) => stream.emit("error", err)).finally(() => {
|
|
3848
|
+
snapshotInFlight = false;
|
|
3849
|
+
});
|
|
3850
|
+
};
|
|
3851
|
+
const resync = () => {
|
|
3852
|
+
if (recovering) return;
|
|
3853
|
+
recovering = true;
|
|
3854
|
+
merger.reset();
|
|
3855
|
+
seedVersion = -1;
|
|
3856
|
+
snapshotDone = false;
|
|
3857
|
+
lastVersion = -1;
|
|
3858
|
+
buffered = [];
|
|
3859
|
+
setTimeout(() => {
|
|
3860
|
+
recovering = false;
|
|
3861
|
+
triggerSnapshot();
|
|
3862
|
+
}, 100);
|
|
3863
|
+
};
|
|
3864
|
+
const processDelta = (d) => {
|
|
3865
|
+
const end = Number(d.end ?? d.version ?? 0);
|
|
3866
|
+
const begin = Number(d.begin ?? end);
|
|
3867
|
+
if (end <= seedVersion) return;
|
|
3868
|
+
let prevSequence;
|
|
3869
|
+
if (lastVersion < 0) {
|
|
3870
|
+
if (begin - 1 > seedVersion) {
|
|
3871
|
+
resync();
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
prevSequence = seedVersion;
|
|
3875
|
+
} else {
|
|
3876
|
+
prevSequence = begin - 1;
|
|
3877
|
+
}
|
|
3878
|
+
const r = merger.apply({
|
|
3879
|
+
kind: "delta",
|
|
3880
|
+
bids: toFuturesLevels(d.bids, scale),
|
|
3881
|
+
asks: toFuturesLevels(d.asks, scale),
|
|
3882
|
+
sequence: end,
|
|
3883
|
+
prevSequence,
|
|
3884
|
+
timestamp: d.timestamp ?? d.cts ?? Date.now()
|
|
3885
|
+
});
|
|
3886
|
+
if (r.ok) {
|
|
3887
|
+
lastVersion = end;
|
|
3888
|
+
emit(r);
|
|
3889
|
+
} else if (r.reason === "gap" || r.reason === "no-snapshot") {
|
|
3890
|
+
resync();
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
const onMessage = (raw) => {
|
|
3894
|
+
if (typeof raw !== "string") return;
|
|
3895
|
+
let msg;
|
|
3896
|
+
try {
|
|
3897
|
+
msg = JSON.parse(raw);
|
|
3898
|
+
} catch {
|
|
3899
|
+
return;
|
|
3900
|
+
}
|
|
3901
|
+
if (msg.channel === "push.depth") {
|
|
3902
|
+
if (msg.symbol && msg.symbol !== symbol) return;
|
|
3903
|
+
const d = msg.data;
|
|
3904
|
+
if (!d) return;
|
|
3905
|
+
if (!snapshotDone) {
|
|
3906
|
+
buffered.push(d);
|
|
3907
|
+
if (!snapshotInFlight) triggerSnapshot();
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
processDelta(d);
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
if (msg.channel === "rs.error" || msg.channel?.startsWith("rs.error")) {
|
|
3914
|
+
stream.emit("error", new ExchangeError("mexc", String(msg.data)));
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
if (msg.channel === "rs.sub.depth" && msg.data !== "success") {
|
|
3918
|
+
stream.emit(
|
|
3919
|
+
"error",
|
|
3920
|
+
new ExchangeError("mexc", `subscribe rejected: ${String(msg.data)}`)
|
|
3921
|
+
);
|
|
3922
|
+
}
|
|
3923
|
+
};
|
|
3924
|
+
ws = new WSClient({
|
|
3925
|
+
url: WS_FUTURES5,
|
|
3926
|
+
onOpen: (handle) => {
|
|
3927
|
+
merger.reset();
|
|
3928
|
+
seedVersion = -1;
|
|
3929
|
+
snapshotDone = false;
|
|
3930
|
+
lastVersion = -1;
|
|
3931
|
+
buffered = [];
|
|
3932
|
+
handle.send(
|
|
3933
|
+
JSON.stringify({ method: "sub.depth", param: { symbol } })
|
|
3934
|
+
);
|
|
3935
|
+
},
|
|
3936
|
+
onMessage,
|
|
3937
|
+
pingIntervalMs: PING_INTERVAL_MS3,
|
|
3938
|
+
pingPayload: () => JSON.stringify({ method: "ping" })
|
|
3939
|
+
});
|
|
3940
|
+
ws.on("open", () => stream.emit("connected"));
|
|
3941
|
+
ws.on("close", (r) => stream.emit("disconnected", r));
|
|
3942
|
+
ws.on("reconnecting", (a, w) => stream.emit("reconnecting", a, w));
|
|
3943
|
+
ws.on("error", (e) => stream.emit("error", e));
|
|
3944
|
+
ws.connect().catch((e) => stream.emit("error", e));
|
|
3945
|
+
return stream;
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
// src/exchanges/mexc/index.ts
|
|
3949
|
+
var MexcClient = class {
|
|
3950
|
+
exchange = "mexc";
|
|
3951
|
+
transformUrl;
|
|
3952
|
+
timeoutMs;
|
|
3953
|
+
constructor(opts = {}) {
|
|
3954
|
+
this.transformUrl = opts.transformUrl;
|
|
3955
|
+
this.timeoutMs = opts.timeoutMs;
|
|
3956
|
+
}
|
|
3957
|
+
fetchOrderbook(symbol, opts = {}) {
|
|
3958
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3959
|
+
return fetchMexcOrderbook({
|
|
3960
|
+
symbol: pair,
|
|
3961
|
+
market,
|
|
3962
|
+
depth: opts.depth,
|
|
3963
|
+
timeoutMs: this.timeoutMs,
|
|
3964
|
+
transformUrl: this.transformUrl
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
/** Perpetuals only — see the class note on spot. */
|
|
3968
|
+
streamOrderbook(symbol, opts = {}) {
|
|
3969
|
+
const { pair, market } = parseSymbol(symbol);
|
|
3970
|
+
return streamMexcOrderbook({
|
|
3971
|
+
symbol: pair,
|
|
3972
|
+
market,
|
|
3973
|
+
depth: opts.depth,
|
|
3974
|
+
transformUrl: this.transformUrl,
|
|
3975
|
+
timeoutMs: this.timeoutMs
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
};
|
|
3979
|
+
|
|
3980
|
+
export { AsterClient, Backoff, BinanceClient, BingxClient, BitgetClient, BookMerger, BybitClient, CoinexClient, DeribitClient, EdgexClient, ExchangeError, GateClient, HyperliquidClient, KucoinClient, LighterClient, MexcClient, OkxClient, OrderbookStream, RateLimitError, SequenceGapError, WhitebitClient, parseSymbol };
|
|
3439
3981
|
//# sourceMappingURL=index.js.map
|
|
3440
3982
|
//# sourceMappingURL=index.js.map
|