@0xmonaco/core 1.0.45 → 1.0.47

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.
@@ -34,6 +34,7 @@ export declare const perpRoutes: {
34
34
  pageSize?: number;
35
35
  }) => string;
36
36
  readonly getTwap: (twapOrderId: string) => string;
37
+ readonly getTwapTca: (twapOrderId: string) => string;
37
38
  readonly cancelTwap: (twapOrderId: string) => string;
38
39
  };
39
40
  readonly delegatedAgents: {
@@ -32,6 +32,7 @@ export const perpRoutes = {
32
32
  createTwap: () => `${API_V1}/orders/twap`,
33
33
  listTwap: (params) => withQuery(`${API_V1}/orders/twap`, params),
34
34
  getTwap: (twapOrderId) => `${API_V1}/orders/twap/${encodeSegment(twapOrderId)}`,
35
+ getTwapTca: (twapOrderId) => `${API_V1}/orders/twap/${encodeSegment(twapOrderId)}/tca`,
35
36
  cancelTwap: (twapOrderId) => `${API_V1}/orders/twap/${encodeSegment(twapOrderId)}/cancel`,
36
37
  },
37
38
  delegatedAgents: {
@@ -21,7 +21,7 @@
21
21
  * );
22
22
  * ```
23
23
  */
24
- import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, CancelTwapOrderResponse, ConditionalOrder, CreateOrderResponse, CreateTwapOrderOptions, CreateTwapOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, ListTwapOrdersParams, ListTwapOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, PositionSide, ReplaceOrderResponse, TimeInForce, TradingAPI, TradingMode, TwapOrder } from "@0xmonaco/types";
24
+ import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, CancelTwapOrderResponse, ConditionalOrder, CreateOrderResponse, CreateTwapOrderOptions, CreateTwapOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, GetTwapOrderTcaResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, ListTwapOrdersParams, ListTwapOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, PositionSide, ReplaceOrderResponse, TimeInForce, TradingAPI, TradingMode, TwapOrder } from "@0xmonaco/types";
25
25
  import { BaseAPI } from "../base";
26
26
  export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
27
27
  /**
@@ -444,6 +444,28 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
444
444
  * ```
445
445
  */
446
446
  getTwapOrder(twapOrderId: string): Promise<TwapOrder>;
447
+ /**
448
+ * Gets the post-execution TCA (transaction cost analysis) report for a
449
+ * TERMINAL TWAP parent order.
450
+ *
451
+ * Available once the parent is COMPLETED or CANCELLED (a non-terminal
452
+ * parent returns 400). Reports realized average price against the arrival
453
+ * price and the window-TWAP benchmark over the parent's active window.
454
+ * Sign convention: positive bps = execution beat the reference. Benchmark
455
+ * fields are null when no price series covered the window sufficiently.
456
+ *
457
+ * @param twapOrderId - The TWAP parent order UUID
458
+ * @returns Promise resolving to the TCA report
459
+ * @throws {APIError} When API communication fails, the order is not found,
460
+ * or the order has not reached a terminal state
461
+ *
462
+ * @example
463
+ * ```typescript
464
+ * const tca = await tradingAPI.getTwapOrderTca(twapOrderId);
465
+ * console.log(`vs benchmark: ${tca.performanceVsBenchmarkBps} bps (${tca.benchmarkSource})`);
466
+ * ```
467
+ */
468
+ getTwapOrderTca(twapOrderId: string): Promise<GetTwapOrderTcaResponse>;
447
469
  /**
448
470
  * Lists TWAP parent orders for the authenticated user.
449
471
  *
@@ -668,6 +668,10 @@ export class TradingAPIImpl extends BaseAPI {
668
668
  strategyKey: options.strategyKey,
669
669
  useMasterBalance: options.useMasterBalance,
670
670
  clientTwapId: options.clientTwapId,
671
+ executionStyle: options.executionStyle,
672
+ triggerPrice: options.triggerPrice,
673
+ triggerDirection: options.triggerDirection,
674
+ conditionalExpiry: options.conditionalExpiry,
671
675
  };
672
676
  return await this.makeAuthenticatedRequest(perpRoutes.orders.createTwap(), {
673
677
  method: "POST",
@@ -720,6 +724,33 @@ export class TradingAPIImpl extends BaseAPI {
720
724
  method: "GET",
721
725
  });
722
726
  }
727
+ /**
728
+ * Gets the post-execution TCA (transaction cost analysis) report for a
729
+ * TERMINAL TWAP parent order.
730
+ *
731
+ * Available once the parent is COMPLETED or CANCELLED (a non-terminal
732
+ * parent returns 400). Reports realized average price against the arrival
733
+ * price and the window-TWAP benchmark over the parent's active window.
734
+ * Sign convention: positive bps = execution beat the reference. Benchmark
735
+ * fields are null when no price series covered the window sufficiently.
736
+ *
737
+ * @param twapOrderId - The TWAP parent order UUID
738
+ * @returns Promise resolving to the TCA report
739
+ * @throws {APIError} When API communication fails, the order is not found,
740
+ * or the order has not reached a terminal state
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * const tca = await tradingAPI.getTwapOrderTca(twapOrderId);
745
+ * console.log(`vs benchmark: ${tca.performanceVsBenchmarkBps} bps (${tca.benchmarkSource})`);
746
+ * ```
747
+ */
748
+ async getTwapOrderTca(twapOrderId) {
749
+ validate(CancelTwapOrderSchema, { twapOrderId });
750
+ return await this.makeAuthenticatedRequest(perpRoutes.orders.getTwapTca(twapOrderId), {
751
+ method: "GET",
752
+ });
753
+ }
723
754
  /**
724
755
  * Lists TWAP parent orders for the authenticated user.
725
756
  *
@@ -1,6 +1,5 @@
1
- import type { AccountEvent, ConditionalOrderEvent, Interval, LiquidationEvent, MarketStatsAllEvent, MarketStatsEvent, MonacoWebSocketConfig, OHLCVEvent, OHLCVPriceType, OrderbookEvent, OrderbookQuotationMode, OrderEvent, PositionEvent, SessionCredentials, TradeEvent, TradingMode, TwapOrderEvent, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
2
- export type { ResyncHandler, StatusHandler, WebSocketResyncInfo } from "@0xmonaco/types";
3
- export type MessageHandler<T> = (data: T) => void;
1
+ import type { MonacoWebSocketConfig, SessionCredentials } from "@0xmonaco/types";
2
+ export type { MessageHandler, MonacoWebSocket, ResyncHandler, StatusHandler, WebSocketResyncInfo } from "@0xmonaco/types";
4
3
  /**
5
4
  * Options for {@link createMonacoWebSocket}. The reconnect/resync knobs and
6
5
  * callbacks are shared with the main SDK config via {@link MonacoWebSocketConfig}
@@ -11,43 +10,3 @@ export interface MonacoWebSocketOptions extends MonacoWebSocketConfig {
11
10
  /** Session keypair for authenticated channels (orders, balances, etc.) */
12
11
  session?: SessionCredentials;
13
12
  }
