@0xmonaco/core 1.0.73 → 1.0.75
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 +0 -5
- package/dist/api/applications/api.d.ts +53 -22
- package/dist/api/applications/api.js +88 -22
- package/dist/api/auth/api.d.ts +1 -3
- package/dist/api/auth/api.js +1 -3
- package/dist/api/base.d.ts +0 -35
- package/dist/api/base.js +0 -54
- package/dist/api/buildercodes/api.d.ts +3 -3
- package/dist/api/buildercodes/api.js +3 -3
- package/dist/api/delegated-agents/api.js +1 -0
- package/dist/api/margin-accounts/api.d.ts +7 -1
- package/dist/api/margin-accounts/api.js +48 -2
- package/dist/api/market/api.js +3 -1
- package/dist/api/perp/routes.d.ts +22 -11
- package/dist/api/perp/routes.js +6 -0
- package/dist/api/pitpass/api.d.ts +19 -8
- package/dist/api/pitpass/api.js +49 -11
- package/dist/api/portfolio/api.d.ts +2 -2
- package/dist/api/portfolio/api.js +11 -2
- package/dist/api/positions/api.d.ts +20 -8
- package/dist/api/positions/api.js +20 -8
- package/dist/api/profile/api.d.ts +55 -18
- package/dist/api/profile/api.js +77 -28
- package/dist/api/trading/api.d.ts +6 -11
- package/dist/api/trading/api.js +6 -11
- package/dist/api/websocket/websocket.js +134 -26
- package/dist/coverage.d.ts +7 -0
- package/dist/coverage.js +7 -0
- package/dist/sdk.d.ts +0 -18
- package/dist/sdk.js +0 -42
- package/package.json +3 -3
package/dist/api/profile/api.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* console.log(`User: ${profile.username} (${profile.address})`);
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
import { GetUserMovementsSchema, GetUserTradesSchema, ListFundingPaymentsSchema, SetSelfTradePreventionDefaultSchema, validate } from "@0xmonaco/types";
|
|
20
|
+
import { GetUserBalancesSchema, GetUserMovementsSchema, GetUserTradesSchema, ListFundingPaymentsSchema, SetSelfTradePreventionDefaultSchema, validate, } from "@0xmonaco/types";
|
|
21
21
|
import { APIError } from "../../errors";
|
|
22
22
|
import { BaseAPI } from "../base";
|
|
23
23
|
export class ProfileAPIImpl extends BaseAPI {
|
|
@@ -53,14 +53,19 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
53
53
|
* Fetches the user's transaction history from the /api/v1/accounts/movements endpoint.
|
|
54
54
|
* Requires a valid access token to be set.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
56
|
+
* Cursor paged (0XM-2788/0XM-2820): omit `pageToken` or pass `""` to start,
|
|
57
|
+
* then follow `nextPageToken` from each response until it comes back empty.
|
|
58
|
+
* There is no `page` — sending one is a `400`.
|
|
59
|
+
*
|
|
60
|
+
* The response `total` and `totalPages` are bounded by the server count
|
|
61
|
+
* ceiling; `totalCapped` is `true` when more than that matched, so `total`
|
|
62
|
+
* is a lower bound. Do not drive a paging loop off `total`: a user past the
|
|
63
|
+
* ceiling would stop early. Follow `nextPageToken`.
|
|
60
64
|
*
|
|
61
65
|
* @param params - Optional query parameters for pagination and filtering
|
|
62
|
-
* @param params.
|
|
63
|
-
* @param params.
|
|
66
|
+
* @param params.pageToken - Cursor from a previous response's `nextPageToken`
|
|
67
|
+
* @param params.pageSize - Number of items per page (default: 20, max: 1000)
|
|
68
|
+
* @param params.orderBy - Sort direction for `createdAt` (default: `"DESC"`)
|
|
64
69
|
* @param params.entryType - Filter by entry type (CREDIT, DEBIT, LOCK, UNLOCK, FEE)
|
|
65
70
|
* @param params.transactionType - Filter by transaction type (DEPOSIT, WITHDRAWAL, TRADE, FEE, FUNDING, LIQUIDATION, INTEREST, REWARD)
|
|
66
71
|
* @param params.assetId - Filter by asset ID (UUID)
|
|
@@ -73,9 +78,14 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
73
78
|
* const movements = await profileAPI.getPaginatedUserMovements();
|
|
74
79
|
* console.log(`Total movements: ${movements.total}`);
|
|
75
80
|
*
|
|
76
|
-
* //
|
|
77
|
-
*
|
|
78
|
-
*
|
|
81
|
+
* // Walk every page
|
|
82
|
+
* let pageToken = "";
|
|
83
|
+
* for (;;) {
|
|
84
|
+
* const page = await profileAPI.getPaginatedUserMovements({ pageToken, pageSize: 50 });
|
|
85
|
+
* console.log(page.movements.length);
|
|
86
|
+
* if (page.nextPageToken === "") break;
|
|
87
|
+
* pageToken = page.nextPageToken;
|
|
88
|
+
* }
|
|
79
89
|
*
|
|
80
90
|
* // Filter by entry type and transaction type
|
|
81
91
|
* const deposits = await profileAPI.getPaginatedUserMovements({ entryType: "CREDIT", transactionType: "DEPOSIT" });
|
|
@@ -89,12 +99,17 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
89
99
|
validate(GetUserMovementsSchema, params);
|
|
90
100
|
}
|
|
91
101
|
const searchParams = new URLSearchParams();
|
|
92
|
-
if (params?.page !== undefined) {
|
|
93
|
-
searchParams.append("page", params.page.toString());
|
|
94
|
-
}
|
|
95
102
|
if (params?.pageSize !== undefined) {
|
|
96
103
|
searchParams.append("pageSize", params.pageSize.toString());
|
|
97
104
|
}
|
|
105
|
+
// An empty token is a distinct, meaningful value — "start the walk" — so
|
|
106
|
+
// it must reach the wire rather than be dropped as falsy.
|
|
107
|
+
if (params?.pageToken !== undefined) {
|
|
108
|
+
searchParams.append("pageToken", params.pageToken);
|
|
109
|
+
}
|
|
110
|
+
if (params?.orderBy !== undefined) {
|
|
111
|
+
searchParams.append("orderBy", params.orderBy);
|
|
112
|
+
}
|
|
98
113
|
if (params?.entryType !== undefined) {
|
|
99
114
|
searchParams.append("entryType", params.entryType);
|
|
100
115
|
}
|
|
@@ -114,9 +129,13 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
114
129
|
* Fetches the user's token balances from the /api/v1/accounts/balances endpoint.
|
|
115
130
|
* Requires a valid access token to be set.
|
|
116
131
|
*
|
|
132
|
+
* Cursor paged (0XM-2788/0XM-2821): omit `pageToken` or pass `""` to start,
|
|
133
|
+
* then follow `nextPageToken` until it comes back empty. There is no `page`
|
|
134
|
+
* — sending one is a `400`.
|
|
135
|
+
*
|
|
117
136
|
* @param params - Optional query parameters for pagination
|
|
118
|
-
* @param params.
|
|
119
|
-
* @param params.pageSize - Number of items per page (default: 20, max:
|
|
137
|
+
* @param params.pageToken - Cursor from a previous response's `nextPageToken`
|
|
138
|
+
* @param params.pageSize - Number of items per page (default: 20, max: 1000)
|
|
120
139
|
* @returns Promise resolving to paginated balances response
|
|
121
140
|
* @throws {APIError} When the request fails or user is not authenticated
|
|
122
141
|
*
|
|
@@ -129,19 +148,29 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
129
148
|
* console.log(`${balance.symbol}: ${balance.availableBalance}`);
|
|
130
149
|
* });
|
|
131
150
|
*
|
|
132
|
-
* //
|
|
133
|
-
*
|
|
134
|
-
*
|
|
151
|
+
* // Walk every page
|
|
152
|
+
* let pageToken = "";
|
|
153
|
+
* for (;;) {
|
|
154
|
+
* const page = await profileAPI.getUserBalances({ pageToken, pageSize: 50 });
|
|
155
|
+
* console.log(page.balances.length);
|
|
156
|
+
* if (page.nextPageToken === "") break;
|
|
157
|
+
* pageToken = page.nextPageToken;
|
|
158
|
+
* }
|
|
135
159
|
* ```
|
|
136
160
|
*/
|
|
137
161
|
async getUserBalances(params) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
searchParams.append("page", params.page.toString());
|
|
162
|
+
if (params) {
|
|
163
|
+
validate(GetUserBalancesSchema, params);
|
|
141
164
|
}
|
|
165
|
+
const searchParams = new URLSearchParams();
|
|
142
166
|
if (params?.pageSize !== undefined) {
|
|
143
167
|
searchParams.append("pageSize", params.pageSize.toString());
|
|
144
168
|
}
|
|
169
|
+
// An empty token is a distinct, meaningful value — "start the walk" — so
|
|
170
|
+
// it must reach the wire rather than be dropped as falsy.
|
|
171
|
+
if (params?.pageToken !== undefined) {
|
|
172
|
+
searchParams.append("pageToken", params.pageToken);
|
|
173
|
+
}
|
|
145
174
|
const queryString = searchParams.toString();
|
|
146
175
|
const url = queryString ? `/api/v1/accounts/balances?${queryString}` : "/api/v1/accounts/balances";
|
|
147
176
|
return await this.makeAuthenticatedRequest(url);
|
|
@@ -315,12 +344,21 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
315
344
|
* Fetches funding payments from the /api/v1/accounts/funding-payments endpoint.
|
|
316
345
|
* Requires a valid access token to be set.
|
|
317
346
|
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
347
|
+
* Cursor paged (0XM-2788/0XM-2818): omit `pageToken` or pass `""` to start,
|
|
348
|
+
* then follow `nextPageToken` from each response until it comes back empty.
|
|
349
|
+
* There is no `page` — sending one is a `400`.
|
|
350
|
+
*
|
|
351
|
+
* The response `total` and `totalPages` are bounded by the server count
|
|
352
|
+
* ceiling; `totalCapped` is `true` when more than that matched, so `total`
|
|
353
|
+
* is a lower bound. Do not drive a paging loop off `total`: a user past the
|
|
354
|
+
* ceiling would stop early. Follow `nextPageToken`.
|
|
322
355
|
*
|
|
323
356
|
* @param params - Optional query parameters for pagination and filtering
|
|
357
|
+
* @param params.pageToken - Cursor from a previous response's `nextPageToken`
|
|
358
|
+
* @param params.pageSize - Number of items per page (default: 20, max: 1000)
|
|
359
|
+
* @param params.tradingPairId - Filter by trading pair ID (UUID)
|
|
360
|
+
* @param params.positionId - Filter by margin position ID (UUID)
|
|
361
|
+
* @param params.marginAccountId - Filter by margin account ID (UUID)
|
|
324
362
|
* @returns Promise resolving to paginated funding payments response
|
|
325
363
|
* @throws {APIError} When the request fails or user is not authenticated
|
|
326
364
|
*
|
|
@@ -328,6 +366,15 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
328
366
|
* ```typescript
|
|
329
367
|
* const funding = await profileAPI.listFundingPayments({ tradingPairId: "550e8400-e29b-41d4-a716-446655440000" });
|
|
330
368
|
* console.log(`Total funding payments: ${funding.total}`);
|
|
369
|
+
*
|
|
370
|
+
* // Walk every page
|
|
371
|
+
* let pageToken = "";
|
|
372
|
+
* for (;;) {
|
|
373
|
+
* const page = await profileAPI.listFundingPayments({ pageToken, pageSize: 100 });
|
|
374
|
+
* console.log(page.records.length);
|
|
375
|
+
* if (page.nextPageToken === "") break;
|
|
376
|
+
* pageToken = page.nextPageToken;
|
|
377
|
+
* }
|
|
331
378
|
* ```
|
|
332
379
|
*/
|
|
333
380
|
async listFundingPayments(params) {
|
|
@@ -335,12 +382,14 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
335
382
|
validate(ListFundingPaymentsSchema, params);
|
|
336
383
|
}
|
|
337
384
|
const searchParams = new URLSearchParams();
|
|
338
|
-
if (params?.page !== undefined) {
|
|
339
|
-
searchParams.append("page", params.page.toString());
|
|
340
|
-
}
|
|
341
385
|
if (params?.pageSize !== undefined) {
|
|
342
386
|
searchParams.append("pageSize", params.pageSize.toString());
|
|
343
387
|
}
|
|
388
|
+
// An empty token is a distinct, meaningful value — "start the walk" — so
|
|
389
|
+
// it must reach the wire rather than be dropped as falsy.
|
|
390
|
+
if (params?.pageToken !== undefined) {
|
|
391
|
+
searchParams.append("pageToken", params.pageToken);
|
|
392
|
+
}
|
|
344
393
|
if (params?.tradingPairId !== undefined) {
|
|
345
394
|
searchParams.append("tradingPairId", params.tradingPairId);
|
|
346
395
|
}
|
|
@@ -315,11 +315,10 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
315
315
|
* Gets orders based on query parameters, cursor-paginated by default.
|
|
316
316
|
*
|
|
317
317
|
* Retrieves orders with optional filtering by status, trading pair, and pagination.
|
|
318
|
-
* Every
|
|
319
|
-
*
|
|
320
|
-
* (
|
|
321
|
-
*
|
|
322
|
-
* working set.
|
|
318
|
+
* Every row is a BOOK order (`LIMIT` or `MARKET`); filter `status` to
|
|
319
|
+
* `["SUBMITTED", "PARTIALLY_FILLED"]` for the resting working set. TWAP parents and
|
|
320
|
+
* conditional (TP/SL) orders are not rows here — list them with `listTwapOrders`
|
|
321
|
+
* and `listConditionalOrders`.
|
|
323
322
|
* When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
|
|
324
323
|
* pagination: it starts a cursor walk from the newest (or oldest, for
|
|
325
324
|
* `orderBy: "ASC"`) row and the response's `nextPageToken` resumes it. Both modes
|
|
@@ -342,13 +341,9 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
342
341
|
*
|
|
343
342
|
* @example
|
|
344
343
|
* ```typescript
|
|
345
|
-
* //
|
|
344
|
+
* // The resting working set (book orders only)
|
|
346
345
|
* const open = await tradingAPI.getPaginatedOrders({ status: ["SUBMITTED", "PARTIALLY_FILLED"] });
|
|
347
|
-
* for (const order of open.orders)
|
|
348
|
-
* if (order.orderType === "TWAP") console.log("TWAP parent", order.twap?.slicesPlaced);
|
|
349
|
-
* else if (order.orderType === "CONDITIONAL") console.log("trigger", order.conditional?.triggerPrice);
|
|
350
|
-
* else console.log("resting", order.price);
|
|
351
|
-
* }
|
|
346
|
+
* for (const order of open.orders) console.log("resting", order.quantity, "@", order.price);
|
|
352
347
|
* ```
|
|
353
348
|
*
|
|
354
349
|
* @example
|
package/dist/api/trading/api.js
CHANGED
|
@@ -495,11 +495,10 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
495
495
|
* Gets orders based on query parameters, cursor-paginated by default.
|
|
496
496
|
*
|
|
497
497
|
* Retrieves orders with optional filtering by status, trading pair, and pagination.
|
|
498
|
-
* Every
|
|
499
|
-
*
|
|
500
|
-
* (
|
|
501
|
-
*
|
|
502
|
-
* working set.
|
|
498
|
+
* Every row is a BOOK order (`LIMIT` or `MARKET`); filter `status` to
|
|
499
|
+
* `["SUBMITTED", "PARTIALLY_FILLED"]` for the resting working set. TWAP parents and
|
|
500
|
+
* conditional (TP/SL) orders are not rows here — list them with `listTwapOrders`
|
|
501
|
+
* and `listConditionalOrders`.
|
|
503
502
|
* When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
|
|
504
503
|
* pagination: it starts a cursor walk from the newest (or oldest, for
|
|
505
504
|
* `orderBy: "ASC"`) row and the response's `nextPageToken` resumes it. Both modes
|
|
@@ -522,13 +521,9 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
522
521
|
*
|
|
523
522
|
* @example
|
|
524
523
|
* ```typescript
|
|
525
|
-
* //
|
|
524
|
+
* // The resting working set (book orders only)
|
|
526
525
|
* const open = await tradingAPI.getPaginatedOrders({ status: ["SUBMITTED", "PARTIALLY_FILLED"] });
|
|
527
|
-
* for (const order of open.orders)
|
|
528
|
-
* if (order.orderType === "TWAP") console.log("TWAP parent", order.twap?.slicesPlaced);
|
|
529
|
-
* else if (order.orderType === "CONDITIONAL") console.log("trigger", order.conditional?.triggerPrice);
|
|
530
|
-
* else console.log("resting", order.price);
|
|
531
|
-
* }
|
|
526
|
+
* for (const order of open.orders) console.log("resting", order.quantity, "@", order.price);
|
|
532
527
|
* ```
|
|
533
528
|
*
|
|
534
529
|
* @example
|
|
@@ -245,6 +245,9 @@ function parsePositionEventData(raw) {
|
|
|
245
245
|
orderId: readOptionalString(data, "orderId"),
|
|
246
246
|
reason: readString(data, "reason"),
|
|
247
247
|
updatedAt: readString(data, "updatedAt"),
|
|
248
|
+
// Optional: an older deployment does not send it, and the server omits it
|
|
249
|
+
// when it does not know when the position opened.
|
|
250
|
+
openedAt: readOptionalString(data, "openedAt"),
|
|
248
251
|
// Opaque durable row version (0XM-2673). `readOptionalSequence`, not
|
|
249
252
|
// `readOptionalNumber`: this is an ordering counter, so a fractional,
|
|
250
253
|
// negative, or unsafe-integer value would silently corrupt every
|
|
@@ -269,6 +272,42 @@ function parsePositionEvent(rawData) {
|
|
|
269
272
|
data: parsePositionEventData(rawData.data),
|
|
270
273
|
};
|
|
271
274
|
}
|
|
275
|
+
/** The `positions` channel's valuation frame discriminator. */
|
|
276
|
+
const POSITION_VALUATION_EVENT_TYPE = "position_valuation";
|
|
277
|
+
/**
|
|
278
|
+
* Whether a `positions` channel frame is a periodic valuation rather than a
|
|
279
|
+
* lifecycle update. Checked on the raw discriminator BEFORE any lifecycle
|
|
280
|
+
* field is read, so a valuation never reaches the lifecycle parser.
|
|
281
|
+
*/
|
|
282
|
+
function isPositionValuationFrame(rawData) {
|
|
283
|
+
return isRecord(rawData) && rawData.event_type === POSITION_VALUATION_EVENT_TYPE;
|
|
284
|
+
}
|
|
285
|
+
function parsePositionValuationEvent(rawData) {
|
|
286
|
+
if (!isRecord(rawData)) {
|
|
287
|
+
throw new Error("Invalid position valuation event: payload must be an object");
|
|
288
|
+
}
|
|
289
|
+
if (!isRecord(rawData.data)) {
|
|
290
|
+
throw new Error("Invalid position valuation event: data must be an object");
|
|
291
|
+
}
|
|
292
|
+
const data = keysToCamelCase(rawData.data);
|
|
293
|
+
// Always present on the wire; `null` when the server cannot derive one.
|
|
294
|
+
const liquidationPrice = data.liquidationPrice === null ? null : readString(data, "liquidationPrice");
|
|
295
|
+
return {
|
|
296
|
+
eventType: readEnum(rawData, "event_type", [POSITION_VALUATION_EVENT_TYPE]),
|
|
297
|
+
userId: readString(rawData, "user_id"),
|
|
298
|
+
data: {
|
|
299
|
+
positionId: readString(data, "positionId"),
|
|
300
|
+
marginAccountId: readString(data, "marginAccountId"),
|
|
301
|
+
tradingPairId: readString(data, "tradingPairId"),
|
|
302
|
+
markPrice: readString(data, "markPrice"),
|
|
303
|
+
unrealizedPnl: readString(data, "unrealizedPnl"),
|
|
304
|
+
liquidationPrice,
|
|
305
|
+
initialMarginRequired: readString(data, "initialMarginRequired"),
|
|
306
|
+
maintenanceMarginRequired: readString(data, "maintenanceMarginRequired"),
|
|
307
|
+
valuedAt: readString(data, "valuedAt"),
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
272
311
|
/**
|
|
273
312
|
* Parse a liquidation alert's payload. Shared by the live event and the
|
|
274
313
|
* subscribe-time snapshot (see {@link parseConditionalOrderEventData}).
|
|
@@ -284,7 +323,7 @@ function parseLiquidationEventData(raw) {
|
|
|
284
323
|
positionId: readOptionalString(data, "positionId"),
|
|
285
324
|
tradingPairId: readOptionalString(data, "tradingPairId"),
|
|
286
325
|
status: readString(data, "status"),
|
|
287
|
-
|
|
326
|
+
riskBucketState: readString(data, "riskBucketState"),
|
|
288
327
|
liquidationPrice: readOptionalString(data, "liquidationPrice"),
|
|
289
328
|
markPrice: readOptionalString(data, "markPrice"),
|
|
290
329
|
estimatedShortfall: readString(data, "estimatedShortfall"),
|
|
@@ -299,7 +338,7 @@ function parseLiquidationEvent(rawData) {
|
|
|
299
338
|
return {
|
|
300
339
|
// event_type is the event-class discriminator, validated strictly so a
|
|
301
340
|
// mis-routed frame is rejected rather than parsed as a liquidation. The
|
|
302
|
-
// free-form value fields in the payload (status/
|
|
341
|
+
// free-form value fields in the payload (status/riskBucketState) stay lenient
|
|
303
342
|
// so an unexpected *value* never drops a risk-critical alert.
|
|
304
343
|
eventType: readEnum(rawData, "event_type", ["liquidation_alert"]),
|
|
305
344
|
userId: readString(rawData, "user_id"),
|
|
@@ -772,6 +811,10 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
772
811
|
let ws = null;
|
|
773
812
|
let session = options.session;
|
|
774
813
|
let reconnectAttempts = 0;
|
|
814
|
+
// Whether a socket has opened since the client was created or last
|
|
815
|
+
// disconnect()ed, so a status consumer can tell a first connect that keeps
|
|
816
|
+
// failing from a lost working connection.
|
|
817
|
+
let hasConnected = false;
|
|
775
818
|
let reconnectTimer = null;
|
|
776
819
|
let heartbeatTimer = null;
|
|
777
820
|
let autoReconnect = options.autoReconnect ?? true;
|
|
@@ -823,7 +866,9 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
823
866
|
return reconnectTimer ? "reconnecting" : "disconnected";
|
|
824
867
|
switch (ws.readyState) {
|
|
825
868
|
case WebSocket.CONNECTING:
|
|
826
|
-
|
|
869
|
+
// A retry's socket is still inside the outage the last status
|
|
870
|
+
// reported, so it reads as reconnecting, not connecting.
|
|
871
|
+
return reconnectAttempts > 0 ? "reconnecting" : "connecting";
|
|
827
872
|
case WebSocket.OPEN:
|
|
828
873
|
return "connected";
|
|
829
874
|
default:
|
|
@@ -833,6 +878,43 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
833
878
|
}
|
|
834
879
|
};
|
|
835
880
|
const getUrl = () => baseUrl;
|
|
881
|
+
/**
|
|
882
|
+
* The single place a status change is reported. Recovery must not hinge on
|
|
883
|
+
* the application's observer, so an error it throws is logged, not
|
|
884
|
+
* propagated into the connection lifecycle. An async observer is assignable
|
|
885
|
+
* to the void-returning handler type, so its rejection is logged the same
|
|
886
|
+
* way rather than left unhandled.
|
|
887
|
+
*/
|
|
888
|
+
const emitStatus = (status, nextRetryDelayMs, attempt = reconnectAttempts) => {
|
|
889
|
+
const info = { attempt, hasConnected };
|
|
890
|
+
if (nextRetryDelayMs !== undefined) {
|
|
891
|
+
info.nextRetryDelayMs = nextRetryDelayMs;
|
|
892
|
+
}
|
|
893
|
+
const logObserverError = (err) => console.error("WebSocket: onStatusChange threw", err);
|
|
894
|
+
try {
|
|
895
|
+
const result = options.onStatusChange?.(status, info);
|
|
896
|
+
// Any thenable, not just this realm's Promise: an async handler from
|
|
897
|
+
// another realm (an iframe) returns a promise `instanceof` misses.
|
|
898
|
+
if (typeof result?.then === "function") {
|
|
899
|
+
Promise.resolve(result).catch(logObserverError);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
catch (err) {
|
|
903
|
+
logObserverError(err);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
/**
|
|
907
|
+
* Report a terminal "disconnected", which ends the retry cycle so the next
|
|
908
|
+
* connect() reports "connecting" with a fresh retry budget. The counter is
|
|
909
|
+
* reset before the observer runs, so a connect() made from inside the
|
|
910
|
+
* callback already starts the new cycle; the status still reports the
|
|
911
|
+
* retries the cycle ran.
|
|
912
|
+
*/
|
|
913
|
+
const emitTerminalDisconnected = () => {
|
|
914
|
+
const attempt = reconnectAttempts;
|
|
915
|
+
reconnectAttempts = 0;
|
|
916
|
+
emitStatus("disconnected", undefined, attempt);
|
|
917
|
+
};
|
|
836
918
|
/**
|
|
837
919
|
* Send the signed auth handshake as the first message after the socket
|
|
838
920
|
* opens. The client signs `WS-AUTH\n<pubkey>\n<ts>` with the session private
|
|
@@ -876,13 +958,15 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
876
958
|
reconnectTimer = null;
|
|
877
959
|
}
|
|
878
960
|
};
|
|
961
|
+
// Whether the retry cycle may schedule another attempt.
|
|
962
|
+
const retryBudgetLeft = () => autoReconnect && reconnectAttempts < maxReconnectAttempts;
|
|
879
963
|
const scheduleReconnect = () => {
|
|
880
964
|
// Single scheduling authority: a failed attempt can report through both
|
|
881
965
|
// the connect() rejection and the socket's own close event — an armed
|
|
882
966
|
// timer means the retry is already owned.
|
|
883
967
|
if (reconnectTimer)
|
|
884
968
|
return;
|
|
885
|
-
if (!
|
|
969
|
+
if (!retryBudgetLeft()) {
|
|
886
970
|
console.error("WebSocket: Max reconnect attempts reached");
|
|
887
971
|
return;
|
|
888
972
|
}
|
|
@@ -902,10 +986,14 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
902
986
|
return;
|
|
903
987
|
connect().catch((err) => {
|
|
904
988
|
console.warn("WebSocket: Failed to reconnect:", err);
|
|
905
|
-
|
|
989
|
+
// Not when the failure already ended the retry cycle (the last
|
|
990
|
+
// retry's socket could not be constructed).
|
|
991
|
+
if (reconnectAttempts > 0) {
|
|
992
|
+
scheduleReconnect();
|
|
993
|
+
}
|
|
906
994
|
});
|
|
907
995
|
}, delay);
|
|
908
|
-
|
|
996
|
+
emitStatus("reconnecting", delay);
|
|
909
997
|
};
|
|
910
998
|
const send = (data) => {
|
|
911
999
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
@@ -1035,6 +1123,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1035
1123
|
return;
|
|
1036
1124
|
}
|
|
1037
1125
|
reconnectAttempts = 0;
|
|
1126
|
+
hasConnected = true;
|
|
1038
1127
|
// A retry armed before this open (e.g. a manual connect() during
|
|
1039
1128
|
// backoff) is moot now — left running it would suppress the
|
|
1040
1129
|
// terminal disconnected emit and reopen a socket after a clean
|
|
@@ -1045,7 +1134,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1045
1134
|
// (orders, balances, …) are authorized when the Subscribe arrives.
|
|
1046
1135
|
sendAuthenticate();
|
|
1047
1136
|
resubscribeAll();
|
|
1048
|
-
|
|
1137
|
+
emitStatus("connected");
|
|
1049
1138
|
// Resync-join order: the live stream resumes first (auth +
|
|
1050
1139
|
// subscribe above), then the application is asked to refetch
|
|
1051
1140
|
// snapshots and reconcile against each channel's authoritative
|
|
@@ -1118,13 +1207,8 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1118
1207
|
if (pendingResyncCode === null) {
|
|
1119
1208
|
pendingResyncCode = event.code;
|
|
1120
1209
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1124
|
-
finally {
|
|
1125
|
-
// Recovery must not hinge on the status observer not throwing.
|
|
1126
|
-
options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
|
|
1127
|
-
}
|
|
1210
|
+
emitStatus("disconnected");
|
|
1211
|
+
options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
|
|
1128
1212
|
return;
|
|
1129
1213
|
}
|
|
1130
1214
|
// The app already handed over a replacement session on this socket.
|
|
@@ -1135,14 +1219,10 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1135
1219
|
if (pendingResyncCode === null) {
|
|
1136
1220
|
pendingResyncCode = event.code;
|
|
1137
1221
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
connect().catch((err) => {
|
|
1143
|
-
console.warn("WebSocket: Failed to connect with the replacement session:", err);
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1222
|
+
emitStatus("disconnected");
|
|
1223
|
+
connect().catch((err) => {
|
|
1224
|
+
console.warn("WebSocket: Failed to connect with the replacement session:", err);
|
|
1225
|
+
});
|
|
1146
1226
|
return;
|
|
1147
1227
|
}
|
|
1148
1228
|
// Reconnect on abnormal close. 1013 is the server's slow-client
|
|
@@ -1159,13 +1239,26 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1159
1239
|
// "disconnected" is terminal: suppressed while a retry is armed
|
|
1160
1240
|
// (scheduleReconnect emitted "reconnecting" instead).
|
|
1161
1241
|
if (!reconnectTimer) {
|
|
1162
|
-
|
|
1242
|
+
emitTerminalDisconnected();
|
|
1163
1243
|
}
|
|
1164
1244
|
};
|
|
1165
1245
|
socket.onerror = () => clearTimeout(timeout);
|
|
1246
|
+
// A retry's socket stays inside the outage "reconnecting" already
|
|
1247
|
+
// reported; only a connection opened outside a retry cycle is new.
|
|
1248
|
+
if (reconnectAttempts === 0) {
|
|
1249
|
+
emitStatus("connecting");
|
|
1250
|
+
}
|
|
1166
1251
|
}
|
|
1167
1252
|
catch (err) {
|
|
1168
|
-
|
|
1253
|
+
// The socket could not be constructed. Inside a retry cycle the retry
|
|
1254
|
+
// timer schedules the next attempt while the budget lasts; otherwise
|
|
1255
|
+
// nothing follows, so this ends the cycle.
|
|
1256
|
+
if (reconnectTimer || (reconnectAttempts > 0 && retryBudgetLeft())) {
|
|
1257
|
+
emitStatus("disconnected");
|
|
1258
|
+
}
|
|
1259
|
+
else {
|
|
1260
|
+
emitTerminalDisconnected();
|
|
1261
|
+
}
|
|
1169
1262
|
reject(err);
|
|
1170
1263
|
}
|
|
1171
1264
|
});
|
|
@@ -1180,7 +1273,8 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1180
1273
|
instrumentBaseline.clear();
|
|
1181
1274
|
ws?.close(1000, "Client disconnect");
|
|
1182
1275
|
ws = null;
|
|
1183
|
-
|
|
1276
|
+
hasConnected = false;
|
|
1277
|
+
emitTerminalDisconnected();
|
|
1184
1278
|
};
|
|
1185
1279
|
const subscribe = (channel, handler, snapshotHandler) => {
|
|
1186
1280
|
const isFirstSubscriber = !handlers.has(channel);
|
|
@@ -1522,8 +1616,11 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1522
1616
|
* handshake. Omit `tradingPairId` for all pairs, or pass one to filter.
|
|
1523
1617
|
* Detected broadcast lag closes the connection with 1013; reconcile
|
|
1524
1618
|
* missed keys and terminal updates on reconnect.
|
|
1619
|
+
*
|
|
1620
|
+
* The channel also carries periodic `position_valuation` frames. Those go to
|
|
1621
|
+
* `onValuation` only, never to `handler`, and are ignored without one.
|
|
1525
1622
|
*/
|
|
1526
|
-
const subscribePositions = (handler, tradingPairId, onSnapshot) => {
|
|
1623
|
+
const subscribePositions = (handler, tradingPairId, onSnapshot, onValuation) => {
|
|
1527
1624
|
if (!handler) {
|
|
1528
1625
|
throw new Error("positions subscription requires a handler");
|
|
1529
1626
|
}
|
|
@@ -1532,6 +1629,17 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
|
|
|
1532
1629
|
}
|
|
1533
1630
|
const channel = tradingPairId ? `positions:${tradingPairId}` : "positions";
|
|
1534
1631
|
return subscribe(channel, (rawData) => {
|
|
1632
|
+
if (isPositionValuationFrame(rawData)) {
|
|
1633
|
+
if (!onValuation)
|
|
1634
|
+
return;
|
|
1635
|
+
try {
|
|
1636
|
+
onValuation(parsePositionValuationEvent(rawData));
|
|
1637
|
+
}
|
|
1638
|
+
catch (err) {
|
|
1639
|
+
console.error("WebSocket: Error processing position valuation event", err);
|
|
1640
|
+
}
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1535
1643
|
try {
|
|
1536
1644
|
handler(parsePositionEvent(rawData));
|
|
1537
1645
|
}
|
package/dist/coverage.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare const COVERED: {
|
|
|
62
62
|
get_rewards_balance: string;
|
|
63
63
|
get_my_referral_position: string;
|
|
64
64
|
get_my_referral_downline: string;
|
|
65
|
+
get_my_referral_tree: string;
|
|
65
66
|
get_self_trade_prevention_default: string;
|
|
66
67
|
get_trader_code_info: string;
|
|
67
68
|
set_self_trade_prevention_default: string;
|
|
@@ -132,6 +133,12 @@ export declare const COVERED: {
|
|
|
132
133
|
transfer_collateral_to_margin_account: string;
|
|
133
134
|
transfer_collateral_to_parent_margin_account: string;
|
|
134
135
|
transfer_collateral_to_risk_bucket: string;
|
|
136
|
+
create_sub_account: string;
|
|
137
|
+
close_sub_account: string;
|
|
138
|
+
get_margin_account_pnl: string;
|
|
139
|
+
transfer_collateral: string;
|
|
140
|
+
transfer_collateral_to_sub_account: string;
|
|
141
|
+
transfer_collateral_from_sub_account: string;
|
|
135
142
|
claim_buildercode_rewards: string;
|
|
136
143
|
set_builder_fee: string;
|
|
137
144
|
transfer_rewards: string;
|
package/dist/coverage.js
CHANGED
|
@@ -62,6 +62,7 @@ export const COVERED = {
|
|
|
62
62
|
get_rewards_balance: "pitpass.getRewardsBalance",
|
|
63
63
|
get_my_referral_position: "pitpass.getMyReferralPosition",
|
|
64
64
|
get_my_referral_downline: "pitpass.getMyReferralDownline",
|
|
65
|
+
get_my_referral_tree: "pitpass.getMyReferralTree",
|
|
65
66
|
get_self_trade_prevention_default: "profile.getSelfTradePreventionDefault",
|
|
66
67
|
get_trader_code_info: "pitpass.getTraderCodeInfo",
|
|
67
68
|
set_self_trade_prevention_default: "profile.setSelfTradePreventionDefault",
|
|
@@ -132,6 +133,12 @@ export const COVERED = {
|
|
|
132
133
|
transfer_collateral_to_margin_account: "transferCollateralToMarginAccount",
|
|
133
134
|
transfer_collateral_to_parent_margin_account: "transferCollateralToParentMarginAccount",
|
|
134
135
|
transfer_collateral_to_risk_bucket: "transferCollateralToRiskBucket",
|
|
136
|
+
create_sub_account: "createMarginSubAccount",
|
|
137
|
+
close_sub_account: "closeMarginSubAccount",
|
|
138
|
+
get_margin_account_pnl: "getMarginAccountPnl",
|
|
139
|
+
transfer_collateral: "transferMarginCollateral",
|
|
140
|
+
transfer_collateral_to_sub_account: "transferCollateralToSubAccount",
|
|
141
|
+
transfer_collateral_from_sub_account: "transferCollateralFromSubAccount",
|
|
135
142
|
claim_buildercode_rewards: "buildercodes.claimBuildercodeRewards",
|
|
136
143
|
set_builder_fee: "buildercodes.setBuilderFee",
|
|
137
144
|
transfer_rewards: "pitpass.transferRewards",
|
package/dist/sdk.d.ts
CHANGED
|
@@ -36,24 +36,6 @@ export declare class MonacoSDKImpl implements MonacoSDK {
|
|
|
36
36
|
* the WebSocket client.
|
|
37
37
|
*/
|
|
38
38
|
private propagateSession;
|
|
39
|
-
/**
|
|
40
|
-
* Set (or clear) the application secret key used for backend-authenticated
|
|
41
|
-
* requests (those annotated `#[require_backend]` on the gateway, e.g. the
|
|
42
|
-
* `applications` reporting endpoints).
|
|
43
|
-
*
|
|
44
|
-
* The raw `sk_...` key is sent in the `x-server-key` header on each such
|
|
45
|
-
* request. This is independent of {@link login}/session auth — a client may
|
|
46
|
-
* hold both a session and a server key at once.
|
|
47
|
-
*
|
|
48
|
-
* @param serverKey - The application secret key (`sk_...`), or `undefined` to clear.
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* sdk.setServerKey("sk_live_...");
|
|
53
|
-
* const orders = await sdk.applications.listApplicationOrders({ status: "FILLED" });
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
setServerKey(serverKey: string | undefined): void;
|
|
57
39
|
/** Extract the session keypair from an auth state. */
|
|
58
40
|
private sessionFromAuthState;
|
|
59
41
|
constructor(cfg: SDKConfig);
|