@0xmonaco/core 0.6.3 → 0.7.1

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.
@@ -4,6 +4,100 @@ import { keysToCamelCase } from "./utils";
4
4
  const CONNECTION_TIMEOUT = 10000;
5
5
  const HEARTBEAT_INTERVAL = 15000;
6
6
  const MAX_RECONNECT_DELAY = 30000;
7
+ const CONDITIONAL_ORDER_REASONS = ["created", "cancelled", "triggered", "failed", "oco_cancelled"];
8
+ const CONDITIONAL_ORDER_CONDITION_TYPES = ["STOP_LOSS", "TAKE_PROFIT"];
9
+ const CONDITIONAL_ORDER_TRIGGER_SOURCES = ["MARK_PRICE"];
10
+ const CONDITIONAL_ORDER_STATES = ["ACTIVE", "TRIGGERING", "TRIGGERED", "CANCELLED", "EXPIRED", "FAILED"];
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function readString(source, field) {
15
+ const value = source[field];
16
+ if (typeof value !== "string" || value.length === 0) {
17
+ throw new Error(`Invalid conditional order event: ${field} must be a non-empty string`);
18
+ }
19
+ return value;
20
+ }
21
+ function readOptionalString(source, field) {
22
+ const value = source[field];
23
+ if (value === undefined || value === null)
24
+ return undefined;
25
+ if (typeof value !== "string") {
26
+ throw new Error(`Invalid conditional order event: ${field} must be a string`);
27
+ }
28
+ return value;
29
+ }
30
+ function readBoolean(source, field) {
31
+ const value = source[field];
32
+ if (typeof value !== "boolean") {
33
+ throw new Error(`Invalid conditional order event: ${field} must be a boolean`);
34
+ }
35
+ return value;
36
+ }
37
+ function readOptionalNumber(source, field) {
38
+ const value = source[field];
39
+ if (value === undefined || value === null)
40
+ return undefined;
41
+ if (typeof value !== "number" || !Number.isFinite(value)) {
42
+ throw new Error(`Invalid conditional order event: ${field} must be a finite number`);
43
+ }
44
+ return value;
45
+ }
46
+ function readEnum(source, field, allowed) {
47
+ const value = readString(source, field);
48
+ if (!allowed.includes(value)) {
49
+ throw new Error(`Invalid conditional order event: ${field} must be one of ${allowed.join(", ")}`);
50
+ }
51
+ return value;
52
+ }
53
+ function readOptionalEnum(source, field, allowed) {
54
+ const value = source[field];
55
+ if (value === undefined || value === null)
56
+ return undefined;
57
+ if (typeof value !== "string" || !allowed.includes(value)) {
58
+ throw new Error(`Invalid conditional order event: ${field} must be one of ${allowed.join(", ")}`);
59
+ }
60
+ return value;
61
+ }
62
+ function parseConditionalOrderEvent(rawData) {
63
+ if (!isRecord(rawData)) {
64
+ throw new Error("Invalid conditional order event: payload must be an object");
65
+ }
66
+ if (!isRecord(rawData.data)) {
67
+ throw new Error("Invalid conditional order event: data must be an object");
68
+ }
69
+ const data = keysToCamelCase(rawData.data);
70
+ return {
71
+ eventType: readEnum(rawData, "event_type", ["conditional_order_update"]),
72
+ userId: readString(rawData, "user_id"),
73
+ data: {
74
+ conditionalOrderId: readString(data, "conditionalOrderId"),
75
+ tradingPairId: readString(data, "tradingPairId"),
76
+ marginAccountId: readString(data, "marginAccountId"),
77
+ positionId: readOptionalString(data, "positionId"),
78
+ linkedGroupId: readOptionalString(data, "linkedGroupId"),
79
+ conditionType: readEnum(data, "conditionType", CONDITIONAL_ORDER_CONDITION_TYPES),
80
+ triggerSource: readEnum(data, "triggerSource", CONDITIONAL_ORDER_TRIGGER_SOURCES),
81
+ triggerPrice: readString(data, "triggerPrice"),
82
+ side: readEnum(data, "side", ["BUY", "SELL"]),
83
+ positionSide: readEnum(data, "positionSide", ["LONG", "SHORT", "NONE"]),
84
+ orderType: readEnum(data, "orderType", ["LIMIT", "MARKET"]),
85
+ limitPrice: readOptionalString(data, "limitPrice"),
86
+ quantity: readOptionalString(data, "quantity"),
87
+ slippageToleranceBps: readOptionalNumber(data, "slippageToleranceBps"),
88
+ reduceOnly: readBoolean(data, "reduceOnly"),
89
+ timeInForce: readOptionalEnum(data, "timeInForce", ["GTC", "IOC"]),
90
+ state: readEnum(data, "state", CONDITIONAL_ORDER_STATES),
91
+ triggeredOrderId: readOptionalString(data, "triggeredOrderId"),
92
+ triggeredAt: readOptionalString(data, "triggeredAt"),
93
+ cancelledAt: readOptionalString(data, "cancelledAt"),
94
+ expiresAt: readOptionalString(data, "expiresAt"),
95
+ failureReason: readOptionalString(data, "failureReason"),
96
+ reason: readEnum(data, "reason", CONDITIONAL_ORDER_REASONS),
97
+ updatedAt: readString(data, "updatedAt"),
98
+ },
99
+ };
100
+ }
7
101
  /**
8
102
  * Create a Monaco WebSocket client
9
103
  */
