@0xmonaco/core 1.0.74 → 1.0.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -315,11 +315,10 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
315
315
  * Gets orders based on query parameters, cursor-paginated by default.
316
316
  *
317
317
  * Retrieves orders with optional filtering by status, trading pair, and pagination.
318
- * Every kind of order comes back in ONE stream ordered by timestamp: book orders,
319
- * TWAP parents (`orderType: "TWAP"`, detail on `twap`) and conditional orders
320
- * (`orderType: "CONDITIONAL"`, detail on `conditional`). There is no separate
321
- * open-orders call — filter `status` to `["SUBMITTED", "PARTIALLY_FILLED"]` for the
322
- * working set.
318
+ * Every row is a BOOK order (`LIMIT` or `MARKET`); filter `status` to
319
+ * `["SUBMITTED", "PARTIALLY_FILLED"]` for the resting working set. TWAP parents and
320
+ * conditional (TP/SL) orders are not rows here — list them with `listTwapOrders`
321
+ * and `listConditionalOrders`.
323
322
  * When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
324
323
  * pagination: it starts a cursor walk from the newest (or oldest, for
325
324
  * `orderBy: "ASC"`) row and the response's `nextPageToken` resumes it. Both modes
@@ -342,13 +341,9 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
342
341
  *
343
342
  * @example
344
343
  * ```typescript
345
- * // Every working order, of every kind, in one page
344
+ * // The resting working set (book orders only)
346
345
  * const open = await tradingAPI.getPaginatedOrders({ status: ["SUBMITTED", "PARTIALLY_FILLED"] });
347
- * for (const order of open.orders) {
348
- * if (order.orderType === "TWAP") console.log("TWAP parent", order.twap?.slicesPlaced);
349
- * else if (order.orderType === "CONDITIONAL") console.log("trigger", order.conditional?.triggerPrice);
350
- * else console.log("resting", order.price);
351
- * }
346
+ * for (const order of open.orders) console.log("resting", order.quantity, "@", order.price);
352
347
  * ```
353
348
  *
354
349
  * @example
