@0xmonaco/core 1.0.25 → 1.0.26-develop.aeb6d5b

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.
@@ -12,7 +12,7 @@
12
12
  * const { minted, failed, remainingRequests24h } = await sdk.faucet.mint({ turnstileToken });
13
13
  * ```
14
14
  */
15
- import type { FaucetAPI, MintTokensParams, MintTokensResponse } from "@0xmonaco/types";
15
+ import type { FaucetAPI, GetFaucetInfoResponse, MintTokensParams, MintTokensResponse } from "@0xmonaco/types";
16
16
  import { BaseAPI } from "../base";
17
17
  export declare class FaucetAPIImpl extends BaseAPI implements FaucetAPI {
18
18
  /**
@@ -30,4 +30,15 @@ export declare class FaucetAPIImpl extends BaseAPI implements FaucetAPI {
30
30
  * @returns Promise resolving to the minted tokens and remaining quota
31
31
  */
32
32
  mint(params: MintTokensParams): Promise<MintTokensResponse>;
33
+ /**
34
+ * Fetches the authenticated wallet's faucet quota and recent requests.
35
+ *
36
+ * Read-only: remaining requests in the rolling 24h window, when the next
37
+ * request becomes available (`nextAvailableAt`, absent when nothing is in
38
+ * the window), and the 5 most recent requests. Wallet-scoped across apps,
39
+ * matching mint enforcement.
40
+ *
41
+ * @returns Promise resolving to the quota and recent-request history
42
+ */
43
+ getInfo(): Promise<GetFaucetInfoResponse>;
33
44
  }
@@ -35,4 +35,19 @@ export class FaucetAPIImpl extends BaseAPI {
35
35
  body: JSON.stringify({ turnstileToken: params.turnstileToken }),
36
36
  });
37
37
  }
38
+ /**
39
+ * Fetches the authenticated wallet's faucet quota and recent requests.
40
+ *
41
+ * Read-only: remaining requests in the rolling 24h window, when the next
42
+ * request becomes available (`nextAvailableAt`, absent when nothing is in
43
+ * the window), and the 5 most recent requests. Wallet-scoped across apps,
44
+ * matching mint enforcement.
45
+ *
46
+ * @returns Promise resolving to the quota and recent-request history
47
+ */
48
+ async getInfo() {
49
+ return await this.makeAuthenticatedRequest(perpRoutes.faucet.info(), {
50
+ method: "GET",
51
+ });
52
+ }
38
53
  }
@@ -36,7 +36,7 @@ export declare class FeesAPIImpl extends BaseAPI implements FeesAPI {
36
36
  * @param params.price - The price per unit as string
37
37
  * @param params.quantity - The quantity to trade as string
38
38
  * @param params.orderType - Order type: "LIMIT" (default) or "MARKET"
39
- * @param params.slippageToleranceBps - Slippage tolerance in bps (0–1000, MARKET only, default 500)
39
+ * @param params.slippageToleranceBps - Slippage tolerance in bps (0–1000, MARKET only, default 1000)
40
40
  * @returns Promise resolving to SimulateFeeResponse with fee breakdown
41
41
  *
42
42
  * @example
@@ -36,7 +36,7 @@ export class FeesAPIImpl extends BaseAPI {
36
36
  * @param params.price - The price per unit as string
37
37
  * @param params.quantity - The quantity to trade as string
38
38
  * @param params.orderType - Order type: "LIMIT" (default) or "MARKET"
39
- * @param params.slippageToleranceBps - Slippage tolerance in bps (0–1000, MARKET only, default 500)
39
+ * @param params.slippageToleranceBps - Slippage tolerance in bps (0–1000, MARKET only, default 1000)
40
40
  * @returns Promise resolving to SimulateFeeResponse with fee breakdown
41
41
  *
42
42
  * @example
@@ -16,6 +16,7 @@ export * from "./pitpass";
16
16
  export * from "./portfolio";
17
17
  export * from "./positions";
18
18
  export * from "./profile";
19
+ export * from "./pulse";
19
20
  export * from "./sub-accounts";
20
21
  export * from "./trades";
21
22
  export * from "./trading";
package/dist/api/index.js CHANGED
@@ -16,6 +16,7 @@ export * from "./pitpass";
16
16
  export * from "./portfolio";
17
17
  export * from "./positions";
18
18
  export * from "./profile";
19
+ export * from "./pulse";
19
20
  export * from "./sub-accounts";
20
21
  export * from "./trades";
21
22
  export * from "./trading";
@@ -1,4 +1,4 @@
1
- import type { Candlestick, FundingState, GetCandlesticksParams, GetScreenerParams, GetScreenerResponse, GetTradingPairsParams, GetTradingPairsResponse, IndexPrice, Interval, ListFundingHistoryParams, ListFundingHistoryResponse, MarketAPI, MarketMetadata, MarketStats, MarkPrice, OpenInterest, PerpMarketConfig, PerpMarketSummary, TradingMode, TradingPair } from "@0xmonaco/types";
1
+ import type { Candlestick, FundingState, GetCandlesticksParams, GetScreenerParams, GetScreenerResponse, GetTradingPairsParams, GetTradingPairsResponse, IndexPrice, Interval, ListAllFundingHistoryParams, ListAllFundingHistoryResponse, ListFundingHistoryParams, ListFundingHistoryResponse, MarketAPI, MarketMetadata, MarketStats, MarkPrice, OpenInterest, PerpMarketConfig, PerpMarketSummary, TradingMode, TradingPair } from "@0xmonaco/types";
2
2
  import { BaseAPI } from "../base";
3
3
  /**
4
4
  * Market API Implementation
@@ -17,6 +17,7 @@ export declare class MarketAPIImpl extends BaseAPI implements MarketAPI {
17
17
  getIndexPrice(tradingPairId: string): Promise<IndexPrice>;
18
18
  getFundingState(tradingPairId: string): Promise<FundingState>;
19
19
  listFundingHistory(tradingPairId: string, params?: ListFundingHistoryParams): Promise<ListFundingHistoryResponse>;
20
+ listAllFundingHistory(params?: ListAllFundingHistoryParams): Promise<ListAllFundingHistoryResponse>;
20
21
  getOpenInterest(tradingPairId: string): Promise<OpenInterest>;
21
22
  getScreener(params?: GetScreenerParams): Promise<GetScreenerResponse>;
22
23
  getMarketStats(): Promise<MarketStats>;
@@ -100,6 +100,9 @@ export class MarketAPIImpl extends BaseAPI {
100
100
  validate(GetMarketMetadataSchema, { tradingPairId });
101
101
  return await this.makePublicRequest(perpRoutes.market.listFundingHistory(tradingPairId, params));
102
102
  }
103
+ async listAllFundingHistory(params) {
104
+ return await this.makePublicRequest(perpRoutes.market.listAllFundingHistory(params));
105
+ }
103
106
  async getOpenInterest(tradingPairId) {
104
107
  validate(GetMarketMetadataSchema, { tradingPairId });
105
108
  return await this.makePublicRequest(perpRoutes.market.getOpenInterest(tradingPairId));
@@ -67,6 +67,14 @@ export declare const perpRoutes: {
67
67
  endTime?: string;
68
68
  page?: number;
69
69
  pageSize?: number;
70
+ orderBy?: string;
71
+ }) => string;
72
+ readonly listAllFundingHistory: (params?: {
73
+ startTime?: string;
74
+ endTime?: string;
75
+ page?: number;
76
+ pageSize?: number;
77
+ orderBy?: string;
70
78
  }) => string;
71
79
  readonly getOpenInterest: (tradingPairId: string) => string;
72
80
  readonly getScreener: (params?: {
@@ -142,6 +150,7 @@ export declare const perpRoutes: {
142
150
  };
143
151
  readonly faucet: {
144
152
  readonly mint: () => string;
153
+ readonly info: () => string;
145
154
  };
146
155
  readonly whitelist: {
147
156
  readonly submit: () => string;
@@ -52,6 +52,7 @@ export const perpRoutes = {
52
52
  getIndexPrice: (tradingPairId) => `${API_V1}/market/pairs/${encodeSegment(tradingPairId)}/index-price`,
53
53
  getFundingState: (tradingPairId) => `${API_V1}/market/pairs/${encodeSegment(tradingPairId)}/funding`,
54
54
  listFundingHistory: (tradingPairId, params) => withQuery(`${API_V1}/market/pairs/${encodeSegment(tradingPairId)}/funding/history`, params),
55
+ listAllFundingHistory: (params) => withQuery(`${API_V1}/market/funding/history`, params),
55
56
  getOpenInterest: (tradingPairId) => `${API_V1}/market/pairs/${encodeSegment(tradingPairId)}/open-interest`,
56
57
  getScreener: (params) => withQuery(`${API_V1}/market/screener`, params),
57
58
  getMarketStats: () => `${API_V1}/market/stats`,
@@ -79,6 +80,7 @@ export const perpRoutes = {
79
80
  },
80
81
  faucet: {
81
82
  mint: () => `${API_V1}/faucet/mint`,
83
+ info: () => `${API_V1}/faucet/info`,
82
84
  },
83
85
  whitelist: {
84
86
  submit: () => `${API_V1}/whitelist`,
@@ -70,6 +70,7 @@ export class PositionsAPIImpl extends BaseAPI {
70
70
  orderType: request.takeProfit.orderType,
71
71
  limitPrice: request.takeProfit.limitPrice,
72
72
  quantity: request.takeProfit.quantity,
73
+ closePosition: request.takeProfit.closePosition,
73
74
  timeInForce: request.takeProfit.timeInForce,
74
75
  slippageToleranceBps: request.takeProfit.slippageToleranceBps,
75
76
  expiresAt: request.takeProfit.expiresAt,
@@ -81,11 +82,13 @@ export class PositionsAPIImpl extends BaseAPI {
81
82
  orderType: request.stopLoss.orderType,
82
83
  limitPrice: request.stopLoss.limitPrice,
83
84
  quantity: request.stopLoss.quantity,
85
+ closePosition: request.stopLoss.closePosition,
84
86
  timeInForce: request.stopLoss.timeInForce,
85
87
  slippageToleranceBps: request.stopLoss.slippageToleranceBps,
86
88
  expiresAt: request.stopLoss.expiresAt,
87
89
  }
88
90
  : undefined,
91
+ oco: request.oco,
89
92
  }),
90
93
  });
91
94
  }
@@ -173,6 +173,11 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
173
173
  * @param params.page - Page number (starts from 1, default: 1)
174
174
  * @param params.pageSize - Number of items per page (default: 20, max: 100)
175
175
  * @param params.tradingPairId - Filter by trading pair ID (UUID)
176
+ * @param params.pageToken - Pagination cursor (optional): omit for legacy page-number
177
+ * pagination; pass `""` to start a cursor walk from the newest row; pass a previous
178
+ * response's `nextPageToken` to resume. Any cursor-walk value (`""` or non-empty)
179
+ * ignores `page` and covers full trade history (hot and archived rows merged), unlike
180
+ * page-number mode.
176
181
  * @returns Promise resolving to paginated trades response
177
182
  * @throws {ValidationError} When the provided params fail client-side validation
178
183
  * @throws {APIError} When the request fails or user is not authenticated
@@ -224,6 +224,11 @@ export class ProfileAPIImpl extends BaseAPI {
224
224
  * @param params.page - Page number (starts from 1, default: 1)
225
225
  * @param params.pageSize - Number of items per page (default: 20, max: 100)
226
226
  * @param params.tradingPairId - Filter by trading pair ID (UUID)
227
+ * @param params.pageToken - Pagination cursor (optional): omit for legacy page-number
228
+ * pagination; pass `""` to start a cursor walk from the newest row; pass a previous
229
+ * response's `nextPageToken` to resume. Any cursor-walk value (`""` or non-empty)
230
+ * ignores `page` and covers full trade history (hot and archived rows merged), unlike
231
+ * page-number mode.
227
232
  * @returns Promise resolving to paginated trades response
228
233
  * @throws {ValidationError} When the provided params fail client-side validation
229
234
  * @throws {APIError} When the request fails or user is not authenticated
@@ -252,6 +257,9 @@ export class ProfileAPIImpl extends BaseAPI {
252
257
  if (params?.tradingPairId !== undefined) {
253
258
  searchParams.append("tradingPairId", params.tradingPairId);
254
259
  }
260
+ if (params?.pageToken !== undefined) {
261
+ searchParams.append("pageToken", params.pageToken);
262
+ }
255
263
  const queryString = searchParams.toString();
256
264
  const url = queryString ? `/api/v1/accounts/trades?${queryString}` : "/api/v1/accounts/trades";
257
265
  return await this.makeAuthenticatedRequest(url);
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pulse API Implementation
3
+ *
4
+ * Read-only access to the market-pulse news feed: AI-synthesized, per-asset
5
+ * headlines with sentiment tags, served from the backend's hourly-refreshed
6
+ * cache. Public — no authentication required.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const pulse = await sdk.pulse.getPulse();
11
+ * for (const item of pulse.items) {
12
+ * console.log(`[${item.sentiment}] ${item.asset}: ${item.summary}`);
13
+ * }
14
+ * ```
15
+ */
16
+ import type { GetPulseResponse, PulseAPI } from "@0xmonaco/types";
17
+ import { BaseAPI } from "../base";
18
+ export declare class PulseAPIImpl extends BaseAPI implements PulseAPI {
19
+ /**
20
+ * Get the latest market pulse snapshot (public, no auth headers).
21
+ * Returns 503 while the backend cache is cold.
22
+ */
23
+ getPulse(): Promise<GetPulseResponse>;
24
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Pulse API Implementation
3
+ *
4
+ * Read-only access to the market-pulse news feed: AI-synthesized, per-asset
5
+ * headlines with sentiment tags, served from the backend's hourly-refreshed
6
+ * cache. Public — no authentication required.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const pulse = await sdk.pulse.getPulse();
11
+ * for (const item of pulse.items) {
12
+ * console.log(`[${item.sentiment}] ${item.asset}: ${item.summary}`);
13
+ * }
14
+ * ```
15
+ */
16
+ import { BaseAPI } from "../base";
17
+ export class PulseAPIImpl extends BaseAPI {
18
+ /**
19
+ * Get the latest market pulse snapshot (public, no auth headers).
20
+ * Returns 503 while the backend cache is cold.
21
+ */
22
+ async getPulse() {
23
+ return await this.makePublicRequest("/api/v1/pulse", {
24
+ method: "GET",
25
+ });
26
+ }
27
+ }
@@ -0,0 +1 @@
1
+ export { PulseAPIImpl } from "./api";
@@ -0,0 +1 @@
1
+ export { PulseAPIImpl } from "./api";
@@ -93,15 +93,17 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
93
93
  /**
94
94
  * Places a market order for immediate execution.
95
95
  *
96
- * Market orders execute immediately at the current market price. They provide
97
- * immediate execution but may experience slippage.
96
+ * Market orders execute immediately against available liquidity, inside a
97
+ * mandatory 1,000 bps server-side protective price band. Fills beyond the band
98
+ * are cut off, so an order may partially fill and cancel the remainder, or be
99
+ * rejected when no liquidity sits inside the band.
98
100
  *
99
101
  * @param tradingPairId - The trading pair UUID
100
102
  * @param side - The order side ("BUY" or "SELL")
101
103
  * @param quantity - The order quantity as string
102
104
  * @param options - Optional parameters for the market order
103
105
  * @param options.tradingMode - Trading mode (e.g., "SPOT")
104
- * @param options.slippageTolerance - Slippage tolerance as decimal (e.g., 0.01 for 1%, 0 for best price only, undefined for unlimited slippage)
106
+ * @param options.slippageTolerance - Tightens the mandatory 1,000 bps price band, as a decimal (e.g., 0.01 for 1%; 0 for best price only). It can only tighten the band, never widen it; omitting it applies the full 1,000 bps band.
105
107
  * @returns Promise resolving to CreateOrderResponse with order details
106
108
  *
107
109
  * @example
@@ -122,12 +124,12 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
122
124
  * { slippageTolerance: 0 } // Only execute at best ask price
123
125
  * );
124
126
  *
125
- * // Market order with unlimited slippage
127
+ * // Market order with the default protective band (no client tightening)
126
128
  * const order3 = await tradingAPI.placeMarketOrder(
127
129
  * "123e4567-e89b-12d3-a456-426614174000",
128
130
  * "BUY",
129
131
  * "1.0"
130
- * // No slippageTolerance specified = unlimited slippage
132
+ * // No slippageTolerance specified = full 1,000 bps band applies
131
133
  * );
132
134
  * ```
133
135
  */
@@ -268,6 +270,11 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
268
270
  * @param params.tradingPairId - Filter by trading pair UUID (optional)
269
271
  * @param params.page - Page number for pagination (defaults to 1, must be > 0)
270
272
  * @param params.pageSize - Number of orders per page (defaults to 10, max 100, must be > 0)
273
+ * @param params.pageToken - Pagination cursor (optional): omit for legacy page-number
274
+ * pagination; pass `""` to start a cursor walk from the newest (or oldest, for
275
+ * `orderBy: "ASC"`) row; pass a previous response's `nextPageToken` to resume. Any
276
+ * cursor-walk value (`""` or non-empty) ignores `page` and covers full order history
277
+ * (hot and archived rows merged), unlike page-number mode.
271
278
  * @returns Promise resolving to GetPaginatedOrdersResponse with orders and pagination info
272
279
  * @throws {APIError} When API communication fails
273
280
  *
@@ -125,15 +125,17 @@ export class TradingAPIImpl extends BaseAPI {
125
125
  /**
126
126
  * Places a market order for immediate execution.
127
127
  *
128
- * Market orders execute immediately at the current market price. They provide
129
- * immediate execution but may experience slippage.
128
+ * Market orders execute immediately against available liquidity, inside a
129
+ * mandatory 1,000 bps server-side protective price band. Fills beyond the band
130
+ * are cut off, so an order may partially fill and cancel the remainder, or be
131
+ * rejected when no liquidity sits inside the band.
130
132
  *
131
133
  * @param tradingPairId - The trading pair UUID
132
134
  * @param side - The order side ("BUY" or "SELL")
133
135
  * @param quantity - The order quantity as string
134
136
  * @param options - Optional parameters for the market order
135
137
  * @param options.tradingMode - Trading mode (e.g., "SPOT")
136
- * @param options.slippageTolerance - Slippage tolerance as decimal (e.g., 0.01 for 1%, 0 for best price only, undefined for unlimited slippage)
138
+ * @param options.slippageTolerance - Tightens the mandatory 1,000 bps price band, as a decimal (e.g., 0.01 for 1%; 0 for best price only). It can only tighten the band, never widen it; omitting it applies the full 1,000 bps band.
137
139
  * @returns Promise resolving to CreateOrderResponse with order details
138
140
  *
139
141
  * @example
@@ -154,12 +156,12 @@ export class TradingAPIImpl extends BaseAPI {
154
156
  * { slippageTolerance: 0 } // Only execute at best ask price
155
157
  * );
156
158
  *
157
- * // Market order with unlimited slippage
159
+ * // Market order with the default protective band (no client tightening)
158
160
  * const order3 = await tradingAPI.placeMarketOrder(
159
161
  * "123e4567-e89b-12d3-a456-426614174000",
160
162
  * "BUY",
161
163
  * "1.0"
162
- * // No slippageTolerance specified = unlimited slippage
164
+ * // No slippageTolerance specified = full 1,000 bps band applies
163
165
  * );
164
166
  * ```
165
167
  */
@@ -417,6 +419,11 @@ export class TradingAPIImpl extends BaseAPI {
417
419
  * @param params.tradingPairId - Filter by trading pair UUID (optional)
418
420
  * @param params.page - Page number for pagination (defaults to 1, must be > 0)
419
421
  * @param params.pageSize - Number of orders per page (defaults to 10, max 100, must be > 0)
422
+ * @param params.pageToken - Pagination cursor (optional): omit for legacy page-number
423
+ * pagination; pass `""` to start a cursor walk from the newest (or oldest, for
424
+ * `orderBy: "ASC"`) row; pass a previous response's `nextPageToken` to resume. Any
425
+ * cursor-walk value (`""` or non-empty) ignores `page` and covers full order history
426
+ * (hot and archived rows merged), unlike page-number mode.
420
427
  * @returns Promise resolving to GetPaginatedOrdersResponse with orders and pagination info
421
428
  * @throws {APIError} When API communication fails
422
429
  *
@@ -441,7 +448,7 @@ export class TradingAPIImpl extends BaseAPI {
441
448
  validate(GetPaginatedOrdersSchema, params);
442
449
  }
443
450
  // Set pagination defaults with destructuring and validation
444
- const { page = 1, pageSize: requestedPageSize = 10, status, tradingPairId, tradingMode, marginAccountId } = params || {};
451
+ const { page = 1, pageSize: requestedPageSize = 10, status, tradingPairId, tradingMode, marginAccountId, pageToken } = params || {};
445
452
  const validPage = page > 0 ? page : 1;
446
453
  const validPageSize = requestedPageSize > 0 ? requestedPageSize : 10;
447
454
  const pageSize = Math.min(validPageSize, 100);
@@ -461,6 +468,13 @@ export class TradingAPIImpl extends BaseAPI {
461
468
  if (marginAccountId) {
462
469
  searchParams.append("marginAccountId", marginAccountId);
463
470
  }
471
+ // `!== undefined` (not a truthy check): an explicitly-provided empty
472
+ // string is the documented way to start a cursor walk (distinct from
473
+ // omitting `pageToken` entirely), so it must still be sent rather than
474
+ // silently dropped like a falsy value would be.
475
+ if (pageToken !== undefined) {
476
+ searchParams.append("pageToken", pageToken);
477
+ }
464
478
  const endpoint = `/api/v1/orders?${searchParams.toString()}`;
465
479
  return await this.makeAuthenticatedRequest(endpoint, {
466
480
  method: "GET",
@@ -1,15 +1,51 @@
1
1
  import type { ConditionalOrderEvent, Interval, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, SessionCredentials, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
2
2
  export type StatusHandler = (status: WebSocketStatus) => void;
3
3
  export type MessageHandler<T> = (data: T) => void;
4
+ /**
5
+ * Why the connection was re-established. Delivered by
6
+ * {@link MonacoWebSocketOptions.onResync} after the client has reconnected,
7
+ * re-authenticated, and re-subscribed — the point where the app should
8
+ * refetch REST snapshots and reconcile by `(id, updatedAt)`.
9
+ */
10
+ export interface WebSocketResyncInfo {
11
+ /** WebSocket close code of the connection that was lost. */
12
+ code: number;
13
+ /**
14
+ * True when the server closed with 1013 (try again later): the client fell
15
+ * behind a sequential stream (orders, conditional orders, liquidations,
16
+ * trades, movements) and the stream has a gap. There is no wire sequence
17
+ * number — the 1013 close is the gap signal, and a snapshot refetch is the
18
+ * only safe repair.
19
+ */
20
+ slowClient: boolean;
21
+ }
22
+ export type ResyncHandler = (info: WebSocketResyncInfo) => void;
4
23
  export interface MonacoWebSocketOptions {
5
24
  /** Session keypair for authenticated channels (orders, balances, etc.) */
6
25
  session?: SessionCredentials;
7
26
  /** Enable auto-reconnect on disconnect (default: true) */
8
27
  autoReconnect?: boolean;
9
- /** Maximum reconnection attempts (default: 5) */
28
+ /**
29
+ * Maximum reconnection attempts (default: unlimited). Retry delay is
30
+ * full-jitter exponential backoff capped at 30s, so unlimited retries are
31
+ * capped-rate, not a tight loop.
32
+ */
10
33
  maxReconnectAttempts?: number;
34
+ /** Base delay for the exponential backoff in milliseconds (default: 1000) */
35
+ reconnectBaseDelayMs?: number;
36
+ /** How long a connection attempt may stay CONNECTING before it is failed
37
+ * and retried, in milliseconds (default: 10000) */
38
+ connectionTimeoutMs?: number;
11
39
  /** Callback for connection status changes */
12
40
  onStatusChange?: StatusHandler;
41
+ /**
42
+ * Called after every automatic reconnect once the live stream is resumed
43
+ * (re-authenticated and re-subscribed). Refetch REST snapshots here and
44
+ * reconcile by `(id, updatedAt)`, last-writer-wins; events that arrived
45
+ * while the snapshot was in flight are newer than the snapshot rows they
46
+ * duplicate.
47
+ */
48
+ onResync?: ResyncHandler;
13
49
  }
14
50
  export interface MonacoWebSocket {
15
51
  /** Connect to the WebSocket server */
@@ -6,6 +6,7 @@ import { keysToCamelCase } from "./utils";
6
6
  const CONNECTION_TIMEOUT = 10000;
7
7
  const HEARTBEAT_INTERVAL = 15000;
8
8
  const MAX_RECONNECT_DELAY = 30000;
9
+ const RECONNECT_BASE_DELAY = 1000;
9
10
  const CONDITIONAL_ORDER_REASONS = ["created", "activated", "cancelled", "parent_cancelled", "triggered", "failed", "oco_cancelled"];
10
11
  const CONDITIONAL_ORDER_CONDITION_TYPES = ["STOP_LOSS", "TAKE_PROFIT"];
11
12
  const CONDITIONAL_ORDER_TRIGGER_SOURCES = ["MARK_PRICE"];
@@ -114,19 +115,33 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
114
115
  let reconnectTimer = null;
115
116
  let heartbeatTimer = null;
116
117
  let autoReconnect = options.autoReconnect ?? true;
117
- const maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
118
+ // Capped-infinite by default: unlimited attempts, delay capped at 30s. A
119
+ // client that gives up permanently (the old 5-attempt default) turns any
120
+ // blip longer than ~1 minute into a dead app.
121
+ const maxReconnectAttempts = options.maxReconnectAttempts ?? Number.POSITIVE_INFINITY;
122
+ // Normalize to a positive finite base: a negative/NaN/Infinity override
123
+ // would degenerate into a tight reconnect loop or no reconnect at all.
124
+ const reconnectBaseDelay = Number.isFinite(options.reconnectBaseDelayMs) && options.reconnectBaseDelayMs > 0
125
+ ? options.reconnectBaseDelayMs
126
+ : RECONNECT_BASE_DELAY;
127
+ const connectionTimeout = Number.isFinite(options.connectionTimeoutMs) && options.connectionTimeoutMs > 0 ? options.connectionTimeoutMs : CONNECTION_TIMEOUT;
128
+ // Close code of the connection loss the next successful reopen recovers
129
+ // from; null when no resync is owed (first connect, clean disconnect).
130
+ let pendingResyncCode = null;
118
131
  // Handler storage: channel -> set of handlers (supports multiple subscribers per channel)
119
132
  const handlers = new Map();
120
133
  const getStatus = () => {
121
134
  if (!ws)
122
- return "disconnected";
135
+ return reconnectTimer ? "reconnecting" : "disconnected";
123
136
  switch (ws.readyState) {
124
137
  case WebSocket.CONNECTING:
125
138
  return "connecting";
126
139
  case WebSocket.OPEN:
127
140
  return "connected";
128
141
  default:
129
- return "disconnected";
142
+ // CLOSING/CLOSED with a retry armed is mid-outage, not terminal:
143
+ // the timeout path arms the retry before the close event lands.
144
+ return reconnectTimer ? "reconnecting" : "disconnected";
130
145
  }
131
146
  };
132
147
  const getUrl = () => baseUrl;
@@ -169,17 +184,35 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
169
184
  }
170
185
  };
171
186
  const scheduleReconnect = () => {
187
+ // Single scheduling authority: a failed attempt can report through both
188
+ // the connect() rejection and the socket's own close event — an armed
189
+ // timer means the retry is already owned.
190
+ if (reconnectTimer)
191
+ return;
172
192
  if (!autoReconnect || reconnectAttempts >= maxReconnectAttempts) {
173
193
  console.error("WebSocket: Max reconnect attempts reached");
174
194
  return;
175
195
  }
196
+ // Full-jitter exponential backoff: uniform in [0, min(base * 2^n, cap)],
197
+ // n starting at 0 so the first retry's ceiling is the base itself.
198
+ // Jitter desynchronizes the reconnect storm after a server deploy or a
199
+ // mass 1013 shed — synchronized clients would otherwise reconnect in
200
+ // lockstep waves.
201
+ const ceiling = Math.min(reconnectBaseDelay * 2 ** reconnectAttempts, MAX_RECONNECT_DELAY);
176
202
  reconnectAttempts++;
177
- const delay = Math.min(1000 * 2 ** reconnectAttempts, MAX_RECONNECT_DELAY);
203
+ const delay = Math.random() * ceiling;
178
204
  reconnectTimer = setTimeout(() => {
205
+ reconnectTimer = null;
206
+ // Re-check: a callback already dequeued when disconnect() ran is not
207
+ // stopped by clearTimeout and must not reopen a socket.
208
+ if (!autoReconnect)
209
+ return;
179
210
  connect().catch((err) => {
180
211
  console.warn("WebSocket: Failed to reconnect:", err);
212
+ scheduleReconnect();
181
213
  });
182
214
  }, delay);
215
+ options.onStatusChange?.("reconnecting");
183
216
  };
184
217
  const send = (data) => {
185
218
  if (ws?.readyState === WebSocket.OPEN) {
@@ -223,7 +256,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
223
256
  socket.close(1000, "Connection timeout");
224
257
  reject(new Error("WebSocket connection timeout"));
225
258
  }
226
- }, CONNECTION_TIMEOUT);
259
+ }, connectionTimeout);
227
260
  socket.onopen = () => {
228
261
  clearTimeout(timeout);
229
262
  if (ws !== socket) {
@@ -231,12 +264,26 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
231
264
  return;
232
265
  }
233
266
  reconnectAttempts = 0;
267
+ // A retry armed before this open (e.g. a manual connect() during
268
+ // backoff) is moot now — left running it would suppress the
269
+ // terminal disconnected emit and reopen a socket after a clean
270
+ // close.
271
+ stopReconnect();
234
272
  startHeartbeat();
235
273
  // Authenticate before resubscribing so user-specific channels
236
274
  // (orders, balances, …) are authorized when the Subscribe arrives.
237
275
  sendAuthenticate();
238
276
  resubscribeAll();
239
277
  options.onStatusChange?.("connected");
278
+ // Resync-join order: resume the live stream first (auth +
279
+ // subscribe above), then tell the app to refetch snapshots and
280
+ // reconcile by (id, updatedAt) — events racing the snapshot are
281
+ // newer than the rows they duplicate.
282
+ if (pendingResyncCode !== null) {
283
+ const code = pendingResyncCode;
284
+ pendingResyncCode = null;
285
+ options.onResync?.({ code, slowClient: code === 1013 });
286
+ }
240
287
  resolve();
241
288
  };
242
289
  socket.onmessage = (event) => {
@@ -247,6 +294,12 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
247
294
  // Handle pong silently
248
295
  if (msg.type === "Pong")
249
296
  return;
297
+ // Answer a server-initiated application ping: the server owns
298
+ // liveness through proxy idle timeouts, the client just echoes.
299
+ if (msg.type === "Ping") {
300
+ send({ type: "Pong" });
301
+ return;
302
+ }
250
303
  routeMessage(msg);
251
304
  }
252
305
  catch (err) {
@@ -261,11 +314,22 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
261
314
  }
262
315
  ws = null;
263
316
  stopHeartbeat();
264
- options.onStatusChange?.("disconnected");
265
- // Reconnect on abnormal close
317
+ // Reconnect on abnormal close. 1013 is the server's slow-client
318
+ // shed: the stream has a gap, so the reopen owes the app a resync.
319
+ // Keep the first owed code (upgrading to 1013 if it arrives later):
320
+ // failures of the reconnect attempts themselves must not launder
321
+ // the slow-client signal into a generic 1006.
266
322
  if (autoReconnect && event.code !== 1000) {
323
+ if (pendingResyncCode === null || event.code === 1013) {
324
+ pendingResyncCode = event.code;
325
+ }
267
326
  scheduleReconnect();
268
327
  }
328
+ // "disconnected" is terminal: suppressed while a retry is armed
329
+ // (scheduleReconnect emitted "reconnecting" instead).
330
+ if (!reconnectTimer) {
331
+ options.onStatusChange?.("disconnected");
332
+ }
269
333
  };
270
334
  socket.onerror = () => clearTimeout(timeout);
271
335
  }
@@ -279,6 +343,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
279
343
  autoReconnect = false;
280
344
  stopReconnect();
281
345
  stopHeartbeat();
346
+ pendingResyncCode = null;
282
347
  handlers.clear();
283
348
  ws?.close(1000, "Client disconnect");
284
349
  ws = null;
@@ -21,6 +21,7 @@ export declare const COVERED: {
21
21
  get_application_stats: string;
22
22
  get_available_collateral: string;
23
23
  get_candles: string;
24
+ get_faucet_info: string;
24
25
  get_funding_state: string;
25
26
  get_index_price: string;
26
27
  get_margin_account_movements: string;
@@ -29,6 +30,7 @@ export declare const COVERED: {
29
30
  get_market_metadata: string;
30
31
  get_market_stats: string;
31
32
  get_my_fee_tier: string;
33
+ get_pulse: string;
32
34
  get_my_trader_code: string;
33
35
  get_rewards_balance: string;
34
36
  get_trader_code_info: string;
@@ -63,6 +65,7 @@ export declare const COVERED: {
63
65
  get_withdrawal: string;
64
66
  initiate_withdrawal: string;
65
67
  list_pending_withdrawals: string;
68
+ list_all_funding_history: string;
66
69
  list_application_balances: string;
67
70
  list_application_movements: string;
68
71
  list_application_orders: string;
package/dist/coverage.js CHANGED
@@ -21,6 +21,7 @@ export const COVERED = {
21
21
  get_application_stats: "getApplicationStats",
22
22
  get_available_collateral: "getAvailableCollateral",
23
23
  get_candles: "getCandlesticks",
24
+ get_faucet_info: "faucet.getInfo",
24
25
  get_funding_state: "getFundingState",
25
26
  get_index_price: "getIndexPrice",
26
27
  get_margin_account_movements: "getMarginAccountMovements",
@@ -29,6 +30,7 @@ export const COVERED = {
29
30
  get_market_metadata: "getMarketMetadata",
30
31
  get_market_stats: "getMarketStats",
31
32
  get_my_fee_tier: "getMyFeeTier",
33
+ get_pulse: "pulse.getPulse",
32
34
  get_my_trader_code: "pitpass.getMyTraderCode",
33
35
  get_rewards_balance: "pitpass.getRewardsBalance",
34
36
  get_trader_code_info: "pitpass.getTraderCodeInfo",
@@ -63,6 +65,7 @@ export const COVERED = {
63
65
  get_withdrawal: "getWithdrawal",
64
66
  initiate_withdrawal: "initiateWithdrawal",
65
67
  list_pending_withdrawals: "listPendingWithdrawals",
68
+ list_all_funding_history: "listAllFundingHistory",
66
69
  list_application_balances: "listApplicationBalances",
67
70
  list_application_movements: "listApplicationMovements",
68
71
  list_application_orders: "listApplicationOrders",
@@ -22,7 +22,10 @@ const DEFAULT_API_URLS = {
22
22
  */
23
23
  const DEFAULT_WS_URLS = {
24
24
  local: "ws://localhost:8085/ws",
25
- development: "wss://develop.apimonaco.xyz/ws",
25
+ // Dedicated WebSocket ingress (NLB behind Cloudflare). The previous
26
+ // ALB path (wss://develop.apimonaco.xyz/ws) keeps working during the
27
+ // migration window.
28
+ development: "wss://ws-develop.apimonaco.xyz/ws",
26
29
  staging: "wss://staging.apimonaco.xyz/ws",
27
30
  mainnet: "wss://api.monaco.xyz/ws",
28
31
  };
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApplicationsAPI, AuthAPI, AuthState, DelegatedAgentsAPI, FaucetAPI, FeesAPI, MarginAccountsAPI, MarketAPI, MonacoSDK, Network, PitpassAPI, PortfolioAPI, PositionsAPI, ProfileAPI, SDKConfig, SubAccountsAPI, TradingAPI, VaultAPI, WhitelistAPI, WithdrawalsAPI } from "@0xmonaco/types";
1
+ import type { ApplicationsAPI, AuthAPI, AuthState, DelegatedAgentsAPI, FaucetAPI, FeesAPI, MarginAccountsAPI, MarketAPI, MonacoSDK, Network, PitpassAPI, PortfolioAPI, PositionsAPI, ProfileAPI, PulseAPI, SDKConfig, SubAccountsAPI, 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 {
@@ -18,6 +18,7 @@ export declare class MonacoSDKImpl implements MonacoSDK {
18
18
  faucet: FaucetAPI;
19
19
  whitelist: WhitelistAPI;
20
20
  profile: ProfileAPI;
21
+ pulse: PulseAPI;
21
22
  orderbook: OrderbookAPIImpl;
22
23
  trades: TradesAPIImpl;
23
24
  ws: MonacoWebSocket;
package/dist/sdk.js CHANGED
@@ -12,6 +12,7 @@ import { PitpassAPIImpl } from "./api/pitpass";
12
12
  import { PortfolioAPIImpl } from "./api/portfolio";
13
13
  import { PositionsAPIImpl } from "./api/positions";
14
14
  import { ProfileAPIImpl } from "./api/profile";
15
+ import { PulseAPIImpl } from "./api/pulse";
15
16
  import { SubAccountsAPIImpl } from "./api/sub-accounts";
16
17
  import { TradingAPIImpl } from "./api/trading";
17
18
  import { VaultAPIImpl } from "./api/vault";
@@ -20,6 +21,13 @@ import { WithdrawalsAPIImpl } from "./api/withdrawals";
20
21
  import { generateSessionKeypair, privateKeyHex, publicKeyHex } from "./crypto/session";
21
22
  import { APIError, InvalidConfigError, InvalidStateError } from "./errors";
22
23
  import { resolveApiUrl, resolveWsUrl } from "./networks";
24
+ /**
25
+ * Default receipt-polling frequency for the SDK's internal viem public client.
26
+ * Sei produces sub-second blocks, so viem's 4000ms default adds seconds of
27
+ * slack to every `waitForTransactionReceipt`; 750ms keeps waits near one to
28
+ * two blocks while staying gentle on public RPC endpoints.
29
+ */
30
+ const DEFAULT_POLLING_INTERVAL_MS = 750;
23
31
  /** Validate a user-supplied URL override, returning it unchanged when valid. */
24
32
  function validateUrl(value, field) {
25
33
  try {
@@ -47,6 +55,7 @@ export class MonacoSDKImpl {
47
55
  faucet;
48
56
  whitelist;
49
57
  profile;
58
+ pulse;
50
59
  orderbook;
51
60
  trades;
52
61
  ws;
@@ -75,6 +84,7 @@ export class MonacoSDKImpl {
75
84
  this.subAccounts.setSessionKeypair(credentials);
76
85
  this.faucet.setSessionKeypair(credentials);
77
86
  this.whitelist.setSessionKeypair(credentials);
87
+ this.pulse.setSessionKeypair(credentials);
78
88
  this.profile.setSessionKeypair(credentials);
79
89
  this.orderbook.setSessionKeypair(credentials);
80
90
  this.trades.setSessionKeypair(credentials);
@@ -113,6 +123,7 @@ export class MonacoSDKImpl {
113
123
  this.subAccounts.setServerKey(serverKey);
114
124
  this.faucet.setServerKey(serverKey);
115
125
  this.whitelist.setServerKey(serverKey);
126
+ this.pulse.setServerKey(serverKey);
116
127
  this.profile.setServerKey(serverKey);
117
128
  this.orderbook.setServerKey(serverKey);
118
129
  this.trades.setServerKey(serverKey);
@@ -146,11 +157,15 @@ export class MonacoSDKImpl {
146
157
  // Use Sei mainnet chain only for "mainnet" network, testnet for everything else
147
158
  const chain = this.network === "mainnet" ? sei : seiTestnet;
148
159
  this.chain = chain;
160
+ if (cfg.pollingInterval !== undefined && (!Number.isFinite(cfg.pollingInterval) || cfg.pollingInterval <= 0)) {
161
+ throw new InvalidConfigError(`pollingInterval must be a positive number of milliseconds, got: ${cfg.pollingInterval}`, "pollingInterval");
162
+ }
149
163
  // Create public client with provided RPC URL
150
164
  const transport = http(cfg.seiRpcUrl);
151
165
  this.publicClient = createPublicClient({
152
166
  chain,
153
167
  transport,
168
+ pollingInterval: cfg.pollingInterval ?? DEFAULT_POLLING_INTERVAL_MS,
154
169
  });
155
170
  // Resolve the API URL: explicit override (validated) or network preset.
156
171
  const apiUrl = cfg.apiUrl ? validateUrl(cfg.apiUrl, "apiUrl") : resolveApiUrl(this.network);
@@ -170,6 +185,7 @@ export class MonacoSDKImpl {
170
185
  this.subAccounts = new SubAccountsAPIImpl(apiUrl);
171
186
  this.faucet = new FaucetAPIImpl(apiUrl);
172
187
  this.whitelist = new WhitelistAPIImpl(apiUrl);
188
+ this.pulse = new PulseAPIImpl(apiUrl);
173
189
  this.auth = new AuthAPIImpl(this.walletClient, this.chain, apiUrl);
174
190
  this.delegatedAgents = new DelegatedAgentsAPIImpl(apiUrl);
175
191
  this.fees = new FeesAPIImpl(apiUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "1.0.25",
3
+ "version": "1.0.26-develop.aeb6d5b",
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.25",
27
- "@0xmonaco/types": "1.0.25",
26
+ "@0xmonaco/contracts": "1.0.26-develop.aeb6d5b",
27
+ "@0xmonaco/types": "1.0.26-develop.aeb6d5b",
28
28
  "@noble/curves": "^1.9.1",
29
29
  "@noble/hashes": "^1.8.0",
30
30
  "http-status-codes": "^2.3.0"