@0xmonaco/core 1.0.67 → 1.0.70

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.
@@ -31,7 +31,10 @@ export declare class BuildercodesAPIImpl extends BaseAPI implements Buildercodes
31
31
  */
32
32
  getConfig(): Promise<GetBuildercodesConfigResponse>;
33
33
  /**
34
- * List this frontend's per-trade BuilderCodes credits, newest first.
34
+ * List this frontend's per-trade BuilderCodes credits, newest first. Each
35
+ * credit carries its payout `kind`: `REVENUE_SHARE` (your share of Monaco's
36
+ * fee) or `BUILDER_FEE` (your builder fee). Pass `kind` to list one stream;
37
+ * `total` then counts only that stream.
35
38
  *
36
39
  * The server scopes every query to the authenticated application; neither
37
40
  * the application id nor payout wallet can be supplied by the caller.
@@ -40,7 +43,9 @@ export declare class BuildercodesAPIImpl extends BaseAPI implements Buildercodes
40
43
  /**
41
44
  * Summarize this frontend's BuilderCodes earnings by token.
42
45
  *
43
- * Amounts are RAW atomic units and remain separated by token.
46
+ * Amounts are RAW atomic units and remain separated by token. `byToken` sums
47
+ * every payout kind; `byTokenAndKind` splits the same totals into the
48
+ * revenue share and the builder fee.
44
49
  */
45
50
  getPayoutSummary(): Promise<GetBuildercodePayoutSummaryResponse>;
46
51
  /**
@@ -103,7 +103,10 @@ export class BuildercodesAPIImpl extends BaseAPI {
103
103
  });
104
104
  }
105
105
  /**
106
- * List this frontend's per-trade BuilderCodes credits, newest first.
106
+ * List this frontend's per-trade BuilderCodes credits, newest first. Each
107
+ * credit carries its payout `kind`: `REVENUE_SHARE` (your share of Monaco's
108
+ * fee) or `BUILDER_FEE` (your builder fee). Pass `kind` to list one stream;
109
+ * `total` then counts only that stream.
107
110
  *
108
111
  * The server scopes every query to the authenticated application; neither
109
112
  * the application id nor payout wallet can be supplied by the caller.
@@ -119,6 +122,9 @@ export class BuildercodesAPIImpl extends BaseAPI {
119
122
  if (params?.pageSize !== undefined) {
120
123
  searchParams.append("pageSize", params.pageSize.toString());
121
124
  }
125
+ if (params?.kind !== undefined) {
126
+ searchParams.append("kind", params.kind);
127
+ }
122
128
  const query = searchParams.toString();
123
129
  const path = query ? `/api/v1/buildercodes/payouts?${query}` : "/api/v1/buildercodes/payouts";
124
130
  return this.makeAuthenticatedRequest(path, { method: "GET" });
@@ -126,7 +132,9 @@ export class BuildercodesAPIImpl extends BaseAPI {
126
132
  /**
127
133
  * Summarize this frontend's BuilderCodes earnings by token.
128
134
  *
129
- * Amounts are RAW atomic units and remain separated by token.
135
+ * Amounts are RAW atomic units and remain separated by token. `byToken` sums
136
+ * every payout kind; `byTokenAndKind` splits the same totals into the
137
+ * revenue share and the builder fee.
130
138
  */
