@0xmonaco/core 1.0.29 → 1.0.32-develop.522174b
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/base.js +122 -3
- package/dist/api/profile/api.d.ts +45 -10
- package/dist/api/profile/api.js +69 -13
- package/dist/api/trading/api.d.ts +47 -21
- package/dist/api/trading/api.js +83 -34
- package/dist/api/websocket/websocket.js +9 -5
- package/dist/errors/errors.js +1 -1
- package/dist/networks/networks.js +3 -1
- package/package.json +3 -3
package/dist/api/base.js
CHANGED
|
@@ -34,6 +34,112 @@ import { APIError } from "../errors";
|
|
|
34
34
|
* only takes effect in server runtimes (Node/undici, Bun, Deno).
|
|
35
35
|
*/
|
|
36
36
|
const SDK_USER_AGENT = "@0xmonaco/core";
|
|
37
|
+
const MONTHS = {
|
|
38
|
+
Jan: 0,
|
|
39
|
+
Feb: 1,
|
|
40
|
+
Mar: 2,
|
|
41
|
+
Apr: 3,
|
|
42
|
+
May: 4,
|
|
43
|
+
Jun: 5,
|
|
44
|
+
Jul: 6,
|
|
45
|
+
Aug: 7,
|
|
46
|
+
Sep: 8,
|
|
47
|
+
Oct: 9,
|
|
48
|
+
Nov: 10,
|
|
49
|
+
Dec: 11,
|
|
50
|
+
};
|
|
51
|
+
// The three RFC 7231 §7.1.1.1 `HTTP-date` grammars a recipient must accept.
|
|
52
|
+
// Each is anchored so trailing junk is rejected, and `Date.parse` is avoided
|
|
53
|
+
// entirely (it is permissive — it reads asctime in local time and rolls
|
|
54
|
+
// impossible dates like 31 Feb forward instead of rejecting them).
|
|
55
|
+
const MONTH = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
|
|
56
|
+
const IMF_FIXDATE = new RegExp(`^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\\d{2}) (${MONTH}) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`);
|
|
57
|
+
const RFC850_DATE = new RegExp(`^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d{2})-(${MONTH})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$`);
|
|
58
|
+
const ASCTIME_DATE = new RegExp(`^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (${MONTH}) ([ \\d]\\d) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$`);
|
|
59
|
+
/**
|
|
60
|
+
* Expand an RFC 850 two-digit year per RFC 7231 §7.1.1.1: a full timestamp that
|
|
61
|
+
* appears more than 50 years in the future is interpreted as the most recent
|
|
62
|
+
* past year with the same last two digits. The comparison is against the exact
|
|
63
|
+
* receipt instant plus 50 years (not just calendar years), so the boundary can
|
|
64
|
+
* fall mid-year — e.g. received 2026-06-01, `31-Dec-76` is 50y7m ahead and maps
|
|
65
|
+
* to 1976, while `01-Jan-76` is under 50 years ahead and stays 2076.
|
|
66
|
+
*/
|
|
67
|
+
function expandTwoDigitYear(twoDigitYear, month, day, hour, minute, second) {
|
|
68
|
+
const now = new Date(Date.now());
|
|
69
|
+
const candidateYear = Math.floor(now.getUTCFullYear() / 100) * 100 + twoDigitYear;
|
|
70
|
+
const candidate = Date.UTC(candidateYear, month, day, hour, minute, second);
|
|
71
|
+
const fiftyYearsAhead = Date.UTC(now.getUTCFullYear() + 50, now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds());
|
|
72
|
+
return candidate > fiftyYearsAhead ? candidateYear - 100 : candidateYear;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse an RFC 7231 `HTTP-date` — in any of the three formats a recipient must
|
|
76
|
+
* accept (IMF-fixdate, the obsolete RFC 850 form, and asctime) — into a UTC
|
|
77
|
+
* epoch in milliseconds, or `undefined` if the value is not a well-formed, real
|
|
78
|
+
* calendar date. All three denote UTC, so timestamps are built with `Date.UTC`
|
|
79
|
+
* and the fields are round-tripped to reject impossible dates such as
|
|
80
|
+
* `31 Feb` or `25:00:00`. RFC 850's 2-digit year follows the RFC 7231 rule
|
|
81
|
+
* (see {@link expandTwoDigitYear}). The redundant day-name is intentionally not
|
|
82
|
+
* validated against the date — RFC 7231 encourages robust parsing, and the
|
|
83
|
+
* date/time fields already determine the instant unambiguously, so a mismatched
|
|
84
|
+
* weekday still yields the intended delay rather than a dropped header and an
|
|
85
|
+
* arbitrary fallback.
|
|
86
|
+
*/
|
|
87
|
+
function parseHttpDate(value) {
|
|
88
|
+
let day;
|
|
89
|
+
let month;
|
|
90
|
+
let year;
|
|
91
|
+
let hour;
|
|
92
|
+
let minute;
|
|
93
|
+
let second;
|
|
94
|
+
const imf = IMF_FIXDATE.exec(value);
|
|
95
|
+
const rfc850 = RFC850_DATE.exec(value);
|
|
96
|
+
const asctime = ASCTIME_DATE.exec(value);
|
|
97
|
+
if (imf) {
|
|
98
|
+
day = Number(imf[1]);
|
|
99
|
+
month = MONTHS[imf[2]];
|
|
100
|
+
year = Number(imf[3]);
|
|
101
|
+
hour = Number(imf[4]);
|
|
102
|
+
minute = Number(imf[5]);
|
|
103
|
+
second = Number(imf[6]);
|
|
104
|
+
}
|
|
105
|
+
else if (rfc850) {
|
|
106
|
+
day = Number(rfc850[1]);
|
|
107
|
+
month = MONTHS[rfc850[2]];
|
|
108
|
+
hour = Number(rfc850[4]);
|
|
109
|
+
minute = Number(rfc850[5]);
|
|
110
|
+
second = Number(rfc850[6]);
|
|
111
|
+
if (month === undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
year = expandTwoDigitYear(Number(rfc850[3]), month, day, hour, minute, second);
|
|
114
|
+
}
|
|
115
|
+
else if (asctime) {
|
|
116
|
+
month = MONTHS[asctime[1]];
|
|
117
|
+
day = Number((asctime[2] ?? "").trim());
|
|
118
|
+
hour = Number(asctime[3]);
|
|
119
|
+
minute = Number(asctime[4]);
|
|
120
|
+
second = Number(asctime[5]);
|
|
121
|
+
year = Number(asctime[6]);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (month === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
const timestamp = Date.UTC(year, month, day, hour, minute, second);
|
|
129
|
+
const utc = new Date(timestamp);
|
|
130
|
+
// Reject impossible calendar/clock values that Date.UTC silently rolls over
|
|
131
|
+
// (e.g. 31 Feb, 25:00:00). A `:60` leap second is likewise dropped rather
|
|
132
|
+
// than special-cased — leap seconds never appear in a Retry-After header.
|
|
133
|
+
if (utc.getUTCFullYear() !== year ||
|
|
134
|
+
utc.getUTCMonth() !== month ||
|
|
135
|
+
utc.getUTCDate() !== day ||
|
|
136
|
+
utc.getUTCHours() !== hour ||
|
|
137
|
+
utc.getUTCMinutes() !== minute ||
|
|
138
|
+
utc.getUTCSeconds() !== second) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
return timestamp;
|
|
142
|
+
}
|
|
37
143
|
export class BaseAPI {
|
|
38
144
|
apiUrl;
|
|
39
145
|
/** Active session keypair (raw bytes) used to sign authenticated requests. */
|
|
@@ -138,12 +244,25 @@ export class BaseAPI {
|
|
|
138
244
|
if (!headers)
|
|
139
245
|
return {};
|
|
140
246
|
const requestId = headers.get("x-request-id") || headers.get("request-id") || headers.get("x-correlation-id") || undefined;
|
|
141
|
-
// Parse Retry-After header
|
|
247
|
+
// Parse the Retry-After header (RFC 7231). It is either a non-negative
|
|
248
|
+
// number of seconds (delay-seconds) or an HTTP-date. Both forms are
|
|
249
|
+
// normalized to a delay in SECONDS, so callers multiply by 1000 for
|
|
250
|
+
// setTimeout. An HTTP-date is converted to the remaining seconds from now
|
|
251
|
+
// (past dates clamp to 0). Values matching neither grammar are dropped so
|
|
252
|
+
// a malformed header can never masquerade as a 0s "retry immediately".
|
|
142
253
|
const retryAfterHeader = headers.get("retry-after");
|
|
143
254
|
let retryAfter;
|
|
144
255
|
if (retryAfterHeader !== null) {
|
|
145
|
-
const
|
|
146
|
-
|
|
256
|
+
const value = retryAfterHeader.trim();
|
|
257
|
+
if (/^\d+$/.test(value)) {
|
|
258
|
+
retryAfter = Number.parseInt(value, 10);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const dateMs = parseHttpDate(value);
|
|
262
|
+
if (dateMs !== undefined) {
|
|
263
|
+
retryAfter = Math.max(0, Math.ceil((dateMs - Date.now()) / 1000));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
147
266
|
}
|
|
148
267
|
return { requestId, retryAfter };
|
|
149
268
|
}
|
|
@@ -169,30 +169,65 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
|
|
|
169
169
|
* Fetches the user's executed trades from the /api/v1/accounts/trades endpoint.
|
|
170
170
|
* Requires a valid access token to be set.
|
|
171
171
|
*
|
|
172
|
+
* When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
|
|
173
|
+
* pagination: it starts a cursor walk from the newest row and the response's
|
|
174
|
+
* `nextPageToken` resumes it. Cursor responses carry no exact totals
|
|
175
|
+
* (`total`/`page`/`totalPages` are 0).
|
|
176
|
+
*
|
|
172
177
|
* @param params - Optional query parameters for pagination and filtering
|
|
173
|
-
* @param params.page -
|
|
174
|
-
*
|
|
178
|
+
* @param params.page - Deprecated: page number for legacy offset pagination (starts
|
|
179
|
+
* from 1). Passing it selects legacy page-number mode, which only sees recent (hot)
|
|
180
|
+
* history and populates `total`/`totalPages`. Use cursor pagination (`pageToken`)
|
|
181
|
+
* instead.
|
|
182
|
+
* @param params.pageSize - Number of items per page (default: 20; max 100
|
|
183
|
+
* in page-number mode, up to 1000 when pageToken is present)
|
|
175
184
|
* @param params.tradingPairId - Filter by trading pair ID (UUID)
|
|
176
|
-
* @param params.pageToken - Pagination cursor
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
185
|
+
* @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
|
|
186
|
+
* the newest row; pass a previous response's `nextPageToken` to resume. Any
|
|
187
|
+
* cursor-walk value (`""` or non-empty) ignores `page` and covers full trade history
|
|
188
|
+
* (hot and archived rows merged), unlike page-number mode. Omitting both `page` and
|
|
189
|
+
* `pageToken` starts a cursor walk (`pageToken=""`).
|
|
181
190
|
* @returns Promise resolving to paginated trades response
|
|
182
191
|
* @throws {ValidationError} When the provided params fail client-side validation
|
|
183
192
|
* @throws {APIError} When the request fails or user is not authenticated
|
|
184
193
|
*
|
|
185
194
|
* @example
|
|
186
195
|
* ```typescript
|
|
187
|
-
* //
|
|
188
|
-
*
|
|
189
|
-
*
|
|
196
|
+
* // Walk full trade history with cursor pagination (the default mode)
|
|
197
|
+
* let pageToken: string | undefined;
|
|
198
|
+
* do {
|
|
199
|
+
* const res = await profileAPI.getUserTrades({ pageToken });
|
|
200
|
+
* handle(res.trades);
|
|
201
|
+
* pageToken = res.nextPageToken;
|
|
202
|
+
* } while (pageToken);
|
|
190
203
|
*
|
|
191
204
|
* // Filter by trading pair
|
|
192
205
|
* const pairTrades = await profileAPI.getUserTrades({ tradingPairId: "550e8400-e29b-41d4-a716-446655440000" });
|
|
193
206
|
* ```
|
|
194
207
|
*/
|
|
195
208
|
getUserTrades(params?: GetUserTradesParams): Promise<GetUserTradesResponse>;
|
|
209
|
+
/**
|
|
210
|
+
* Walks the user's full trade history with cursor pagination, yielding one
|
|
211
|
+
* trade at a time.
|
|
212
|
+
*
|
|
213
|
+
* Starts a cursor walk (`pageToken: ""`) and follows each response's
|
|
214
|
+
* `nextPageToken` until the server proves exhaustion (a page with zero rows
|
|
215
|
+
* and an empty token). Covers full trade history — hot and archived rows
|
|
216
|
+
* merged — unlike the deprecated page-number mode.
|
|
217
|
+
*
|
|
218
|
+
* @param params - Optional filters and page size; `page`/`pageToken` are
|
|
219
|
+
* managed by the iterator and cannot be passed.
|
|
220
|
+
* @returns Async generator yielding trades newest-first
|
|
221
|
+
* @throws {APIError} When any underlying request fails
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* for await (const trade of profileAPI.iterateUserTrades()) {
|
|
226
|
+
* console.log(trade.tradeId);
|
|
227
|
+
* }
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
iterateUserTrades(params?: Omit<GetUserTradesParams, "page" | "pageToken">): AsyncGenerator<GetUserTradesResponse["trades"][number], void, undefined>;
|
|
196
231
|
/**
|
|
197
232
|
* List the current user's funding payment history with pagination and filters.
|
|
198
233
|
*
|
package/dist/api/profile/api.js
CHANGED
|
@@ -220,24 +220,37 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
220
220
|
* Fetches the user's executed trades from the /api/v1/accounts/trades endpoint.
|
|
221
221
|
* Requires a valid access token to be set.
|
|
222
222
|
*
|
|
223
|
+
* When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
|
|
224
|
+
* pagination: it starts a cursor walk from the newest row and the response's
|
|
225
|
+
* `nextPageToken` resumes it. Cursor responses carry no exact totals
|
|
226
|
+
* (`total`/`page`/`totalPages` are 0).
|
|
227
|
+
*
|
|
223
228
|
* @param params - Optional query parameters for pagination and filtering
|
|
224
|
-
* @param params.page -
|
|
225
|
-
*
|
|
229
|
+
* @param params.page - Deprecated: page number for legacy offset pagination (starts
|
|
230
|
+
* from 1). Passing it selects legacy page-number mode, which only sees recent (hot)
|
|
231
|
+
* history and populates `total`/`totalPages`. Use cursor pagination (`pageToken`)
|
|
232
|
+
* instead.
|
|
233
|
+
* @param params.pageSize - Number of items per page (default: 20; max 100
|
|
234
|
+
* in page-number mode, up to 1000 when pageToken is present)
|
|
226
235
|
* @param params.tradingPairId - Filter by trading pair ID (UUID)
|
|
227
|
-
* @param params.pageToken - Pagination cursor
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
236
|
+
* @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
|
|
237
|
+
* the newest row; pass a previous response's `nextPageToken` to resume. Any
|
|
238
|
+
* cursor-walk value (`""` or non-empty) ignores `page` and covers full trade history
|
|
239
|
+
* (hot and archived rows merged), unlike page-number mode. Omitting both `page` and
|
|
240
|
+
* `pageToken` starts a cursor walk (`pageToken=""`).
|
|
232
241
|
* @returns Promise resolving to paginated trades response
|
|
233
242
|
* @throws {ValidationError} When the provided params fail client-side validation
|
|
234
243
|
* @throws {APIError} When the request fails or user is not authenticated
|
|
235
244
|
*
|
|
236
245
|
* @example
|
|
237
246
|
* ```typescript
|
|
238
|
-
* //
|
|
239
|
-
*
|
|
240
|
-
*
|
|
247
|
+
* // Walk full trade history with cursor pagination (the default mode)
|
|
248
|
+
* let pageToken: string | undefined;
|
|
249
|
+
* do {
|
|
250
|
+
* const res = await profileAPI.getUserTrades({ pageToken });
|
|
251
|
+
* handle(res.trades);
|
|
252
|
+
* pageToken = res.nextPageToken;
|
|
253
|
+
* } while (pageToken);
|
|
241
254
|
*
|
|
242
255
|
* // Filter by trading pair
|
|
243
256
|
* const pairTrades = await profileAPI.getUserTrades({ tradingPairId: "550e8400-e29b-41d4-a716-446655440000" });
|
|
@@ -247,8 +260,11 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
247
260
|
if (params) {
|
|
248
261
|
validate(GetUserTradesSchema, params);
|
|
249
262
|
}
|
|
263
|
+
// Legacy offset mode only when the caller explicitly asked for a page and
|
|
264
|
+
// no cursor was given; everything else drives cursor (pageToken) mode.
|
|
265
|
+
const legacyOffsetMode = params?.page !== undefined && params?.pageToken === undefined;
|
|
250
266
|
const searchParams = new URLSearchParams();
|
|
251
|
-
if (params?.page !== undefined) {
|
|
267
|
+
if (legacyOffsetMode && params?.page !== undefined) {
|
|
252
268
|
searchParams.append("page", params.page.toString());
|
|
253
269
|
}
|
|
254
270
|
if (params?.pageSize !== undefined) {
|
|
@@ -257,13 +273,53 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
257
273
|
if (params?.tradingPairId !== undefined) {
|
|
258
274
|
searchParams.append("tradingPairId", params.tradingPairId);
|
|
259
275
|
}
|
|
260
|
-
|
|
261
|
-
|
|
276
|
+
// Cursor mode selects itself by the *presence* of `pageToken` on the wire,
|
|
277
|
+
// even when it is the empty string (the documented way to start a walk),
|
|
278
|
+
// so it must be sent explicitly rather than dropped like a falsy value.
|
|
279
|
+
// When the caller gave neither `page` nor `pageToken`, default to starting
|
|
280
|
+
// a cursor walk (`pageToken=""`).
|
|
281
|
+
if (!legacyOffsetMode) {
|
|
282
|
+
searchParams.append("pageToken", params?.pageToken ?? "");
|
|
262
283
|
}
|
|
263
284
|
const queryString = searchParams.toString();
|
|
264
285
|
const url = queryString ? `/api/v1/accounts/trades?${queryString}` : "/api/v1/accounts/trades";
|
|
265
286
|
return await this.makeAuthenticatedRequest(url);
|
|
266
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Walks the user's full trade history with cursor pagination, yielding one
|
|
290
|
+
* trade at a time.
|
|
291
|
+
*
|
|
292
|
+
* Starts a cursor walk (`pageToken: ""`) and follows each response's
|
|
293
|
+
* `nextPageToken` until the server proves exhaustion (a page with zero rows
|
|
294
|
+
* and an empty token). Covers full trade history — hot and archived rows
|
|
295
|
+
* merged — unlike the deprecated page-number mode.
|
|
296
|
+
*
|
|
297
|
+
* @param params - Optional filters and page size; `page`/`pageToken` are
|
|
298
|
+
* managed by the iterator and cannot be passed.
|
|
299
|
+
* @returns Async generator yielding trades newest-first
|
|
300
|
+
* @throws {APIError} When any underlying request fails
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```typescript
|
|
304
|
+
* for await (const trade of profileAPI.iterateUserTrades()) {
|
|
305
|
+
* console.log(trade.tradeId);
|
|
306
|
+
* }
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
async *iterateUserTrades(params) {
|
|
310
|
+
let pageToken = "";
|
|
311
|
+
// A non-empty token never guarantees further results (AIP-158 "maybe
|
|
312
|
+
// more"): keep following it until a response returns zero rows and an
|
|
313
|
+
// empty token, the sole proof the walk is exhausted.
|
|
314
|
+
for (;;) {
|
|
315
|
+
const response = await this.getUserTrades({ ...params, pageToken });
|
|
316
|
+
yield* response.trades;
|
|
317
|
+
if (!response.nextPageToken) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
pageToken = response.nextPageToken;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
267
323
|
/**
|
|
268
324
|
* List the current user's funding payment history with pagination and filters.
|
|
269
325
|
*
|
|
@@ -260,40 +260,66 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
260
260
|
*/
|
|
261
261
|
batchReplace(orders: BatchReplaceOrderParams[]): Promise<BatchReplaceOrdersResponse>;
|
|
262
262
|
/**
|
|
263
|
-
* Gets
|
|
263
|
+
* Gets orders based on query parameters, cursor-paginated by default.
|
|
264
264
|
*
|
|
265
265
|
* Retrieves orders with optional filtering by status, trading pair, and pagination.
|
|
266
|
-
*
|
|
266
|
+
* When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
|
|
267
|
+
* pagination: it starts a cursor walk from the newest (or oldest, for
|
|
268
|
+
* `orderBy: "ASC"`) row and the response's `nextPageToken` resumes it. Cursor
|
|
269
|
+
* responses carry no exact totals (`total`/`page`/`totalPages` are 0).
|
|
267
270
|
*
|
|
268
271
|
* @param params - Query parameters for filtering orders
|
|
269
|
-
* @param params.status - Filter by order status (e.g., "SUBMITTED", "FILLED")
|
|
272
|
+
* @param params.status - Filter by order status (e.g., "SUBMITTED", "FILLED"), or by
|
|
273
|
+
* any of several statuses at once by passing an array (e.g., ["SUBMITTED", "PARTIALLY_FILLED"]) (optional)
|
|
270
274
|
* @param params.tradingPairId - Filter by trading pair UUID (optional)
|
|
271
|
-
* @param params.page -
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* (
|
|
275
|
+
* @param params.page - Deprecated: page number for legacy offset pagination (must be > 0).
|
|
276
|
+
* Passing it selects legacy page-number mode, which only sees recent (hot) history and
|
|
277
|
+
* populates `total`/`totalPages`. Use cursor pagination (`pageToken`) instead.
|
|
278
|
+
* @param params.pageSize - Number of orders per page (defaults to 10, must be > 0; max 100
|
|
279
|
+
* in page-number mode, up to 1000 when pageToken is present)
|
|
280
|
+
* @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
|
|
281
|
+
* the newest (or oldest, for `orderBy: "ASC"`) row; pass a previous response's
|
|
282
|
+
* `nextPageToken` to resume. Any cursor-walk value (`""` or non-empty) ignores `page`
|
|
283
|
+
* and covers full order history (hot and archived rows merged), unlike page-number
|
|
284
|
+
* mode. Omitting both `page` and `pageToken` starts a cursor walk (`pageToken=""`).
|
|
278
285
|
* @returns Promise resolving to GetPaginatedOrdersResponse with orders and pagination info
|
|
279
286
|
* @throws {APIError} When API communication fails
|
|
280
287
|
*
|
|
281
288
|
* @example
|
|
282
289
|
* ```typescript
|
|
283
|
-
* //
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* });
|
|
290
|
-
* console.log(`Found ${orders.total} orders, showing page ${orders.page}`);
|
|
291
|
-
*
|
|
292
|
-
* // Get all orders with default pagination (page=1, pageSize=10)
|
|
293
|
-
* const allOrders = await tradingAPI.getPaginatedOrders();
|
|
290
|
+
* // Walk full order history with cursor pagination (the default mode)
|
|
291
|
+
* let pageToken: string | undefined;
|
|
292
|
+
* do {
|
|
293
|
+
* const res = await tradingAPI.getPaginatedOrders({ status: "FILLED", pageToken });
|
|
294
|
+
* handle(res.orders);
|
|
295
|
+
* pageToken = res.nextPageToken;
|
|
296
|
+
* } while (pageToken);
|
|
294
297
|
* ```
|
|
295
298
|
*/
|
|
296
299
|
getPaginatedOrders(params?: GetPaginatedOrdersParams): Promise<GetPaginatedOrdersResponse>;
|
|
300
|
+
/**
|
|
301
|
+
* Walks the user's full order history with cursor pagination, yielding one
|
|
302
|
+
* order at a time.
|
|
303
|
+
*
|
|
304
|
+
* Starts a cursor walk (`pageToken: ""`) and follows each response's
|
|
305
|
+
* `nextPageToken` until the server proves exhaustion (a page with zero rows
|
|
306
|
+
* and an empty token). Covers full order history — hot and archived rows
|
|
307
|
+
* merged — unlike the deprecated page-number mode.
|
|
308
|
+
*
|
|
309
|
+
* @param params - Optional filters and page size; `page`/`pageToken` are
|
|
310
|
+
* managed by the iterator and cannot be passed.
|
|
311
|
+
* @returns Async generator yielding orders newest-first (or oldest-first for
|
|
312
|
+
* `orderBy: "ASC"`)
|
|
313
|
+
* @throws {APIError} When any underlying request fails
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* for await (const order of tradingAPI.iterateOrders({ status: "FILLED" })) {
|
|
318
|
+
* console.log(order.id);
|
|
319
|
+
* }
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
iterateOrders(params?: Omit<GetPaginatedOrdersParams, "page" | "pageToken">): AsyncGenerator<GetPaginatedOrdersResponse["orders"][number], void, undefined>;
|
|
297
323
|
/**
|
|
298
324
|
* Gets a single order by its ID.
|
|
299
325
|
*
|
package/dist/api/trading/api.js
CHANGED
|
@@ -409,37 +409,40 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
409
409
|
});
|
|
410
410
|
}
|
|
411
411
|
/**
|
|
412
|
-
* Gets
|
|
412
|
+
* Gets orders based on query parameters, cursor-paginated by default.
|
|
413
413
|
*
|
|
414
414
|
* Retrieves orders with optional filtering by status, trading pair, and pagination.
|
|
415
|
-
*
|
|
415
|
+
* When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
|
|
416
|
+
* pagination: it starts a cursor walk from the newest (or oldest, for
|
|
417
|
+
* `orderBy: "ASC"`) row and the response's `nextPageToken` resumes it. Cursor
|
|
418
|
+
* responses carry no exact totals (`total`/`page`/`totalPages` are 0).
|
|
416
419
|
*
|
|
417
420
|
* @param params - Query parameters for filtering orders
|
|
418
|
-
* @param params.status - Filter by order status (e.g., "SUBMITTED", "FILLED")
|
|
421
|
+
* @param params.status - Filter by order status (e.g., "SUBMITTED", "FILLED"), or by
|
|
422
|
+
* any of several statuses at once by passing an array (e.g., ["SUBMITTED", "PARTIALLY_FILLED"]) (optional)
|
|
419
423
|
* @param params.tradingPairId - Filter by trading pair UUID (optional)
|
|
420
|
-
* @param params.page -
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
* (
|
|
424
|
+
* @param params.page - Deprecated: page number for legacy offset pagination (must be > 0).
|
|
425
|
+
* Passing it selects legacy page-number mode, which only sees recent (hot) history and
|
|
426
|
+
* populates `total`/`totalPages`. Use cursor pagination (`pageToken`) instead.
|
|
427
|
+
* @param params.pageSize - Number of orders per page (defaults to 10, must be > 0; max 100
|
|
428
|
+
* in page-number mode, up to 1000 when pageToken is present)
|
|
429
|
+
* @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
|
|
430
|
+
* the newest (or oldest, for `orderBy: "ASC"`) row; pass a previous response's
|
|
431
|
+
* `nextPageToken` to resume. Any cursor-walk value (`""` or non-empty) ignores `page`
|
|
432
|
+
* and covers full order history (hot and archived rows merged), unlike page-number
|
|
433
|
+
* mode. Omitting both `page` and `pageToken` starts a cursor walk (`pageToken=""`).
|
|
427
434
|
* @returns Promise resolving to GetPaginatedOrdersResponse with orders and pagination info
|
|
428
435
|
* @throws {APIError} When API communication fails
|
|
429
436
|
*
|
|
430
437
|
* @example
|
|
431
438
|
* ```typescript
|
|
432
|
-
* //
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* });
|
|
439
|
-
* console.log(`Found ${orders.total} orders, showing page ${orders.page}`);
|
|
440
|
-
*
|
|
441
|
-
* // Get all orders with default pagination (page=1, pageSize=10)
|
|
442
|
-
* const allOrders = await tradingAPI.getPaginatedOrders();
|
|
439
|
+
* // Walk full order history with cursor pagination (the default mode)
|
|
440
|
+
* let pageToken: string | undefined;
|
|
441
|
+
* do {
|
|
442
|
+
* const res = await tradingAPI.getPaginatedOrders({ status: "FILLED", pageToken });
|
|
443
|
+
* handle(res.orders);
|
|
444
|
+
* pageToken = res.nextPageToken;
|
|
445
|
+
* } while (pageToken);
|
|
443
446
|
* ```
|
|
444
447
|
*/
|
|
445
448
|
async getPaginatedOrders(params) {
|
|
@@ -448,16 +451,25 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
448
451
|
validate(GetPaginatedOrdersSchema, params);
|
|
449
452
|
}
|
|
450
453
|
// Set pagination defaults with destructuring and validation
|
|
451
|
-
const { page
|
|
452
|
-
const validPage = page > 0 ? page : 1;
|
|
454
|
+
const { page, pageSize: requestedPageSize = 10, status, tradingPairId, tradingMode, marginAccountId, pageToken } = params || {};
|
|
453
455
|
const validPageSize = requestedPageSize > 0 ? requestedPageSize : 10;
|
|
454
|
-
|
|
456
|
+
// Legacy offset mode only when the caller explicitly asked for a page and
|
|
457
|
+
// no cursor was given; everything else drives cursor (pageToken) mode.
|
|
458
|
+
const legacyOffsetMode = page !== undefined && pageToken === undefined;
|
|
459
|
+
// Cursor requests may use pages up to 1000 (keyset reads); page-number
|
|
460
|
+
// requests keep the server's 100 cap.
|
|
461
|
+
const pageSize = Math.min(validPageSize, legacyOffsetMode ? 100 : 1000);
|
|
455
462
|
const searchParams = new URLSearchParams();
|
|
456
|
-
|
|
457
|
-
|
|
463
|
+
if (legacyOffsetMode) {
|
|
464
|
+
const validPage = page > 0 ? page : 1;
|
|
465
|
+
searchParams.append("page", validPage.toString());
|
|
466
|
+
}
|
|
458
467
|
searchParams.append("pageSize", pageSize.toString());
|
|
459
|
-
|
|
460
|
-
|
|
468
|
+
// An array filter is sent as one comma-separated `status` value, which the
|
|
469
|
+
// API reads as "any of these", so several statuses cost a single request.
|
|
470
|
+
const statusFilter = typeof status === "string" ? status : status?.join(",");
|
|
471
|
+
if (statusFilter) {
|
|
472
|
+
searchParams.append("status", statusFilter);
|
|
461
473
|
}
|
|
462
474
|
if (tradingPairId) {
|
|
463
475
|
searchParams.append("tradingPairId", tradingPairId);
|
|
@@ -468,18 +480,55 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
468
480
|
if (marginAccountId) {
|
|
469
481
|
searchParams.append("marginAccountId", marginAccountId);
|
|
470
482
|
}
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
483
|
+
// Cursor mode selects itself by the *presence* of `pageToken` on the wire,
|
|
484
|
+
// even when it is the empty string (the documented way to start a walk),
|
|
485
|
+
// so it must be sent explicitly rather than dropped like a falsy value.
|
|
486
|
+
// When the caller gave neither `page` nor `pageToken`, default to starting
|
|
487
|
+
// a cursor walk (`pageToken=""`).
|
|
488
|
+
if (!legacyOffsetMode) {
|
|
489
|
+
searchParams.append("pageToken", pageToken ?? "");
|
|
477
490
|
}
|
|
478
491
|
const endpoint = `/api/v1/orders?${searchParams.toString()}`;
|
|
479
492
|
return await this.makeAuthenticatedRequest(endpoint, {
|
|
480
493
|
method: "GET",
|
|
481
494
|
});
|
|
482
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Walks the user's full order history with cursor pagination, yielding one
|
|
498
|
+
* order at a time.
|
|
499
|
+
*
|
|
500
|
+
* Starts a cursor walk (`pageToken: ""`) and follows each response's
|
|
501
|
+
* `nextPageToken` until the server proves exhaustion (a page with zero rows
|
|
502
|
+
* and an empty token). Covers full order history — hot and archived rows
|
|
503
|
+
* merged — unlike the deprecated page-number mode.
|
|
504
|
+
*
|
|
505
|
+
* @param params - Optional filters and page size; `page`/`pageToken` are
|
|
506
|
+
* managed by the iterator and cannot be passed.
|
|
507
|
+
* @returns Async generator yielding orders newest-first (or oldest-first for
|
|
508
|
+
* `orderBy: "ASC"`)
|
|
509
|
+
* @throws {APIError} When any underlying request fails
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* for await (const order of tradingAPI.iterateOrders({ status: "FILLED" })) {
|
|
514
|
+
* console.log(order.id);
|
|
515
|
+
* }
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
async *iterateOrders(params) {
|
|
519
|
+
let pageToken = "";
|
|
520
|
+
// A non-empty token never guarantees further results (AIP-158 "maybe
|
|
521
|
+
// more"): keep following it until a response returns zero rows and an
|
|
522
|
+
// empty token, the sole proof the walk is exhausted.
|
|
523
|
+
for (;;) {
|
|
524
|
+
const response = await this.getPaginatedOrders({ ...params, pageToken });
|
|
525
|
+
yield* response.orders;
|
|
526
|
+
if (!response.nextPageToken) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
pageToken = response.nextPageToken;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
483
532
|
/**
|
|
484
533
|
* Gets a single order by its ID.
|
|
485
534
|
*
|
|
@@ -150,6 +150,10 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
150
150
|
* opens. The client signs `WS-AUTH\n<pubkey>\n<ts>` with the session private
|
|
151
151
|
* key; the server binds the connection to the session. No-op if no session
|
|
152
152
|
* is set (public channels work unauthenticated).
|
|
153
|
+
*
|
|
154
|
+
* `timestamp` is the current epoch-ms and is bound into the signature; the
|
|
155
|
+
* server rejects it outside a ±30s skew window, so the handshake is freshly
|
|
156
|
+
* signed on every (re)connect rather than cached and replayed.
|
|
153
157
|
*/
|
|
154
158
|
const sendAuthenticate = () => {
|
|
155
159
|
if (!session || ws?.readyState !== WebSocket.OPEN)
|
|
@@ -504,7 +508,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
504
508
|
});
|
|
505
509
|
};
|
|
506
510
|
const subscribeMovements = (handler) => {
|
|
507
|
-
// User is identified from the
|
|
511
|
+
// User is identified from the authenticated session, so channel is just "movements"
|
|
508
512
|
const channel = "movements";
|
|
509
513
|
return subscribe(channel, (rawData) => {
|
|
510
514
|
try {
|
|
@@ -541,10 +545,10 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
541
545
|
};
|
|
542
546
|
/**
|
|
543
547
|
* Subscribe to authenticated user's order events (all trading pairs)
|
|
544
|
-
* Requires authentication - user is identified from the
|
|
548
|
+
* Requires authentication - user is identified from the signed session-key handshake
|
|
545
549
|
*/
|
|
546
550
|
const subscribeUserOrders = (handler) => {
|
|
547
|
-
// User is identified from the
|
|
551
|
+
// User is identified from the authenticated session, so channel is just "orders"
|
|
548
552
|
const channel = "orders";
|
|
549
553
|
return subscribe(channel, (rawData) => {
|
|
550
554
|
try {
|
|
@@ -564,10 +568,10 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
564
568
|
};
|
|
565
569
|
/**
|
|
566
570
|
* Subscribe to authenticated user's balance events
|
|
567
|
-
* Requires authentication - user is identified from the
|
|
571
|
+
* Requires authentication - user is identified from the signed session-key handshake
|
|
568
572
|
*/
|
|
569
573
|
const subscribeBalances = (handler) => {
|
|
570
|
-
// User is identified from the
|
|
574
|
+
// User is identified from the authenticated session, so channel is just "balances"
|
|
571
575
|
const channel = "balances";
|
|
572
576
|
return subscribe(channel, (rawData) => {
|
|
573
577
|
try {
|
package/dist/errors/errors.js
CHANGED
|
@@ -421,7 +421,7 @@ function getSuggestionForConfigField(field) {
|
|
|
421
421
|
case "seirpcurl":
|
|
422
422
|
return "Use 'https://evm-rpc-testnet.sei-apis.com' for testnet or 'https://evm-rpc.sei-apis.com' for mainnet.";
|
|
423
423
|
case "wsurl":
|
|
424
|
-
return "Use 'wss://
|
|
424
|
+
return "Use 'wss://ws-develop.apimonaco.xyz/ws' for testnet or 'wss://ws.apimonaco.xyz/ws' for mainnet.";
|
|
425
425
|
case "network":
|
|
426
426
|
return "Valid networks are: 'mainnet', 'development', 'staging', 'local'.";
|
|
427
427
|
default:
|
|
@@ -27,7 +27,9 @@ const DEFAULT_WS_URLS = {
|
|
|
27
27
|
// keep working during the migration window.
|
|
28
28
|
development: "wss://ws-develop.apimonaco.xyz/ws",
|
|
29
29
|
staging: "wss://ws-staging.apimonaco.xyz/ws",
|
|
30
|
-
|
|
30
|
+
// wss://api.monaco.xyz/ws never resolved (the monaco.xyz zone has no NS
|
|
31
|
+
// delegation), so this is not a behavior change for any working client.
|
|
32
|
+
mainnet: "wss://ws.apimonaco.xyz/ws",
|
|
31
33
|
};
|
|
32
34
|
/**
|
|
33
35
|
* Resolve the API URL based on network
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32-develop.522174b",
|
|
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.32-develop.522174b",
|
|
27
|
+
"@0xmonaco/types": "1.0.32-develop.522174b",
|
|
28
28
|
"@noble/curves": "^1.9.1",
|
|
29
29
|
"@noble/hashes": "^1.8.0",
|
|
30
30
|
"http-status-codes": "^2.3.0"
|