@@ -495,11 +495,10 @@ export class TradingAPIImpl extends BaseAPI {
495
495
  * Gets orders based on query parameters, cursor-paginated by default.
496
496
  *
497
497
  * Retrieves orders with optional filtering by status, trading pair, and pagination.
498
- * Every kind of order comes back in ONE stream ordered by timestamp: book orders,
499
- * TWAP parents (`orderType: "TWAP"`, detail on `twap`) and conditional orders
500
- * (`orderType: "CONDITIONAL"`, detail on `conditional`). There is no separate
501
- * open-orders call — filter `status` to `["SUBMITTED", "PARTIALLY_FILLED"]` for the
502
- * working set.
498
+ * Every row is a BOOK order (`LIMIT` or `MARKET`); filter `status` to
499
+ * `["SUBMITTED", "PARTIALLY_FILLED"]` for the resting working set. TWAP parents and
500
+ * conditional (TP/SL) orders are not rows here — list them with `listTwapOrders`
501
+ * and `listConditionalOrders`.
503
502
  * When neither `page` nor `pageToken` is passed, the SDK drives cursor (pageToken)
504
503
  * pagination: it starts a cursor walk from the newest (or oldest, for
505
504
  * `orderBy: "ASC"`) row and the response's `nextPageToken` resumes it. Both modes
@@ -522,13 +521,9 @@ export class TradingAPIImpl extends BaseAPI {
522
521
  *
523
522
  * @example
524
523
  * ```typescript
525
- * // Every working order, of every kind, in one page
524
+ * // The resting working set (book orders only)
526
525
  * const open = await tradingAPI.getPaginatedOrders({ status: ["SUBMITTED", "PARTIALLY_FILLED"] });
527
- * for (const order of open.orders) {
528
- * if (order.orderType === "TWAP") console.log("TWAP parent", order.twap?.slicesPlaced);
529
- * else if (order.orderType === "CONDITIONAL") console.log("trigger", order.conditional?.triggerPrice);
530
- * else console.log("resting", order.price);
531
- * }
526
+ * for (const order of open.orders) console.log("resting", order.quantity, "@", order.price);
532
527
  * ```
533
528
  *
534
529
  * @example
@@ -245,6 +245,9 @@ function parsePositionEventData(raw) {
245
245
  orderId: readOptionalString(data, "orderId"),
246
246
  reason: readString(data, "reason"),
247
247
  updatedAt: readString(data, "updatedAt"),
248
+ // Optional: an older deployment does not send it, and the server omits it
249
+ // when it does not know when the position opened.
250
+ openedAt: readOptionalString(data, "openedAt"),
248
251
  // Opaque durable row version (0XM-2673). `readOptionalSequence`, not
249
252
  // `readOptionalNumber`: this is an ordering counter, so a fractional,
250
253
  // negative, or unsafe-integer value would silently corrupt every
@@ -269,6 +272,42 @@ function parsePositionEvent(rawData) {
269
272
  data: parsePositionEventData(rawData.data),
270
273
  };
271
274
  }
275
+ /** The `positions` channel's valuation frame discriminator. */
276
+ const POSITION_VALUATION_EVENT_TYPE = "position_valuation";
277
+ /**
278
+ * Whether a `positions` channel frame is a periodic valuation rather than a
279
+ * lifecycle update. Checked on the raw discriminator BEFORE any lifecycle
280
+ * field is read, so a valuation never reaches the lifecycle parser.
281
+ */
282
+ function isPositionValuationFrame(rawData) {
283
+ return isRecord(rawData) && rawData.event_type === POSITION_VALUATION_EVENT_TYPE;
284
+ }
285
+ function parsePositionValuationEvent(rawData) {
286
+ if (!isRecord(rawData)) {
287
+ throw new Error("Invalid position valuation event: payload must be an object");
288
+ }
289
+ if (!isRecord(rawData.data)) {
290
+ throw new Error("Invalid position valuation event: data must be an object");
291
+ }
292
+ const data = keysToCamelCase(rawData.data);
293
+ // Always present on the wire; `null` when the server cannot derive one.
294
+ const liquidationPrice = data.liquidationPrice === null ? null : readString(data, "liquidationPrice");
295
+ return {
296
+ eventType: readEnum(rawData, "event_type", [POSITION_VALUATION_EVENT_TYPE]),
297
+ userId: readString(rawData, "user_id"),
298
+ data: {
299
+ positionId: readString(data, "positionId"),
300
+ marginAccountId: readString(data, "marginAccountId"),
301
+ tradingPairId: readString(data, "tradingPairId"),
302
+ markPrice: readString(data, "markPrice"),
303
+ unrealizedPnl: readString(data, "unrealizedPnl"),
304
+ liquidationPrice,
305
+ initialMarginRequired: readString(data, "initialMarginRequired"),
306
+ maintenanceMarginRequired: readString(data, "maintenanceMarginRequired"),
307
+ valuedAt: readString(data, "valuedAt"),
308
+ },
309
+ };
310
+ }
272
311
  /**
273
312
  * Parse a liquidation alert's payload. Shared by the live event and the
274
313
  * subscribe-time snapshot (see {@link parseConditionalOrderEventData}).
@@ -772,6 +811,10 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
772
811
  let ws = null;
773
812
  let session = options.session;
774
813
  let reconnectAttempts = 0;
814
+ // Whether a socket has opened since the client was created or last
815
+ // disconnect()ed, so a status consumer can tell a first connect that keeps
816
+ // failing from a lost working connection.
817
+ let hasConnected = false;
775
818
  let reconnectTimer = null;
776
819
  let heartbeatTimer = null;
777
820
  let autoReconnect = options.autoReconnect ?? true;
@@ -823,7 +866,9 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
823
866
  return reconnectTimer ? "reconnecting" : "disconnected";
824
867
  switch (ws.readyState) {
825
868
  case WebSocket.CONNECTING:
826
- return "connecting";
869
+ // A retry's socket is still inside the outage the last status
870
+ // reported, so it reads as reconnecting, not connecting.
871
+ return reconnectAttempts > 0 ? "reconnecting" : "connecting";
827
872
  case WebSocket.OPEN:
828
873
  return "connected";
829
874
  default:
@@ -833,6 +878,43 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
833
878
  }
834
879
  };
835
880
  const getUrl = () => baseUrl;
881
+ /**
882
+ * The single place a status change is reported. Recovery must not hinge on
883
+ * the application's observer, so an error it throws is logged, not
884
+ * propagated into the connection lifecycle. An async observer is assignable
885
+ * to the void-returning handler type, so its rejection is logged the same
886
+ * way rather than left unhandled.
887
+ */
888
+ const emitStatus = (status, nextRetryDelayMs, attempt = reconnectAttempts) => {
889
+ const info = { attempt, hasConnected };
890
+ if (nextRetryDelayMs !== undefined) {
891
+ info.nextRetryDelayMs = nextRetryDelayMs;
892
+ }
893
+ const logObserverError = (err) => console.error("WebSocket: onStatusChange threw", err);
894
+ try {
895
+ const result = options.onStatusChange?.(status, info);
896
+ // Any thenable, not just this realm's Promise: an async handler from
897
+ // another realm (an iframe) returns a promise `instanceof` misses.
898
+ if (typeof result?.then === "function") {
899
+ Promise.resolve(result).catch(logObserverError);
900
+ }
901
+ }
902
+ catch (err) {
903
+ logObserverError(err);
904
+ }
905
+ };
906
+ /**
907
+ * Report a terminal "disconnected", which ends the retry cycle so the next
908
+ * connect() reports "connecting" with a fresh retry budget. The counter is
909
+ * reset before the observer runs, so a connect() made from inside the
910
+ * callback already starts the new cycle; the status still reports the
911
+ * retries the cycle ran.
912
+ */
913
+ const emitTerminalDisconnected = () => {
914
+ const attempt = reconnectAttempts;
915
+ reconnectAttempts = 0;
916
+ emitStatus("disconnected", undefined, attempt);
917
+ };
836
918
  /**
837
919
  * Send the signed auth handshake as the first message after the socket
838
920
  * opens. The client signs `WS-AUTH\n<pubkey>\n<ts>` with the session private
@@ -876,13 +958,15 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
876
958
  reconnectTimer = null;
877
959
  }
878
960
  };
961
+ // Whether the retry cycle may schedule another attempt.
962
+ const retryBudgetLeft = () => autoReconnect && reconnectAttempts < maxReconnectAttempts;
879
963
  const scheduleReconnect = () => {
880
964
  // Single scheduling authority: a failed attempt can report through both
881
965
  // the connect() rejection and the socket's own close event — an armed
882
966
  // timer means the retry is already owned.
883
967
  if (reconnectTimer)
884
968
  return;
885
- if (!autoReconnect || reconnectAttempts >= maxReconnectAttempts) {
969
+ if (!retryBudgetLeft()) {
886
970
  console.error("WebSocket: Max reconnect attempts reached");
887
971
  return;
888
972
  }
@@ -902,10 +986,14 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
902
986
  return;
903
987
  connect().catch((err) => {
904
988
  console.warn("WebSocket: Failed to reconnect:", err);
905
- scheduleReconnect();
989
+ // Not when the failure already ended the retry cycle (the last
990
+ // retry's socket could not be constructed).
991
+ if (reconnectAttempts > 0) {
992
+ scheduleReconnect();
993
+ }
906
994
  });
907
995
  }, delay);
908
- options.onStatusChange?.("reconnecting");
996
+ emitStatus("reconnecting", delay);
909
997
  };
910
998
  const send = (data) => {
911
999
  if (ws?.readyState === WebSocket.OPEN) {
@@ -1035,6 +1123,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1035
1123
  return;
1036
1124
  }
1037
1125
  reconnectAttempts = 0;
1126
+ hasConnected = true;
1038
1127
  // A retry armed before this open (e.g. a manual connect() during
1039
1128
  // backoff) is moot now — left running it would suppress the
1040
1129
  // terminal disconnected emit and reopen a socket after a clean
@@ -1045,7 +1134,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1045
1134
  // (orders, balances, …) are authorized when the Subscribe arrives.
1046
1135
  sendAuthenticate();
1047
1136
  resubscribeAll();
1048
- options.onStatusChange?.("connected");
1137
+ emitStatus("connected");
1049
1138
  // Resync-join order: the live stream resumes first (auth +
1050
1139
  // subscribe above), then the application is asked to refetch
1051
1140
  // snapshots and reconcile against each channel's authoritative
@@ -1118,13 +1207,8 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1118
1207
  if (pendingResyncCode === null) {
1119
1208
  pendingResyncCode = event.code;
1120
1209
  }
1121
- try {
1122
- options.onStatusChange?.("disconnected");
1123
- }
1124
- finally {
1125
- // Recovery must not hinge on the status observer not throwing.
1126
- options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
1127
- }
1210
+ emitStatus("disconnected");
1211
+ options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
1128
1212
  return;
1129
1213
  }
1130
1214
  // The app already handed over a replacement session on this socket.
@@ -1135,14 +1219,10 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1135
1219
  if (pendingResyncCode === null) {
1136
1220
  pendingResyncCode = event.code;
1137
1221
  }
1138
- try {
1139
- options.onStatusChange?.("disconnected");
1140
- }
1141
- finally {
1142
- connect().catch((err) => {
1143
- console.warn("WebSocket: Failed to connect with the replacement session:", err);
1144
- });
1145
- }
1222
+ emitStatus("disconnected");
1223
+ connect().catch((err) => {
1224
+ console.warn("WebSocket: Failed to connect with the replacement session:", err);
1225
+ });
1146
1226
  return;
1147
1227
  }
1148
1228
  // Reconnect on abnormal close. 1013 is the server's slow-client
@@ -1159,13 +1239,26 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1159
1239
  // "disconnected" is terminal: suppressed while a retry is armed
1160
1240
  // (scheduleReconnect emitted "reconnecting" instead).
1161
1241
  if (!reconnectTimer) {
1162
- options.onStatusChange?.("disconnected");
1242
+ emitTerminalDisconnected();
1163
1243
  }
1164
1244
  };
1165
1245
  socket.onerror = () => clearTimeout(timeout);
1246
+ // A retry's socket stays inside the outage "reconnecting" already
1247
+ // reported; only a connection opened outside a retry cycle is new.
1248
+ if (reconnectAttempts === 0) {
1249
+ emitStatus("connecting");
1250
+ }
1166
1251
  }
1167
1252
  catch (err) {
1168
- options.onStatusChange?.("disconnected");
1253
+ // The socket could not be constructed. Inside a retry cycle the retry
1254
+ // timer schedules the next attempt while the budget lasts; otherwise
1255
+ // nothing follows, so this ends the cycle.
1256
+ if (reconnectTimer || (reconnectAttempts > 0 && retryBudgetLeft())) {
1257
+ emitStatus("disconnected");
1258
+ }
1259
+ else {
1260
+ emitTerminalDisconnected();
1261
+ }
1169
1262
  reject(err);
1170
1263
  }
1171
1264
  });
@@ -1180,7 +1273,8 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1180
1273
  instrumentBaseline.clear();
1181
1274
  ws?.close(1000, "Client disconnect");
1182
1275
  ws = null;
1183
- options.onStatusChange?.("disconnected");
1276
+ hasConnected = false;
1277
+ emitTerminalDisconnected();
1184
1278
  };
1185
1279
  const subscribe = (channel, handler, snapshotHandler) => {
1186
1280
  const isFirstSubscriber = !handlers.has(channel);
@@ -1522,8 +1616,11 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1522
1616
  * handshake. Omit `tradingPairId` for all pairs, or pass one to filter.
1523
1617
  * Detected broadcast lag closes the connection with 1013; reconcile
1524
1618
  * missed keys and terminal updates on reconnect.
1619
+ *
1620
+ * The channel also carries periodic `position_valuation` frames. Those go to
1621
+ * `onValuation` only, never to `handler`, and are ignored without one.
1525
1622
  */
1526
- const subscribePositions = (handler, tradingPairId, onSnapshot) => {
1623
+ const subscribePositions = (handler, tradingPairId, onSnapshot, onValuation) => {
1527
1624
  if (!handler) {
1528
1625
  throw new Error("positions subscription requires a handler");
1529
1626
  }
@@ -1532,6 +1629,17 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1532
1629
  }
1533
1630
  const channel = tradingPairId ? `positions:${tradingPairId}` : "positions";
1534
1631
  return subscribe(channel, (rawData) => {
1632
+ if (isPositionValuationFrame(rawData)) {
1633
+ if (!onValuation)
1634
+ return;
1635
+ try {
1636
+ onValuation(parsePositionValuationEvent(rawData));
1637
+ }
1638
+ catch (err) {
1639
+ console.error("WebSocket: Error processing position valuation event", err);
1640
+ }
1641
+ return;
1642
+ }
1535
1643
  try {
1536
1644
  handler(parsePositionEvent(rawData));
1537
1645
  }
@@ -137,6 +137,8 @@ export declare const COVERED: {
137
137
  close_sub_account: string;
138
138
  get_margin_account_pnl: string;
139
139
  transfer_collateral: string;
140
+ transfer_collateral_to_sub_account: string;
141
+ transfer_collateral_from_sub_account: string;
140
142
  claim_buildercode_rewards: string;
141
143
  set_builder_fee: string;
142
144
  transfer_rewards: string;
package/dist/coverage.js CHANGED
@@ -137,6 +137,8 @@ export const COVERED = {
137
137
  close_sub_account: "closeMarginSubAccount",
138
138
  get_margin_account_pnl: "getMarginAccountPnl",
139
139
  transfer_collateral: "transferMarginCollateral",
140
+ transfer_collateral_to_sub_account: "transferCollateralToSubAccount",
141
+ transfer_collateral_from_sub_account: "transferCollateralFromSubAccount",
140
142
  claim_buildercode_rewards: "buildercodes.claimBuildercodeRewards",
141
143
  set_builder_fee: "buildercodes.setBuilderFee",
142
144
  transfer_rewards: "pitpass.transferRewards",
package/dist/sdk.d.ts CHANGED
@@ -36,24 +36,6 @@ export declare class MonacoSDKImpl implements MonacoSDK {
36
36
  * the WebSocket client.
37
37
  */
38
38
  private propagateSession;
39
- /**
40
- * Set (or clear) the application secret key used for backend-authenticated
41
- * requests (those annotated `#[require_backend]` on the gateway, e.g. the
42
- * `applications` reporting endpoints).
43
- *
44
- * The raw `sk_...` key is sent in the `x-server-key` header on each such
45
- * request. This is independent of {@link login}/session auth — a client may
46
- * hold both a session and a server key at once.
47
- *
48
- * @param serverKey - The application secret key (`sk_...`), or `undefined` to clear.
49
- *
50
- * @example
51
- * ```typescript
52
- * sdk.setServerKey("sk_live_...");
53
- * const orders = await sdk.applications.listApplicationOrders({ status: "FILLED" });
54
- * ```
55
- */
56
- setServerKey(serverKey: string | undefined): void;
57
39
  /** Extract the session keypair from an auth state. */
58
40
  private sessionFromAuthState;
59
41
  constructor(cfg: SDKConfig);
package/dist/sdk.js CHANGED
@@ -103,48 +103,6 @@ export class MonacoSDKImpl {
103
103
  this.trades.setSessionKeypair(credentials);
104
104
  this.ws.setSessionKeypair(credentials);
105
105
  }
106
- /**
107
- * Set (or clear) the application secret key used for backend-authenticated
108
- * requests (those annotated `#[require_backend]` on the gateway, e.g. the
109
- * `applications` reporting endpoints).
110
- *
111
- * The raw `sk_...` key is sent in the `x-server-key` header on each such
112
- * request. This is independent of {@link login}/session auth — a client may
113
- * hold both a session and a server key at once.
114
- *
115
- * @param serverKey - The application secret key (`sk_...`), or `undefined` to clear.
116
- *
117
- * @example
118
- * ```typescript
119
- * sdk.setServerKey("sk_live_...");
120
- * const orders = await sdk.applications.listApplicationOrders({ status: "FILLED" });
121
- * ```
122
- */
123
- setServerKey(serverKey) {
124
- this.auth.setServerKey(serverKey);
125
- this.delegatedAgents.setServerKey(serverKey);
126
- this.applications.setServerKey(serverKey);
127
- this.fees.setServerKey(serverKey);
128
- this.buildercodes.setServerKey(serverKey);
129
- this.copyTrading.setServerKey(serverKey);
130
- this.pitpass.setServerKey(serverKey);
131
- this.vault.setServerKey(serverKey);
132
- this.withdrawals.setServerKey(serverKey);
133
- this.trading.setServerKey(serverKey);
134
- this.market.setServerKey(serverKey);
135
- this.marginAccounts.setServerKey(serverKey);
136
- this.positions.setServerKey(serverKey);
137
- this.portfolio.setServerKey(serverKey);
138
- this.subAccounts.setServerKey(serverKey);
139
- this.faucet.setServerKey(serverKey);
140
- this.sweeper.setServerKey(serverKey);
141
- this.managedMarkets.setServerKey(serverKey);
142
- this.whitelist.setServerKey(serverKey);
143
- this.pulse.setServerKey(serverKey);
144
- this.profile.setServerKey(serverKey);
145
- this.orderbook.setServerKey(serverKey);
146
- this.trades.setServerKey(serverKey);
147
- }
148
106
  /** Extract the session keypair from an auth state. */
149
107
  sessionFromAuthState(authState) {
150
108
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "1.0.74",
3
+ "version": "1.0.75",
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.74",
23
- "@0xmonaco/types": "1.0.74",
22
+ "@0xmonaco/contracts": "1.0.75",
23
+ "@0xmonaco/types": "1.0.75",
24
24
  "@noble/curves": "^1.9.1",
25
25
  "@noble/hashes": "^2.4.0",
26
26
  "http-status-codes": "^2.3.0"