@0xmonaco/core 1.0.73 → 1.0.74-develop.367860f

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.
@@ -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}).
@@ -284,7 +323,7 @@ function parseLiquidationEventData(raw) {
284
323
  positionId: readOptionalString(data, "positionId"),
285
324
  tradingPairId: readOptionalString(data, "tradingPairId"),
286
325
  status: readString(data, "status"),
287
- accountState: readString(data, "accountState"),
326
+ riskBucketState: readString(data, "riskBucketState"),
288
327
  liquidationPrice: readOptionalString(data, "liquidationPrice"),
289
328
  markPrice: readOptionalString(data, "markPrice"),
290
329
  estimatedShortfall: readString(data, "estimatedShortfall"),
@@ -299,7 +338,7 @@ function parseLiquidationEvent(rawData) {
299
338
  return {
300
339
  // event_type is the event-class discriminator, validated strictly so a
301
340
  // mis-routed frame is rejected rather than parsed as a liquidation. The
302
- // free-form value fields in the payload (status/accountState) stay lenient
341
+ // free-form value fields in the payload (status/riskBucketState) stay lenient
303
342
  // so an unexpected *value* never drops a risk-critical alert.
304
343
  eventType: readEnum(rawData, "event_type", ["liquidation_alert"]),
305
344
  userId: readString(rawData, "user_id"),
@@ -360,6 +399,7 @@ function parseMarketStatsData(raw) {
360
399
  bestAskPrice: readOptionalString(data, "bestAskPrice"),
361
400
  openInterest: readOptionalString(data, "openInterest"),
362
401
  openInterestLimit: readOptionalString(data, "openInterestLimit"),
402
+ openInterestHeadroom: readOptionalString(data, "openInterestHeadroom"),
363
403
  lastTradePrice: readOptionalString(data, "lastTradePrice"),
364
404
  dailyBaseTokenVolume: readOptionalString(data, "dailyBaseTokenVolume"),
365
405
  dailyQuoteTokenVolume: readOptionalString(data, "dailyQuoteTokenVolume"),
@@ -374,6 +414,11 @@ function parseMarketStatsData(raw) {
374
414
  baseInterestRate: readOptionalString(data, "baseInterestRate"),
375
415
  fundingClampSmall: readOptionalString(data, "fundingClampSmall"),
376
416
  fundingClampBig: readOptionalString(data, "fundingClampBig"),
417
+ // Lenient like the summary's marketStatus: an unrecognized value passes
418
+ // through rather than dropping the frame.
419
+ marketStatus: readOptionalString(data, "marketStatus"),
420
+ fundingIntervalSeconds: readOptionalNumber(data, "fundingIntervalSeconds"),
421
+ lastFundingTime: readOptionalNumber(data, "lastFundingTime"),
377
422
  };
378
423
  }
379
424
  function parseMarketStatsEvent(rawData) {
@@ -772,6 +817,10 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
772
817
  let ws = null;
773
818
  let session = options.session;
774
819
  let reconnectAttempts = 0;
820
+ // Whether a socket has opened since the client was created or last
821
+ // disconnect()ed, so a status consumer can tell a first connect that keeps
822
+ // failing from a lost working connection.
823
+ let hasConnected = false;
775
824
  let reconnectTimer = null;
776
825
  let heartbeatTimer = null;
777
826
  let autoReconnect = options.autoReconnect ?? true;
@@ -823,7 +872,9 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
823
872
  return reconnectTimer ? "reconnecting" : "disconnected";
824
873
  switch (ws.readyState) {
825
874
  case WebSocket.CONNECTING:
826
- return "connecting";
875
+ // A retry's socket is still inside the outage the last status
876
+ // reported, so it reads as reconnecting, not connecting.
877
+ return reconnectAttempts > 0 ? "reconnecting" : "connecting";
827
878
  case WebSocket.OPEN:
828
879
  return "connected";
829
880
  default:
@@ -833,6 +884,43 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
833
884
  }
834
885
  };
835
886
  const getUrl = () => baseUrl;
887
+ /**
888
+ * The single place a status change is reported. Recovery must not hinge on
889
+ * the application's observer, so an error it throws is logged, not
890
+ * propagated into the connection lifecycle. An async observer is assignable
891
+ * to the void-returning handler type, so its rejection is logged the same
892
+ * way rather than left unhandled.
893
+ */
894
+ const emitStatus = (status, nextRetryDelayMs, attempt = reconnectAttempts) => {
895
+ const info = { attempt, hasConnected };
896
+ if (nextRetryDelayMs !== undefined) {
897
+ info.nextRetryDelayMs = nextRetryDelayMs;
898
+ }
899
+ const logObserverError = (err) => console.error("WebSocket: onStatusChange threw", err);
900
+ try {
901
+ const result = options.onStatusChange?.(status, info);
902
+ // Any thenable, not just this realm's Promise: an async handler from
903
+ // another realm (an iframe) returns a promise `instanceof` misses.
904
+ if (typeof result?.then === "function") {
905
+ Promise.resolve(result).catch(logObserverError);
906
+ }
907
+ }
908
+ catch (err) {
909
+ logObserverError(err);
910
+ }
911
+ };
912
+ /**
913
+ * Report a terminal "disconnected", which ends the retry cycle so the next
914
+ * connect() reports "connecting" with a fresh retry budget. The counter is
915
+ * reset before the observer runs, so a connect() made from inside the
916
+ * callback already starts the new cycle; the status still reports the
917
+ * retries the cycle ran.
918
+ */
919
+ const emitTerminalDisconnected = () => {
920
+ const attempt = reconnectAttempts;
921
+ reconnectAttempts = 0;
922
+ emitStatus("disconnected", undefined, attempt);
923
+ };
836
924
  /**
837
925
  * Send the signed auth handshake as the first message after the socket
838
926
  * opens. The client signs `WS-AUTH\n<pubkey>\n<ts>` with the session private
@@ -876,13 +964,15 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
876
964
  reconnectTimer = null;
877
965
  }
878
966
  };
967
+ // Whether the retry cycle may schedule another attempt.
968
+ const retryBudgetLeft = () => autoReconnect && reconnectAttempts < maxReconnectAttempts;
879
969
  const scheduleReconnect = () => {
880
970
  // Single scheduling authority: a failed attempt can report through both
881
971
  // the connect() rejection and the socket's own close event — an armed
882
972
  // timer means the retry is already owned.
883
973
  if (reconnectTimer)
884
974
  return;
885
- if (!autoReconnect || reconnectAttempts >= maxReconnectAttempts) {
975
+ if (!retryBudgetLeft()) {
886
976
  console.error("WebSocket: Max reconnect attempts reached");
887
977
  return;
888
978
  }
@@ -902,10 +992,14 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
902
992
  return;
903
993
  connect().catch((err) => {
904
994
  console.warn("WebSocket: Failed to reconnect:", err);
905
- scheduleReconnect();
995
+ // Not when the failure already ended the retry cycle (the last
996
+ // retry's socket could not be constructed).
997
+ if (reconnectAttempts > 0) {
998
+ scheduleReconnect();
999
+ }
906
1000
  });
907
1001
  }, delay);
908
- options.onStatusChange?.("reconnecting");
1002
+ emitStatus("reconnecting", delay);
909
1003
  };
910
1004
  const send = (data) => {
911
1005
  if (ws?.readyState === WebSocket.OPEN) {
@@ -1035,6 +1129,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1035
1129
  return;
1036
1130
  }
1037
1131
  reconnectAttempts = 0;
1132
+ hasConnected = true;
1038
1133
  // A retry armed before this open (e.g. a manual connect() during
1039
1134
  // backoff) is moot now — left running it would suppress the
1040
1135
  // terminal disconnected emit and reopen a socket after a clean
@@ -1045,7 +1140,7 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1045
1140
  // (orders, balances, …) are authorized when the Subscribe arrives.
1046
1141
  sendAuthenticate();
1047
1142
  resubscribeAll();
1048
- options.onStatusChange?.("connected");
1143
+ emitStatus("connected");
1049
1144
  // Resync-join order: the live stream resumes first (auth +
1050
1145
  // subscribe above), then the application is asked to refetch
1051
1146
  // snapshots and reconcile against each channel's authoritative
@@ -1118,13 +1213,8 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1118
1213
  if (pendingResyncCode === null) {
1119
1214
  pendingResyncCode = event.code;
1120
1215
  }
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
- }
1216
+ emitStatus("disconnected");
1217
+ options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
1128
1218
  return;
1129
1219
  }
1130
1220
  // The app already handed over a replacement session on this socket.
@@ -1135,14 +1225,10 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1135
1225
  if (pendingResyncCode === null) {
1136
1226
  pendingResyncCode = event.code;
1137
1227
  }
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
- }
1228
+ emitStatus("disconnected");
1229
+ connect().catch((err) => {
1230
+ console.warn("WebSocket: Failed to connect with the replacement session:", err);
1231
+ });
1146
1232
  return;
1147
1233
  }
1148
1234
  // Reconnect on abnormal close. 1013 is the server's slow-client
@@ -1159,13 +1245,26 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1159
1245
  // "disconnected" is terminal: suppressed while a retry is armed
1160
1246
  // (scheduleReconnect emitted "reconnecting" instead).
1161
1247
  if (!reconnectTimer) {
1162
- options.onStatusChange?.("disconnected");
1248
+ emitTerminalDisconnected();
1163
1249
  }
1164
1250
  };
1165
1251
  socket.onerror = () => clearTimeout(timeout);
1252
+ // A retry's socket stays inside the outage "reconnecting" already
1253
+ // reported; only a connection opened outside a retry cycle is new.
1254
+ if (reconnectAttempts === 0) {
1255
+ emitStatus("connecting");
1256
+ }
1166
1257
  }
1167
1258
  catch (err) {
1168
- options.onStatusChange?.("disconnected");
1259
+ // The socket could not be constructed. Inside a retry cycle the retry
1260
+ // timer schedules the next attempt while the budget lasts; otherwise
1261
+ // nothing follows, so this ends the cycle.
1262
+ if (reconnectTimer || (reconnectAttempts > 0 && retryBudgetLeft())) {
1263
+ emitStatus("disconnected");
1264
+ }
1265
+ else {
1266
+ emitTerminalDisconnected();
1267
+ }
1169
1268
  reject(err);
1170
1269
  }
1171
1270
  });
@@ -1180,7 +1279,8 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1180
1279
  instrumentBaseline.clear();
1181
1280
  ws?.close(1000, "Client disconnect");
1182
1281
  ws = null;
1183
- options.onStatusChange?.("disconnected");
1282
+ hasConnected = false;
1283
+ emitTerminalDisconnected();
1184
1284
  };
1185
1285
  const subscribe = (channel, handler, snapshotHandler) => {
1186
1286
  const isFirstSubscriber = !handlers.has(channel);
@@ -1522,8 +1622,11 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1522
1622
  * handshake. Omit `tradingPairId` for all pairs, or pass one to filter.
1523
1623
  * Detected broadcast lag closes the connection with 1013; reconcile
1524
1624
  * missed keys and terminal updates on reconnect.
1625
+ *
1626
+ * The channel also carries periodic `position_valuation` frames. Those go to
1627
+ * `onValuation` only, never to `handler`, and are ignored without one.
1525
1628
  */
1526
- const subscribePositions = (handler, tradingPairId, onSnapshot) => {
1629
+ const subscribePositions = (handler, tradingPairId, onSnapshot, onValuation) => {
1527
1630
  if (!handler) {
1528
1631
  throw new Error("positions subscription requires a handler");
1529
1632
  }
@@ -1532,6 +1635,17 @@ export function createMonacoWebSocket(baseUrl, options = {}, serverClock = new S
1532
1635
  }
1533
1636
  const channel = tradingPairId ? `positions:${tradingPairId}` : "positions";
1534
1637
  return subscribe(channel, (rawData) => {
1638
+ if (isPositionValuationFrame(rawData)) {
1639
+ if (!onValuation)
1640
+ return;
1641
+ try {
1642
+ onValuation(parsePositionValuationEvent(rawData));
1643
+ }
1644
+ catch (err) {
1645
+ console.error("WebSocket: Error processing position valuation event", err);
1646
+ }
1647
+ return;
1648
+ }
1535
1649
  try {
1536
1650
  handler(parsePositionEvent(rawData));
1537
1651
  }
@@ -1,3 +1,4 @@
1
+ import { ListPendingWithdrawalsSchema, validate } from "@0xmonaco/types";
1
2
  import { BaseAPI } from "../base";
2
3
  import { perpRoutes } from "../perp";
3
4
  /**
@@ -30,6 +31,7 @@ export class WithdrawalsAPIImpl extends BaseAPI {
30
31
  async listPendingWithdrawals(params) {
31
32
  // The gateway serves camelCase (the REST wire format), which matches the
32
33
  // public surface, so the response is returned directly with no remapping.
33
- return this.makeAuthenticatedRequest(perpRoutes.withdrawals.list({ page: params?.page, pageSize: params?.pageSize }));
34
+ validate(ListPendingWithdrawalsSchema, params ?? {});
35
+ return this.makeAuthenticatedRequest(perpRoutes.withdrawals.list({ pageSize: params?.pageSize, pageToken: params?.pageToken }));
34
36
  }
35
37
  }
@@ -62,6 +62,7 @@ export declare const COVERED: {
62
62
  get_rewards_balance: string;
63
63
  get_my_referral_position: string;
64
64
  get_my_referral_downline: string;
65
+ get_my_referral_tree: string;
65
66
  get_self_trade_prevention_default: string;
66
67
  get_trader_code_info: string;
67
68
  set_self_trade_prevention_default: string;
@@ -132,6 +133,12 @@ export declare const COVERED: {
132
133
  transfer_collateral_to_margin_account: string;
133
134
  transfer_collateral_to_parent_margin_account: string;
134
135
  transfer_collateral_to_risk_bucket: string;
136
+ create_sub_account: string;
137
+ close_sub_account: string;
138
+ get_margin_account_pnl: string;
139
+ transfer_collateral: string;
140
+ transfer_collateral_to_sub_account: string;
141
+ transfer_collateral_from_sub_account: string;
135
142
  claim_buildercode_rewards: string;
136
143
  set_builder_fee: string;
137
144
  transfer_rewards: string;
package/dist/coverage.js CHANGED
@@ -62,6 +62,7 @@ export const COVERED = {
62
62
  get_rewards_balance: "pitpass.getRewardsBalance",
63
63
  get_my_referral_position: "pitpass.getMyReferralPosition",
64
64
  get_my_referral_downline: "pitpass.getMyReferralDownline",
65
+ get_my_referral_tree: "pitpass.getMyReferralTree",
65
66
  get_self_trade_prevention_default: "profile.getSelfTradePreventionDefault",
66
67
  get_trader_code_info: "pitpass.getTraderCodeInfo",
67
68
  set_self_trade_prevention_default: "profile.setSelfTradePreventionDefault",
@@ -132,6 +133,12 @@ export const COVERED = {
132
133
  transfer_collateral_to_margin_account: "transferCollateralToMarginAccount",
133
134
  transfer_collateral_to_parent_margin_account: "transferCollateralToParentMarginAccount",
134
135
  transfer_collateral_to_risk_bucket: "transferCollateralToRiskBucket",
136
+ create_sub_account: "createMarginSubAccount",
137
+ close_sub_account: "closeMarginSubAccount",
138
+ get_margin_account_pnl: "getMarginAccountPnl",
139
+ transfer_collateral: "transferMarginCollateral",
140
+ transfer_collateral_to_sub_account: "transferCollateralToSubAccount",
141
+ transfer_collateral_from_sub_account: "transferCollateralFromSubAccount",
135
142
  claim_buildercode_rewards: "buildercodes.claimBuildercodeRewards",
136
143
  set_builder_fee: "buildercodes.setBuilderFee",
137
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.73",
3
+ "version": "1.0.74-develop.367860f",
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.73",
23
- "@0xmonaco/types": "1.0.73",
22
+ "@0xmonaco/contracts": "1.0.74-develop.367860f",
23
+ "@0xmonaco/types": "1.0.74-develop.367860f",
24
24
  "@noble/curves": "^1.9.1",
25
25
  "@noble/hashes": "^2.4.0",
26
26
  "http-status-codes": "^2.3.0"