@0xmonaco/core 1.0.41 → 1.0.42-develop.791e6f3
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.
- package/dist/api/market/api.js +4 -0
- package/dist/api/perp/routes.d.ts +9 -0
- package/dist/api/perp/routes.js +4 -0
- package/dist/api/trading/api.d.ts +106 -1
- package/dist/api/trading/api.js +163 -1
- package/dist/api/vault/api.d.ts +24 -3
- package/dist/api/vault/api.js +40 -10
- package/dist/api/websocket/types.d.ts +10 -2
- package/dist/api/websocket/websocket.js +263 -3
- package/dist/coverage.d.ts +4 -0
- package/dist/coverage.js +4 -0
- package/package.json +3 -3
package/dist/api/market/api.js
CHANGED
|
@@ -54,6 +54,7 @@ export class MarketAPIImpl extends BaseAPI {
|
|
|
54
54
|
startTime: params?.startTime,
|
|
55
55
|
endTime: params?.endTime,
|
|
56
56
|
limit: params?.limit,
|
|
57
|
+
priceType: params?.priceType,
|
|
57
58
|
});
|
|
58
59
|
const searchParams = new URLSearchParams();
|
|
59
60
|
if (params?.startTime !== undefined) {
|
|
@@ -65,6 +66,9 @@ export class MarketAPIImpl extends BaseAPI {
|
|
|
65
66
|
if (params?.limit !== undefined) {
|
|
66
67
|
searchParams.append("limit", params.limit.toString());
|
|
67
68
|
}
|
|
69
|
+
if (params?.priceType !== undefined) {
|
|
70
|
+
searchParams.append("priceType", params.priceType);
|
|
71
|
+
}
|
|
68
72
|
const queryString = searchParams.toString();
|
|
69
73
|
const url = queryString
|
|
70
74
|
? `/api/v1/market/pairs/charts/candlestick/${encodeURIComponent(tradingPairId)}/${interval}?${queryString}`
|
|
@@ -26,6 +26,15 @@ export declare const perpRoutes: {
|
|
|
26
26
|
}) => string;
|
|
27
27
|
readonly cancelConditional: (conditionalOrderId: string) => string;
|
|
28
28
|
readonly getConditional: (conditionalOrderId: string) => string;
|
|
29
|
+
readonly createTwap: () => string;
|
|
30
|
+
readonly listTwap: (params?: {
|
|
31
|
+
state?: string;
|
|
32
|
+
tradingPairId?: string;
|
|
33
|
+
page?: number;
|
|
34
|
+
pageSize?: number;
|
|
35
|
+
}) => string;
|
|
36
|
+
readonly getTwap: (twapOrderId: string) => string;
|
|
37
|
+
readonly cancelTwap: (twapOrderId: string) => string;
|
|
29
38
|
};
|
|
30
39
|
readonly delegatedAgents: {
|
|
31
40
|
readonly list: () => string;
|
package/dist/api/perp/routes.js
CHANGED
|
@@ -29,6 +29,10 @@ export const perpRoutes = {
|
|
|
29
29
|
listConditional: (params) => withQuery(`${API_V1}/orders/conditional`, params),
|
|
30
30
|
cancelConditional: (conditionalOrderId) => `${API_V1}/orders/conditional/${encodeSegment(conditionalOrderId)}`,
|
|
31
31
|
getConditional: (conditionalOrderId) => `${API_V1}/orders/conditional/${encodeSegment(conditionalOrderId)}`,
|
|
32
|
+
createTwap: () => `${API_V1}/orders/twap`,
|
|
33
|
+
listTwap: (params) => withQuery(`${API_V1}/orders/twap`, params),
|
|
34
|
+
getTwap: (twapOrderId) => `${API_V1}/orders/twap/${encodeSegment(twapOrderId)}`,
|
|
35
|
+
cancelTwap: (twapOrderId) => `${API_V1}/orders/twap/${encodeSegment(twapOrderId)}/cancel`,
|
|
32
36
|
},
|
|
33
37
|
delegatedAgents: {
|
|
34
38
|
list: () => `${API_V1}/delegated-agents`,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* );
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, ConditionalOrder, CreateOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, PositionSide, ReplaceOrderResponse, TimeInForce, TradingAPI, TradingMode } from "@0xmonaco/types";
|
|
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";
|
|
25
25
|
import { BaseAPI } from "../base";
|
|
26
26
|
export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
27
27
|
/**
|
|
@@ -344,4 +344,109 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
344
344
|
* ```
|
|
345
345
|
*/
|
|
346
346
|
getOrder(orderId: string): Promise<GetOrderResponse>;
|
|
347
|
+
/**
|
|
348
|
+
* Creates a server-side TWAP (time-weighted average price) parent order.
|
|
349
|
+
*
|
|
350
|
+
* The matching engine executes the parent as a schedule of MARKET child
|
|
351
|
+
* orders across the window, with seeded size (±30%) and timing (±30%)
|
|
352
|
+
* randomization when `randomize` is enabled (the default). Execution stops
|
|
353
|
+
* hard at the window end — any unfilled remainder is reported as shortfall,
|
|
354
|
+
* never traded past the end.
|
|
355
|
+
*
|
|
356
|
+
* @param tradingPairId - The trading pair UUID
|
|
357
|
+
* @param side - The order side ("BUY" or "SELL")
|
|
358
|
+
* @param quantity - Total quantity to execute across the window as string
|
|
359
|
+
* (must be at least 2x the pair's minimum order size so it can be sliced)
|
|
360
|
+
* @param options - Window and execution parameters
|
|
361
|
+
* @param options.durationSeconds - Window length in seconds from launch;
|
|
362
|
+
* exactly one of `durationSeconds` or `endTime` must be set
|
|
363
|
+
* @param options.endTime - Window end (ISO 8601); exactly one of
|
|
364
|
+
* `durationSeconds` or `endTime` must be set
|
|
365
|
+
* @param options.startTime - Window start (ISO 8601); only valid together
|
|
366
|
+
* with `endTime`, defaults to now
|
|
367
|
+
* @param options.limitPrice - Optional parent-level limit price; BUY child
|
|
368
|
+
* orders never execute above it, SELL children never below it
|
|
369
|
+
* @param options.slippageToleranceBps - Per-slice protective price band in
|
|
370
|
+
* basis points (default 100 = 1%)
|
|
371
|
+
* @param options.clientTwapId - Idempotency key (max 36 chars), unique per
|
|
372
|
+
* user and application; a duplicate is rejected with 409
|
|
373
|
+
* @returns Promise resolving to CreateTwapOrderResponse with the parent id,
|
|
374
|
+
* initial state, and the planned schedule summary
|
|
375
|
+
* @throws {APIError} When API communication fails
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```typescript
|
|
379
|
+
* // Buy 10 units spread across one hour
|
|
380
|
+
* const twap = await tradingAPI.createTwapOrder(
|
|
381
|
+
* "123e4567-e89b-12d3-a456-426614174000",
|
|
382
|
+
* "BUY",
|
|
383
|
+
* "10",
|
|
384
|
+
* { durationSeconds: 3600 }
|
|
385
|
+
* );
|
|
386
|
+
*
|
|
387
|
+
* // Margin TWAP with an explicit window and a parent limit price
|
|
388
|
+
* const marginTwap = await tradingAPI.createTwapOrder(
|
|
389
|
+
* "123e4567-e89b-12d3-a456-426614174000",
|
|
390
|
+
* "SELL",
|
|
391
|
+
* "5",
|
|
392
|
+
* {
|
|
393
|
+
* startTime: "2026-08-01T10:00:00Z",
|
|
394
|
+
* endTime: "2026-08-01T11:00:00Z",
|
|
395
|
+
* limitPrice: "35000.00",
|
|
396
|
+
* tradingMode: "MARGIN",
|
|
397
|
+
* leverage: "5",
|
|
398
|
+
* }
|
|
399
|
+
* );
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
createTwapOrder(tradingPairId: string, side: OrderSide, quantity: string, options: CreateTwapOrderOptions): Promise<CreateTwapOrderResponse>;
|
|
403
|
+
/**
|
|
404
|
+
* Cancels a TWAP parent order.
|
|
405
|
+
*
|
|
406
|
+
* Child orders are IOC and never rest, so the cancel is atomic and
|
|
407
|
+
* immediate; executed quantity stands.
|
|
408
|
+
*
|
|
409
|
+
* @param twapOrderId - The TWAP parent order UUID to cancel
|
|
410
|
+
* @returns Promise resolving to CancelTwapOrderResponse with cancellation details
|
|
411
|
+
* @throws {APIError} When API communication fails or the parent is already terminal
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```typescript
|
|
415
|
+
* const result = await tradingAPI.cancelTwapOrder("123e4567-e89b-12d3-a456-426614174000");
|
|
416
|
+
* console.log(`TWAP cancelled: ${result.status}`);
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
cancelTwapOrder(twapOrderId: string): Promise<CancelTwapOrderResponse>;
|
|
420
|
+
/**
|
|
421
|
+
* Gets a single TWAP parent order by its UUID.
|
|
422
|
+
*
|
|
423
|
+
* Users can only access their own TWAP orders. The read model carries the
|
|
424
|
+
* live execution progress (executed quantity/notional, VWAP, slice
|
|
425
|
+
* counters) alongside the immutable schedule configuration.
|
|
426
|
+
*
|
|
427
|
+
* @param twapOrderId - The TWAP parent order UUID to retrieve
|
|
428
|
+
* @returns Promise resolving to the TwapOrder read model
|
|
429
|
+
* @throws {APIError} When API communication fails or the order is not found
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```typescript
|
|
433
|
+
* const twap = await tradingAPI.getTwapOrder("123e4567-e89b-12d3-a456-426614174000");
|
|
434
|
+
* console.log(`State: ${twap.state}, progress: ${twap.progress}`);
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
getTwapOrder(twapOrderId: string): Promise<TwapOrder>;
|
|
438
|
+
/**
|
|
439
|
+
* Lists TWAP parent orders for the authenticated user.
|
|
440
|
+
*
|
|
441
|
+
* @param params - Optional pagination and state / trading-pair filters
|
|
442
|
+
* @returns Promise resolving to ListTwapOrdersResponse with orders and pagination info
|
|
443
|
+
* @throws {APIError} When API communication fails
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* const active = await tradingAPI.listTwapOrders({ state: "ACTIVE", pageSize: 50 });
|
|
448
|
+
* console.log(`Found ${active.total} active TWAP orders`);
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
listTwapOrders(params?: ListTwapOrdersParams): Promise<ListTwapOrdersResponse>;
|
|
347
452
|
}
|
package/dist/api/trading/api.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* );
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import { BatchCreateOrdersSchema, BatchReplaceOrdersSchema, CancelConditionalOrderSchema, CancelOrderSchema, GetPaginatedOrdersSchema, ListConditionalOrdersSchema, PlaceLimitOrderSchema, PlaceMarketOrderSchema, ReplaceOrderSchema, validate, } from "@0xmonaco/types";
|
|
24
|
+
import { BatchCreateOrdersSchema, BatchReplaceOrdersSchema, CancelConditionalOrderSchema, CancelOrderSchema, CancelTwapOrderSchema, CreateTwapOrderSchema, GetPaginatedOrdersSchema, ListConditionalOrdersSchema, ListTwapOrdersSchema, PlaceLimitOrderSchema, PlaceMarketOrderSchema, ReplaceOrderSchema, validate, } from "@0xmonaco/types";
|
|
25
25
|
import { BaseAPI } from "../base";
|
|
26
26
|
import { perpRoutes } from "../perp";
|
|
27
27
|
function parentTpSlLegToRequest(leg) {
|
|
@@ -573,4 +573,166 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
573
573
|
const order = await this.makeAuthenticatedRequest(perpRoutes.orders.get(orderId), { method: "GET" });
|
|
574
574
|
return { order, status: "SUCCESS" };
|
|
575
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Creates a server-side TWAP (time-weighted average price) parent order.
|
|
578
|
+
*
|
|
579
|
+
* The matching engine executes the parent as a schedule of MARKET child
|
|
580
|
+
* orders across the window, with seeded size (±30%) and timing (±30%)
|
|
581
|
+
* randomization when `randomize` is enabled (the default). Execution stops
|
|
582
|
+
* hard at the window end — any unfilled remainder is reported as shortfall,
|
|
583
|
+
* never traded past the end.
|
|
584
|
+
*
|
|
585
|
+
* @param tradingPairId - The trading pair UUID
|
|
586
|
+
* @param side - The order side ("BUY" or "SELL")
|
|
587
|
+
* @param quantity - Total quantity to execute across the window as string
|
|
588
|
+
* (must be at least 2x the pair's minimum order size so it can be sliced)
|
|
589
|
+
* @param options - Window and execution parameters
|
|
590
|
+
* @param options.durationSeconds - Window length in seconds from launch;
|
|
591
|
+
* exactly one of `durationSeconds` or `endTime` must be set
|
|
592
|
+
* @param options.endTime - Window end (ISO 8601); exactly one of
|
|
593
|
+
* `durationSeconds` or `endTime` must be set
|
|
594
|
+
* @param options.startTime - Window start (ISO 8601); only valid together
|
|
595
|
+
* with `endTime`, defaults to now
|
|
596
|
+
* @param options.limitPrice - Optional parent-level limit price; BUY child
|
|
597
|
+
* orders never execute above it, SELL children never below it
|
|
598
|
+
* @param options.slippageToleranceBps - Per-slice protective price band in
|
|
599
|
+
* basis points (default 100 = 1%)
|
|
600
|
+
* @param options.clientTwapId - Idempotency key (max 36 chars), unique per
|
|
601
|
+
* user and application; a duplicate is rejected with 409
|
|
602
|
+
* @returns Promise resolving to CreateTwapOrderResponse with the parent id,
|
|
603
|
+
* initial state, and the planned schedule summary
|
|
604
|
+
* @throws {APIError} When API communication fails
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* ```typescript
|
|
608
|
+
* // Buy 10 units spread across one hour
|
|
609
|
+
* const twap = await tradingAPI.createTwapOrder(
|
|
610
|
+
* "123e4567-e89b-12d3-a456-426614174000",
|
|
611
|
+
* "BUY",
|
|
612
|
+
* "10",
|
|
613
|
+
* { durationSeconds: 3600 }
|
|
614
|
+
* );
|
|
615
|
+
*
|
|
616
|
+
* // Margin TWAP with an explicit window and a parent limit price
|
|
617
|
+
* const marginTwap = await tradingAPI.createTwapOrder(
|
|
618
|
+
* "123e4567-e89b-12d3-a456-426614174000",
|
|
619
|
+
* "SELL",
|
|
620
|
+
* "5",
|
|
621
|
+
* {
|
|
622
|
+
* startTime: "2026-08-01T10:00:00Z",
|
|
623
|
+
* endTime: "2026-08-01T11:00:00Z",
|
|
624
|
+
* limitPrice: "35000.00",
|
|
625
|
+
* tradingMode: "MARGIN",
|
|
626
|
+
* leverage: "5",
|
|
627
|
+
* }
|
|
628
|
+
* );
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
async createTwapOrder(tradingPairId, side, quantity, options) {
|
|
632
|
+
// Validate inputs before making API call
|
|
633
|
+
validate(CreateTwapOrderSchema, {
|
|
634
|
+
tradingPairId,
|
|
635
|
+
side,
|
|
636
|
+
quantity,
|
|
637
|
+
options,
|
|
638
|
+
});
|
|
639
|
+
const requestBody = {
|
|
640
|
+
tradingPairId: tradingPairId,
|
|
641
|
+
side,
|
|
642
|
+
quantity,
|
|
643
|
+
// The server deserializes durationSeconds into an int64 with plain
|
|
644
|
+
// serde, which rejects strings — send a JSON number (durations are
|
|
645
|
+
// bounded at 30 days = 2,592,000, far inside the safe-integer range).
|
|
646
|
+
durationSeconds: options.durationSeconds !== undefined ? Number(options.durationSeconds) : undefined,
|
|
647
|
+
startTime: options.startTime,
|
|
648
|
+
endTime: options.endTime,
|
|
649
|
+
limitPrice: options.limitPrice,
|
|
650
|
+
slippageToleranceBps: options.slippageToleranceBps,
|
|
651
|
+
randomize: options.randomize,
|
|
652
|
+
reduceOnly: options.reduceOnly,
|
|
653
|
+
tradingMode: options.tradingMode || "SPOT",
|
|
654
|
+
marginAccountId: options.marginAccountId,
|
|
655
|
+
riskBucketId: options.riskBucketId,
|
|
656
|
+
marginMode: options.marginMode,
|
|
657
|
+
leverage: options.leverage,
|
|
658
|
+
strategyKey: options.strategyKey,
|
|
659
|
+
useMasterBalance: options.useMasterBalance,
|
|
660
|
+
clientTwapId: options.clientTwapId,
|
|
661
|
+
};
|
|
662
|
+
return await this.makeAuthenticatedRequest(perpRoutes.orders.createTwap(), {
|
|
663
|
+
method: "POST",
|
|
664
|
+
body: JSON.stringify(requestBody),
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Cancels a TWAP parent order.
|
|
669
|
+
*
|
|
670
|
+
* Child orders are IOC and never rest, so the cancel is atomic and
|
|
671
|
+
* immediate; executed quantity stands.
|
|
672
|
+
*
|
|
673
|
+
* @param twapOrderId - The TWAP parent order UUID to cancel
|
|
674
|
+
* @returns Promise resolving to CancelTwapOrderResponse with cancellation details
|
|
675
|
+
* @throws {APIError} When API communication fails or the parent is already terminal
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```typescript
|
|
679
|
+
* const result = await tradingAPI.cancelTwapOrder("123e4567-e89b-12d3-a456-426614174000");
|
|
680
|
+
* console.log(`TWAP cancelled: ${result.status}`);
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
683
|
+
async cancelTwapOrder(twapOrderId) {
|
|
684
|
+
// Validate inputs before making API call
|
|
685
|
+
validate(CancelTwapOrderSchema, { twapOrderId });
|
|
686
|
+
return await this.makeAuthenticatedRequest(perpRoutes.orders.cancelTwap(twapOrderId), {
|
|
687
|
+
method: "POST",
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Gets a single TWAP parent order by its UUID.
|
|
692
|
+
*
|
|
693
|
+
* Users can only access their own TWAP orders. The read model carries the
|
|
694
|
+
* live execution progress (executed quantity/notional, VWAP, slice
|
|
695
|
+
* counters) alongside the immutable schedule configuration.
|
|
696
|
+
*
|
|
697
|
+
* @param twapOrderId - The TWAP parent order UUID to retrieve
|
|
698
|
+
* @returns Promise resolving to the TwapOrder read model
|
|
699
|
+
* @throws {APIError} When API communication fails or the order is not found
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* ```typescript
|
|
703
|
+
* const twap = await tradingAPI.getTwapOrder("123e4567-e89b-12d3-a456-426614174000");
|
|
704
|
+
* console.log(`State: ${twap.state}, progress: ${twap.progress}`);
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
async getTwapOrder(twapOrderId) {
|
|
708
|
+
validate(CancelTwapOrderSchema, { twapOrderId });
|
|
709
|
+
return await this.makeAuthenticatedRequest(perpRoutes.orders.getTwap(twapOrderId), {
|
|
710
|
+
method: "GET",
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Lists TWAP parent orders for the authenticated user.
|
|
715
|
+
*
|
|
716
|
+
* @param params - Optional pagination and state / trading-pair filters
|
|
717
|
+
* @returns Promise resolving to ListTwapOrdersResponse with orders and pagination info
|
|
718
|
+
* @throws {APIError} When API communication fails
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```typescript
|
|
722
|
+
* const active = await tradingAPI.listTwapOrders({ state: "ACTIVE", pageSize: 50 });
|
|
723
|
+
* console.log(`Found ${active.total} active TWAP orders`);
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
726
|
+
async listTwapOrders(params) {
|
|
727
|
+
if (params) {
|
|
728
|
+
validate(ListTwapOrdersSchema, params);
|
|
729
|
+
}
|
|
730
|
+
const { page = 1, pageSize = 20, state, tradingPairId } = params || {};
|
|
731
|
+
return await this.makeAuthenticatedRequest(perpRoutes.orders.listTwap({
|
|
732
|
+
page,
|
|
733
|
+
pageSize: Math.min(Math.max(pageSize, 1), 100),
|
|
734
|
+
state,
|
|
735
|
+
tradingPairId,
|
|
736
|
+
}), { method: "GET" });
|
|
737
|
+
}
|
|
576
738
|
}
|
package/dist/api/vault/api.d.ts
CHANGED
|
@@ -78,10 +78,17 @@ export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
|
|
|
78
78
|
*/
|
|
79
79
|
private getClientId;
|
|
80
80
|
/**
|
|
81
|
-
* Resolves an asset ID to its token address and determines
|
|
81
|
+
* Resolves an asset ID to its token address and determines whether it is the
|
|
82
|
+
* chain's wrapped-native asset (e.g. WSEI).
|
|
83
|
+
*
|
|
84
|
+
* The wrapped-native asset is a real ERC20 — its token address is never the
|
|
85
|
+
* zero address — so native handling keys on the balance response's
|
|
86
|
+
* `wrappedNative` flag, not on the address. Deposits of that asset are made
|
|
87
|
+
* in native coin via the payable `depositNative` (the vault wraps on entry);
|
|
88
|
+
* withdrawals of it pay out native coin (the vault unwraps).
|
|
82
89
|
*
|
|
83
90
|
* @param assetId - The asset identifier (UUID) to resolve
|
|
84
|
-
* @returns Promise resolving to object with tokenAddress and
|
|
91
|
+
* @returns Promise resolving to object with tokenAddress and isWrappedNative flag
|
|
85
92
|
* @throws {ContractError} When asset ID cannot be resolved
|
|
86
93
|
* @private
|
|
87
94
|
*/
|
|
@@ -128,7 +135,12 @@ export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
|
|
|
128
135
|
* first checks if sufficient approval exists, then obtains a signature from the
|
|
129
136
|
* API Gateway, and finally executes the deposit transaction on-chain.
|
|
130
137
|
*
|
|
131
|
-
* Note: This method requires prior approval via the `approve()` method
|
|
138
|
+
* Note: This method requires prior approval via the `approve()` method for
|
|
139
|
+
* standard ERC20 assets. The wrapped-native asset (e.g. WSEI, flagged
|
|
140
|
+
* `wrappedNative` in the balances API) is deposited in native coin via the
|
|
141
|
+
* payable `depositNative` — the vault wraps it into the ERC20 on entry — so
|
|
142
|
+
* no approval is needed and `amount` is sent as msg.value. Withdrawals of the
|
|
143
|
+
* wrapped-native asset pay out native coin (the vault unwraps).
|
|
132
144
|
*
|
|
133
145
|
* @param assetId - The asset identifier (UUID) to deposit
|
|
134
146
|
* @param amount - The amount of tokens to deposit (as bigint)
|
|
@@ -209,6 +221,10 @@ export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
|
|
|
209
221
|
* the index would strand the funds; the caller keeps it and finishes the
|
|
210
222
|
* withdrawal later with `retryWithdrawal(withdrawalIndex)`.
|
|
211
223
|
*
|
|
224
|
+
* Withdrawals of the wrapped-native asset (e.g. WSEI) pay out native coin —
|
|
225
|
+
* the vault unwraps on exit. This is decided server-side; no request change
|
|
226
|
+
* is needed.
|
|
227
|
+
*
|
|
212
228
|
* @param assetId - The asset identifier (UUID) to withdraw
|
|
213
229
|
* @param amount - The raw token amount to withdraw (as bigint)
|
|
214
230
|
* @param autoWait - When `true` (default) polls for the merkle-proof calldata
|
|
@@ -309,6 +325,11 @@ export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
|
|
|
309
325
|
* Compares the current allowance with the requested amount to determine if
|
|
310
326
|
* the user needs to approve more tokens before performing operations.
|
|
311
327
|
*
|
|
328
|
+
* The wrapped-native asset (e.g. WSEI) never needs approval: its deposits
|
|
329
|
+
* are made in native coin via the payable `depositNative` (the vault wraps
|
|
330
|
+
* on entry), so no ERC20 allowance is consumed and this method returns
|
|
331
|
+
* `false` without reading the chain.
|
|
332
|
+
*
|
|
312
333
|
* @param assetId - The asset identifier (UUID) to check for
|
|
313
334
|
* @param amount - The amount to check approval for (as bigint)
|
|
314
335
|
* @returns Promise resolving to true if approval is needed, false otherwise
|
package/dist/api/vault/api.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { CONTRACT_ABIS } from "@0xmonaco/contracts";
|
|
27
27
|
import { ApproveTokenSchema, DepositSchema, validate, WithdrawSchema } from "@0xmonaco/types";
|
|
28
|
-
import { erc20Abi
|
|
28
|
+
import { erc20Abi } from "viem";
|
|
29
29
|
import { APIError, ContractError, InvalidConfigError } from "../../errors";
|
|
30
30
|
import { BaseAPI } from "../base";
|
|
31
31
|
/** Default delay between polls while waiting for a withdrawal's proof. */
|
|
@@ -108,18 +108,27 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
108
108
|
return config.clientId;
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
|
-
* Resolves an asset ID to its token address and determines
|
|
111
|
+
* Resolves an asset ID to its token address and determines whether it is the
|
|
112
|
+
* chain's wrapped-native asset (e.g. WSEI).
|
|
113
|
+
*
|
|
114
|
+
* The wrapped-native asset is a real ERC20 — its token address is never the
|
|
115
|
+
* zero address — so native handling keys on the balance response's
|
|
116
|
+
* `wrappedNative` flag, not on the address. Deposits of that asset are made
|
|
117
|
+
* in native coin via the payable `depositNative` (the vault wraps on entry);
|
|
118
|
+
* withdrawals of it pay out native coin (the vault unwraps).
|
|
112
119
|
*
|
|
113
120
|
* @param assetId - The asset identifier (UUID) to resolve
|
|
114
|
-
* @returns Promise resolving to object with tokenAddress and
|
|
121
|
+
* @returns Promise resolving to object with tokenAddress and isWrappedNative flag
|
|
115
122
|
* @throws {ContractError} When asset ID cannot be resolved
|
|
116
123
|
* @private
|
|
117
124
|
*/
|
|
118
125
|
async resolveAsset(assetId) {
|
|
119
126
|
let tokenAddress;
|
|
127
|
+
let isWrappedNative;
|
|
120
128
|
try {
|
|
121
129
|
const assetBalance = await this.profileAPI.getUserBalanceByAssetId(assetId);
|
|
122
130
|
tokenAddress = assetBalance.token;
|
|
131
|
+
isWrappedNative = assetBalance.wrappedNative === true;
|
|
123
132
|
}
|
|
124
133
|
catch (error) {
|
|
125
134
|
// Differentiate between network errors and asset not found
|
|
@@ -142,8 +151,7 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
142
151
|
cause: error instanceof Error ? error : undefined,
|
|
143
152
|
});
|
|
144
153
|
}
|
|
145
|
-
|
|
146
|
-
return { tokenAddress, isNativeToken };
|
|
154
|
+
return { tokenAddress, isWrappedNative };
|
|
147
155
|
}
|
|
148
156
|
/**
|
|
149
157
|
* Approves the vault contract to spend tokens on behalf of the user.
|
|
@@ -216,7 +224,12 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
216
224
|
* first checks if sufficient approval exists, then obtains a signature from the
|
|
217
225
|
* API Gateway, and finally executes the deposit transaction on-chain.
|
|
218
226
|
*
|
|
219
|
-
* Note: This method requires prior approval via the `approve()` method
|
|
227
|
+
* Note: This method requires prior approval via the `approve()` method for
|
|
228
|
+
* standard ERC20 assets. The wrapped-native asset (e.g. WSEI, flagged
|
|
229
|
+
* `wrappedNative` in the balances API) is deposited in native coin via the
|
|
230
|
+
* payable `depositNative` — the vault wraps it into the ERC20 on entry — so
|
|
231
|
+
* no approval is needed and `amount` is sent as msg.value. Withdrawals of the
|
|
232
|
+
* wrapped-native asset pay out native coin (the vault unwraps).
|
|
220
233
|
*
|
|
221
234
|
* @param assetId - The asset identifier (UUID) to deposit
|
|
222
235
|
* @param amount - The amount of tokens to deposit (as bigint)
|
|
@@ -269,8 +282,8 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
269
282
|
// The on-chain applicationData carries the client id plus, for margin
|
|
270
283
|
// deposits, the routing hint the indexer decodes.
|
|
271
284
|
const applicationData = encodeDepositApplicationData(clientId, target);
|
|
272
|
-
const { tokenAddress,
|
|
273
|
-
if (!
|
|
285
|
+
const { tokenAddress, isWrappedNative } = await this.resolveAsset(assetId);
|
|
286
|
+
if (!isWrappedNative) {
|
|
274
287
|
// Check if approval is needed before proceeding
|
|
275
288
|
const needsApproval = await this.needsApproval(assetId, amount);
|
|
276
289
|
if (needsApproval) {
|
|
@@ -283,9 +296,11 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
283
296
|
if (!walletAccount) {
|
|
284
297
|
throw new InvalidConfigError("No account available in wallet client", "account");
|
|
285
298
|
}
|
|
286
|
-
// For native
|
|
299
|
+
// For the wrapped-native asset (e.g. WSEI), use depositNative (payable) — the
|
|
300
|
+
// vault wraps the native coin into the ERC20 on entry, so no allowance is
|
|
301
|
+
// involved. All other ERC20 tokens go through approve + depositERC20.
|
|
287
302
|
let hash;
|
|
288
|
-
if (
|
|
303
|
+
if (isWrappedNative) {
|
|
289
304
|
hash = await this.walletClient.writeContract({
|
|
290
305
|
address: vaultAddress,
|
|
291
306
|
abi: CONTRACT_ABIS.vault,
|
|
@@ -380,6 +395,10 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
380
395
|
* the index would strand the funds; the caller keeps it and finishes the
|
|
381
396
|
* withdrawal later with `retryWithdrawal(withdrawalIndex)`.
|
|
382
397
|
*
|
|
398
|
+
* Withdrawals of the wrapped-native asset (e.g. WSEI) pay out native coin —
|
|
399
|
+
* the vault unwraps on exit. This is decided server-side; no request change
|
|
400
|
+
* is needed.
|
|
401
|
+
*
|
|
383
402
|
* @param assetId - The asset identifier (UUID) to withdraw
|
|
384
403
|
* @param amount - The raw token amount to withdraw (as bigint)
|
|
385
404
|
* @param autoWait - When `true` (default) polls for the merkle-proof calldata
|
|
@@ -597,6 +616,11 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
597
616
|
* Compares the current allowance with the requested amount to determine if
|
|
598
617
|
* the user needs to approve more tokens before performing operations.
|
|
599
618
|
*
|
|
619
|
+
* The wrapped-native asset (e.g. WSEI) never needs approval: its deposits
|
|
620
|
+
* are made in native coin via the payable `depositNative` (the vault wraps
|
|
621
|
+
* on entry), so no ERC20 allowance is consumed and this method returns
|
|
622
|
+
* `false` without reading the chain.
|
|
623
|
+
*
|
|
600
624
|
* @param assetId - The asset identifier (UUID) to check for
|
|
601
625
|
* @param amount - The amount to check approval for (as bigint)
|
|
602
626
|
* @returns Promise resolving to true if approval is needed, false otherwise
|
|
@@ -616,6 +640,12 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
616
640
|
* ```
|
|
617
641
|
*/
|
|
618
642
|
async needsApproval(assetId, amount) {
|
|
643
|
+
const { isWrappedNative } = await this.resolveAsset(assetId);
|
|
644
|
+
if (isWrappedNative) {
|
|
645
|
+
// Wrapped-native deposits route to the payable depositNative — no ERC20
|
|
646
|
+
// allowance is consumed, so approval is never needed.
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
619
649
|
const allowance = await this.getAllowance(assetId);
|
|
620
650
|
return allowance < amount;
|
|
621
651
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConditionalOrderEvent, Interval, LiquidationEvent, MonacoWebSocketConfig, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, PositionEvent, SessionCredentials, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
|
|
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
2
|
export type { ResyncHandler, StatusHandler, WebSocketResyncInfo } from "@0xmonaco/types";
|
|
3
3
|
export type MessageHandler<T> = (data: T) => void;
|
|
4
4
|
/**
|
|
@@ -27,7 +27,7 @@ export interface MonacoWebSocket {
|
|
|
27
27
|
/** Subscribe to orderbook events (public) */
|
|
28
28
|
orderbook: (tradingPairId: string, tradingMode: TradingMode, magnitude: number, quotationMode: OrderbookQuotationMode, handler: MessageHandler<OrderbookEvent>) => () => void;
|
|
29
29
|
/** Subscribe to OHLCV candlestick events (public) */
|
|
30
|
-
ohlcv: (tradingPairId: string, tradingMode: TradingMode, interval: Interval, handler: MessageHandler<OHLCVEvent
|
|
30
|
+
ohlcv: (tradingPairId: string, tradingMode: TradingMode, interval: Interval, handler: MessageHandler<OHLCVEvent>, priceType?: OHLCVPriceType) => () => void;
|
|
31
31
|
/** Subscribe to trade events (public) */
|
|
32
32
|
trades: (tradingPairId: string, handler: MessageHandler<TradeEvent>) => () => void;
|
|
33
33
|
/** Subscribe to user movement events (requires authentication) */
|
|
@@ -38,8 +38,16 @@ export interface MonacoWebSocket {
|
|
|
38
38
|
balances: (handler: MessageHandler<UserBalanceEvent>) => () => void;
|
|
39
39
|
/** Subscribe to conditional TP/SL lifecycle events (requires authentication) */
|
|
40
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;
|
|
41
43
|
/** Subscribe to position state updates (requires authentication); omit `tradingPairId` for all pairs */
|
|
42
44
|
positions: (handler: MessageHandler<PositionEvent>, tradingPairId?: string) => () => void;
|
|
43
45
|
/** Subscribe to liquidation alerts (requires authentication); omit `tradingPairId` for all pairs */
|
|
44
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;
|
|
45
53
|
}
|
|
@@ -12,6 +12,7 @@ const CONDITIONAL_ORDER_CONDITION_TYPES = ["STOP_LOSS", "TAKE_PROFIT"];
|
|
|
12
12
|
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
|
+
const TWAP_ORDER_STATES = ["PENDING", "ACTIVE", "COMPLETED", "CANCELLED"];
|
|
15
16
|
function isRecord(value) {
|
|
16
17
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
18
|
}
|
|
@@ -170,6 +171,147 @@ function parseLiquidationEvent(rawData) {
|
|
|
170
171
|
},
|
|
171
172
|
};
|
|
172
173
|
}
|
|
174
|
+
function parseAccountEvent(rawData) {
|
|
175
|
+
if (!isRecord(rawData)) {
|
|
176
|
+
throw new Error("Invalid account event: payload must be an object");
|
|
177
|
+
}
|
|
178
|
+
if (!isRecord(rawData.data)) {
|
|
179
|
+
throw new Error("Invalid account event: data must be an object");
|
|
180
|
+
}
|
|
181
|
+
const data = keysToCamelCase(rawData.data);
|
|
182
|
+
return {
|
|
183
|
+
// event_type is the event-class discriminator, validated strictly so a
|
|
184
|
+
// mis-routed frame is rejected rather than parsed as an account update.
|
|
185
|
+
eventType: readEnum(rawData, "event_type", ["account_update"]),
|
|
186
|
+
userId: readString(rawData, "user_id"),
|
|
187
|
+
data: {
|
|
188
|
+
marginAccountId: readString(data, "marginAccountId"),
|
|
189
|
+
equity: readString(data, "equity"),
|
|
190
|
+
collateral: readString(data, "collateral"),
|
|
191
|
+
freeCollateral: readString(data, "freeCollateral"),
|
|
192
|
+
marginRatio: readString(data, "marginRatio"),
|
|
193
|
+
maintenanceRequirement: readString(data, "maintenanceRequirement"),
|
|
194
|
+
updatedAt: readString(data, "updatedAt"),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function parseMarketStatsData(raw) {
|
|
199
|
+
if (!isRecord(raw)) {
|
|
200
|
+
throw new Error("Invalid market stats event: data must be an object");
|
|
201
|
+
}
|
|
202
|
+
const data = keysToCamelCase(raw);
|
|
203
|
+
return {
|
|
204
|
+
symbol: readString(data, "symbol"),
|
|
205
|
+
tradingPairId: readString(data, "tradingPairId"),
|
|
206
|
+
indexPrice: readOptionalString(data, "indexPrice"),
|
|
207
|
+
markPrice: readOptionalString(data, "markPrice"),
|
|
208
|
+
midPrice: readOptionalString(data, "midPrice"),
|
|
209
|
+
bestBidPrice: readOptionalString(data, "bestBidPrice"),
|
|
210
|
+
bestAskPrice: readOptionalString(data, "bestAskPrice"),
|
|
211
|
+
openInterest: readOptionalString(data, "openInterest"),
|
|
212
|
+
openInterestLimit: readOptionalString(data, "openInterestLimit"),
|
|
213
|
+
lastTradePrice: readOptionalString(data, "lastTradePrice"),
|
|
214
|
+
dailyBaseTokenVolume: readOptionalString(data, "dailyBaseTokenVolume"),
|
|
215
|
+
dailyQuoteTokenVolume: readOptionalString(data, "dailyQuoteTokenVolume"),
|
|
216
|
+
dailyPriceLow: readOptionalString(data, "dailyPriceLow"),
|
|
217
|
+
dailyPriceHigh: readOptionalString(data, "dailyPriceHigh"),
|
|
218
|
+
dailyPriceChange: readOptionalString(data, "dailyPriceChange"),
|
|
219
|
+
dailyPriceChangePercent: readOptionalString(data, "dailyPriceChangePercent"),
|
|
220
|
+
currentFundingRate: readOptionalString(data, "currentFundingRate"),
|
|
221
|
+
fundingRate: readOptionalString(data, "fundingRate"),
|
|
222
|
+
fundingTimestamp: readOptionalNumber(data, "fundingTimestamp"),
|
|
223
|
+
premium: readOptionalString(data, "premium"),
|
|
224
|
+
baseInterestRate: readOptionalString(data, "baseInterestRate"),
|
|
225
|
+
fundingClampSmall: readOptionalString(data, "fundingClampSmall"),
|
|
226
|
+
fundingClampBig: readOptionalString(data, "fundingClampBig"),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function parseMarketStatsEvent(rawData) {
|
|
230
|
+
if (!isRecord(rawData)) {
|
|
231
|
+
throw new Error("Invalid market stats event: payload must be an object");
|
|
232
|
+
}
|
|
233
|
+
// The per-market frame's top-level `trading_pair_id` is not inside `data`, so
|
|
234
|
+
// it stays snake_case (only `data` is camelCased by parseMarketStatsData).
|
|
235
|
+
return {
|
|
236
|
+
tradingPairId: readString(rawData, "trading_pair_id"),
|
|
237
|
+
data: parseMarketStatsData(rawData.data),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function parseMarketStatsSummary(raw) {
|
|
241
|
+
if (!isRecord(raw)) {
|
|
242
|
+
throw new Error("Invalid market stats summary: entry must be an object");
|
|
243
|
+
}
|
|
244
|
+
const data = keysToCamelCase(raw);
|
|
245
|
+
return {
|
|
246
|
+
tradingPairId: readString(data, "tradingPairId"),
|
|
247
|
+
symbol: readString(data, "symbol"),
|
|
248
|
+
lastTradePrice: readOptionalString(data, "lastTradePrice"),
|
|
249
|
+
markPrice: readOptionalString(data, "markPrice"),
|
|
250
|
+
dailyPriceChange: readOptionalString(data, "dailyPriceChange"),
|
|
251
|
+
dailyPriceChangePercent: readOptionalString(data, "dailyPriceChangePercent"),
|
|
252
|
+
dailyBaseTokenVolume: readOptionalString(data, "dailyBaseTokenVolume"),
|
|
253
|
+
dailyQuoteTokenVolume: readOptionalString(data, "dailyQuoteTokenVolume"),
|
|
254
|
+
dailyPriceHigh: readOptionalString(data, "dailyPriceHigh"),
|
|
255
|
+
dailyPriceLow: readOptionalString(data, "dailyPriceLow"),
|
|
256
|
+
openInterest: readOptionalString(data, "openInterest"),
|
|
257
|
+
currentFundingRate: readOptionalString(data, "currentFundingRate"),
|
|
258
|
+
fundingTimestamp: readOptionalNumber(data, "fundingTimestamp"),
|
|
259
|
+
// marketStatus stays lenient (read as string, not a strict enum) so an
|
|
260
|
+
// unrecognized status value never drops an otherwise-valid summary row.
|
|
261
|
+
marketStatus: readString(data, "marketStatus"),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function parseMarketStatsAllEvent(rawData) {
|
|
265
|
+
if (!isRecord(rawData) || !Array.isArray(rawData.markets)) {
|
|
266
|
+
throw new Error("Invalid market stats all event: markets must be an array");
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
markets: rawData.markets.map(parseMarketStatsSummary),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Parse a `twap_orders` channel frame into a typed event. Only the envelope
|
|
274
|
+
* and the snapshot's identity/state fields are validated strictly — the rest
|
|
275
|
+
* of the TwapOrder snapshot is passed through camelCased, so additive server
|
|
276
|
+
* fields never break older clients. The server serializes absent optional
|
|
277
|
+
* fields as explicit `null`; those are dropped so the parsed snapshot matches
|
|
278
|
+
* the optional (`?:`) fields of `TwapOrderEventData`.
|
|
279
|
+
*/
|
|
280
|
+
function parseTwapOrderEvent(rawData) {
|
|
281
|
+
if (!isRecord(rawData)) {
|
|
282
|
+
throw new Error("Invalid TWAP order event: payload must be an object");
|
|
283
|
+
}
|
|
284
|
+
if (!isRecord(rawData.data)) {
|
|
285
|
+
throw new Error("Invalid TWAP order event: data must be an object");
|
|
286
|
+
}
|
|
287
|
+
const eventType = rawData.event_type;
|
|
288
|
+
if (eventType !== "twap_order_update") {
|
|
289
|
+
throw new Error("Invalid TWAP order event: event_type must be twap_order_update");
|
|
290
|
+
}
|
|
291
|
+
const userId = rawData.user_id;
|
|
292
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
293
|
+
throw new Error("Invalid TWAP order event: user_id must be a non-empty string");
|
|
294
|
+
}
|
|
295
|
+
const data = keysToCamelCase(rawData.data);
|
|
296
|
+
for (const key of Object.keys(data)) {
|
|
297
|
+
if (data[key] === null) {
|
|
298
|
+
delete data[key];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const twapOrderId = data.twapOrderId;
|
|
302
|
+
if (typeof twapOrderId !== "string" || twapOrderId.length === 0) {
|
|
303
|
+
throw new Error("Invalid TWAP order event: twapOrderId must be a non-empty string");
|
|
304
|
+
}
|
|
305
|
+
const state = data.state;
|
|
306
|
+
if (typeof state !== "string" || !TWAP_ORDER_STATES.includes(state)) {
|
|
307
|
+
throw new Error(`Invalid TWAP order event: state must be one of ${TWAP_ORDER_STATES.join(", ")}`);
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
eventType: "twap_order_update",
|
|
311
|
+
userId,
|
|
312
|
+
data: data,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
173
315
|
/**
|
|
174
316
|
* Create a Monaco WebSocket client
|
|
175
317
|
*/
|
|
@@ -294,12 +436,35 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
294
436
|
send({ type: "Subscribe", channels });
|
|
295
437
|
}
|
|
296
438
|
};
|
|
439
|
+
// A subscription channel matches a frame when it is identical, is a prefix of
|
|
440
|
+
// the frame's dotted-by-colon path, or ends in a `:*` wildcard segment whose
|
|
441
|
+
// prefix the frame shares. The wildcard case exists because a `*` price-series
|
|
442
|
+
// subscription (`ohlcv:...:interval:*`) must receive the concrete series
|
|
443
|
+
// frames the server publishes (`ohlcv:...:interval:trade` / `:mark`), which are
|
|
444
|
+
// neither equal to nor prefixed by the literal `:*` channel.
|
|
445
|
+
// A legacy four-token OHLCV subscription (`ohlcv:pair:mode:interval`, no price
|
|
446
|
+
// series) is trade-only by contract. The server suffixes every OHLCV frame
|
|
447
|
+
// with a series, so it must match the bare four-token frame or the explicit
|
|
448
|
+
// `:trade` frame — never `:mark` — otherwise a connection that also holds a
|
|
449
|
+
// mark or wildcard subscription for the same pair would leak mark frames into
|
|
450
|
+
// the legacy handler via the generic prefix match.
|
|
451
|
+
const isLegacyOhlcvKey = (channel) => channel.startsWith("ohlcv:") && channel.split(":").length === 4;
|
|
452
|
+
const channelMatches = (subscriptionChannel, frameChannel) => {
|
|
453
|
+
if (subscriptionChannel.endsWith(":*")) {
|
|
454
|
+
// Keep the trailing colon so only frames with a further segment match.
|
|
455
|
+
return frameChannel.startsWith(subscriptionChannel.slice(0, -1));
|
|
456
|
+
}
|
|
457
|
+
if (isLegacyOhlcvKey(subscriptionChannel)) {
|
|
458
|
+
return frameChannel === subscriptionChannel || frameChannel === `${subscriptionChannel}:trade`;
|
|
459
|
+
}
|
|
460
|
+
return frameChannel === subscriptionChannel || frameChannel.startsWith(`${subscriptionChannel}:`);
|
|
461
|
+
};
|
|
297
462
|
const routeMessage = (msg) => {
|
|
298
463
|
if (msg.type !== "Event" || !msg.channel)
|
|
299
464
|
return;
|
|
300
465
|
// Find matching handlers and call all of them
|
|
301
466
|
for (const [subscriptionChannel, channelHandlers] of handlers) {
|
|
302
|
-
if (
|
|
467
|
+
if (channelMatches(subscriptionChannel, msg.channel)) {
|
|
303
468
|
for (const handler of channelHandlers) {
|
|
304
469
|
handler(msg.data);
|
|
305
470
|
}
|
|
@@ -521,18 +686,26 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
521
686
|
}
|
|
522
687
|
});
|
|
523
688
|
};
|
|
524
|
-
const subscribeOHLCV = (tradingPairId, tradingMode, interval, handler) => {
|
|
525
|
-
|
|
689
|
+
const subscribeOHLCV = (tradingPairId, tradingMode, interval, handler, priceType) => {
|
|
690
|
+
// Four-token channel selects the trade-derived candles (back-compat); the
|
|
691
|
+
// optional fifth token selects the price series (`mark` = close-only mark
|
|
692
|
+
// series, `*` = both).
|
|
693
|
+
const channel = priceType === undefined ? `ohlcv:${tradingPairId}:${tradingMode}:${interval}` : `ohlcv:${tradingPairId}:${tradingMode}:${interval}:${priceType}`;
|
|
526
694
|
return subscribe(channel, (rawData) => {
|
|
527
695
|
try {
|
|
528
696
|
const data = rawData;
|
|
529
697
|
const ohlcvData = data.data;
|
|
698
|
+
// Concrete price series of this candle (`trade`/`mark`). Present on
|
|
699
|
+
// every current frame; a wildcard (`*`) subscriber reads it to tell the
|
|
700
|
+
// two streams apart. Omitted when a legacy frame carries no `price_type`.
|
|
701
|
+
const priceType = data.price_type === "trade" || data.price_type === "mark" ? data.price_type : undefined;
|
|
530
702
|
const event = {
|
|
531
703
|
// `trading_pair_id` is the pair UUID; `symbol` is the display string.
|
|
532
704
|
// `tradingPairId` must carry the UUID (the symbol lives in `candlestick.s`).
|
|
533
705
|
tradingPairId: data.trading_pair_id,
|
|
534
706
|
tradingMode: data.trading_mode,
|
|
535
707
|
interval: data.interval,
|
|
708
|
+
priceType,
|
|
536
709
|
candlestick: {
|
|
537
710
|
T: ohlcvData.period_start || 0,
|
|
538
711
|
t: ohlcvData.period_end || 0,
|
|
@@ -693,6 +866,23 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
693
866
|
}
|
|
694
867
|
});
|
|
695
868
|
};
|
|
869
|
+
const subscribeTwapOrders = (handler, tradingPairId) => {
|
|
870
|
+
if (!handler) {
|
|
871
|
+
throw new Error("twapOrders subscription requires a handler");
|
|
872
|
+
}
|
|
873
|
+
if (tradingPairId !== undefined && tradingPairId.length === 0) {
|
|
874
|
+
throw new Error("twapOrders tradingPairId cannot be empty");
|
|
875
|
+
}
|
|
876
|
+
const channel = tradingPairId ? `twap_orders:${tradingPairId}` : "twap_orders";
|
|
877
|
+
return subscribe(channel, (rawData) => {
|
|
878
|
+
try {
|
|
879
|
+
handler(parseTwapOrderEvent(rawData));
|
|
880
|
+
}
|
|
881
|
+
catch (err) {
|
|
882
|
+
console.error("WebSocket: Error processing TWAP order event", err);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
};
|
|
696
886
|
/**
|
|
697
887
|
* Subscribe to authenticated user's position updates.
|
|
698
888
|
* Requires authentication - user is identified from the signed session-key
|
|
@@ -741,6 +931,72 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
741
931
|
}
|
|
742
932
|
});
|
|
743
933
|
};
|
|
934
|
+
/**
|
|
935
|
+
* Subscribe to the authenticated user's account-level margin health updates
|
|
936
|
+
* (equity, collateral, free collateral, margin ratio, maintenance
|
|
937
|
+
* requirement). Requires authentication - the user is identified from the
|
|
938
|
+
* signed session-key handshake. Lag policy is Conflate: a lagging subscriber
|
|
939
|
+
* is fast-forwarded, no gap signal. Each frame is a terminal-state snapshot
|
|
940
|
+
* per margin account, not an additive delta.
|
|
941
|
+
*/
|
|
942
|
+
const subscribeAccount = (handler) => {
|
|
943
|
+
if (!handler) {
|
|
944
|
+
throw new Error("account subscription requires a handler");
|
|
945
|
+
}
|
|
946
|
+
return subscribe("account", (rawData) => {
|
|
947
|
+
try {
|
|
948
|
+
handler(parseAccountEvent(rawData));
|
|
949
|
+
}
|
|
950
|
+
catch (err) {
|
|
951
|
+
console.error("WebSocket: Error processing account event", err);
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
};
|
|
955
|
+
/**
|
|
956
|
+
* Subscribe to per-market statistics for a single trading pair (public). Each
|
|
957
|
+
* frame carries the market's full current stats as a terminal-state snapshot.
|
|
958
|
+
* Use {@link subscribeMarketStatsAll} for the all-markets summary instead.
|
|
959
|
+
*/
|
|
960
|
+
const subscribeMarketStats = (tradingPairId, handler) => {
|
|
961
|
+
if (!tradingPairId) {
|
|
962
|
+
throw new Error("marketStats subscription requires a tradingPairId");
|
|
963
|
+
}
|
|
964
|
+
if (!handler) {
|
|
965
|
+
throw new Error("marketStats subscription requires a handler");
|
|
966
|
+
}
|
|
967
|
+
return subscribe(`market_stats:${tradingPairId}`, (rawData) => {
|
|
968
|
+
try {
|
|
969
|
+
handler(parseMarketStatsEvent(rawData));
|
|
970
|
+
}
|
|
971
|
+
catch (err) {
|
|
972
|
+
console.error("WebSocket: Error processing market stats event", err);
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
};
|
|
976
|
+
/**
|
|
977
|
+
* Subscribe to the all-markets statistics summary (public): one summary row
|
|
978
|
+
* per market in a single frame per push.
|
|
979
|
+
*/
|
|
980
|
+
const subscribeMarketStatsAll = (handler) => {
|
|
981
|
+
if (!handler) {
|
|
982
|
+
throw new Error("marketStatsAll subscription requires a handler");
|
|
983
|
+
}
|
|
984
|
+
return subscribe("market_stats", (rawData) => {
|
|
985
|
+
// routeMessage prefix-matches the bare `market_stats` subscription against
|
|
986
|
+
// per-market `market_stats:<pair>` frames too; those carry
|
|
987
|
+
// `{ trading_pair_id, data }`, not `{ markets }`. Drop them here so the
|
|
988
|
+
// all-markets handler only ever sees all-markets frames.
|
|
989
|
+
if (!isRecord(rawData) || !Array.isArray(rawData.markets)) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
handler(parseMarketStatsAllEvent(rawData));
|
|
994
|
+
}
|
|
995
|
+
catch (err) {
|
|
996
|
+
console.error("WebSocket: Error processing market stats all event", err);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
};
|
|
744
1000
|
return {
|
|
745
1001
|
connect,
|
|
746
1002
|
disconnect,
|
|
@@ -769,7 +1025,11 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
769
1025
|
userOrders: subscribeUserOrders,
|
|
770
1026
|
balances: subscribeBalances,
|
|
771
1027
|
conditionalOrders: subscribeConditionalOrders,
|
|
1028
|
+
twapOrders: subscribeTwapOrders,
|
|
772
1029
|
positions: subscribePositions,
|
|
773
1030
|
liquidations: subscribeLiquidations,
|
|
1031
|
+
account: subscribeAccount,
|
|
1032
|
+
marketStats: subscribeMarketStats,
|
|
1033
|
+
marketStatsAll: subscribeMarketStatsAll,
|
|
774
1034
|
};
|
|
775
1035
|
}
|
package/dist/coverage.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare const COVERED: {
|
|
|
10
10
|
cancel_conditional_order: string;
|
|
11
11
|
get_conditional_order: string;
|
|
12
12
|
cancel_order: string;
|
|
13
|
+
cancel_twap_order: string;
|
|
14
|
+
create_twap_order: string;
|
|
15
|
+
get_twap_order: string;
|
|
16
|
+
list_twap_orders: string;
|
|
13
17
|
close_position: string;
|
|
14
18
|
create_challenge: string;
|
|
15
19
|
create_delegated_session: string;
|
package/dist/coverage.js
CHANGED
|
@@ -10,6 +10,10 @@ export const COVERED = {
|
|
|
10
10
|
cancel_conditional_order: "cancelConditionalOrder",
|
|
11
11
|
get_conditional_order: "getConditionalOrder",
|
|
12
12
|
cancel_order: "cancelOrder",
|
|
13
|
+
cancel_twap_order: "cancelTwapOrder",
|
|
14
|
+
create_twap_order: "createTwapOrder",
|
|
15
|
+
get_twap_order: "getTwapOrder",
|
|
16
|
+
list_twap_orders: "listTwapOrders",
|
|
13
17
|
close_position: "closePosition",
|
|
14
18
|
create_challenge: "createChallenge",
|
|
15
19
|
create_delegated_session: "createDelegatedSession",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.42-develop.791e6f3",
|
|
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.
|
|
27
|
-
"@0xmonaco/types": "1.0.
|
|
26
|
+
"@0xmonaco/contracts": "1.0.42-develop.791e6f3",
|
|
27
|
+
"@0xmonaco/types": "1.0.42-develop.791e6f3",
|
|
28
28
|
"@noble/curves": "^1.9.1",
|
|
29
29
|
"@noble/hashes": "^1.8.0",
|
|
30
30
|
"http-status-codes": "^2.3.0"
|