14
- export interface MonacoWebSocket {
15
- /** Connect to the WebSocket server */
16
- connect: () => Promise<void>;
17
- /** Disconnect from the WebSocket server */
18
- disconnect: () => void;
19
- /** Check if currently connected */
20
- isConnected: () => boolean;
21
- /** Get current websocket connection status */
22
- getStatus: () => WebSocketStatus;
23
- /** Set (or clear) the session keypair used to authenticate the connection */
24
- setSessionKeypair: (credentials: SessionCredentials | undefined) => void;
25
- /** Subscribe to order events (requires authentication) */
26
- orders: (tradingPairId: string, tradingMode: TradingMode, handler: MessageHandler<OrderEvent>) => () => void;
27
- /** Subscribe to orderbook events (public) */
28
- orderbook: (tradingPairId: string, tradingMode: TradingMode, magnitude: number, quotationMode: OrderbookQuotationMode, handler: MessageHandler<OrderbookEvent>) => () => void;
29
- /** Subscribe to OHLCV candlestick events (public) */
30
- ohlcv: (tradingPairId: string, tradingMode: TradingMode, interval: Interval, handler: MessageHandler<OHLCVEvent>, priceType?: OHLCVPriceType) => () => void;
31
- /** Subscribe to trade events (public) */
32
- trades: (tradingPairId: string, handler: MessageHandler<TradeEvent>) => () => void;
33
- /** Subscribe to user movement events (requires authentication) */
34
- movements: (handler: MessageHandler<UserMovementEvent>) => () => void;
35
- /** Subscribe to user order events for all trading pairs (requires authentication) */
36
- userOrders: (handler: MessageHandler<OrderEvent>) => () => void;
37
- /** Subscribe to user balance events (requires authentication) */
38
- balances: (handler: MessageHandler<UserBalanceEvent>) => () => void;
39
- /** Subscribe to conditional TP/SL lifecycle events (requires authentication) */
40
- conditionalOrders: (handler: MessageHandler<ConditionalOrderEvent>, tradingPairId?: string) => () => void;
41
- /** Subscribe to TWAP parent-order lifecycle events (requires authentication) */
42
- twapOrders: (handler: MessageHandler<TwapOrderEvent>, tradingPairId?: string) => () => void;
43
- /** Subscribe to position state updates (requires authentication); omit `tradingPairId` for all pairs */
44
- positions: (handler: MessageHandler<PositionEvent>, tradingPairId?: string) => () => void;
45
- /** Subscribe to liquidation alerts (requires authentication); omit `tradingPairId` for all pairs */
46
- liquidations: (handler: MessageHandler<LiquidationEvent>, tradingPairId?: string) => () => void;
47
- /** Subscribe to account-level margin health updates for the authenticated user (requires authentication) */
48
- account: (handler: MessageHandler<AccountEvent>) => () => void;
49
- /** Subscribe to per-market statistics for one trading pair (public) */
50
- marketStats: (tradingPairId: string, handler: MessageHandler<MarketStatsEvent>) => () => void;
51
- /** Subscribe to the all-markets statistics summary (public) */
52
- marketStatsAll: (handler: MessageHandler<MarketStatsAllEvent>) => () => void;
53
- }
@@ -13,6 +13,7 @@ const CONDITIONAL_ORDER_TRIGGER_SOURCES = ["MARK_PRICE"];
13
13
  const CONDITIONAL_ORDER_STATES = ["PENDING_PARENT", "ACTIVE", "TRIGGERING", "TRIGGERED", "CANCELLED", "EXPIRED", "FAILED"];
