@0xmonaco/core 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/faucet/api.d.ts +12 -1
- package/dist/api/faucet/api.js +15 -0
- package/dist/api/perp/routes.d.ts +1 -0
- package/dist/api/perp/routes.js +1 -0
- package/dist/api/positions/api.js +3 -0
- package/dist/api/websocket/types.d.ts +37 -1
- package/dist/api/websocket/websocket.js +72 -7
- package/dist/coverage.d.ts +1 -0
- package/dist/coverage.js +1 -0
- package/dist/networks/networks.js +4 -1
- package/dist/sdk.js +11 -0
- package/package.json +3 -3
package/dist/api/faucet/api.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* const { minted, failed, remainingRequests24h } = await sdk.faucet.mint({ turnstileToken });
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
import type { FaucetAPI, MintTokensParams, MintTokensResponse } from "@0xmonaco/types";
|
|
15
|
+
import type { FaucetAPI, GetFaucetInfoResponse, MintTokensParams, MintTokensResponse } from "@0xmonaco/types";
|
|
16
16
|
import { BaseAPI } from "../base";
|
|
17
17
|
export declare class FaucetAPIImpl extends BaseAPI implements FaucetAPI {
|
|
18
18
|
/**
|
|
@@ -30,4 +30,15 @@ export declare class FaucetAPIImpl extends BaseAPI implements FaucetAPI {
|
|
|
30
30
|
* @returns Promise resolving to the minted tokens and remaining quota
|
|
31
31
|
*/
|
|
32
32
|
mint(params: MintTokensParams): Promise<MintTokensResponse>;
|
|
33
|
+
/**
|
|
34
|
+
* Fetches the authenticated wallet's faucet quota and recent requests.
|
|
35
|
+
*
|
|
36
|
+
* Read-only: remaining requests in the rolling 24h window, when the next
|
|
37
|
+
* request becomes available (`nextAvailableAt`, absent when nothing is in
|
|
38
|
+
* the window), and the 5 most recent requests. Wallet-scoped across apps,
|
|
39
|
+
* matching mint enforcement.
|
|
40
|
+
*
|
|
41
|
+
* @returns Promise resolving to the quota and recent-request history
|
|
42
|
+
*/
|
|
43
|
+
getInfo(): Promise<GetFaucetInfoResponse>;
|
|
33
44
|
}
|
package/dist/api/faucet/api.js
CHANGED
|
@@ -35,4 +35,19 @@ export class FaucetAPIImpl extends BaseAPI {
|
|
|
35
35
|
body: JSON.stringify({ turnstileToken: params.turnstileToken }),
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetches the authenticated wallet's faucet quota and recent requests.
|
|
40
|
+
*
|
|
41
|
+
* Read-only: remaining requests in the rolling 24h window, when the next
|
|
42
|
+
* request becomes available (`nextAvailableAt`, absent when nothing is in
|
|
43
|
+
* the window), and the 5 most recent requests. Wallet-scoped across apps,
|
|
44
|
+
* matching mint enforcement.
|
|
45
|
+
*
|
|
46
|
+
* @returns Promise resolving to the quota and recent-request history
|
|
47
|
+
*/
|
|
48
|
+
async getInfo() {
|
|
49
|
+
return await this.makeAuthenticatedRequest(perpRoutes.faucet.info(), {
|
|
50
|
+
method: "GET",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
38
53
|
}
|
package/dist/api/perp/routes.js
CHANGED
|
@@ -70,6 +70,7 @@ export class PositionsAPIImpl extends BaseAPI {
|
|
|
70
70
|
orderType: request.takeProfit.orderType,
|
|
71
71
|
limitPrice: request.takeProfit.limitPrice,
|
|
72
72
|
quantity: request.takeProfit.quantity,
|
|
73
|
+
closePosition: request.takeProfit.closePosition,
|
|
73
74
|
timeInForce: request.takeProfit.timeInForce,
|
|
74
75
|
slippageToleranceBps: request.takeProfit.slippageToleranceBps,
|
|
75
76
|
expiresAt: request.takeProfit.expiresAt,
|
|
@@ -81,11 +82,13 @@ export class PositionsAPIImpl extends BaseAPI {
|
|
|
81
82
|
orderType: request.stopLoss.orderType,
|
|
82
83
|
limitPrice: request.stopLoss.limitPrice,
|
|
83
84
|
quantity: request.stopLoss.quantity,
|
|
85
|
+
closePosition: request.stopLoss.closePosition,
|
|
84
86
|
timeInForce: request.stopLoss.timeInForce,
|
|
85
87
|
slippageToleranceBps: request.stopLoss.slippageToleranceBps,
|
|
86
88
|
expiresAt: request.stopLoss.expiresAt,
|
|
87
89
|
}
|
|
88
90
|
: undefined,
|
|
91
|
+
oco: request.oco,
|
|
89
92
|
}),
|
|
90
93
|
});
|
|
91
94
|
}
|
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
import type { ConditionalOrderEvent, Interval, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, SessionCredentials, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
|
|
2
2
|
export type StatusHandler = (status: WebSocketStatus) => void;
|
|
3
3
|
export type MessageHandler<T> = (data: T) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Why the connection was re-established. Delivered by
|
|
6
|
+
* {@link MonacoWebSocketOptions.onResync} after the client has reconnected,
|
|
7
|
+
* re-authenticated, and re-subscribed — the point where the app should
|
|
8
|
+
* refetch REST snapshots and reconcile by `(id, updatedAt)`.
|
|
9
|
+
*/
|
|
10
|
+
export interface WebSocketResyncInfo {
|
|
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;
|
|
4
23
|
export interface MonacoWebSocketOptions {
|
|
5
24
|
/** Session keypair for authenticated channels (orders, balances, etc.) */
|
|
6
25
|
session?: SessionCredentials;
|
|
7
26
|
/** Enable auto-reconnect on disconnect (default: true) */
|
|
8
27
|
autoReconnect?: boolean;
|
|
9
|
-
/**
|
|
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
|
+
*/
|
|
10
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;
|
|
11
39
|
/** Callback for connection status changes */
|
|
12
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;
|
|
13
49
|
}
|
|
14
50
|
export interface MonacoWebSocket {
|
|
15
51
|
/** Connect to the WebSocket server */
|
|
@@ -6,6 +6,7 @@ import { keysToCamelCase } from "./utils";
|
|
|
6
6
|
const CONNECTION_TIMEOUT = 10000;
|
|
7
7
|
const HEARTBEAT_INTERVAL = 15000;
|
|
8
8
|
const MAX_RECONNECT_DELAY = 30000;
|
|
9
|
+
const RECONNECT_BASE_DELAY = 1000;
|
|
9
10
|
const CONDITIONAL_ORDER_REASONS = ["created", "activated", "cancelled", "parent_cancelled", "triggered", "failed", "oco_cancelled"];
|
|
10
11
|
const CONDITIONAL_ORDER_CONDITION_TYPES = ["STOP_LOSS", "TAKE_PROFIT"];
|
|
11
12
|
const CONDITIONAL_ORDER_TRIGGER_SOURCES = ["MARK_PRICE"];
|
|
@@ -114,19 +115,33 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
114
115
|
let reconnectTimer = null;
|
|
115
116
|
let heartbeatTimer = null;
|
|
116
117
|
let autoReconnect = options.autoReconnect ?? true;
|
|
117
|
-
|
|
118
|
+
// Capped-infinite by default: unlimited attempts, delay capped at 30s. A
|
|
119
|
+
// client that gives up permanently (the old 5-attempt default) turns any
|
|
120
|
+
// blip longer than ~1 minute into a dead app.
|
|
121
|
+
const maxReconnectAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
|
|
122
|
+
// Normalize to a positive finite base: a negative/NaN/Infinity override
|
|
123
|
+
// would degenerate into a tight reconnect loop or no reconnect at all.
|
|
124
|
+
const reconnectBaseDelay = Number.isFinite(options.reconnectBaseDelayMs) && options.reconnectBaseDelayMs > 0
|
|
125
|
+
? options.reconnectBaseDelayMs
|
|
126
|
+
: RECONNECT_BASE_DELAY;
|
|
127
|
+
const connectionTimeout = Number.isFinite(options.connectionTimeoutMs) && options.connectionTimeoutMs > 0 ? options.connectionTimeoutMs : CONNECTION_TIMEOUT;
|
|
128
|
+
// Close code of the connection loss the next successful reopen recovers
|
|
129
|
+
// from; null when no resync is owed (first connect, clean disconnect).
|
|
130
|
+
let pendingResyncCode = null;
|
|
118
131
|
// Handler storage: channel -> set of handlers (supports multiple subscribers per channel)
|
|
119
132
|
const handlers = new Map();
|
|
120
133
|
const getStatus = () => {
|
|
121
134
|
if (!ws)
|
|
122
|
-
return "disconnected";
|
|
135
|
+
return reconnectTimer ? "reconnecting" : "disconnected";
|
|
123
136
|
switch (ws.readyState) {
|
|
124
137
|
case WebSocket.CONNECTING:
|
|
125
138
|
return "connecting";
|
|
126
139
|
case WebSocket.OPEN:
|
|
127
140
|
return "connected";
|
|
128
141
|
default:
|
|
129
|
-
|
|
142
|
+
// CLOSING/CLOSED with a retry armed is mid-outage, not terminal:
|
|
143
|
+
// the timeout path arms the retry before the close event lands.
|
|
144
|
+
return reconnectTimer ? "reconnecting" : "disconnected";
|
|
130
145
|
}
|
|
131
146
|
};
|
|
132
147
|
const getUrl = () => baseUrl;
|
|
@@ -169,17 +184,35 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
169
184
|
}
|
|
170
185
|
};
|
|
171
186
|
const scheduleReconnect = () => {
|
|
187
|
+
// Single scheduling authority: a failed attempt can report through both
|
|
188
|
+
// the connect() rejection and the socket's own close event — an armed
|
|
189
|
+
// timer means the retry is already owned.
|
|
190
|
+
if (reconnectTimer)
|
|
191
|
+
return;
|
|
172
192
|
if (!autoReconnect || reconnectAttempts >= maxReconnectAttempts) {
|
|
173
193
|
console.error("WebSocket: Max reconnect attempts reached");
|
|
174
194
|
return;
|
|
175
195
|
}
|
|
196
|
+
// Full-jitter exponential backoff: uniform in [0, min(base * 2^n, cap)],
|
|
197
|
+
// n starting at 0 so the first retry's ceiling is the base itself.
|
|
198
|
+
// Jitter desynchronizes the reconnect storm after a server deploy or a
|
|
199
|
+
// mass 1013 shed — synchronized clients would otherwise reconnect in
|
|
200
|
+
// lockstep waves.
|
|
201
|
+
const ceiling = Math.min(reconnectBaseDelay * 2 ** reconnectAttempts, MAX_RECONNECT_DELAY);
|
|
176
202
|
reconnectAttempts++;
|
|
177
|
-
const delay = Math.
|
|
203
|
+
const delay = Math.random() * ceiling;
|
|
178
204
|
reconnectTimer = setTimeout(() => {
|
|
205
|
+
reconnectTimer = null;
|
|
206
|
+
// Re-check: a callback already dequeued when disconnect() ran is not
|
|
207
|
+
// stopped by clearTimeout and must not reopen a socket.
|
|
208
|
+
if (!autoReconnect)
|
|
209
|
+
return;
|
|
179
210
|
connect().catch((err) => {
|
|
180
211
|
console.warn("WebSocket: Failed to reconnect:", err);
|
|
212
|
+
scheduleReconnect();
|
|
181
213
|
});
|
|
182
214
|
}, delay);
|
|
215
|
+
options.onStatusChange?.("reconnecting");
|
|
183
216
|
};
|
|
184
217
|
const send = (data) => {
|
|
185
218
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
@@ -223,7 +256,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
223
256
|
socket.close(1000, "Connection timeout");
|
|
224
257
|
reject(new Error("WebSocket connection timeout"));
|
|
225
258
|
}
|
|
226
|
-
},
|
|
259
|
+
}, connectionTimeout);
|
|
227
260
|
socket.onopen = () => {
|
|
228
261
|
clearTimeout(timeout);
|
|
229
262
|
if (ws !== socket) {
|
|
@@ -231,12 +264,26 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
231
264
|
return;
|
|
232
265
|
}
|
|
233
266
|
reconnectAttempts = 0;
|
|
267
|
+
// A retry armed before this open (e.g. a manual connect() during
|
|
268
|
+
// backoff) is moot now — left running it would suppress the
|
|
269
|
+
// terminal disconnected emit and reopen a socket after a clean
|
|
270
|
+
// close.
|
|
271
|
+
stopReconnect();
|
|
234
272
|
startHeartbeat();
|
|
235
273
|
// Authenticate before resubscribing so user-specific channels
|
|
236
274
|
// (orders, balances, …) are authorized when the Subscribe arrives.
|
|
237
275
|
sendAuthenticate();
|
|
238
276
|
resubscribeAll();
|
|
239
277
|
options.onStatusChange?.("connected");
|
|
278
|
+
// Resync-join order: resume the live stream first (auth +
|
|
279
|
+
// subscribe above), then tell the app to refetch snapshots and
|
|
280
|
+
// reconcile by (id, updatedAt) — events racing the snapshot are
|
|
281
|
+
// newer than the rows they duplicate.
|
|
282
|
+
if (pendingResyncCode !== null) {
|
|
283
|
+
const code = pendingResyncCode;
|
|
284
|
+
pendingResyncCode = null;
|
|
285
|
+
options.onResync?.({ code, slowClient: code === 1013 });
|
|
286
|
+
}
|
|
240
287
|
resolve();
|
|
241
288
|
};
|
|
242
289
|
socket.onmessage = (event) => {
|
|
@@ -247,6 +294,12 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
247
294
|
// Handle pong silently
|
|
248
295
|
if (msg.type === "Pong")
|
|
249
296
|
return;
|
|
297
|
+
// Answer a server-initiated application ping: the server owns
|
|
298
|
+
// liveness through proxy idle timeouts, the client just echoes.
|
|
299
|
+
if (msg.type === "Ping") {
|
|
300
|
+
send({ type: "Pong" });
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
250
303
|
routeMessage(msg);
|
|
251
304
|
}
|
|
252
305
|
catch (err) {
|
|
@@ -261,11 +314,22 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
261
314
|
}
|
|
262
315
|
ws = null;
|
|
263
316
|
stopHeartbeat();
|
|
264
|
-
|
|
265
|
-
//
|
|
317
|
+
// Reconnect on abnormal close. 1013 is the server's slow-client
|
|
318
|
+
// shed: the stream has a gap, so the reopen owes the app a resync.
|
|
319
|
+
// Keep the first owed code (upgrading to 1013 if it arrives later):
|
|
320
|
+
// failures of the reconnect attempts themselves must not launder
|
|
321
|
+
// the slow-client signal into a generic 1006.
|
|
266
322
|
if (autoReconnect && event.code !== 1000) {
|
|
323
|
+
if (pendingResyncCode === null || event.code === 1013) {
|
|
324
|
+
pendingResyncCode = event.code;
|
|
325
|
+
}
|
|
267
326
|
scheduleReconnect();
|
|
268
327
|
}
|
|
328
|
+
// "disconnected" is terminal: suppressed while a retry is armed
|
|
329
|
+
// (scheduleReconnect emitted "reconnecting" instead).
|
|
330
|
+
if (!reconnectTimer) {
|
|
331
|
+
options.onStatusChange?.("disconnected");
|
|
332
|
+
}
|
|
269
333
|
};
|
|
270
334
|
socket.onerror = () => clearTimeout(timeout);
|
|
271
335
|
}
|
|
@@ -279,6 +343,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
279
343
|
autoReconnect = false;
|
|
280
344
|
stopReconnect();
|
|
281
345
|
stopHeartbeat();
|
|
346
|
+
pendingResyncCode = null;
|
|
282
347
|
handlers.clear();
|
|
283
348
|
ws?.close(1000, "Client disconnect");
|
|
284
349
|
ws = null;
|
package/dist/coverage.d.ts
CHANGED
package/dist/coverage.js
CHANGED
|
@@ -21,6 +21,7 @@ export const COVERED = {
|
|
|
21
21
|
get_application_stats: "getApplicationStats",
|
|
22
22
|
get_available_collateral: "getAvailableCollateral",
|
|
23
23
|
get_candles: "getCandlesticks",
|
|
24
|
+
get_faucet_info: "faucet.getInfo",
|
|
24
25
|
get_funding_state: "getFundingState",
|
|
25
26
|
get_index_price: "getIndexPrice",
|
|
26
27
|
get_margin_account_movements: "getMarginAccountMovements",
|
|
@@ -22,7 +22,10 @@ const DEFAULT_API_URLS = {
|
|
|
22
22
|
*/
|
|
23
23
|
const DEFAULT_WS_URLS = {
|
|
24
24
|
local: "ws://localhost:8085/ws",
|
|
25
|
-
|
|
25
|
+
// Dedicated WebSocket ingress (NLB behind Cloudflare). The previous
|
|
26
|
+
// ALB path (wss://develop.apimonaco.xyz/ws) keeps working during the
|
|
27
|
+
// migration window.
|
|
28
|
+
development: "wss://ws-develop.apimonaco.xyz/ws",
|
|
26
29
|
staging: "wss://staging.apimonaco.xyz/ws",
|
|
27
30
|
mainnet: "wss://api.monaco.xyz/ws",
|
|
28
31
|
};
|
package/dist/sdk.js
CHANGED
|
@@ -20,6 +20,13 @@ import { WithdrawalsAPIImpl } from "./api/withdrawals";
|
|
|
20
20
|
import { generateSessionKeypair, privateKeyHex, publicKeyHex } from "./crypto/session";
|
|
21
21
|
import { APIError, InvalidConfigError, InvalidStateError } from "./errors";
|
|
22
22
|
import { resolveApiUrl, resolveWsUrl } from "./networks";
|
|
23
|
+
/**
|
|
24
|
+
* Default receipt-polling frequency for the SDK's internal viem public client.
|
|
25
|
+
* Sei produces sub-second blocks, so viem's 4000ms default adds seconds of
|
|
26
|
+
* slack to every `waitForTransactionReceipt`; 750ms keeps waits near one to
|
|
27
|
+
* two blocks while staying gentle on public RPC endpoints.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_POLLING_INTERVAL_MS = 750;
|
|
23
30
|
/** Validate a user-supplied URL override, returning it unchanged when valid. */
|
|
24
31
|
function validateUrl(value, field) {
|
|
25
32
|
try {
|
|
@@ -146,11 +153,15 @@ export class MonacoSDKImpl {
|
|
|
146
153
|
// Use Sei mainnet chain only for "mainnet" network, testnet for everything else
|
|
147
154
|
const chain = this.network === "mainnet" ? sei : seiTestnet;
|
|
148
155
|
this.chain = chain;
|
|
156
|
+
if (cfg.pollingInterval !== undefined && (!Number.isFinite(cfg.pollingInterval) || cfg.pollingInterval <= 0)) {
|
|
157
|
+
throw new InvalidConfigError(`pollingInterval must be a positive number of milliseconds, got: ${cfg.pollingInterval}`, "pollingInterval");
|
|
158
|
+
}
|
|
149
159
|
// Create public client with provided RPC URL
|
|
150
160
|
const transport = http(cfg.seiRpcUrl);
|
|
151
161
|
this.publicClient = createPublicClient({
|
|
152
162
|
chain,
|
|
153
163
|
transport,
|
|
164
|
+
pollingInterval: cfg.pollingInterval ?? DEFAULT_POLLING_INTERVAL_MS,
|
|
154
165
|
});
|
|
155
166
|
// Resolve the API URL: explicit override (validated) or network preset.
|
|
156
167
|
const apiUrl = cfg.apiUrl ? validateUrl(cfg.apiUrl, "apiUrl") : resolveApiUrl(this.network);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
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.26",
|
|
27
|
+
"@0xmonaco/types": "1.0.26",
|
|
28
28
|
"@noble/curves": "^1.9.1",
|
|
29
29
|
"@noble/hashes": "^1.8.0",
|
|
30
30
|
"http-status-codes": "^2.3.0"
|