131
139
  async getPayoutSummary() {
132
140
  return this.makeAuthenticatedRequest("/api/v1/buildercodes/payouts/summary", { method: "GET" });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Copy Trading API Implementation (0XM-2851)
3
+ *
4
+ * Lead trader profiles and the public leaderboard, and the caller's follows.
5
+ * Followers allocate collateral to a per-leader copy account (a cross risk
6
+ * bucket in their margin account) and every fill the leader makes is mirrored
7
+ * into it.
8
+ *
9
+ * Leaders are addressed by their custom TraderCode handle. The leaderboard, a
10
+ * leader's profile and a leader's closed trades are public; every other call
11
+ * is signed and scoped to the session's account.
12
+ *
13
+ * The REST gateway rejects unknown request fields, so every request body and
14
+ * query string is built key by key from validated params.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { leaders } = await sdk.copyTrading.listLeadTraders({ sort: "roi", window: "30d" });
19
+ * const leader = leaders[0].profile.leader.handle!;
20
+ * const { followId } = await sdk.copyTrading.upsertFollow({ leader, allocation: "1000", slPct: "0.25" });
21
+ * ```
22
+ */
23
+ import type { CopyTradingAPI, GetFollowResponse, GetLeadTraderResponse, ListFollowLeaderPositionsResponse, ListLeadTraderClosedTradesParams, ListLeadTraderClosedTradesResponse, ListLeadTraderOpenPositionsResponse, ListLeadTradersParams, ListLeadTradersResponse, ListMyFollowersParams, ListMyFollowersResponse, ListMyFollowsParams, ListMyFollowsResponse, PreviewFollowParams, PreviewFollowResponse, StopFollowResponse, UpsertFollowParams, UpsertFollowResponse, UpsertLeadTraderParams, UpsertLeadTraderResponse } from "@0xmonaco/types";
24
+ import { BaseAPI } from "../base";
25
+ export declare class CopyTradingAPIImpl extends BaseAPI implements CopyTradingAPI {
26
+ /**
27
+ * List lead traders (the public leaderboard), highest first by `sort`
28
+ * (default `roi`) over `window` (default `30d`). No session needed.
29
+ *
30
+ * Cursor paged: pass the previous response's `nextPageToken` with the SAME
31
+ * sort and window; an empty token means the end. Rows come from a periodic
32
+ * leaderboard snapshot taken at `asOf`.
33
+ */
34
+ listLeadTraders(params?: ListLeadTradersParams): Promise<ListLeadTradersResponse>;
35
+ /**
36
+ * Get one lead trader by custom TraderCode handle: profile, follow summary
37
+ * and stats for 7d, 30d, 90d and all. No session needed. 404 for an unknown
38
+ * or suspended leader.
39
+ */
40
+ getLeadTrader(handle: string): Promise<GetLeadTraderResponse>;
41
+ /**
42
+ * List a lead trader's closed trades — partial and full closes,
43
+ * liquidations and auto-deleveraging — newest first. No session needed.
44
+ */
45
+ listLeadTraderClosedTrades(handle: string, params?: ListLeadTraderClosedTradesParams): Promise<ListLeadTraderClosedTradesResponse>;
46
+ /**
47
+ * List a lead trader's open positions. Only current followers of that
48
+ * leader may read them (403 otherwise).
49
+ */
50
+ listLeadTraderOpenPositions(handle: string): Promise<ListLeadTraderOpenPositionsResponse>;
51
+ /**
52
+ * Register as a lead trader, or edit your profile. The profile is replaced
53
+ * as sent: an omitted `bio` or `minAllocation` clears it and an omitted
54
+ * `status` means `ACTIVE`; an omitted `shareBps` keeps the current share.
55
+ */
56
+ upsertLeadTrader(params?: UpsertLeadTraderParams): Promise<UpsertLeadTraderResponse>;
57
+ /** Get your own lead trader profile (404 when you are not a lead trader). */
58
+ getMyLeadTrader(): Promise<GetLeadTraderResponse>;
59
+ /** List your followers, as a leader. Cursor paged, up to 1000 per page. */
60
+ listMyFollowers(params?: ListMyFollowersParams): Promise<ListMyFollowersResponse>;
61
+ /**
62
+ * Follow a lead trader (no `followId`), or edit one of your follows
63
+ * (`followId` set).
64
+ *
65
+ * An edit REPLACES the follow's settings: every optional setting left out
66
+ * reverts to its default, so an edit that omits `slPct` removes the
67
+ * stop-loss. The leader comes from the stored follow on an edit, so
68
+ * `leader` may be omitted there.
69
+ */
70
+ upsertFollow(params: UpsertFollowParams): Promise<UpsertFollowResponse>;
71
+ /**
72
+ * Preview what a follow with these terms would open if it set
73
+ * `copyExisting`, as totals only: estimated margin and fees, and how many
74
+ * of the leader's positions would and would not be copied. Read-only;
75
+ * nothing is placed or reserved.
76
+ */
77
+ previewFollow(params: PreviewFollowParams): Promise<PreviewFollowResponse>;
78
+ /** Stop one of your follows: the leader's fills stop being mirrored. */
79
+ stopFollow(followId: string): Promise<StopFollowResponse>;
80
+ /** List your follows, newest first. Cursor paged, up to 1000 per page. */
81
+ listMyFollows(params?: ListMyFollowsParams): Promise<ListMyFollowsResponse>;
82
+ /**
83
+ * Get one of your follows with its most recent skipped mirrors (up to 100)
84
+ * and `copyAccountEquity`, the copy account's marked equity (absent when it
85
+ * could not be read).
86
+ */
87
+ getFollow(followId: string): Promise<GetFollowResponse>;
88
+ /**
89
+ * List the open positions of the leader one of your follows copies. The
90
+ * leader comes from the follow, so this works after the leader releases
91
+ * their handle. 404 for a follow that is not yours; 403 for a stopped one.
92
+ */
93
+ listFollowLeaderPositions(followId: string): Promise<ListFollowLeaderPositionsResponse>;
94
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Copy Trading API Implementation (0XM-2851)
3
+ *
4
+ * Lead trader profiles and the public leaderboard, and the caller's follows.
5
+ * Followers allocate collateral to a per-leader copy account (a cross risk
6
+ * bucket in their margin account) and every fill the leader makes is mirrored
7
+ * into it.
8
+ *
9
+ * Leaders are addressed by their custom TraderCode handle. The leaderboard, a
10
+ * leader's profile and a leader's closed trades are public; every other call
11
+ * is signed and scoped to the session's account.
12
+ *
13
+ * The REST gateway rejects unknown request fields, so every request body and
14
+ * query string is built key by key from validated params.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { leaders } = await sdk.copyTrading.listLeadTraders({ sort: "roi", window: "30d" });
19
+ * const leader = leaders[0].profile.leader.handle!;
20
+ * const { followId } = await sdk.copyTrading.upsertFollow({ leader, allocation: "1000", slPct: "0.25" });
21
+ * ```
22
+ */
23
+ import { LeaderHandleSchema, ListLeadTraderClosedTradesSchema, ListLeadTradersSchema, ListMyFollowersSchema, ListMyFollowsSchema, PreviewFollowSchema, UpsertFollowSchema, UpsertLeadTraderSchema, UUIDSchema, validate, } from "@0xmonaco/types";
24
+ import { BaseAPI } from "../base";
25
+ const BASE = "/api/v1/copy-trading";
26
+ /** `?k=v&...` from the defined entries, or `""` when there are none. */
27
+ function queryString(params) {
28
+ const search = new URLSearchParams();
29
+ for (const [key, value] of Object.entries(params)) {
30
+ if (value !== undefined)
31
+ search.append(key, String(value));
32
+ }
33
+ const encoded = search.toString();
34
+ return encoded ? `?${encoded}` : "";
35
+ }
36
+ /** The leader path segment for a validated handle. */
37
+ function leaderPath(handle) {
38
+ return `${BASE}/leaders/${encodeURIComponent(validate(LeaderHandleSchema, handle))}`;
39
+ }
40
+ /** The follow path segment for a validated follow id. */
41
+ function followPath(followId) {
42
+ return `${BASE}/follows/${validate(UUIDSchema, followId)}`;
43
+ }
44
+ export class CopyTradingAPIImpl extends BaseAPI {
45
+ /**
46
+ * List lead traders (the public leaderboard), highest first by `sort`
47
+ * (default `roi`) over `window` (default `30d`). No session needed.
48
+ *
49
+ * Cursor paged: pass the previous response's `nextPageToken` with the SAME
50
+ * sort and window; an empty token means the end. Rows come from a periodic
51
+ * leaderboard snapshot taken at `asOf`.
52
+ */
53
+ async listLeadTraders(params = {}) {
54
+ const { sort, window, pageSize, pageToken } = validate(ListLeadTradersSchema, params);
55
+ return await this.makePublicRequest(`${BASE}/leaders${queryString({ sort, window, pageSize, pageToken })}`, {
56
+ method: "GET",
57
+ });
58
+ }
59
+ /**
60
+ * Get one lead trader by custom TraderCode handle: profile, follow summary
61
+ * and stats for 7d, 30d, 90d and all. No session needed. 404 for an unknown
62
+ * or suspended leader.
63
+ */
64
+ async getLeadTrader(handle) {
65
+ return await this.makePublicRequest(leaderPath(handle), { method: "GET" });
66
+ }
67
+ /**
68
+ * List a lead trader's closed trades — partial and full closes,
69
+ * liquidations and auto-deleveraging — newest first. No session needed.
70
+ */
71
+ async listLeadTraderClosedTrades(handle, params = {}) {
72
+ const path = leaderPath(handle);
73
+ const { pageSize, pageToken } = validate(ListLeadTraderClosedTradesSchema, params);
74
+ return await this.makePublicRequest(`${path}/closed-trades${queryString({ pageSize, pageToken })}`, {
75
+ method: "GET",
76
+ });
77
+ }
78
+ /**
79
+ * List a lead trader's open positions. Only current followers of that
80
+ * leader may read them (403 otherwise).
81
+ */
82
+ async listLeadTraderOpenPositions(handle) {
83
+ return await this.makeAuthenticatedRequest(`${leaderPath(handle)}/positions`, { method: "GET" });
84
+ }
85
+ /**
86
+ * Register as a lead trader, or edit your profile. The profile is replaced
87
+ * as sent: an omitted `bio` or `minAllocation` clears it and an omitted
88
+ * `status` means `ACTIVE`; an omitted `shareBps` keeps the current share.
89
+ */
90
+ async upsertLeadTrader(params = {}) {
91
+ const { shareBps, bio, status, minAllocation } = validate(UpsertLeadTraderSchema, params);
92
+ const body = {};
93
+ if (shareBps !== undefined)
94
+ body.shareBps = shareBps;
95
+ if (bio !== undefined)
96
+ body.bio = bio;
97
+ if (status !== undefined)
98
+ body.status = status;
99
+ if (minAllocation !== undefined)
100
+ body.minAllocation = minAllocation;
101
+ return await this.makeAuthenticatedRequest(`${BASE}/leaders/me`, {
102
+ method: "PUT",
103
+ body: JSON.stringify(body),
104
+ });
105
+ }
106
+ /** Get your own lead trader profile (404 when you are not a lead trader). */
107
+ async getMyLeadTrader() {
108
+ return await this.makeAuthenticatedRequest(`${BASE}/leaders/me`, { method: "GET" });
109
+ }
110
+ /** List your followers, as a leader. Cursor paged, up to 1000 per page. */
111
+ async listMyFollowers(params = {}) {
112
+ const { pageSize, pageToken } = validate(ListMyFollowersSchema, params);
113
+ return await this.makeAuthenticatedRequest(`${BASE}/leaders/me/followers${queryString({ pageSize, pageToken })}`, {
114
+ method: "GET",
115
+ });
116
+ }
117
+ /**
118
+ * Follow a lead trader (no `followId`), or edit one of your follows
119
+ * (`followId` set).
120
+ *
121
+ * An edit REPLACES the follow's settings: every optional setting left out
122
+ * reverts to its default, so an edit that omits `slPct` removes the
123
+ * stop-loss. The leader comes from the stored follow on an edit, so
124
+ * `leader` may be omitted there.
125
+ */
126
+ async upsertFollow(params) {
127
+ const validated = validate(UpsertFollowSchema, params);
128
+ const body = {};
129
+ if (validated.followId !== undefined)
130
+ body.followId = validated.followId;
131
+ // The gateway requires the `leader` key on every request; an edit takes the
132
+ // leader from the stored follow and accepts it empty.
133
+ body.leader = validated.leader ?? "";
134
+ body.allocation = validated.allocation;
135
+ if (validated.marginModePolicy !== undefined)
136
+ body.marginModePolicy = validated.marginModePolicy;
137
+ if (validated.sizingMode !== undefined)
138
+ body.sizingMode = validated.sizingMode;
139
+ if (validated.sizingParam !== undefined)
140
+ body.sizingParam = validated.sizingParam;
141
+ if (validated.leverageMode !== undefined)
142
+ body.leverageMode = validated.leverageMode;
143
+ if (validated.fixedLeverage !== undefined)
144
+ body.fixedLeverage = validated.fixedLeverage;
145
+ if (validated.slPct !== undefined)
146
+ body.slPct = validated.slPct;
147
+ if (validated.tradingPairIds !== undefined)
148
+ body.markets = { tradingPairIds: validated.tradingPairIds };
149
+ if (validated.copyExisting !== undefined)
150
+ body.copyExisting = validated.copyExisting;
151
+ return await this.makeAuthenticatedRequest(`${BASE}/follows`, {
152
+ method: "POST",
153
+ body: JSON.stringify(body),
154
+ });
155
+ }
156
+ /**
157
+ * Preview what a follow with these terms would open if it set
158
+ * `copyExisting`, as totals only: estimated margin and fees, and how many
159
+ * of the leader's positions would and would not be copied. Read-only;
160
+ * nothing is placed or reserved.
161
+ */
162
+ async previewFollow(params) {
163
+ const validated = validate(PreviewFollowSchema, params);
164
+ const body = { leader: validated.leader, allocation: validated.allocation };
165
+ if (validated.marginModePolicy !== undefined)
166
+ body.marginModePolicy = validated.marginModePolicy;
167
+ if (validated.sizingMode !== undefined)
168
+ body.sizingMode = validated.sizingMode;
169
+ if (validated.sizingParam !== undefined)
170
+ body.sizingParam = validated.sizingParam;
171
+ if (validated.leverageMode !== undefined)
172
+ body.leverageMode = validated.leverageMode;
173
+ if (validated.fixedLeverage !== undefined)
174
+ body.fixedLeverage = validated.fixedLeverage;
175
+ if (validated.tradingPairIds !== undefined)
176
+ body.markets = { tradingPairIds: validated.tradingPairIds };
177
+ return await this.makeAuthenticatedRequest(`${BASE}/follows/preview`, {
178
+ method: "POST",
179
+ body: JSON.stringify(body),
180
+ });
181
+ }
182
+ /** Stop one of your follows: the leader's fills stop being mirrored. */
183
+ async stopFollow(followId) {
184
+ return await this.makeAuthenticatedRequest(`${followPath(followId)}/stop`, { method: "POST" });
185
+ }
186
+ /** List your follows, newest first. Cursor paged, up to 1000 per page. */
187
+ async listMyFollows(params = {}) {
188
+ const { pageSize, pageToken } = validate(ListMyFollowsSchema, params);
189
+ return await this.makeAuthenticatedRequest(`${BASE}/follows${queryString({ pageSize, pageToken })}`, { method: "GET" });
190
+ }
191
+ /**
192
+ * Get one of your follows with its most recent skipped mirrors (up to 100)
193
+ * and `copyAccountEquity`, the copy account's marked equity (absent when it
194
+ * could not be read).
195
+ */
196
+ async getFollow(followId) {
197
+ return await this.makeAuthenticatedRequest(followPath(followId), { method: "GET" });
198
+ }
199
+ /**
200
+ * List the open positions of the leader one of your follows copies. The
201
+ * leader comes from the follow, so this works after the leader releases
202
+ * their handle. 404 for a follow that is not yours; 403 for a stopped one.
203
+ */
204
+ async listFollowLeaderPositions(followId) {
205
+ return await this.makeAuthenticatedRequest(`${followPath(followId)}/leader-positions`, { method: "GET" });
206
+ }
207
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copy Trading API Module
3
+ *
4
+ * Exports the copy trading API implementation.
5
+ */
6
+ export { CopyTradingAPIImpl } from "./api";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Copy Trading API Module
3
+ *
4
+ * Exports the copy trading API implementation.
5
+ */
6
+ export { CopyTradingAPIImpl } from "./api";
@@ -35,8 +35,8 @@ export declare class FaucetAPIImpl extends BaseAPI implements FaucetAPI {
35
35
  *
36
36
  * Read-only: remaining requests in the rolling 24h window, when the next
37
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.
38
+ * the window), and the 5 most recent requests. Scoped to the wallet on this
39
+ * app (each app has its own quota), matching mint enforcement.
40
40
  *
41
41
  * @returns Promise resolving to the quota and recent-request history
42
42
  */
@@ -40,8 +40,8 @@ export class FaucetAPIImpl extends BaseAPI {
40
40
  *
41
41
  * Read-only: remaining requests in the rolling 24h window, when the next
42
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.
43
+ * the window), and the 5 most recent requests. Scoped to the wallet on this
44
+ * app (each app has its own quota), matching mint enforcement.
45
45
  *
46
46
  * @returns Promise resolving to the quota and recent-request history
47
47
  */
@@ -6,6 +6,7 @@
6
6
  export * from "./applications/index";
7
7
  export * from "./base";
8
8
  export * from "./buildercodes";
9
+ export * from "./copy-trading";
9
10
  export * from "./delegated-agents";
10
11
  export * from "./faucet";
11
12
  export * from "./fees";
package/dist/api/index.js CHANGED
@@ -6,6 +6,7 @@
6
6
  export * from "./applications/index";
7
7
  export * from "./base";
8
8
  export * from "./buildercodes";
9
+ export * from "./copy-trading";
9
10
  export * from "./delegated-agents";
10
11
  export * from "./faucet";
11
12
  export * from "./fees";
@@ -2,8 +2,8 @@ export declare const perpRoutes: {
2
2
  readonly orders: {
3
3
  readonly create: () => string;
4
4
  readonly list: (params?: {
5
- page?: number;
6
5
  pageSize?: number;
6
+ pageToken?: string;
7
7
  status?: string;
8
8
  tradingPair?: string;
9
9
  tradingMode?: string;
@@ -21,7 +21,6 @@ export declare const perpRoutes: {
21
21
  marginAccountId?: string;
22
22
  tradingPairId?: string;
23
23
  state?: string;
24
- page?: number;
25
24
  pageSize?: number;
26
25
  pageToken?: string;
27
26
  }) => string;
@@ -31,7 +30,6 @@ export declare const perpRoutes: {
31
30
  readonly listTwap: (params?: {
32
31
  state?: string;
33
32
  tradingPairId?: string;
34
- page?: number;
35
33
  pageSize?: number;
36
34
  pageToken?: string;
37
35
  }) => string;
@@ -115,8 +113,8 @@ export declare const perpRoutes: {
115
113
  readonly user: (params?: {
116
114
  marginAccountId?: string;
117
115
  tradingPairId?: string;
118
- page?: number;
119
116
  pageSize?: number;
117
+ pageToken?: string;
120
118
  }) => string;
121
119
  };
122
120
  readonly applications: {
@@ -50,8 +50,13 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
50
50
  * Fetches the user's transaction history from the /api/v1/accounts/movements endpoint.
51
51
  * Requires a valid access token to be set.
52
52
  *
53
+ * The response `total` and `totalPages` are bounded by the listing's pagination reach:
54
+ * exact up to `pageSize` x 10,000 rows (10,000 pages) and saturating there, so a user
55
+ * with more matching movements than the listing can page through reads the cap, not
56
+ * their lifetime total. A `page` above 10,000 is rejected.
57
+ *
53
58
  * @param params - Optional query parameters for pagination and filtering
54
- * @param params.page - Page number (starts from 1, default: 1)
59
+ * @param params.page - Page number (starts from 1, default: 1, at most 10,000)
55
60
  * @param params.limit - Number of items per page (default: 20, max: 100)
56
61
  * @param params.entryType - Filter by entry type (CREDIT, DEBIT, LOCK, UNLOCK, FEE)
57
62
  * @param params.transactionType - Filter by transaction type (DEPOSIT, WITHDRAWAL, TRADE, FEE, FUNDING, LIQUIDATION, INTEREST, REWARD)
@@ -171,23 +176,16 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
171
176
  *
172
177
  * When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
173
178
  * 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).
179
+ * `nextPageToken` resumes it. Responses carry no item count: the `trades`
180
+ * table is too large to count behind a filter.
176
181
  *
177
182
  * @param params - Optional query parameters for pagination and filtering
178
- * @param params.page - Deprecated: page number for legacy offset pagination (starts
179
- * from 1, at most 10,000). Passing it selects legacy page-number mode, which only sees
180
- * recent (hot) history and populates `total`/`totalPages` — bounded at the pagination
181
- * reach (`total` saturates at `pageSize` × 10,000 rows, `totalPages` at 10,000 pages).
182
- * Use cursor pagination (`pageToken`) instead.
183
- * @param params.pageSize - Number of items per page (default: 20; max 100
184
- * in page-number mode, up to 1000 when pageToken is present)
183
+ * @param params.pageSize - Number of items per page (default: 20; up to 1000)
185
184
  * @param params.tradingPairId - Filter by trading pair ID (UUID)
186
185
  * @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
187
- * the newest row; pass a previous response's `nextPageToken` to resume. Any
188
- * cursor-walk value (`""` or non-empty) ignores `page` and covers full trade history
189
- * (hot and archived rows merged), unlike page-number mode. Omitting both `page` and
190
- * `pageToken` starts a cursor walk (`pageToken=""`).
186
+ * the newest row; pass a previous response's `nextPageToken` to resume. Omitting it
187
+ * starts the same walk. Every walk covers full trade history, hot and archived rows
188
+ * merged.
191
189
  * @returns Promise resolving to paginated trades response
192
190
  * @throws {ValidationError} When the provided params fail client-side validation
193
191
  * @throws {APIError} When the request fails or user is not authenticated
@@ -214,10 +212,10 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
214
212
  * Starts a cursor walk (`pageToken: ""`) and follows each response's
215
213
  * `nextPageToken` until the server proves exhaustion (a page with zero rows
216
214
  * and an empty token). Covers full trade history — hot and archived rows
217
- * merged — unlike the deprecated page-number mode.
215
+ * merged.
218
216
  *
219
- * @param params - Optional filters and page size; `page`/`pageToken` are
220
- * managed by the iterator and cannot be passed.
217
+ * @param params - Optional filters and page size; `pageToken` is managed by
218
+ * the iterator and cannot be passed.
221
219
  * @returns Async generator yielding trades newest-first
222
220
  * @throws {APIError} When any underlying request fails
223
221
  *
@@ -235,6 +233,11 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
235
233
  * Fetches funding payments from the /api/v1/accounts/funding-payments endpoint.
236
234
  * Requires a valid access token to be set.
237
235
  *
236
+ * The response `total` and `totalPages` are bounded by the listing's pagination reach:
237
+ * exact up to `pageSize` x 10,000 rows (10,000 pages) and saturating there, so a user
238
+ * with more matching payments than the listing can page through reads the cap, not
239
+ * their lifetime total. A `page` above 10,000 is rejected.
240
+ *
238
241
  * @param params - Optional query parameters for pagination and filtering
239
242
  * @returns Promise resolving to paginated funding payments response
240
243
  * @throws {APIError} When the request fails or user is not authenticated
@@ -53,8 +53,13 @@ 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
+ * The response `total` and `totalPages` are bounded by the listing's pagination reach:
57
+ * exact up to `pageSize` x 10,000 rows (10,000 pages) and saturating there, so a user
58
+ * with more matching movements than the listing can page through reads the cap, not
59
+ * their lifetime total. A `page` above 10,000 is rejected.
60
+ *
56
61
  * @param params - Optional query parameters for pagination and filtering
57
- * @param params.page - Page number (starts from 1, default: 1)
62
+ * @param params.page - Page number (starts from 1, default: 1, at most 10,000)
58
63
  * @param params.limit - Number of items per page (default: 20, max: 100)
59
64
  * @param params.entryType - Filter by entry type (CREDIT, DEBIT, LOCK, UNLOCK, FEE)
60
65
  * @param params.transactionType - Filter by transaction type (DEPOSIT, WITHDRAWAL, TRADE, FEE, FUNDING, LIQUIDATION, INTEREST, REWARD)
@@ -222,23 +227,16 @@ export class ProfileAPIImpl extends BaseAPI {
222
227
  *
223
228
  * When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
224
229
  * 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).
230
+ * `nextPageToken` resumes it. Responses carry no item count: the `trades`
231
+ * table is too large to count behind a filter.
227
232
  *
228
233
  * @param params - Optional query parameters for pagination and filtering
229
- * @param params.page - Deprecated: page number for legacy offset pagination (starts
230
- * from 1, at most 10,000). Passing it selects legacy page-number mode, which only sees
231
- * recent (hot) history and populates `total`/`totalPages` — bounded at the pagination
232
- * reach (`total` saturates at `pageSize` × 10,000 rows, `totalPages` at 10,000 pages).
233
- * Use cursor pagination (`pageToken`) instead.
234
- * @param params.pageSize - Number of items per page (default: 20; max 100
235
- * in page-number mode, up to 1000 when pageToken is present)
234
+ * @param params.pageSize - Number of items per page (default: 20; up to 1000)
236
235
  * @param params.tradingPairId - Filter by trading pair ID (UUID)
237
236
  * @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
238
- * the newest row; pass a previous response's `nextPageToken` to resume. Any
239
- * cursor-walk value (`""` or non-empty) ignores `page` and covers full trade history
240
- * (hot and archived rows merged), unlike page-number mode. Omitting both `page` and
241
- * `pageToken` starts a cursor walk (`pageToken=""`).
237
+ * the newest row; pass a previous response's `nextPageToken` to resume. Omitting it
238
+ * starts the same walk. Every walk covers full trade history, hot and archived rows
239
+ * merged.
242
240
  * @returns Promise resolving to paginated trades response
243
241
  * @throws {ValidationError} When the provided params fail client-side validation
244
242
  * @throws {APIError} When the request fails or user is not authenticated
@@ -261,27 +259,17 @@ export class ProfileAPIImpl extends BaseAPI {
261
259
  if (params) {
262
260
  validate(GetUserTradesSchema, params);
263
261
  }
264
- // Legacy offset mode only when the caller explicitly asked for a page and
265
- // no cursor was given; everything else drives cursor (pageToken) mode.
266
- const legacyOffsetMode = params?.page !== undefined && params?.pageToken === undefined;
267
262
  const searchParams = new URLSearchParams();
268
- if (legacyOffsetMode && params?.page !== undefined) {
269
- searchParams.append("page", params.page.toString());
270
- }
271
263
  if (params?.pageSize !== undefined) {
272
264
  searchParams.append("pageSize", params.pageSize.toString());
273
265
  }
274
266
  if (params?.tradingPairId !== undefined) {
275
267
  searchParams.append("tradingPairId", params.tradingPairId);
276
268
  }
277
- // Cursor mode selects itself by the *presence* of `pageToken` on the wire,
278
- // even when it is the empty string (the documented way to start a walk),
279
- // so it must be sent explicitly rather than dropped like a falsy value.
280
- // When the caller gave neither `page` nor `pageToken`, default to starting
281
- // a cursor walk (`pageToken=""`).
282
- if (!legacyOffsetMode) {
283
- searchParams.append("pageToken", params?.pageToken ?? "");
284
- }
269
+ // One pagination mode. `pageToken` is sent even as the empty string — the
270
+ // server selects the walk by the token's PRESENCE, not its value — so it
271
+ // must not be dropped the way a falsy value usually would be.
272
+ searchParams.append("pageToken", params?.pageToken ?? "");
285
273
  const queryString = searchParams.toString();
286
274
  const url = queryString ? `/api/v1/accounts/trades?${queryString}` : "/api/v1/accounts/trades";
287
275
  return await this.makeAuthenticatedRequest(url);
@@ -293,10 +281,10 @@ export class ProfileAPIImpl extends BaseAPI {
293
281
  * Starts a cursor walk (`pageToken: ""`) and follows each response's
294
282
  * `nextPageToken` until the server proves exhaustion (a page with zero rows
295
283
  * and an empty token). Covers full trade history — hot and archived rows
296
- * merged — unlike the deprecated page-number mode.
284
+ * merged.
297
285
  *
298
- * @param params - Optional filters and page size; `page`/`pageToken` are
299
- * managed by the iterator and cannot be passed.
286
+ * @param params - Optional filters and page size; `pageToken` is managed by
287
+ * the iterator and cannot be passed.
300
288
  * @returns Async generator yielding trades newest-first
301
289
  * @throws {APIError} When any underlying request fails
302
290
  *
@@ -327,6 +315,11 @@ export class ProfileAPIImpl extends BaseAPI {
327
315
  * Fetches funding payments from the /api/v1/accounts/funding-payments endpoint.
328
316
  * Requires a valid access token to be set.
329
317
  *
318
+ * The response `total` and `totalPages` are bounded by the listing's pagination reach:
319
+ * exact up to `pageSize` x 10,000 rows (10,000 pages) and saturating there, so a user
320
+ * with more matching payments than the listing can page through reads the cap, not
321
+ * their lifetime total. A `page` above 10,000 is rejected.
322
+ *
330
323
  * @param params - Optional query parameters for pagination and filtering
331
324
  * @returns Promise resolving to paginated funding payments response
332
325
  * @throws {APIError} When the request fails or user is not authenticated
@@ -197,14 +197,10 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
197
197
  * via each response's `nextPageToken` until that token comes back empty. A
198
198
  * cursor page size may go up to 1000.
199
199
  *
200
- * Passing the deprecated `page` (without `pageToken`) selects legacy
201
- * page-number mode instead, which keeps the 100 page-size cap and populates a
202
- * bounded `total` (exact up to `pageSize` x 10,000 rows, saturating there);
203
- * `page` is capped at 10,000. Cursor mode returns `total`/`page` as 0 and runs
204
- * no COUNT.
205
- *
206
- * @param params - Optional filters and pagination; `pageToken` drives cursor
207
- * mode, `page` drives the deprecated legacy mode
200
+ * `total` is the count of matching rows, bounded at a server ceiling of
201
+ * 10,000 and saturating there.
202
+ *
203
+ * @param params - Optional filters and cursor pagination via `pageToken`
208
204
  * @returns Promise resolving to ListConditionalOrdersResponse with orders and pagination info
209
205
  * @throws {APIError} When API communication fails
210
206
  */
@@ -334,17 +330,13 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
334
330
  * @param params.status - Filter by order status (e.g., "SUBMITTED", "FILLED"), or by
335
331
  * any of several statuses at once by passing an array (e.g., ["SUBMITTED", "PARTIALLY_FILLED"]) (optional)
336
332
  * @param params.tradingPairId - Filter by trading pair UUID (optional)
337
- * @param params.page - Deprecated: page number for legacy offset pagination (must be > 0).
338
- * Passing it selects legacy page-number mode, which only sees recent (hot) history. Both
339
- * modes populate `total`/`totalPages` (bounded — see `totalCapped`); use cursor
340
- * pagination (`pageToken`) instead for full history.
341
- * @param params.pageSize - Number of orders per page (defaults to 10, must be > 0; max 100
342
- * in page-number mode, up to 1000 when pageToken is present)
333
+ * @param params.pageSize - Number of orders per page (defaults to 10, must be > 0;
334
+ * up to 1000)
343
335
  * @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
344
336
  * the newest (or oldest, for `orderBy: "ASC"`) row; pass a previous response's
345
- * `nextPageToken` to resume. Any cursor-walk value (`""` or non-empty) ignores `page`
346
- * and covers full order history (hot and archived rows merged), unlike page-number
347
- * mode. Omitting both `page` and `pageToken` starts a cursor walk (`pageToken=""`).
337
+ * `nextPageToken` to resume. Omitting it starts the same walk. Every walk covers
338
+ * full order history, hot and archived rows merged. `total`/`totalPages` are
339
+ * bounded — see `totalCapped`.
348
340
  * @returns Promise resolving to GetPaginatedOrdersResponse with orders and pagination info
349
341
  * @throws {APIError} When API communication fails
350
342
  *
@@ -378,10 +370,10 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
378
370
  * Starts a cursor walk (`pageToken: ""`) and follows each response's
379
371
  * `nextPageToken` until the server proves exhaustion (a page with zero rows
380
372
  * and an empty token). Covers full order history — hot and archived rows
381
- * merged — unlike the deprecated page-number mode.
373
+ * merged.
382
374
  *
383
- * @param params - Optional filters and page size; `page`/`pageToken` are
384
- * managed by the iterator and cannot be passed.
375
+ * @param params - Optional filters and page size; `pageToken` is managed by
376
+ * the iterator and cannot be passed.
385
377
  * @returns Async generator yielding orders newest-first (or oldest-first for
386
378
  * `orderBy: "ASC"`)
387
379
  * @throws {APIError} When any underlying request fails
@@ -535,14 +527,10 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
535
527
  * via each response's `nextPageToken` until that token comes back empty. A
536
528
  * cursor page size may go up to 1000.
537
529
  *
538
- * Passing the deprecated `page` (without `pageToken`) selects legacy
539
- * page-number mode instead, which keeps the 100 page-size cap and populates a
540
- * bounded `total` (exact up to `pageSize` x 10,000 rows, saturating there);
541
- * `page` is capped at 10,000. Cursor mode returns `total`/`page` as 0 and runs
542
- * no COUNT.
530
+ * `total` is the count of matching rows, bounded at a server ceiling of
531
+ * 10,000 and saturating there.
543
532
  *
544
- * @param params - Optional filters and pagination; `pageToken` drives cursor
545
- * mode, `page` drives the deprecated legacy mode
533
+ * @param params - Optional filters and cursor pagination via `pageToken`
546
534
  * @returns Promise resolving to ListTwapOrdersResponse with orders and pagination info
547
535
  * @throws {APIError} When API communication fails
548
536
  *
@@ -282,14 +282,10 @@ export class TradingAPIImpl extends BaseAPI {
282
282
  * via each response's `nextPageToken` until that token comes back empty. A
283
283
  * cursor page size may go up to 1000.
284
284
  *
285
- * Passing the deprecated `page` (without `pageToken`) selects legacy
286
- * page-number mode instead, which keeps the 100 page-size cap and populates a
287
- * bounded `total` (exact up to `pageSize` x 10,000 rows, saturating there);
288
- * `page` is capped at 10,000. Cursor mode returns `total`/`page` as 0 and runs
289
- * no COUNT.
290
- *
291
- * @param params - Optional filters and pagination; `pageToken` drives cursor
292
- * mode, `page` drives the deprecated legacy mode
285
+ * `total` is the count of matching rows, bounded at a server ceiling of
286
+ * 10,000 and saturating there.
287
+ *
288
+ * @param params - Optional filters and cursor pagination via `pageToken`
293
289
  * @returns Promise resolving to ListConditionalOrdersResponse with orders and pagination info
294
290
  * @throws {APIError} When API communication fails
295
291
  */
@@ -297,24 +293,16 @@ export class TradingAPIImpl extends BaseAPI {
297
293
  if (params) {
298
294
  validate(ListConditionalOrdersSchema, params);
299
295
  }
300
- const { page, pageSize = 20, marginAccountId, tradingPairId, state, pageToken } = params || {};
301
- // Legacy offset mode only when the caller explicitly asked for a page and no
302
- // cursor was given; everything else drives cursor (pageToken) mode.
303
- const legacyOffsetMode = page !== undefined && pageToken === undefined;
304
- // Cursor requests may use pages up to 1000 (keyset reads); page-number
305
- // requests keep the server's 100 cap.
306
- const boundedPageSize = Math.min(Math.max(pageSize, 1), legacyOffsetMode ? 100 : 1000);
296
+ const { pageSize = 20, marginAccountId, tradingPairId, state, pageToken } = params || {};
297
+ const boundedPageSize = Math.min(Math.max(pageSize, 1), 1000);
307
298
  return await this.makeAuthenticatedRequest(perpRoutes.orders.listConditional({
308
299
  marginAccountId,
309
300
  tradingPairId,
310
301
  state,
311
- // Legacy mode sends `page` and omits `pageToken`; cursor mode omits
312
- // `page` and sends `pageToken` — present even as the empty string, the
313
- // documented way to start a walk (the server selects cursor mode by the
314
- // token's presence, not its value).
315
- page: legacyOffsetMode ? page : undefined,
316
302
  pageSize: boundedPageSize,
317
- pageToken: legacyOffsetMode ? undefined : (pageToken ?? ""),
303
+ // Sent even as the empty string: the server starts a walk on the
304
+ // token's PRESENCE, not its value.
305
+ pageToken: pageToken ?? "",
318
306
  }), { method: "GET" });
319
307
  }
320
308
  /**
@@ -522,17 +510,13 @@ export class TradingAPIImpl extends BaseAPI {
522
510
  * @param params.status - Filter by order status (e.g., "SUBMITTED", "FILLED"), or by
523
511
  * any of several statuses at once by passing an array (e.g., ["SUBMITTED", "PARTIALLY_FILLED"]) (optional)
524
512
  * @param params.tradingPairId - Filter by trading pair UUID (optional)
525
- * @param params.page - Deprecated: page number for legacy offset pagination (must be > 0).
526
- * Passing it selects legacy page-number mode, which only sees recent (hot) history. Both
527
- * modes populate `total`/`totalPages` (bounded — see `totalCapped`); use cursor
528
- * pagination (`pageToken`) instead for full history.
529
- * @param params.pageSize - Number of orders per page (defaults to 10, must be > 0; max 100
530
- * in page-number mode, up to 1000 when pageToken is present)
513
+ * @param params.pageSize - Number of orders per page (defaults to 10, must be > 0;
514
+ * up to 1000)
531
515
  * @param params.pageToken - Pagination cursor: pass `""` to start a cursor walk from
532
516
  * the newest (or oldest, for `orderBy: "ASC"`) row; pass a previous response's
533
- * `nextPageToken` to resume. Any cursor-walk value (`""` or non-empty) ignores `page`
534
- * and covers full order history (hot and archived rows merged), unlike page-number
535
- * mode. Omitting both `page` and `pageToken` starts a cursor walk (`pageToken=""`).
517
+ * `nextPageToken` to resume. Omitting it starts the same walk. Every walk covers
518
+ * full order history, hot and archived rows merged. `total`/`totalPages` are
519
+ * bounded — see `totalCapped`.
536
520
  * @returns Promise resolving to GetPaginatedOrdersResponse with orders and pagination info
537
521
  * @throws {APIError} When API communication fails
538
522
  *
@@ -564,19 +548,10 @@ export class TradingAPIImpl extends BaseAPI {
564
548
  validate(GetPaginatedOrdersSchema, params);
565
549
  }
566
550
  // Set pagination defaults with destructuring and validation
567
- const { page, pageSize: requestedPageSize = 10, status, tradingPairId, tradingMode, marginAccountId, pageToken } = params || {};
551
+ const { pageSize: requestedPageSize = 10, status, tradingPairId, tradingMode, marginAccountId, pageToken } = params || {};
568
552
  const validPageSize = requestedPageSize > 0 ? requestedPageSize : 10;
569
- // Legacy offset mode only when the caller explicitly asked for a page and
570
- // no cursor was given; everything else drives cursor (pageToken) mode.
571
- const legacyOffsetMode = page !== undefined && pageToken === undefined;
572
- // Cursor requests may use pages up to 1000 (keyset reads); page-number
573
- // requests keep the server's 100 cap.
574
- const pageSize = Math.min(validPageSize, legacyOffsetMode ? 100 : 1000);
553
+ const pageSize = Math.min(validPageSize, 1000);
575
554
  const searchParams = new URLSearchParams();
576
- if (legacyOffsetMode) {
577
- const validPage = page > 0 ? page : 1;
578
- searchParams.append("page", validPage.toString());
579
- }
580
555
  searchParams.append("pageSize", pageSize.toString());
581
556
  // An array filter is sent as one comma-separated `status` value, which the
582
557
  // API reads as "any of these", so several statuses cost a single request.
@@ -593,14 +568,10 @@ export class TradingAPIImpl extends BaseAPI {
593
568
  if (marginAccountId) {
594
569
  searchParams.append("marginAccountId", marginAccountId);
595
570
  }
596
- // Cursor mode selects itself by the *presence* of `pageToken` on the wire,
597
- // even when it is the empty string (the documented way to start a walk),
598
- // so it must be sent explicitly rather than dropped like a falsy value.
599
- // When the caller gave neither `page` nor `pageToken`, default to starting
600
- // a cursor walk (`pageToken=""`).
601
- if (!legacyOffsetMode) {
602
- searchParams.append("pageToken", pageToken ?? "");
603
- }
571
+ // One pagination mode. `pageToken` is sent even as the empty string — the
572
+ // server selects the walk by the token's PRESENCE, not its value — so it
573
+ // must not be dropped the way a falsy value usually would be.
574
+ searchParams.append("pageToken", pageToken ?? "");
604
575
  const endpoint = `/api/v1/orders?${searchParams.toString()}`;
605
576
  return await this.makeAuthenticatedRequest(endpoint, {
606
577
  method: "GET",
@@ -613,10 +584,10 @@ export class TradingAPIImpl extends BaseAPI {
613
584
  * Starts a cursor walk (`pageToken: ""`) and follows each response's
614
585
  * `nextPageToken` until the server proves exhaustion (a page with zero rows
615
586
  * and an empty token). Covers full order history — hot and archived rows
616
- * merged — unlike the deprecated page-number mode.
587
+ * merged.
617
588
  *
618
- * @param params - Optional filters and page size; `page`/`pageToken` are
619
- * managed by the iterator and cannot be passed.
589
+ * @param params - Optional filters and page size; `pageToken` is managed by
590
+ * the iterator and cannot be passed.
620
591
  * @returns Async generator yielding orders newest-first (or oldest-first for
621
592
  * `orderBy: "ASC"`)
622
593
  * @throws {APIError} When any underlying request fails
@@ -845,14 +816,10 @@ export class TradingAPIImpl extends BaseAPI {
845
816
  * via each response's `nextPageToken` until that token comes back empty. A
846
817
  * cursor page size may go up to 1000.
847
818
  *
848
- * Passing the deprecated `page` (without `pageToken`) selects legacy
849
- * page-number mode instead, which keeps the 100 page-size cap and populates a
850
- * bounded `total` (exact up to `pageSize` x 10,000 rows, saturating there);
851
- * `page` is capped at 10,000. Cursor mode returns `total`/`page` as 0 and runs
852
- * no COUNT.
819
+ * `total` is the count of matching rows, bounded at a server ceiling of
820
+ * 10,000 and saturating there.
853
821
  *
854
- * @param params - Optional filters and pagination; `pageToken` drives cursor
855
- * mode, `page` drives the deprecated legacy mode
822
+ * @param params - Optional filters and cursor pagination via `pageToken`
856
823
  * @returns Promise resolving to ListTwapOrdersResponse with orders and pagination info
857
824
  * @throws {APIError} When API communication fails
858
825
  *
@@ -866,23 +833,15 @@ export class TradingAPIImpl extends BaseAPI {
866
833
  if (params) {
867
834
  validate(ListTwapOrdersSchema, params);
868
835
  }
869
- const { page, pageSize = 20, state, tradingPairId, pageToken } = params || {};
870
- // Legacy offset mode only when the caller explicitly asked for a page and no
871
- // cursor was given; everything else drives cursor (pageToken) mode.
872
- const legacyOffsetMode = page !== undefined && pageToken === undefined;
873
- // Cursor requests may use pages up to 1000 (keyset reads); page-number
874
- // requests keep the server's 100 cap.
875
- const boundedPageSize = Math.min(Math.max(pageSize, 1), legacyOffsetMode ? 100 : 1000);
836
+ const { pageSize = 20, state, tradingPairId, pageToken } = params || {};
837
+ const boundedPageSize = Math.min(Math.max(pageSize, 1), 1000);
876
838
  return await this.makeAuthenticatedRequest(perpRoutes.orders.listTwap({
877
839
  state,
878
840
  tradingPairId,
879
- // Legacy mode sends `page` and omits `pageToken`; cursor mode omits
880
- // `page` and sends `pageToken` — present even as the empty string, the
881
- // documented way to start a walk (the server selects cursor mode by the
882
- // token's presence, not its value).
883
- page: legacyOffsetMode ? page : undefined,
884
841
  pageSize: boundedPageSize,
885
- pageToken: legacyOffsetMode ? undefined : (pageToken ?? ""),
842
+ // Sent even as the empty string: the server starts a walk on the
843
+ // token's PRESENCE, not its value.
844
+ pageToken: pageToken ?? "",
886
845
  }), { method: "GET" });
887
846
  }
888
847
  }
@@ -69,6 +69,12 @@ function readBoolean(source, field) {
69
69
  }
70
70
  return value;
71
71
  }
72
+ function readOptionalBoolean(source, field) {
73
+ const value = source[field];
74
+ if (value === undefined || value === null)
75
+ return undefined;
76
+ return readBoolean(source, field);
77
+ }
72
78
  /**
73
79
  * Read a wire sequence counter: a non-negative safe integer. The server
74
80
  * serializes Rust `u64` sequence fields as plain JSON numbers, so a negative,
@@ -180,9 +186,8 @@ function parseConditionalOrderEventData(raw) {
180
186
  // update.
181
187
  createdAt: readOptionalString(data, "createdAt"),
182
188
  updatedAt: readString(data, "updatedAt"),
183
- // Populated on the subscribe-time snapshot frame and absent on live
184
- // updates (0XM-2440). Reading it optionally is what keeps the snapshot's
185
- // value from being silently dropped before the subscriber.
189
+ // Populated on live updates and on the subscribe-time snapshot frame
190
+ // (0XM-2440); absent only for a row written before the counter existed.
186
191
  version: readOptionalNumber(data, "version"),
187
192
  };
188
193
  }
@@ -529,6 +534,67 @@ function parseTwapOrderEventData(raw) {
529
534
  }
530
535
  return data;
531
536
  }
537
+ /**
538
+ * camelCase a `copy` channel payload and drop explicit `null`s, so the result
539
+ * matches the optional (`?:`) fields of the published copy event types. Only
540
+ * the named identity fields are validated.
541
+ */
542
+ function copyPayload(raw, what, required) {
543
+ if (!isRecord(raw)) {
544
+ throw new Error(`Invalid copy ${what}: data must be an object`);
545
+ }
546
+ const data = keysToCamelCase(raw);
547
+ for (const key of Object.keys(data)) {
548
+ if (data[key] === null) {
549
+ delete data[key];
550
+ }
551
+ }
552
+ for (const field of required) {
553
+ const value = data[field];
554
+ if (typeof value !== "string" || value.length === 0) {
555
+ throw new Error(`Invalid copy ${what}: ${field} must be a non-empty string`);
556
+ }
557
+ }
558
+ return data;
559
+ }
560
+ /**
561
+ * Parse a copy relationship row: the `copy_relationship_update` payload, and
562
+ * each subscribe-time snapshot row. The identity fields and `status` are
563
+ * required; every value field (status, policies, `change`, `stopReason`) is
564
+ * passed through unchecked, so a label this SDK version predates is delivered
565
+ * rather than dropping the update (0XM-2627). The declared unions document the
566
+ * values the server sends today.
567
+ */
568
+ function parseCopyRelationshipEventData(raw) {
569
+ return copyPayload(raw, "relationship update", ["relationshipId", "status"]);
570
+ }
571
+ /**
572
+ * Parse a `copy_skip` payload. The skip `reason` is passed through unchecked
573
+ * for the same reason as the relationship labels.
574
+ */
575
+ function parseCopySkipEventData(raw) {
576
+ return copyPayload(raw, "skip", ["relationshipId", "reason"]);
577
+ }
578
+ /**
579
+ * Parse a `copy` channel frame. `event_type` is the channel-class
580
+ * discriminator that picks the payload shape, so it is matched strictly: a
581
+ * frame of any other type (such as the reserved `copy_payout`) is rejected
582
+ * rather than parsed as the wrong shape.
583
+ */
584
+ function parseCopyEvent(rawData) {
585
+ if (!isRecord(rawData)) {
586
+ throw new Error("Invalid copy event: payload must be an object");
587
+ }
588
+ const userId = rawData.user_id;
589
+ if (typeof userId !== "string" || userId.length === 0) {
590
+ throw new Error("Invalid copy event: user_id must be a non-empty string");
591
+ }
592
+ const eventType = readEnum(rawData, "event_type", ["copy_relationship_update", "copy_skip"]);
593
+ if (eventType === "copy_relationship_update") {
594
+ return { eventType, userId, data: parseCopyRelationshipEventData(rawData.data) };
595
+ }
596
+ return { eventType, userId, data: parseCopySkipEventData(rawData.data) };
597
+ }
532
598
  /**
533
599
  * Parse a user balance payload. Shared by the live event and the subscribe-time
534
600
  * snapshot (see {@link parseConditionalOrderEventData}).
@@ -597,6 +663,11 @@ function parseOrderSnapshotItem(raw) {
597
663
  // `clientOrderId: undefined`, which a structural comparison would see.
598
664
  const clientOrderId = readOptionalString(item, "clientOrderId");
599
665
  const selfTradePreventionMode = readOptionalString(item, "selfTradePreventionMode");
666
+ const parentOrderId = readOptionalString(item, "parentOrderId");
667
+ const postOnly = readOptionalBoolean(item, "postOnly");
668
+ const expirationDate = readOptionalString(item, "expirationDate");
669
+ const triggerPrice = readOptionalString(item, "triggerPrice");
670
+ const positionSide = readOptionalString(item, "positionSide");
600
671
  const version = readOptionalNumber(item, "version");
601
672
  return {
602
673
  orderId: readString(item, "orderId"),
@@ -631,6 +702,15 @@ function parseOrderSnapshotItem(raw) {
631
702
  // Spread in conditionally like `clientOrderId`: absent means the order
632
703
  // requested no override, and must stay absent (0XM-2733).
633
704
  ...(selfTradePreventionMode === undefined ? {} : { selfTradePreventionMode }),
705
+ // Spread in conditionally too: absent for an order that is not a TWAP
706
+ // child, and absent must stay absent.
707
+ ...(parentOrderId === undefined ? {} : { parentOrderId }),
708
+ // The four REST order-shape fields, each omitted by the server when REST
709
+ // omits it, so absent must stay absent.
710
+ ...(postOnly === undefined ? {} : { postOnly }),
711
+ ...(expirationDate === undefined ? {} : { expirationDate }),
712
+ ...(triggerPrice === undefined ? {} : { triggerPrice }),
713
+ ...(positionSide === undefined ? {} : { positionSide }),
634
714
  // Spread in conditionally for the same reason as `clientOrderId`: the
635
715
  // server omits the key when unknown, and absent must stay absent rather
636
716
  // than becoming `version: undefined` — a resync compares this value, and
@@ -1417,6 +1497,25 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1417
1497
  }
1418
1498
  }, onSnapshot && snapshotFrameHandler("TWAP orders", parseTwapOrderEventData, onSnapshot));
1419
1499
  };
1500
+ /**
1501
+ * Subscribe to the authenticated follower's copy trading stream: every
1502
+ * change to one of their follows, and every leader fill a follow could not
1503
+ * mirror. Bare channel only — relationships span markets. The snapshot is
1504
+ * the follower's live follows (`change: "snapshot"`).
1505
+ */
1506
+ const subscribeCopy = (handler, onSnapshot) => {
1507
+ if (!handler) {
1508
+ throw new Error("copy subscription requires a handler");
1509
+ }
1510
+ return subscribe("copy", (rawData) => {
1511
+ try {
1512
+ handler(parseCopyEvent(rawData));
1513
+ }
1514
+ catch (err) {
1515
+ console.error("WebSocket: Error processing copy event", err);
1516
+ }
1517
+ }, onSnapshot && snapshotFrameHandler("copy", parseCopyRelationshipEventData, onSnapshot));
1518
+ };
1420
1519
  /**
1421
1520
  * Subscribe to authenticated user's position updates.
1422
1521
  * Requires authentication - user is identified from the signed session-key
@@ -1715,6 +1814,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1715
1814
  balances: subscribeBalances,
1716
1815
  conditionalOrders: subscribeConditionalOrders,
1717
1816
  twapOrders: subscribeTwapOrders,
1817
+ copy: subscribeCopy,
1718
1818
  positions: subscribePositions,
1719
1819
  liquidations: subscribeLiquidations,
1720
1820
  account: subscribeAccount,
@@ -1,27 +1,13 @@
1
1
  /**
2
- * Whitelist API Implementation
3
- *
4
- * Public (unauthenticated) whitelist/waitlist application submission. The
5
- * server validates and de-duplicates by wallet address and email, creating an
6
- * inactive user pending manual approval. This is an onboarding endpoint, not a
7
- * trading operation.
8
- *
9
- * @example
10
- * ```typescript
11
- * const { message, userId } = await sdk.whitelist.submit({
12
- * walletAddress: "0x...",
13
- * email: "user@example.com",
14
- * });
15
- * ```
2
+ * Retained client for the retired public whitelist endpoint.
3
+ * Submissions return HTTP 403 FORBIDDEN without changing applicant data.
16
4
  */
17
5
  import type { SubmitWhitelistRequest, SubmitWhitelistResponse, WhitelistAPI } from "@0xmonaco/types";
18
6
  import { BaseAPI } from "../base";
19
7
  export declare class WhitelistAPIImpl extends BaseAPI implements WhitelistAPI {
20
8
  /**
21
- * Submits a whitelist (waitlist) application. Public — no auth required.
22
- *
23
- * @param body - Applicant details
24
- * @returns Promise resolving to the status message and created user id
9
+ * @deprecated Public whitelist applications are closed. The server returns HTTP 403.
10
+ * @param body - Legacy applicant details; the request shape is retained for compatibility.
25
11
  */
26
12
  submit(body: SubmitWhitelistRequest): Promise<SubmitWhitelistResponse>;
27
13
  }
@@ -1,27 +1,13 @@
1
1
  /**
2
- * Whitelist API Implementation
3
- *
4
- * Public (unauthenticated) whitelist/waitlist application submission. The
5
- * server validates and de-duplicates by wallet address and email, creating an
6
- * inactive user pending manual approval. This is an onboarding endpoint, not a
7
- * trading operation.
8
- *
9
- * @example
10
- * ```typescript
11
- * const { message, userId } = await sdk.whitelist.submit({
12
- * walletAddress: "0x...",
13
- * email: "user@example.com",
14
- * });
15
- * ```
2
+ * Retained client for the retired public whitelist endpoint.
3
+ * Submissions return HTTP 403 FORBIDDEN without changing applicant data.
16
4
  */
17
5
  import { BaseAPI } from "../base";
18
6
  import { perpRoutes } from "../perp/routes";
19
7
  export class WhitelistAPIImpl extends BaseAPI {
20
8
  /**
21
- * Submits a whitelist (waitlist) application. Public — no auth required.
22
- *
23
- * @param body - Applicant details
24
- * @returns Promise resolving to the status message and created user id
9
+ * @deprecated Public whitelist applications are closed. The server returns HTTP 403.
10
+ * @param body - Legacy applicant details; the request shape is retained for compatibility.
25
11
  */
26
12
  async submit(body) {
27
13
  return await this.makePublicRequest(perpRoutes.whitelist.submit(), {
@@ -10,6 +10,19 @@ export declare const COVERED: {
10
10
  cancel_conditional_order: string;
11
11
  get_conditional_order: string;
12
12
  cancel_order: string;
13
+ upsert_lead_trader: string;
14
+ get_my_lead_trader: string;
15
+ list_my_followers: string;
16
+ list_lead_traders: string;
17
+ get_lead_trader: string;
18
+ list_lead_trader_closed_trades: string;
19
+ list_lead_trader_open_positions: string;
20
+ upsert_follow: string;
21
+ stop_follow: string;
22
+ list_my_follows: string;
23
+ get_follow: string;
24
+ list_follow_leader_positions: string;
25
+ preview_follow: string;
13
26
  cancel_twap_order: string;
14
27
  create_twap_order: string;
15
28
  get_twap_order: string;
package/dist/coverage.js CHANGED
@@ -10,6 +10,19 @@ export const COVERED = {
10
10
  cancel_conditional_order: "cancelConditionalOrder",
11
11
  get_conditional_order: "getConditionalOrder",
12
12
  cancel_order: "cancelOrder",
13
+ upsert_lead_trader: "copyTrading.upsertLeadTrader",
14
+ get_my_lead_trader: "copyTrading.getMyLeadTrader",
15
+ list_my_followers: "copyTrading.listMyFollowers",
16
+ list_lead_traders: "copyTrading.listLeadTraders",
17
+ get_lead_trader: "copyTrading.getLeadTrader",
18
+ list_lead_trader_closed_trades: "copyTrading.listLeadTraderClosedTrades",
19
+ list_lead_trader_open_positions: "copyTrading.listLeadTraderOpenPositions",
20
+ upsert_follow: "copyTrading.upsertFollow",
21
+ stop_follow: "copyTrading.stopFollow",
22
+ list_my_follows: "copyTrading.listMyFollows",
23
+ get_follow: "copyTrading.getFollow",
24
+ list_follow_leader_positions: "copyTrading.listFollowLeaderPositions",
25
+ preview_follow: "copyTrading.previewFollow",
13
26
  cancel_twap_order: "cancelTwapOrder",
14
27
  create_twap_order: "createTwapOrder",
15
28
  get_twap_order: "getTwapOrder",
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApplicationsAPI, AuthAPI, AuthState, BuildercodesAPI, DelegatedAgentsAPI, FaucetAPI, FeesAPI, ManagedMarketsAPI, MarginAccountsAPI, MarketAPI, MonacoSDK, Network, PitpassAPI, PortfolioAPI, PositionsAPI, ProfileAPI, PulseAPI, SDKConfig, SubAccountsAPI, SweeperAPI, TradingAPI, VaultAPI, WhitelistAPI, WithdrawalsAPI } from "@0xmonaco/types";
1
+ import type { ApplicationsAPI, AuthAPI, AuthState, BuildercodesAPI, CopyTradingAPI, DelegatedAgentsAPI, FaucetAPI, FeesAPI, ManagedMarketsAPI, MarginAccountsAPI, MarketAPI, MonacoSDK, Network, PitpassAPI, PortfolioAPI, PositionsAPI, ProfileAPI, PulseAPI, SDKConfig, SubAccountsAPI, SweeperAPI, TradingAPI, VaultAPI, WhitelistAPI, WithdrawalsAPI } from "@0xmonaco/types";
2
2
  import { type Address, type PublicClient, type TransactionReceipt, type WalletClient } from "viem";
3
3
  import { type MonacoWebSocket, OrderbookAPIImpl, TradesAPIImpl } from "./api";
4
4
  export declare class MonacoSDKImpl implements MonacoSDK {
@@ -7,6 +7,7 @@ export declare class MonacoSDKImpl implements MonacoSDK {
7
7
  applications: ApplicationsAPI;
8
8
  fees: FeesAPI;
9
9
  buildercodes: BuildercodesAPI;
10
+ copyTrading: CopyTradingAPI;
10
11
  pitpass: PitpassAPI;
11
12
  vault: VaultAPI;
12
13
  withdrawals: WithdrawalsAPI;
package/dist/sdk.js CHANGED
@@ -4,6 +4,7 @@ import { sei, seiTestnet } from "viem/chains";
4
4
  import { ApplicationsAPIImpl, createMonacoWebSocket, OrderbookAPIImpl, TradesAPIImpl } from "./api";
5
5
  import { AuthAPIImpl } from "./api/auth";
6
6
  import { BuildercodesAPIImpl } from "./api/buildercodes";
7
+ import { CopyTradingAPIImpl } from "./api/copy-trading";
7
8
  import { DelegatedAgentsAPIImpl } from "./api/delegated-agents";
8
9
  import { FaucetAPIImpl } from "./api/faucet";
9
10
  import { FeesAPIImpl } from "./api/fees";
@@ -48,6 +49,7 @@ export class MonacoSDKImpl {
48
49
  applications;
49
50
  fees;
50
51
  buildercodes;
52
+ copyTrading;
51
53
  pitpass;
52
54
  vault;
53
55
  withdrawals;
@@ -81,6 +83,7 @@ export class MonacoSDKImpl {
81
83
  this.applications.setSessionKeypair(credentials);
82
84
  this.fees.setSessionKeypair(credentials);
83
85
  this.buildercodes.setSessionKeypair(credentials);
86
+ this.copyTrading.setSessionKeypair(credentials);
84
87
  this.pitpass.setSessionKeypair(credentials);
85
88
  this.vault.setSessionKeypair(credentials);
86
89
  this.withdrawals.setSessionKeypair(credentials);
@@ -123,6 +126,7 @@ export class MonacoSDKImpl {
123
126
  this.applications.setServerKey(serverKey);
124
127
  this.fees.setServerKey(serverKey);
125
128
  this.buildercodes.setServerKey(serverKey);
129
+ this.copyTrading.setServerKey(serverKey);
126
130
  this.pitpass.setServerKey(serverKey);
127
131
  this.vault.setServerKey(serverKey);
128
132
  this.withdrawals.setServerKey(serverKey);
@@ -211,6 +215,7 @@ export class MonacoSDKImpl {
211
215
  this.delegatedAgents = configureSigning(new DelegatedAgentsAPIImpl(apiUrl));
212
216
  this.fees = configureSigning(new FeesAPIImpl(apiUrl));
213
217
  this.buildercodes = configureSigning(new BuildercodesAPIImpl(apiUrl));
218
+ this.copyTrading = configureSigning(new CopyTradingAPIImpl(apiUrl));
214
219
  this.pitpass = configureSigning(new PitpassAPIImpl(apiUrl));
215
220
  this.profile = configureSigning(new ProfileAPIImpl(apiUrl));
216
221
  this.vault = configureSigning(new VaultAPIImpl(this.publicClient, this.walletClient, this.chain, this.applications, this.profile, apiUrl));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "1.0.67",
3
+ "version": "1.0.70",
4
4
  "type": "module",
5
5
  "homepage": "https://docs.0xmonaco.com/sdk/typescript",
6
6
  "main": "./dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "viem": "^2.45.2"
20
20
  },
21
21
  "dependencies": {
22
- "@0xmonaco/contracts": "1.0.67",
23
- "@0xmonaco/types": "1.0.67",
22
+ "@0xmonaco/contracts": "1.0.70",
23
+ "@0xmonaco/types": "1.0.70",
24
24
  "@noble/curves": "^1.9.1",
25
25
  "@noble/hashes": "^2.4.0",
26
26
  "http-status-codes": "^2.3.0"