@@ -62,7 +156,11 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
62
156
  }
63
157
  reconnectAttempts++;
64
158
  const delay = Math.min(1000 * 2 ** reconnectAttempts, MAX_RECONNECT_DELAY);
65
- reconnectTimer = setTimeout(() => connect(), delay);
159
+ reconnectTimer = setTimeout(() => {
160
+ connect().catch((err) => {
161
+ console.warn("WebSocket: Failed to reconnect:", err);
162
+ });
163
+ }, delay);
66
164
  };
67
165
  const send = (data) => {
68
166
  if (ws?.readyState === WebSocket.OPEN) {
@@ -84,7 +182,6 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
84
182
  for (const handler of channelHandlers) {
85
183
  handler(msg.data);
86
184
  }
87
- return;
88
185
  }
89
186
  }
90
187
  };
@@ -96,22 +193,33 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
96
193
  return;
97
194
  }
98
195
  try {
99
- ws = new WebSocket(getUrl());
196
+ const socket = new WebSocket(getUrl());
197
+ ws = socket;
100
198
  const timeout = setTimeout(() => {
101
- if (ws?.readyState === WebSocket.CONNECTING) {
102
- ws.close();
199
+ if (ws !== socket) {
200
+ resolve();
201
+ return;
202
+ }
203
+ if (socket.readyState === WebSocket.CONNECTING) {
204
+ socket.close(1000, "Connection timeout");
103
205
  reject(new Error("WebSocket connection timeout"));
104
206
  }
105
207
  }, CONNECTION_TIMEOUT);
106
- ws.onopen = () => {
208
+ socket.onopen = () => {
107
209
  clearTimeout(timeout);
210
+ if (ws !== socket) {
211
+ resolve();
212
+ return;
213
+ }
108
214
  reconnectAttempts = 0;
109
215
  startHeartbeat();
110
216
  resubscribeAll();
111
217
  options.onStatusChange?.("connected");
112
218
  resolve();
113
219
  };
114
- ws.onmessage = (event) => {
220
+ socket.onmessage = (event) => {
221
+ if (ws !== socket)
222
+ return;
115
223
  try {
116
224
  const msg = JSON.parse(event.data);
117
225
  // Handle pong silently
@@ -123,8 +231,13 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
123
231
  console.warn("WebSocket: Failed to parse message", err);
124
232
  }
125
233
  };
126
- ws.onclose = (event) => {
234
+ socket.onclose = (event) => {
127
235
  clearTimeout(timeout);
236
+ if (ws !== socket) {
237
+ resolve();
238
+ return;
239
+ }
240
+ ws = null;
128
241
  stopHeartbeat();
129
242
  options.onStatusChange?.("disconnected");
130
243
  // Reconnect on abnormal close
@@ -132,7 +245,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
132
245
  scheduleReconnect();
133
246
  }
134
247
  };
135
- ws.onerror = () => clearTimeout(timeout);
248
+ socket.onerror = () => clearTimeout(timeout);
136
249
  }
137
250
  catch (err) {
138
251
  options.onStatusChange?.("disconnected");
@@ -207,7 +320,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
207
320
  const data = rawData;
208
321
  const orderbookData = data.data;
209
322
  const event = {
210
- tradingPairId: data.pair,
323
+ tradingPairId: data.symbol,
211
324
  tradingMode: data.trading_mode,
212
325
  bids: (orderbookData?.bids || []).map((level) => ({
213
326
  price: level.price,
@@ -251,7 +364,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
251
364
  const data = rawData;
252
365
  const ohlcvData = data.data;
253
366
  const event = {
254
- tradingPairId: data.pair,
367
+ tradingPairId: data.symbol,
255
368
  tradingMode: data.trading_mode,
256
369
  interval: data.interval,
257
370
  candlestick: {
@@ -262,7 +375,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
262
375
  l: ohlcvData.low || "0",
263
376
  c: ohlcvData.close || "0",
264
377
  v: ohlcvData.volume || "0",
265
- s: data.pair,
378
+ s: data.symbol,
266
379
  i: data.interval,
267
380
  n: ohlcvData.trades_count || 0,
268
381
  },
@@ -282,7 +395,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
282
395
  const tradeData = data.data;
283
396
  const event = {
284
397
  eventType: "trade",
285
- tradingPairId: data.pair_id,
398
+ tradingPairId: data.trading_pair_id,
286
399
  tradingMode: data.trading_mode.toUpperCase(),
287
400
  data: {
288
401
  tradeId: tradeData.trade_id,
@@ -393,18 +506,40 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
393
506
  }
394
507
  });
395
508
  };
509
+ const subscribeConditionalOrders = (handler, tradingPairId) => {
510
+ if (!handler) {
511
+ throw new Error("conditionalOrders subscription requires a handler");
512
+ }
513
+ if (tradingPairId !== undefined && tradingPairId.length === 0) {
514
+ throw new Error("conditionalOrders tradingPairId cannot be empty");
515
+ }
516
+ const channel = tradingPairId ? `conditional_orders:${tradingPairId}` : "conditional_orders";
517
+ return subscribe(channel, (rawData) => {
518
+ try {
519
+ handler(parseConditionalOrderEvent(rawData));
520
+ }
521
+ catch (err) {
522
+ console.error("WebSocket: Error processing conditional order event", err);
523
+ }
524
+ });
525
+ };
396
526
  return {
397
527
  connect,
398
528
  disconnect,
399
529
  isConnected: () => ws?.readyState === WebSocket.OPEN,
400
530
  getStatus,
401
531
  setToken: (newToken) => {
402
- token = newToken;
403
- // Reconnect to include token in ws URL
404
- if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) {
405
- ws.close(1000, "Token updated, reconnecting.");
532
+ token = newToken || undefined;
533
+ stopReconnect();
534
+ const currentSocket = ws;
535
+ if (currentSocket?.readyState === WebSocket.OPEN ||
536
+ currentSocket?.readyState === WebSocket.CONNECTING ||
537
+ currentSocket?.readyState === WebSocket.CLOSING) {
538
+ currentSocket.close(1000, newToken ? "Token updated, reconnecting." : "Token cleared.");
406
539
  ws = null;
407
540
  }
541
+ if (!newToken)
542
+ return;
408
543
  connect().catch((err) => {
409
544
  console.warn("WebSocket: Failed to reconnect after token update:", err);
410
545
  });
@@ -416,5 +551,6 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
416
551
  movements: subscribeMovements,
417
552
  userOrders: subscribeUserOrders,
418
553
  balances: subscribeBalances,
554
+ conditionalOrders: subscribeConditionalOrders,
419
555
  };
420
556
  }
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApplicationsAPI, AuthAPI, AuthState, FeesAPI, MarketAPI, MonacoSDK, Network, ProfileAPI, SDKConfig, TradingAPI, VaultAPI } from "@0xmonaco/types";
1
+ import type { ApplicationsAPI, AuthAPI, AuthState, FeesAPI, MarginAccountsAPI, MarketAPI, MonacoSDK, Network, PositionsAPI, ProfileAPI, SDKConfig, TradingAPI, VaultAPI } from "@0xmonaco/types";
2
2
  import { 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 {
@@ -8,6 +8,8 @@ export declare class MonacoSDKImpl implements MonacoSDK {
8
8
  vault: VaultAPI;
9
9
  trading: TradingAPI;
10
10
  market: MarketAPI;
11
+ marginAccounts: MarginAccountsAPI;
12
+ positions: PositionsAPI;
11
13
  profile: ProfileAPI;
12
14
  orderbook: OrderbookAPIImpl;
13
15
  trades: TradesAPIImpl;
package/dist/sdk.js CHANGED
@@ -4,7 +4,9 @@ import { sei, seiTestnet } from "viem/chains";
4
4
  import { ApplicationsAPIImpl, createMonacoWebSocket, OrderbookAPIImpl, TradesAPIImpl } from "./api";
5
5
  import { AuthAPIImpl } from "./api/auth";
6
6
  import { FeesAPIImpl } from "./api/fees";
7
+ import { MarginAccountsAPIImpl } from "./api/margin-accounts";
7
8
  import { MarketAPIImpl } from "./api/market";
9
+ import { PositionsAPIImpl } from "./api/positions";
8
10
  import { ProfileAPIImpl } from "./api/profile";
9
11
  import { TradingAPIImpl } from "./api/trading";
10
12
  import { VaultAPIImpl } from "./api/vault";
@@ -17,6 +19,8 @@ export class MonacoSDKImpl {
17
19
  vault;
18
20
  trading;
19
21
  market;
22
+ marginAccounts;
23
+ positions;
20
24
  profile;
21
25
  orderbook;
22
26
  trades;
@@ -36,7 +40,11 @@ export class MonacoSDKImpl {
36
40
  this.vault.setAccessToken(accessToken);
37
41
  this.trading.setAccessToken(accessToken);
38
42
  this.market.setAccessToken(accessToken);
43
+ this.marginAccounts.setAccessToken(accessToken);
44
+ this.positions.setAccessToken(accessToken);
39
45
  this.profile.setAccessToken(accessToken);
46
+ this.orderbook.setAccessToken(accessToken);
47
+ this.trades.setAccessToken(accessToken);
40
48
  this.ws.setToken(accessToken);
41
49
  }
42
50
  constructor(cfg) {
@@ -79,6 +87,8 @@ export class MonacoSDKImpl {
79
87
  // Instantiate APIs (wallet-dependent APIs will be initialized lazily or error if wallet not set)
80
88
  this.applications = new ApplicationsAPIImpl(apiUrl);
81
89
  this.market = new MarketAPIImpl(apiUrl);
90
+ this.marginAccounts = new MarginAccountsAPIImpl(apiUrl);
91
+ this.positions = new PositionsAPIImpl(apiUrl);
82
92
  this.auth = new AuthAPIImpl(this.walletClient, this.chain, apiUrl);
83
93
  this.fees = new FeesAPIImpl(apiUrl);
84
94
  this.profile = new ProfileAPIImpl(apiUrl);
@@ -179,11 +189,9 @@ export class MonacoSDKImpl {
179
189
  console.warn("Failed to revoke token on logout:", error);
180
190
  }
181
191
  }
182
- // Disconnect WebSocket if connected
183
- if (this.ws.isConnected()) {
184
- this.ws.disconnect();
185
- }
186
192
  this.authState = undefined;
193
+ this.propagateAccessToken("");
194
+ this.ws.disconnect();
187
195
  }
188
196
  /**
189
197
  * Refresh the access token
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "0.6.3",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",