@0xmonaco/core 1.0.37 → 1.0.38-develop.ebf8e8c
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/dist/api/websocket/types.d.ts +11 -43
- package/dist/api/websocket/websocket.js +119 -6
- package/dist/sdk.js +4 -1
- package/package.json +3 -3
|
@@ -1,51 +1,15 @@
|
|
|
1
|
-
import type { ConditionalOrderEvent, Interval, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, SessionCredentials, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
|
|
2
|
-
export type StatusHandler
|
|
1
|
+
import type { ConditionalOrderEvent, Interval, LiquidationEvent, MonacoWebSocketConfig, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, PositionEvent, SessionCredentials, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
|
|
2
|
+
export type { ResyncHandler, StatusHandler, WebSocketResyncInfo } from "@0xmonaco/types";
|
|
3
3
|
export type MessageHandler<T> = (data: T) => void;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Options for {@link createMonacoWebSocket}. The reconnect/resync knobs and
|
|
6
|
+
* callbacks are shared with the main SDK config via {@link MonacoWebSocketConfig}
|
|
7
|
+
* (settable as `SDKConfig.ws`); the session keypair is factory-only because the
|
|
8
|
+
* SDK propagates it after {@link login} rather than at construction.
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
11
|
-
/** WebSocket close code of the connection that was lost. */
|
|
12
|
-
code: number;
|
|
13
|
-
/**
|
|
14
|
-
* True when the server closed with 1013 (try again later): the client fell
|
|
15
|
-
* behind a sequential stream (orders, conditional orders, liquidations,
|
|
16
|
-
* trades, movements) and the stream has a gap. There is no wire sequence
|
|
17
|
-
* number — the 1013 close is the gap signal, and a snapshot refetch is the
|
|
18
|
-
* only safe repair.
|
|
19
|
-
*/
|
|
20
|
-
slowClient: boolean;
|
|
21
|
-
}
|
|
22
|
-
export type ResyncHandler = (info: WebSocketResyncInfo) => void;
|
|
23
|
-
export interface MonacoWebSocketOptions {
|
|
10
|
+
export interface MonacoWebSocketOptions extends MonacoWebSocketConfig {
|
|
24
11
|
/** Session keypair for authenticated channels (orders, balances, etc.) */
|
|
25
12
|
session?: SessionCredentials;
|
|
26
|
-
/** Enable auto-reconnect on disconnect (default: true) */
|
|
27
|
-
autoReconnect?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Maximum reconnection attempts (default: unlimited). Retry delay is
|
|
30
|
-
* full-jitter exponential backoff capped at 30s, so unlimited retries are
|
|
31
|
-
* capped-rate, not a tight loop.
|
|
32
|
-
*/
|
|
33
|
-
maxReconnectAttempts?: number;
|
|
34
|
-
/** Base delay for the exponential backoff in milliseconds (default: 1000) */
|
|
35
|
-
reconnectBaseDelayMs?: number;
|
|
36
|
-
/** How long a connection attempt may stay CONNECTING before it is failed
|
|
37
|
-
* and retried, in milliseconds (default: 10000) */
|
|
38
|
-
connectionTimeoutMs?: number;
|
|
39
|
-
/** Callback for connection status changes */
|
|
40
|
-
onStatusChange?: StatusHandler;
|
|
41
|
-
/**
|
|
42
|
-
* Called after every automatic reconnect once the live stream is resumed
|
|
43
|
-
* (re-authenticated and re-subscribed). Refetch REST snapshots here and
|
|
44
|
-
* reconcile by `(id, updatedAt)`, last-writer-wins; events that arrived
|
|
45
|
-
* while the snapshot was in flight are newer than the snapshot rows they
|
|
46
|
-
* duplicate.
|
|
47
|
-
*/
|
|
48
|
-
onResync?: ResyncHandler;
|
|
49
13
|
}
|
|
50
14
|
export interface MonacoWebSocket {
|
|
51
15
|
/** Connect to the WebSocket server */
|
|
@@ -74,4 +38,8 @@ export interface MonacoWebSocket {
|
|
|
74
38
|
balances: (handler: MessageHandler<UserBalanceEvent>) => () => void;
|
|
75
39
|
/** Subscribe to conditional TP/SL lifecycle events (requires authentication) */
|
|
76
40
|
conditionalOrders: (handler: MessageHandler<ConditionalOrderEvent>, tradingPairId?: string) => () => void;
|
|
41
|
+
/** Subscribe to position state updates (requires authentication); omit `tradingPairId` for all pairs */
|
|
42
|
+
positions: (handler: MessageHandler<PositionEvent>, tradingPairId?: string) => () => void;
|
|
43
|
+
/** Subscribe to liquidation alerts (requires authentication); omit `tradingPairId` for all pairs */
|
|
44
|
+
liquidations: (handler: MessageHandler<LiquidationEvent>, tradingPairId?: string) => () => void;
|
|
77
45
|
}
|
|
@@ -18,7 +18,7 @@ function isRecord(value) {
|
|
|
18
18
|
function readString(source, field) {
|
|
19
19
|
const value = source[field];
|
|
20
20
|
if (typeof value !== "string" || value.length === 0) {
|
|
21
|
-
throw new Error(`Invalid
|
|
21
|
+
throw new Error(`Invalid WebSocket event: ${field} must be a non-empty string`);
|
|
22
22
|
}
|
|
23
23
|
return value;
|
|
24
24
|
}
|
|
@@ -27,14 +27,14 @@ function readOptionalString(source, field) {
|
|
|
27
27
|
if (value === undefined || value === null)
|
|
28
28
|
return undefined;
|
|
29
29
|
if (typeof value !== "string") {
|
|
30
|
-
throw new Error(`Invalid
|
|
30
|
+
throw new Error(`Invalid WebSocket event: ${field} must be a string`);
|
|
31
31
|
}
|
|
32
32
|
return value;
|
|
33
33
|
}
|
|
34
34
|
function readBoolean(source, field) {
|
|
35
35
|
const value = source[field];
|
|
36
36
|
if (typeof value !== "boolean") {
|
|
37
|
-
throw new Error(`Invalid
|
|
37
|
+
throw new Error(`Invalid WebSocket event: ${field} must be a boolean`);
|
|
38
38
|
}
|
|
39
39
|
return value;
|
|
40
40
|
}
|
|
@@ -43,14 +43,14 @@ function readOptionalNumber(source, field) {
|
|
|
43
43
|
if (value === undefined || value === null)
|
|
44
44
|
return undefined;
|
|
45
45
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
46
|
-
throw new Error(`Invalid
|
|
46
|
+
throw new Error(`Invalid WebSocket event: ${field} must be a finite number`);
|
|
47
47
|
}
|
|
48
48
|
return value;
|
|
49
49
|
}
|
|
50
50
|
function readEnum(source, field, allowed) {
|
|
51
51
|
const value = readString(source, field);
|
|
52
52
|
if (!allowed.includes(value)) {
|
|
53
|
-
throw new Error(`Invalid
|
|
53
|
+
throw new Error(`Invalid WebSocket event: ${field} must be one of ${allowed.join(", ")}`);
|
|
54
54
|
}
|
|
55
55
|
return value;
|
|
56
56
|
}
|
|
@@ -59,7 +59,7 @@ function readOptionalEnum(source, field, allowed) {
|
|
|
59
59
|
if (value === undefined || value === null)
|
|
60
60
|
return undefined;
|
|
61
61
|
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
62
|
-
throw new Error(`Invalid
|
|
62
|
+
throw new Error(`Invalid WebSocket event: ${field} must be one of ${allowed.join(", ")}`);
|
|
63
63
|
}
|
|
64
64
|
return value;
|
|
65
65
|
}
|
|
@@ -105,6 +105,69 @@ function parseConditionalOrderEvent(rawData) {
|
|
|
105
105
|
},
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
+
function parsePositionEvent(rawData) {
|
|
109
|
+
if (!isRecord(rawData)) {
|
|
110
|
+
throw new Error("Invalid position event: payload must be an object");
|
|
111
|
+
}
|
|
112
|
+
if (!isRecord(rawData.data)) {
|
|
113
|
+
throw new Error("Invalid position event: data must be an object");
|
|
114
|
+
}
|
|
115
|
+
const data = keysToCamelCase(rawData.data);
|
|
116
|
+
return {
|
|
117
|
+
// event_type is the event-class discriminator, validated strictly so a
|
|
118
|
+
// mis-routed frame is rejected rather than parsed as a position. The value
|
|
119
|
+
// fields below (side/status/reason) stay lenient so an unexpected *value*
|
|
120
|
+
// never drops a risk-critical update.
|
|
121
|
+
eventType: readEnum(rawData, "event_type", ["position_update"]),
|
|
122
|
+
userId: readString(rawData, "user_id"),
|
|
123
|
+
data: {
|
|
124
|
+
positionId: readString(data, "positionId"),
|
|
125
|
+
marginAccountId: readString(data, "marginAccountId"),
|
|
126
|
+
tradingPairId: readString(data, "tradingPairId"),
|
|
127
|
+
side: readString(data, "side"),
|
|
128
|
+
status: readString(data, "status"),
|
|
129
|
+
size: readString(data, "size"),
|
|
130
|
+
entryPrice: readString(data, "entryPrice"),
|
|
131
|
+
markPrice: readOptionalString(data, "markPrice"),
|
|
132
|
+
unrealizedPnl: readOptionalString(data, "unrealizedPnl"),
|
|
133
|
+
liquidationPrice: readOptionalString(data, "liquidationPrice"),
|
|
134
|
+
isolatedMargin: readString(data, "isolatedMargin"),
|
|
135
|
+
leverage: readString(data, "leverage"),
|
|
136
|
+
reason: readString(data, "reason"),
|
|
137
|
+
updatedAt: readString(data, "updatedAt"),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function parseLiquidationEvent(rawData) {
|
|
142
|
+
if (!isRecord(rawData)) {
|
|
143
|
+
throw new Error("Invalid liquidation event: payload must be an object");
|
|
144
|
+
}
|
|
145
|
+
if (!isRecord(rawData.data)) {
|
|
146
|
+
throw new Error("Invalid liquidation event: data must be an object");
|
|
147
|
+
}
|
|
148
|
+
const data = keysToCamelCase(rawData.data);
|
|
149
|
+
return {
|
|
150
|
+
// event_type is the event-class discriminator, validated strictly so a
|
|
151
|
+
// mis-routed frame is rejected rather than parsed as a liquidation. The
|
|
152
|
+
// free-form value fields below (status/accountState) stay lenient so an
|
|
153
|
+
// unexpected *value* never drops a risk-critical alert.
|
|
154
|
+
eventType: readEnum(rawData, "event_type", ["liquidation_alert"]),
|
|
155
|
+
userId: readString(rawData, "user_id"),
|
|
156
|
+
data: {
|
|
157
|
+
liquidationId: readString(data, "liquidationId"),
|
|
158
|
+
marginAccountId: readString(data, "marginAccountId"),
|
|
159
|
+
positionId: readOptionalString(data, "positionId"),
|
|
160
|
+
tradingPairId: readOptionalString(data, "tradingPairId"),
|
|
161
|
+
status: readString(data, "status"),
|
|
162
|
+
accountState: readString(data, "accountState"),
|
|
163
|
+
liquidationPrice: readOptionalString(data, "liquidationPrice"),
|
|
164
|
+
markPrice: readOptionalString(data, "markPrice"),
|
|
165
|
+
estimatedShortfall: readString(data, "estimatedShortfall"),
|
|
166
|
+
message: readString(data, "message"),
|
|
167
|
+
triggeredAt: readString(data, "triggeredAt"),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
108
171
|
/**
|
|
109
172
|
* Create a Monaco WebSocket client
|
|
110
173
|
*/
|
|
@@ -618,6 +681,54 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
618
681
|
}
|
|
619
682
|
});
|
|
620
683
|
};
|
|
684
|
+
/**
|
|
685
|
+
* Subscribe to authenticated user's position updates.
|
|
686
|
+
* Requires authentication - user is identified from the signed session-key
|
|
687
|
+
* handshake. Omit `tradingPairId` for all pairs, or pass one to filter.
|
|
688
|
+
* Lag policy is Conflate: a lagging subscriber is fast-forwarded, no gap
|
|
689
|
+
* signal.
|
|
690
|
+
*/
|
|
691
|
+
const subscribePositions = (handler, tradingPairId) => {
|
|
692
|
+
if (!handler) {
|
|
693
|
+
throw new Error("positions subscription requires a handler");
|
|
694
|
+
}
|
|
695
|
+
if (tradingPairId !== undefined && tradingPairId.length === 0) {
|
|
696
|
+
throw new Error("positions tradingPairId cannot be empty");
|
|
697
|
+
}
|
|
698
|
+
const channel = tradingPairId ? `positions:${tradingPairId}` : "positions";
|
|
699
|
+
return subscribe(channel, (rawData) => {
|
|
700
|
+
try {
|
|
701
|
+
handler(parsePositionEvent(rawData));
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
console.error("WebSocket: Error processing position event", err);
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* Subscribe to authenticated user's liquidation alerts.
|
|
710
|
+
* Requires authentication - user is identified from the signed session-key
|
|
711
|
+
* handshake. Omit `tradingPairId` for all pairs, or pass one to filter.
|
|
712
|
+
* Lag policy is Disconnect: a subscriber that falls behind is closed with
|
|
713
|
+
* `1013`; resync by refetching a snapshot (no wire sequence number).
|
|
714
|
+
*/
|
|
715
|
+
const subscribeLiquidations = (handler, tradingPairId) => {
|
|
716
|
+
if (!handler) {
|
|
717
|
+
throw new Error("liquidations subscription requires a handler");
|
|
718
|
+
}
|
|
719
|
+
if (tradingPairId !== undefined && tradingPairId.length === 0) {
|
|
720
|
+
throw new Error("liquidations tradingPairId cannot be empty");
|
|
721
|
+
}
|
|
722
|
+
const channel = tradingPairId ? `liquidations:${tradingPairId}` : "liquidations";
|
|
723
|
+
return subscribe(channel, (rawData) => {
|
|
724
|
+
try {
|
|
725
|
+
handler(parseLiquidationEvent(rawData));
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
console.error("WebSocket: Error processing liquidation event", err);
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
};
|
|
621
732
|
return {
|
|
622
733
|
connect,
|
|
623
734
|
disconnect,
|
|
@@ -646,5 +757,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
646
757
|
userOrders: subscribeUserOrders,
|
|
647
758
|
balances: subscribeBalances,
|
|
648
759
|
conditionalOrders: subscribeConditionalOrders,
|
|
760
|
+
positions: subscribePositions,
|
|
761
|
+
liquidations: subscribeLiquidations,
|
|
649
762
|
};
|
|
650
763
|
}
|
package/dist/sdk.js
CHANGED
|
@@ -201,7 +201,10 @@ export class MonacoSDKImpl {
|
|
|
201
201
|
this.trading = new TradingAPIImpl(apiUrl);
|
|
202
202
|
this.orderbook = new OrderbookAPIImpl(apiUrl);
|
|
203
203
|
this.trades = new TradesAPIImpl(apiUrl);
|
|
204
|
-
|
|
204
|
+
// Thread the reconnect/resync knobs and callbacks from SDK config into the
|
|
205
|
+
// internally-built ws client. The session keypair is not passed here — it
|
|
206
|
+
// is propagated on login via propagateSession/setSessionKeypair.
|
|
207
|
+
this.ws = createMonacoWebSocket(wsUrl, cfg.ws);
|
|
205
208
|
// Auto-connect WebSocket, log errors
|
|
206
209
|
this.ws.connect().catch(console.error);
|
|
207
210
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.38-develop.ebf8e8c",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"viem": "^2.45.2"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@0xmonaco/contracts": "1.0.
|
|
27
|
-
"@0xmonaco/types": "1.0.
|
|
26
|
+
"@0xmonaco/contracts": "1.0.38-develop.ebf8e8c",
|
|
27
|
+
"@0xmonaco/types": "1.0.38-develop.ebf8e8c",
|
|
28
28
|
"@noble/curves": "^1.9.1",
|
|
29
29
|
"@noble/hashes": "^1.8.0",
|
|
30
30
|
"http-status-codes": "^2.3.0"
|