14
14
  const CONDITIONAL_ORDER_ASSOCIATION_TYPES = ["POSITION", "PARENT_ORDER"];
15
15
  const TWAP_ORDER_STATES = ["PENDING", "ACTIVE", "COMPLETED", "CANCELLED"];
16
+ const INSTRUMENT_CHANGE_KINDS = ["listing", "delisting", "halt", "unhalt", "config_change"];
16
17
  function isRecord(value) {
17
18
  return typeof value === "object" && value !== null && !Array.isArray(value);
18
19
  }
@@ -39,6 +40,19 @@ function readBoolean(source, field) {
39
40
  }
40
41
  return value;
41
42
  }
43
+ /**
44
+ * Read a wire sequence counter: a non-negative safe integer. The server
45
+ * serializes Rust `u64` sequence fields as plain JSON numbers, so a negative,
46
+ * fractional, or unsafe (> 2^53 - 1) value is a malformed frame that would
47
+ * corrupt ordering/dedup logic — reject it rather than pass it through.
48
+ */
49
+ function readSequence(source, field) {
50
+ const value = source[field];
51
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
52
+ throw new Error(`Invalid WebSocket event: ${field} must be a non-negative safe integer`);
53
+ }
54
+ return value;
55
+ }
42
56
  function readOptionalNumber(source, field) {
43
57
  const value = source[field];
44
58
  if (value === undefined || value === null)
@@ -48,6 +62,13 @@ function readOptionalNumber(source, field) {
48
62
  }
49
63
  return value;
50
64
  }
