@0xmonaco/core 1.0.93 → 1.0.94
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.
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compliance API Implementation
|
|
3
3
|
*
|
|
4
|
-
* Reports whether
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* required.
|
|
4
|
+
* Reports whether trading is available in the caller's region. A caller from a
|
|
5
|
+
* restricted region keeps every read, signing in and withdrawing; every other
|
|
6
|
+
* write is refused with HTTP 451 `REGION_RESTRICTED`. This endpoint always
|
|
7
|
+
* answers. Public — no authentication required.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
|
11
11
|
* const { restricted } = await sdk.compliance.getRegionStatus();
|
|
12
|
-
* if (restricted)
|
|
12
|
+
* if (restricted) disableTrading();
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
import type { ComplianceAPI, GetRegionStatusResponse } from "@0xmonaco/types";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compliance API Implementation
|
|
3
3
|
*
|
|
4
|
-
* Reports whether
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* required.
|
|
4
|
+
* Reports whether trading is available in the caller's region. A caller from a
|
|
5
|
+
* restricted region keeps every read, signing in and withdrawing; every other
|
|
6
|
+
* write is refused with HTTP 451 `REGION_RESTRICTED`. This endpoint always
|
|
7
|
+
* answers. Public — no authentication required.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
|
11
11
|
* const { restricted } = await sdk.compliance.getRegionStatus();
|
|
12
|
-
* if (restricted)
|
|
12
|
+
* if (restricted) disableTrading();
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
import { BaseAPI } from "../base";
|
|
@@ -6,6 +6,7 @@ import { keysToCamelCase } from "./utils";
|
|
|
6
6
|
// Connection constants
|
|
7
7
|
const CONNECTION_TIMEOUT = 10000;
|
|
8
8
|
const HEARTBEAT_INTERVAL = 15000;
|
|
9
|
+
const IDLE_TIMEOUT = 30000;
|
|
9
10
|
const MAX_RECONNECT_DELAY = 30000;
|
|
10
11
|
const RECONNECT_BASE_DELAY = 1000;
|
|
11
12
|
/**
|
|
@@ -576,6 +577,68 @@ function parseTradeEventData(raw) {
|
|
|
576
577
|
executedAt: readString(raw, "executed_at"),
|
|
577
578
|
};
|
|
578
579
|
}
|
|
580
|
+
/**
|
|
581
|
+
* Parse one orderbook frame: a live `orderbook_update` event, or the row of an
|
|
582
|
+
* `orderbook` snapshot, which has the same shape.
|
|
583
|
+
*/
|
|
584
|
+
function parseOrderbookEvent(rawData) {
|
|
585
|
+
const data = rawData;
|
|
586
|
+
const orderbookData = data.data;
|
|
587
|
+
return {
|
|
588
|
+
// `trading_pair_id` is the pair UUID; `symbol` is the display string
|
|
589
|
+
// (e.g. "AMZN/USDC"). `tradingPairId` must carry the UUID.
|
|
590
|
+
tradingPairId: data.trading_pair_id,
|
|
591
|
+
tradingMode: data.trading_mode,
|
|
592
|
+
bids: (orderbookData?.bids || []).map((level) => ({
|
|
593
|
+
price: level.price,
|
|
594
|
+
quantity: level.quantity,
|
|
595
|
+
orderCount: level.order_count || 0,
|
|
596
|
+
})),
|
|
597
|
+
asks: (orderbookData?.asks || []).map((level) => ({
|
|
598
|
+
price: level.price,
|
|
599
|
+
quantity: level.quantity,
|
|
600
|
+
orderCount: level.order_count || 0,
|
|
601
|
+
})),
|
|
602
|
+
// Rust sends `null` for an empty side; normalize the nullable
|
|
603
|
+
// `Option` to `undefined` so it matches the `string | undefined` type
|
|
604
|
+
// rather than leaking `null` past the cast.
|
|
605
|
+
bestBid: orderbookData?.best_bid ?? undefined,
|
|
606
|
+
bestAsk: orderbookData?.best_ask ?? undefined,
|
|
607
|
+
bidVolume: orderbookData?.bid_volume || undefined,
|
|
608
|
+
askVolume: orderbookData?.ask_volume || undefined,
|
|
609
|
+
priceChange: orderbookData?.price_change
|
|
610
|
+
? {
|
|
611
|
+
side: orderbookData.price_change.side,
|
|
612
|
+
// Normalize nullable Rust `Option` prices to `undefined` (a
|
|
613
|
+
// level-add has no old price, a level-remove no new price) so
|
|
614
|
+
// neither leaks `null` past the `string | undefined` type.
|
|
615
|
+
oldPrice: orderbookData.price_change.old_price ?? undefined,
|
|
616
|
+
newPrice: orderbookData.price_change.new_price ?? undefined,
|
|
617
|
+
levelRemoved: orderbookData.price_change.level_removed || false,
|
|
618
|
+
levelAdded: orderbookData.price_change.level_added || false,
|
|
619
|
+
}
|
|
620
|
+
: undefined,
|
|
621
|
+
baseDecimals: data.base_decimals || 0,
|
|
622
|
+
quoteDecimals: data.quote_decimals || 0,
|
|
623
|
+
timestamp: data.timestamp || new Date().toISOString(),
|
|
624
|
+
sequence: data.sequence_number || 0,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Parse the one row of an `orderbook` snapshot: the channel's current book in
|
|
629
|
+
* the live frame's shape, read the same way.
|
|
630
|
+
*
|
|
631
|
+
* The live parser casts; here a row that is not an object, or carries no `data`
|
|
632
|
+
* object, throws, so {@link snapshotFrameHandler} withholds the frame rather
|
|
633
|
+
* than handing over an empty book. The engine's book read carries no sequence,
|
|
634
|
+
* so the row's `sequence` is 0.
|
|
635
|
+
*/
|
|
636
|
+
function parseOrderbookSnapshotRow(raw) {
|
|
637
|
+
if (!isRecord(raw) || !isRecord(raw.data)) {
|
|
638
|
+
throw new Error("Invalid orderbook snapshot row: must be an object with a data object");
|
|
639
|
+
}
|
|
640
|
+
return parseOrderbookEvent(raw);
|
|
641
|
+
}
|
|
579
642
|
/**
|
|
580
643
|
* Parse one `ohlcv` snapshot row: the live candle frame's `data` body.
|
|
581
644
|
*
|
|
@@ -992,6 +1055,17 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
992
1055
|
? options.reconnectBaseDelayMs
|
|
993
1056
|
: RECONNECT_BASE_DELAY;
|
|
994
1057
|
const connectionTimeout = Number.isFinite(options.connectionTimeoutMs) && options.connectionTimeoutMs > 0 ? options.connectionTimeoutMs : CONNECTION_TIMEOUT;
|
|
1058
|
+
const idleTimeout = Number.isFinite(options.idleTimeoutMs) && options.idleTimeoutMs > 0 ? options.idleTimeoutMs : IDLE_TIMEOUT;
|
|
1059
|
+
// A connection is dropped one tick after the idle window opens with a Ping
|
|
1060
|
+
// unanswered, so a third of the window leaves room for a second Ping and its
|
|
1061
|
+
// Pong: one lost Pong never trips a live connection.
|
|
1062
|
+
const heartbeatInterval = Math.min(HEARTBEAT_INTERVAL, idleTimeout / 3);
|
|
1063
|
+
// When the current socket last delivered anything. The server's own
|
|
1064
|
+
// transport-level ping never reaches JavaScript, so this is the only
|
|
1065
|
+
// liveness signal the client has.
|
|
1066
|
+
let lastMessageAt = 0;
|
|
1067
|
+
// When the current socket's last Ping went out; 0 before the first.
|
|
1068
|
+
let lastPingAt = 0;
|
|
995
1069
|
// Close code of the connection loss the next successful reopen recovers
|
|
996
1070
|
// from; null when no resync is owed (first connect, clean disconnect).
|
|
997
1071
|
let pendingResyncCode = null;
|
|
@@ -1124,11 +1198,30 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1124
1198
|
};
|
|
1125
1199
|
const startHeartbeat = () => {
|
|
1126
1200
|
stopHeartbeat();
|
|
1201
|
+
lastMessageAt = Date.now();
|
|
1202
|
+
lastPingAt = 0;
|
|
1127
1203
|
heartbeatTimer = setInterval(() => {
|
|
1128
|
-
|
|
1129
|
-
|
|
1204
|
+
const socket = ws;
|
|
1205
|
+
if (socket?.readyState !== WebSocket.OPEN)
|
|
1206
|
+
return;
|
|
1207
|
+
// An open socket that has delivered nothing, not even the Pong to an
|
|
1208
|
+
// earlier Ping, is dead: the browser still reports it OPEN, so without
|
|
1209
|
+
// this the app freezes on its last data with no reconnect. Only an
|
|
1210
|
+
// unanswered Ping counts: a background tab's throttled timer can tick
|
|
1211
|
+
// long after the last Pong, and that gap is not silence.
|
|
1212
|
+
if (lastPingAt > lastMessageAt && Date.now() - lastMessageAt >= idleTimeout) {
|
|
1213
|
+
socket.close(1000, "Idle timeout");
|
|
1214
|
+
// Its close event may take long to land on a dead network, so the
|
|
1215
|
+
// loss is handled now as an abnormal close. The late event then
|
|
1216
|
+
// belongs to an abandoned socket and is ignored. The server follows
|
|
1217
|
+
// SESSION_INVALID with a 1008 close, so when that close is what got
|
|
1218
|
+
// lost, the loss is read as the 1008 it announced.
|
|
1219
|
+
handleSocketLoss(lastAuthError?.code === "SESSION_INVALID" ? 1008 : 1006);
|
|
1220
|
+
return;
|
|
1130
1221
|
}
|
|
1131
|
-
|
|
1222
|
+
lastPingAt = Date.now();
|
|
1223
|
+
socket.send(JSON.stringify({ type: "Ping" }));
|
|
1224
|
+
}, heartbeatInterval);
|
|
1132
1225
|
};
|
|
1133
1226
|
const stopReconnect = () => {
|
|
1134
1227
|
if (reconnectTimer) {
|
|
@@ -1380,6 +1473,83 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1380
1473
|
}
|
|
1381
1474
|
}
|
|
1382
1475
|
};
|
|
1476
|
+
/**
|
|
1477
|
+
* The current socket's connection is gone, by its close event or because it
|
|
1478
|
+
* went silent: decide whether to reconnect, ask for fresh credentials, or
|
|
1479
|
+
* report a terminal disconnect.
|
|
1480
|
+
*/
|
|
1481
|
+
const handleSocketLoss = (code) => {
|
|
1482
|
+
ws = null;
|
|
1483
|
+
stopHeartbeat();
|
|
1484
|
+
// The server dropped every subscription with the connection.
|
|
1485
|
+
resetSubscriptionAcks();
|
|
1486
|
+
// Terminal authentication loss: the server revoked the session
|
|
1487
|
+
// (SESSION_INVALID, then 1008) or rejected this connection's
|
|
1488
|
+
// handshake (AUTH_FAILED, then a close). Reconnecting would replay
|
|
1489
|
+
// the same rejected credentials into the same rejection forever, so
|
|
1490
|
+
// drop them, skip the reconnect, and let the app supply a fresh
|
|
1491
|
+
// session. Handlers stay registered so that session resubscribes
|
|
1492
|
+
// them. A 1008 without SESSION_INVALID (the message-rate cap) is a
|
|
1493
|
+
// back-off-and-retry and falls through to the reconnect below, as
|
|
1494
|
+
// does a close after the app already replaced the rejected session
|
|
1495
|
+
// (e.g. re-authenticating from onError): the fresh session is what
|
|
1496
|
+
// the reconnect uses.
|
|
1497
|
+
const authError = lastAuthError;
|
|
1498
|
+
// A handshake stamped outside the server's window is a clock
|
|
1499
|
+
// problem, not a dead session: reconnect and re-sign, a bounded
|
|
1500
|
+
// number of times, instead of discarding the credentials.
|
|
1501
|
+
const skewRetry = autoReconnect && authError?.code === "AUTH_FAILED" && SKEW_REJECTION.test(authError.message) && skewAuthRetries < MAX_SKEW_AUTH_RETRIES;
|
|
1502
|
+
if (skewRetry)
|
|
1503
|
+
skewAuthRetries++;
|
|
1504
|
+
const authLoss = !skewRetry &&
|
|
1505
|
+
authError !== null &&
|
|
1506
|
+
authError.session !== undefined &&
|
|
1507
|
+
(authError.code === "AUTH_FAILED" || authError.code === "ACCOUNT_DEACTIVATED" || authError.code === "ACCOUNT_RESTRICTED" || code === 1008);
|
|
1508
|
+
const terminalAuthLoss = authLoss && session === authError.session;
|
|
1509
|
+
if (terminalAuthLoss) {
|
|
1510
|
+
session = undefined;
|
|
1511
|
+
// The reopen with a fresh session still owes the app a resync —
|
|
1512
|
+
// nothing was delivered while the session was dead — whether or
|
|
1513
|
+
// not automatic reconnect is on, since that reopen is driven by
|
|
1514
|
+
// the new session, not by the reconnect loop.
|
|
1515
|
+
if (pendingResyncCode === null) {
|
|
1516
|
+
pendingResyncCode = code;
|
|
1517
|
+
}
|
|
1518
|
+
emitStatus("disconnected");
|
|
1519
|
+
options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: code });
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
// The app already handed over a replacement session on this socket.
|
|
1523
|
+
// Had it arrived after the close, setSessionKeypair would have
|
|
1524
|
+
// connected with it; honour that even with automatic reconnect off
|
|
1525
|
+
// (with it on, the reconnect below uses the new session).
|
|
1526
|
+
if (authLoss && session !== undefined && !autoReconnect) {
|
|
1527
|
+
if (pendingResyncCode === null) {
|
|
1528
|
+
pendingResyncCode = code;
|
|
1529
|
+
}
|
|
1530
|
+
emitStatus("disconnected");
|
|
1531
|
+
connect().catch((err) => {
|
|
1532
|
+
console.warn("WebSocket: Failed to connect with the replacement session:", err);
|
|
1533
|
+
});
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
// Reconnect on abnormal close. 1013 is the server's slow-client
|
|
1537
|
+
// shed: the stream has a gap, so the reopen owes the app a resync.
|
|
1538
|
+
// Keep the first owed code (upgrading to 1013 if it arrives later):
|
|
1539
|
+
// failures of the reconnect attempts themselves must not launder
|
|
1540
|
+
// the slow-client signal into a generic 1006.
|
|
1541
|
+
if (autoReconnect && code !== 1000) {
|
|
1542
|
+
if (pendingResyncCode === null || code === 1013) {
|
|
1543
|
+
pendingResyncCode = code;
|
|
1544
|
+
}
|
|
1545
|
+
scheduleReconnect();
|
|
1546
|
+
}
|
|
1547
|
+
// "disconnected" is terminal: suppressed while a retry is armed
|
|
1548
|
+
// (scheduleReconnect emitted "reconnecting" instead).
|
|
1549
|
+
if (!reconnectTimer) {
|
|
1550
|
+
emitTerminalDisconnected();
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1383
1553
|
const connect = () => {
|
|
1384
1554
|
return new Promise((resolve, reject) => {
|
|
1385
1555
|
// Use WebSocket's built-in readyState instead of manual tracking
|
|
@@ -1441,6 +1611,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1441
1611
|
socket.onmessage = (event) => {
|
|
1442
1612
|
if (ws !== socket)
|
|
1443
1613
|
return;
|
|
1614
|
+
lastMessageAt = Date.now();
|
|
1444
1615
|
try {
|
|
1445
1616
|
const msg = JSON.parse(event.data);
|
|
1446
1617
|
// Handle pong silently
|
|
@@ -1464,76 +1635,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1464
1635
|
resolve();
|
|
1465
1636
|
return;
|
|
1466
1637
|
}
|
|
1467
|
-
|
|
1468
|
-
stopHeartbeat();
|
|
1469
|
-
// The server dropped every subscription with the connection.
|
|
1470
|
-
resetSubscriptionAcks();
|
|
1471
|
-
// Terminal authentication loss: the server revoked the session
|
|
1472
|
-
// (SESSION_INVALID, then 1008) or rejected this connection's
|
|
1473
|
-
// handshake (AUTH_FAILED, then a close). Reconnecting would replay
|
|
1474
|
-
// the same rejected credentials into the same rejection forever, so
|
|
1475
|
-
// drop them, skip the reconnect, and let the app supply a fresh
|
|
1476
|
-
// session. Handlers stay registered so that session resubscribes
|
|
1477
|
-
// them. A 1008 without SESSION_INVALID (the message-rate cap) is a
|
|
1478
|
-
// back-off-and-retry and falls through to the reconnect below, as
|
|
1479
|
-
// does a close after the app already replaced the rejected session
|
|
1480
|
-
// (e.g. re-authenticating from onError): the fresh session is what
|
|
1481
|
-
// the reconnect uses.
|
|
1482
|
-
const authError = lastAuthError;
|
|
1483
|
-
// A handshake stamped outside the server's window is a clock
|
|
1484
|
-
// problem, not a dead session: reconnect and re-sign, a bounded
|
|
1485
|
-
// number of times, instead of discarding the credentials.
|
|
1486
|
-
const skewRetry = autoReconnect && authError?.code === "AUTH_FAILED" && SKEW_REJECTION.test(authError.message) && skewAuthRetries < MAX_SKEW_AUTH_RETRIES;
|
|
1487
|
-
if (skewRetry)
|
|
1488
|
-
skewAuthRetries++;
|
|
1489
|
-
const authLoss = !skewRetry &&
|
|
1490
|
-
authError !== null &&
|
|
1491
|
-
authError.session !== undefined &&
|
|
1492
|
-
(authError.code === "AUTH_FAILED" || authError.code === "ACCOUNT_DEACTIVATED" || authError.code === "ACCOUNT_RESTRICTED" || event.code === 1008);
|
|
1493
|
-
const terminalAuthLoss = authLoss && session === authError.session;
|
|
1494
|
-
if (terminalAuthLoss) {
|
|
1495
|
-
session = undefined;
|
|
1496
|
-
// The reopen with a fresh session still owes the app a resync —
|
|
1497
|
-
// nothing was delivered while the session was dead — whether or
|
|
1498
|
-
// not automatic reconnect is on, since that reopen is driven by
|
|
1499
|
-
// the new session, not by the reconnect loop.
|
|
1500
|
-
if (pendingResyncCode === null) {
|
|
1501
|
-
pendingResyncCode = event.code;
|
|
1502
|
-
}
|
|
1503
|
-
emitStatus("disconnected");
|
|
1504
|
-
options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
// The app already handed over a replacement session on this socket.
|
|
1508
|
-
// Had it arrived after the close, setSessionKeypair would have
|
|
1509
|
-
// connected with it; honour that even with automatic reconnect off
|
|
1510
|
-
// (with it on, the reconnect below uses the new session).
|
|
1511
|
-
if (authLoss && session !== undefined && !autoReconnect) {
|
|
1512
|
-
if (pendingResyncCode === null) {
|
|
1513
|
-
pendingResyncCode = event.code;
|
|
1514
|
-
}
|
|
1515
|
-
emitStatus("disconnected");
|
|
1516
|
-
connect().catch((err) => {
|
|
1517
|
-
console.warn("WebSocket: Failed to connect with the replacement session:", err);
|
|
1518
|
-
});
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
// Reconnect on abnormal close. 1013 is the server's slow-client
|
|
1522
|
-
// shed: the stream has a gap, so the reopen owes the app a resync.
|
|
1523
|
-
// Keep the first owed code (upgrading to 1013 if it arrives later):
|
|
1524
|
-
// failures of the reconnect attempts themselves must not launder
|
|
1525
|
-
// the slow-client signal into a generic 1006.
|
|
1526
|
-
if (autoReconnect && event.code !== 1000) {
|
|
1527
|
-
if (pendingResyncCode === null || event.code === 1013) {
|
|
1528
|
-
pendingResyncCode = event.code;
|
|
1529
|
-
}
|
|
1530
|
-
scheduleReconnect();
|
|
1531
|
-
}
|
|
1532
|
-
// "disconnected" is terminal: suppressed while a retry is armed
|
|
1533
|
-
// (scheduleReconnect emitted "reconnecting" instead).
|
|
1534
|
-
if (!reconnectTimer) {
|
|
1535
|
-
emitTerminalDisconnected();
|
|
1536
|
-
}
|
|
1638
|
+
handleSocketLoss(event.code);
|
|
1537
1639
|
};
|
|
1538
1640
|
socket.onerror = () => clearTimeout(timeout);
|
|
1539
1641
|
// A retry's socket stays inside the outage "reconnecting" already
|
|
@@ -1626,7 +1728,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1626
1728
|
}
|
|
1627
1729
|
}, onSnapshot && snapshotFrameHandler("orders", parseOrderSnapshotItem, onSnapshot));
|
|
1628
1730
|
};
|
|
1629
|
-
const subscribeOrderbook = (tradingPairId, tradingMode, magnitude, quotationMode, handler) => {
|
|
1731
|
+
const subscribeOrderbook = (tradingPairId, tradingMode, magnitude, quotationMode, handler, onSnapshot) => {
|
|
1630
1732
|
if (!tradingPairId || !tradingMode || magnitude === undefined || !quotationMode) {
|
|
1631
1733
|
throw new Error(`orderbook subscription requires all params: tradingPairId="${tradingPairId}", tradingMode="${tradingMode}", magnitude=${magnitude} (valid: ${ALL_MAGNITUDES.join(", ")}), quotationMode="${quotationMode}" (valid: "BASE", "QUOTE")`);
|
|
1632
1734
|
}
|
|
@@ -1636,53 +1738,12 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1636
1738
|
const channel = `orderbook:${tradingPairId}:${tradingMode}:${magnitude}:${quotationMode.toLowerCase()}`;
|
|
1637
1739
|
return subscribe(channel, (rawData) => {
|
|
1638
1740
|
try {
|
|
1639
|
-
|
|
1640
|
-
const orderbookData = data.data;
|
|
1641
|
-
const event = {
|
|
1642
|
-
// `trading_pair_id` is the pair UUID; `symbol` is the display string
|
|
1643
|
-
// (e.g. "AMZN/USDC"). `tradingPairId` must carry the UUID.
|
|
1644
|
-
tradingPairId: data.trading_pair_id,
|
|
1645
|
-
tradingMode: data.trading_mode,
|
|
1646
|
-
bids: (orderbookData?.bids || []).map((level) => ({
|
|
1647
|
-
price: level.price,
|
|
1648
|
-
quantity: level.quantity,
|
|
1649
|
-
orderCount: level.order_count || 0,
|
|
1650
|
-
})),
|
|
1651
|
-
asks: (orderbookData?.asks || []).map((level) => ({
|
|
1652
|
-
price: level.price,
|
|
1653
|
-
quantity: level.quantity,
|
|
1654
|
-
orderCount: level.order_count || 0,
|
|
1655
|
-
})),
|
|
1656
|
-
// Rust sends `null` for an empty side; normalize the nullable
|
|
1657
|
-
// `Option` to `undefined` so it matches the `string | undefined` type
|
|
1658
|
-
// rather than leaking `null` past the cast.
|
|
1659
|
-
bestBid: orderbookData?.best_bid ?? undefined,
|
|
1660
|
-
bestAsk: orderbookData?.best_ask ?? undefined,
|
|
1661
|
-
bidVolume: orderbookData?.bid_volume || undefined,
|
|
1662
|
-
askVolume: orderbookData?.ask_volume || undefined,
|
|
1663
|
-
priceChange: orderbookData?.price_change
|
|
1664
|
-
? {
|
|
1665
|
-
side: orderbookData.price_change.side,
|
|
1666
|
-
// Normalize nullable Rust `Option` prices to `undefined` (a
|
|
1667
|
-
// level-add has no old price, a level-remove no new price) so
|
|
1668
|
-
// neither leaks `null` past the `string | undefined` type.
|
|
1669
|
-
oldPrice: orderbookData.price_change.old_price ?? undefined,
|
|
1670
|
-
newPrice: orderbookData.price_change.new_price ?? undefined,
|
|
1671
|
-
levelRemoved: orderbookData.price_change.level_removed || false,
|
|
1672
|
-
levelAdded: orderbookData.price_change.level_added || false,
|
|
1673
|
-
}
|
|
1674
|
-
: undefined,
|
|
1675
|
-
baseDecimals: data.base_decimals || 0,
|
|
1676
|
-
quoteDecimals: data.quote_decimals || 0,
|
|
1677
|
-
timestamp: data.timestamp || new Date().toISOString(),
|
|
1678
|
-
sequence: data.sequence_number || 0,
|
|
1679
|
-
};
|
|
1680
|
-
handler(event);
|
|
1741
|
+
handler(parseOrderbookEvent(rawData));
|
|
1681
1742
|
}
|
|
1682
1743
|
catch (err) {
|
|
1683
1744
|
console.error("WebSocket: Error processing orderbook event", err);
|
|
1684
1745
|
}
|
|
1685
|
-
});
|
|
1746
|
+
}, onSnapshot && snapshotFrameHandler("orderbook", parseOrderbookSnapshotRow, onSnapshot));
|
|
1686
1747
|
};
|
|
1687
1748
|
const subscribeOHLCV = (tradingPairId, tradingMode, interval, handler, priceType, onSnapshot) => {
|
|
1688
1749
|
// Four-token channel selects the trade-derived candles (back-compat); the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.94",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://docs.0xmonaco.com/sdk/typescript",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"viem": "^2.45.2"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@0xmonaco/contracts": "1.0.
|
|
23
|
-
"@0xmonaco/types": "1.0.
|
|
22
|
+
"@0xmonaco/contracts": "1.0.94",
|
|
23
|
+
"@0xmonaco/types": "1.0.94",
|
|
24
24
|
"@noble/curves": "^1.9.1",
|
|
25
25
|
"@noble/hashes": "^2.4.0",
|
|
26
26
|
"http-status-codes": "^2.3.0"
|