65
+ function readStringArray(source, field) {
66
+ const value = source[field];
67
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
68
+ throw new Error(`Invalid WebSocket event: ${field} must be an array of strings`);
69
+ }
70
+ return value;
71
+ }
51
72
  function readEnum(source, field, allowed) {
52
73
  const value = readString(source, field);
53
74
  if (!allowed.includes(value)) {
@@ -186,6 +207,10 @@ function parseAccountEvent(rawData) {
186
207
  userId: readString(rawData, "user_id"),
187
208
  data: {
188
209
  marginAccountId: readString(data, "marginAccountId"),
210
+ // Monotonic per-account publish counter (0XM-2498): a plain JSON number on
211
+ // the wire, used to order/dedupe frames across reconnect races. Must be a
212
+ // non-negative safe integer — anything else is a malformed frame.
213
+ sequence: readSequence(data, "sequence"),
189
214
  equity: readString(data, "equity"),
190
215
  collateral: readString(data, "collateral"),
191
216
  freeCollateral: readString(data, "freeCollateral"),
@@ -269,6 +294,42 @@ function parseMarketStatsAllEvent(rawData) {
269
294
  markets: rawData.markets.map(parseMarketStatsSummary),
270
295
  };
271
296
  }
297
+ function parseInstrumentEventData(raw) {
298
+ if (!isRecord(raw)) {
299
+ throw new Error("Invalid instrument event: data must be an object");
300
+ }
301
+ const data = keysToCamelCase(raw);
302
+ return {
303
+ change: readEnum(data, "change", INSTRUMENT_CHANGE_KINDS),
304
+ tradingPairId: readString(data, "tradingPairId"),
305
+ symbol: readString(data, "symbol"),
306
+ isActive: readBoolean(data, "isActive"),
307
+ tickSize: readString(data, "tickSize"),
308
+ quantityStepSize: readString(data, "quantityStepSize"),
309
+ minOrderSize: readString(data, "minOrderSize"),
310
+ maxOrderSize: readString(data, "maxOrderSize"),
311
+ marketType: readString(data, "marketType"),
312
+ category: readString(data, "category"),
313
+ maxLeverage: readOptionalString(data, "maxLeverage"),
314
+ initialMarginRate: readOptionalString(data, "initialMarginRate"),
315
+ maintenanceMarginRate: readOptionalString(data, "maintenanceMarginRate"),
316
+ // Values are snake_case wire field NAMES (e.g. "tick_size"); only object
317
+ // keys are camelCased, so they pass through untouched.
318
+ changedFields: readStringArray(data, "changedFields"),
319
+ updatedAt: readString(data, "updatedAt"),
320
+ };
321
+ }
322
+ function parseInstrumentEvent(rawData) {
323
+ if (!isRecord(rawData)) {
324
+ throw new Error("Invalid instrument event: payload must be an object");
325
+ }
326
+ // The frame's top-level `trading_pair_id` is not inside `data`, so it stays
327
+ // snake_case (only `data` is camelCased by parseInstrumentEventData).
328
+ return {
329
+ tradingPairId: readString(rawData, "trading_pair_id"),
330
+ data: parseInstrumentEventData(rawData.data),
331
+ };
332
+ }
272
333
  /**
273
334
  * Parse a `twap_orders` channel frame into a typed event. Only the envelope
274
335
  * and the snapshot's identity/state fields are validated strictly — the rest
@@ -337,6 +398,23 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
337
398
  let pendingResyncCode = null;
338
399
  // Handler storage: channel -> set of handlers (supports multiple subscribers per channel)
339
400
  const handlers = new Map();
401
+ // Subscribe-time Snapshot frame handlers, keyed by the RAW subscribed
402
+ // channel: the server echoes that exact channel on the Snapshot frame
403
+ // (unlike live Event frames, which can arrive on a more specific channel and
404
+ // are prefix-matched). Only subscriptions that opt in receive them — a
405
+ // snapshot's data shape differs from the channel's Event shape, so blindly
406
+ // reusing the Event handlers would feed every other channel's parser a frame
407
+ // it must reject.
408
+ const snapshotHandlers = new Map();
409
+ // Latest known full-config frame per market for each subscribed instrument
410
+ // channel, seeded by the subscribe-time Snapshot and superseded by every
411
+ // live frame (instrument frames are terminal-state, never deltas). The
412
+ // server sends a snapshot only for a genuinely new wire subscription, so a
413
+ // late local subscriber on an already-held channel bootstraps from this
414
+ // cache instead of starting blind. Dropped when the channel's last local
415
+ // subscriber unsubscribes — the next first subscriber triggers a fresh wire
416
+ // Subscribe whose snapshot is authoritative, never the stale cache.
417
+ const instrumentBaseline = new Map();
340
418
  const getStatus = () => {
341
419
  if (!ws)
342
420
  return reconnectTimer ? "reconnecting" : "disconnected";
@@ -431,6 +509,11 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
431
509
  }
432
510
  };
433
511
  const resubscribeAll = () => {
512
+ // Every re-sent Subscribe is genuinely new to the server (new
513
+ // connection), so fresh authoritative snapshots are inbound: the
514
+ // pre-outage instrument cache is stale — markets may have been delisted
515
+ // during the gap — and must not be replayed to late subscribers.
516
+ instrumentBaseline.clear();
434
517
  const channels = Array.from(handlers.keys());
435
518
  if (channels.length > 0) {
436
519
  send({ type: "Subscribe", channels });
@@ -460,7 +543,17 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
460
543
  return frameChannel === subscriptionChannel || frameChannel.startsWith(`${subscriptionChannel}:`);
461
544
  };
462
545
  const routeMessage = (msg) => {
463
- if (msg.type !== "Event" || !msg.channel)
546
+ if (!msg.channel)
547
+ return;
548
+ if (msg.type === "Snapshot") {
549
+ // Exact match only: the Snapshot frame's channel echoes the raw
550
+ // subscribed channel, never a more specific one.
551
+ for (const handler of snapshotHandlers.get(msg.channel) ?? []) {
552
+ handler(msg.data);
553
+ }
554
+ return;
555
+ }
556
+ if (msg.type !== "Event")
464
557
  return;
465
558
  // Find matching handlers and call all of them
466
559
  for (const [subscriptionChannel, channelHandlers] of handlers) {
@@ -579,23 +672,38 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
579
672
  stopHeartbeat();
580
673
  pendingResyncCode = null;
581
674
  handlers.clear();
675
+ snapshotHandlers.clear();
676
+ instrumentBaseline.clear();
582
677
  ws?.close(1000, "Client disconnect");
583
678
  ws = null;
584
679
  options.onStatusChange?.("disconnected");
585
680
  };
586
- const subscribe = (channel, handler) => {
681
+ const subscribe = (channel, handler, snapshotHandler) => {
587
682
  const isFirstSubscriber = !handlers.has(channel);
588
683
  // Add handler to the set for this channel
589
684
  if (!handlers.has(channel)) {
590
685
  handlers.set(channel, new Set());
591
686
  }
592
687
  handlers.get(channel)?.add(handler);
688
+ if (snapshotHandler) {
689
+ if (!snapshotHandlers.has(channel)) {
690
+ snapshotHandlers.set(channel, new Set());
691
+ }
692
+ snapshotHandlers.get(channel)?.add(snapshotHandler);
693
+ }
593
694
  // Only send Subscribe message on first subscriber for this channel
594
695
  if (isFirstSubscriber && ws?.readyState === WebSocket.OPEN) {
595
696
  send({ type: "Subscribe", channels: [channel] });
596
697
  }
597
698
  // Return unsubscribe function
598
699
  return () => {
700
+ if (snapshotHandler) {
701
+ const channelSnapshotHandlers = snapshotHandlers.get(channel);
702
+ channelSnapshotHandlers?.delete(snapshotHandler);
703
+ if (channelSnapshotHandlers?.size === 0) {
704
+ snapshotHandlers.delete(channel);
705
+ }
706
+ }
599
707
  const channelHandlers = handlers.get(channel);
600
708
  if (channelHandlers) {
601
709
  channelHandlers.delete(handler);
@@ -952,6 +1060,110 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
952
1060
  }
953
1061
  });
954
1062
  };
1063
+ /**
1064
+ * Subscribe to market lifecycle + configuration changes (public): listings,
1065
+ * delistings, and edits to tick size, step size, order-size bounds, or (for
1066
+ * perp markets) leverage/margin-rate bounds. Omit `tradingPairId` for all
1067
+ * markets, or pass one to filter. Every frame carries the market's FULL
1068
+ * current config as a terminal-state snapshot (never an additive delta);
1069
+ * `changedFields` names what the mutation touched. A subscribe-time snapshot
1070
+ * of the current active-pair list arrives before the live diffs; a
1071
+ * subscriber joining a channel this client already holds is replayed the
1072
+ * current baseline from a local cache instead (the server snapshots only
1073
+ * genuinely new wire subscriptions). Lag policy is Conflate: a lagging
1074
+ * subscriber is fast-forwarded, no gap signal.
1075
+ */
1076
+ const subscribeInstruments = (handler, tradingPairId) => {
1077
+ if (!handler) {
1078
+ throw new Error("instruments subscription requires a handler");
1079
+ }
1080
+ if (tradingPairId !== undefined && tradingPairId.length === 0) {
1081
+ throw new Error("instruments tradingPairId cannot be empty");
1082
+ }
1083
+ // Every live frame is published on instrument:<pairId>; a bare
1084
+ // `instrument` subscription receives all of them via routeMessage's prefix
1085
+ // match, so unlike market_stats there is no distinct all-markets frame
1086
+ // shape to guard against.
1087
+ const channel = tradingPairId ? `instrument:${tradingPairId}` : "instrument";
1088
+ const cacheEvent = (event) => {
1089
+ let byPair = instrumentBaseline.get(channel);
1090
+ if (!byPair) {
1091
+ byPair = new Map();
1092
+ instrumentBaseline.set(channel, byPair);
1093
+ }
1094
+ byPair.set(event.tradingPairId, event);
1095
+ };
1096
+ // A subscriber joining a channel this connection already holds gets no
1097
+ // fresh server snapshot (the wire Subscribe is suppressed, and the server
1098
+ // snapshots only genuinely new subscriptions), so replay the cached
1099
+ // baseline to it instead. Asynchronously, matching how a real snapshot
1100
+ // arrives — never during the subscribe call itself.
1101
+ const isLateSubscriber = handlers.has(channel);
1102
+ let unsubscribed = false;
1103
+ const unsubscribe = subscribe(channel, (rawData) => {
1104
+ try {
1105
+ const event = parseInstrumentEvent(rawData);
1106
+ cacheEvent(event);
1107
+ handler(event);
1108
+ }
1109
+ catch (err) {
1110
+ console.error("WebSocket: Error processing instrument event", err);
1111
+ }
1112
+ },
1113
+ // The subscribe-time baseline arrives as one Snapshot frame whose data is
1114
+ // a flat InstrumentEventData[] (a `listing` per active market, no
1115
+ // per-item envelope). Fan it out as one event per market so the handler
1116
+ // sees the same shape for baseline and live frames.
1117
+ (rawData) => {
1118
+ try {
1119
+ if (!Array.isArray(rawData)) {
1120
+ throw new Error("Invalid instrument snapshot: data must be an array");
1121
+ }
1122
+ const events = rawData.map(parseInstrumentEventData).map((item) => ({ tradingPairId: item.tradingPairId, data: item }));
1123
+ // A snapshot is the authoritative active-market set: replace the
1124
+ // channel's cache wholesale rather than upserting, so markets absent
1125
+ // from it (delisted while disconnected) don't linger.
1126
+ instrumentBaseline.set(channel, new Map(events.map((event) => [event.tradingPairId, event])));
1127
+ for (const event of events) {
1128
+ handler(event);
1129
+ }
1130
+ }
1131
+ catch (err) {
1132
+ console.error("WebSocket: Error processing instrument snapshot", err);
1133
+ }
1134
+ });
1135
+ // Replay only while the socket is open: during abnormal-close backoff the
1136
+ // handlers and cache survive but the state is pre-outage — the reconnect's
1137
+ // resubscribe clears it and a fresh authoritative snapshot follows, so a
1138
+ // backoff-time subscriber must wait for that instead.
1139
+ if (isLateSubscriber && ws?.readyState === WebSocket.OPEN) {
1140
+ const cached = instrumentBaseline.get(channel);
1141
+ if (cached && cached.size > 0) {
1142
+ const replay = [...cached.values()];
1143
+ queueMicrotask(() => {
1144
+ if (unsubscribed)
1145
+ return;
1146
+ for (const event of replay) {
1147
+ try {
1148
+ handler(event);
1149
+ }
1150
+ catch (err) {
1151
+ console.error("WebSocket: Error processing instrument baseline replay", err);
1152
+ }
1153
+ }
1154
+ });
1155
+ }
1156
+ }
1157
+ return () => {
1158
+ unsubscribed = true;
1159
+ unsubscribe();
1160
+ // Last local subscriber gone: the wire Unsubscribe was sent, so the
1161
+ // cache would only go stale from here — drop it.
1162
+ if (!handlers.has(channel)) {
1163
+ instrumentBaseline.delete(channel);
1164
+ }
1165
+ };
1166
+ };
955
1167
  /**
956
1168
  * Subscribe to per-market statistics for a single trading pair (public). Each
957
1169
  * frame carries the market's full current stats as a terminal-state snapshot.
@@ -1029,6 +1241,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
1029
1241
  positions: subscribePositions,
1030
1242
  liquidations: subscribeLiquidations,
1031
1243
  account: subscribeAccount,
1244
+ instruments: subscribeInstruments,
1032
1245
  marketStats: subscribeMarketStats,
1033
1246
  marketStatsAll: subscribeMarketStatsAll,
1034
1247
  };
@@ -13,6 +13,7 @@ export declare const COVERED: {
13
13
  cancel_twap_order: string;
14
14
  create_twap_order: string;
15
15
  get_twap_order: string;
16
+ get_twap_order_tca: string;
16
17
  list_twap_orders: string;
17
18
  close_position: string;
18
19
  create_challenge: string;
package/dist/coverage.js CHANGED
@@ -13,6 +13,7 @@ export const COVERED = {
13
13
  cancel_twap_order: "cancelTwapOrder",
14
14
  create_twap_order: "createTwapOrder",
15
15
  get_twap_order: "getTwapOrder",
16
+ get_twap_order_tca: "getTwapOrderTca",
16
17
  list_twap_orders: "listTwapOrders",
17
18
  close_position: "closePosition",
18
19
  create_challenge: "createChallenge",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
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.45",
23
- "@0xmonaco/types": "1.0.45",
22
+ "@0xmonaco/contracts": "1.0.47",
23
+ "@0xmonaco/types": "1.0.47",
24
24
  "@noble/curves": "^1.9.1",
25
25
  "@noble/hashes": "^1.8.0",
26
26
  "http-status-codes": "^2.3.0"