@0xmonaco/core 1.0.65 → 1.0.66
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.
|
@@ -75,6 +75,16 @@ export class PositionsAPIImpl extends BaseAPI {
|
|
|
75
75
|
expiresAt: request.stopLoss.expiresAt,
|
|
76
76
|
}
|
|
77
77
|
: undefined,
|
|
78
|
+
trailingStop: request.trailingStop
|
|
79
|
+
? {
|
|
80
|
+
trailBps: request.trailingStop.trailBps,
|
|
81
|
+
activationPrice: request.trailingStop.activationPrice,
|
|
82
|
+
quantity: request.trailingStop.quantity,
|
|
83
|
+
closePosition: request.trailingStop.closePosition,
|
|
84
|
+
slippageToleranceBps: request.trailingStop.slippageToleranceBps,
|
|
85
|
+
expiresAt: request.trailingStop.expiresAt,
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
78
88
|
oco: request.oco,
|
|
79
89
|
}),
|
|
80
90
|
});
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* );
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, CancelTwapOrderResponse, ConditionalOrder, CreateOrderResponse, CreateTwapOrderOptions, CreateTwapOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, GetTwapOrderTcaResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, ListTwapOrdersParams, ListTwapOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, PositionSide, ReplaceOrderResponse, SelfTradePreventionMode, TimeInForce, TradingAPI, TradingMode, TwapOrder } from "@0xmonaco/types";
|
|
24
|
+
import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, CancelTwapOrderResponse, ConditionalOrder, CreateOrderResponse, CreateTwapOrderOptions, CreateTwapOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, GetTwapOrderTcaResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, ListTwapOrdersParams, ListTwapOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, ParentTrailingStopLegParams, PositionSide, ReplaceOrderResponse, SelfTradePreventionMode, 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
|
/**
|
|
@@ -94,6 +94,8 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
94
94
|
idempotencyKey?: string;
|
|
95
95
|
takeProfit?: ParentTpSlLegParams;
|
|
96
96
|
stopLoss?: ParentTpSlLegParams;
|
|
97
|
+
/** Trailing stop that arms when this order fully fills; margin entry orders only. */
|
|
98
|
+
trailingStop?: ParentTrailingStopLegParams;
|
|
97
99
|
}): Promise<CreateOrderResponse>;
|
|
98
100
|
/**
|
|
99
101
|
* Places a market order for immediate execution.
|
|
@@ -152,6 +154,8 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
152
154
|
selfTradePreventionMode?: SelfTradePreventionMode;
|
|
153
155
|
takeProfit?: ParentTpSlLegParams;
|
|
154
156
|
stopLoss?: ParentTpSlLegParams;
|
|
157
|
+
/** Trailing stop that arms when this order fully fills; margin entry orders only. */
|
|
158
|
+
trailingStop?: ParentTrailingStopLegParams;
|
|
155
159
|
clientOrderId?: string;
|
|
156
160
|
/** Generated once per call when omitted. Supply the same key to retry a logical order within 24 hours. */
|
|
157
161
|
idempotencyKey?: string;
|
package/dist/api/trading/api.js
CHANGED
|
@@ -41,6 +41,19 @@ function parentTpSlLegToRequest(leg) {
|
|
|
41
41
|
closePosition: leg.closePosition,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
function parentTrailingStopLegToRequest(leg) {
|
|
45
|
+
if (!leg)
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
trailBps: leg.trailBps,
|
|
49
|
+
activationPrice: leg.activationPrice,
|
|
50
|
+
// Same sizing contract as the TP/SL legs above — see the note there.
|
|
51
|
+
quantity: leg.quantity,
|
|
52
|
+
closePosition: leg.closePosition,
|
|
53
|
+
slippageToleranceBps: leg.slippageToleranceBps,
|
|
54
|
+
expiresAt: leg.expiresAt,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
44
57
|
export class TradingAPIImpl extends BaseAPI {
|
|
45
58
|
/**
|
|
46
59
|
* Places a limit order on the order book.
|
|
@@ -125,6 +138,7 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
125
138
|
idempotencyKey: options?.idempotencyKey === undefined ? crypto.randomUUID() : options.idempotencyKey,
|
|
126
139
|
takeProfit: parentTpSlLegToRequest(options?.takeProfit),
|
|
127
140
|
stopLoss: parentTpSlLegToRequest(options?.stopLoss),
|
|
141
|
+
trailingStop: parentTrailingStopLegToRequest(options?.trailingStop),
|
|
128
142
|
};
|
|
129
143
|
return await this.makeAuthenticatedRequest(perpRoutes.orders.create(), {
|
|
130
144
|
method: "POST",
|
|
@@ -201,6 +215,7 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
201
215
|
selfTradePreventionMode: options?.selfTradePreventionMode,
|
|
202
216
|
takeProfit: parentTpSlLegToRequest(options?.takeProfit),
|
|
203
217
|
stopLoss: parentTpSlLegToRequest(options?.stopLoss),
|
|
218
|
+
trailingStop: parentTrailingStopLegToRequest(options?.trailingStop),
|
|
204
219
|
clientOrderId: options?.clientOrderId,
|
|
205
220
|
idempotencyKey: options?.idempotencyKey === undefined ? crypto.randomUUID() : options.idempotencyKey,
|
|
206
221
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MonacoWebSocketConfig, SessionCredentials } from "@0xmonaco/types";
|
|
2
|
-
export type { MessageHandler, MonacoWebSocket, ResyncHandler, StatusHandler, WebSocketResyncInfo } from "@0xmonaco/types";
|
|
2
|
+
export type { MessageHandler, MonacoWebSocket, ReauthenticationRequiredHandler, ResyncHandler, StatusHandler, WebSocketReauthenticationInfo, WebSocketResyncInfo, } from "@0xmonaco/types";
|
|
3
3
|
/**
|
|
4
4
|
* Options for {@link createMonacoWebSocket}. The reconnect/resync knobs and
|
|
5
5
|
* callbacks are shared with the main SDK config via {@link MonacoWebSocketConfig}
|
|
@@ -155,6 +155,13 @@ function parseConditionalOrderEventData(raw) {
|
|
|
155
155
|
cancelledAt: readOptionalString(data, "cancelledAt"),
|
|
156
156
|
expiresAt: readOptionalString(data, "expiresAt"),
|
|
157
157
|
failureReason: readOptionalString(data, "failureReason"),
|
|
158
|
+
// Trailing-stop fields (0XM-2827), explicit `null` on TP/SL rows. A live
|
|
159
|
+
// `ratcheted` frame carries the moved `triggerPrice` together with the
|
|
160
|
+
// `watermarkPrice` it was derived from; none of these is validated.
|
|
161
|
+
trailBps: readOptionalNumber(data, "trailBps"),
|
|
162
|
+
activationPrice: readOptionalString(data, "activationPrice"),
|
|
163
|
+
watermarkPrice: readOptionalString(data, "watermarkPrice"),
|
|
164
|
+
trailArmedAt: readOptionalString(data, "trailArmedAt"),
|
|
158
165
|
// Free-form on the wire (the server field is a plain string), so it is
|
|
159
166
|
// passed through untouched rather than matched against a value list.
|
|
160
167
|
reason: readString(data, "reason"),
|
|
@@ -683,6 +690,15 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
683
690
|
// Close code of the connection loss the next successful reopen recovers
|
|
684
691
|
// from; null when no resync is owed (first connect, clean disconnect).
|
|
685
692
|
let pendingResyncCode = null;
|
|
693
|
+
// Authentication-ending Error frame received on the current socket, tagged
|
|
694
|
+
// with the session it rejected. The server answers handshakes in the order
|
|
695
|
+
// they were sent, so each Authenticated / AUTH_FAILED reply belongs to the
|
|
696
|
+
// oldest pending handshake, and SESSION_INVALID revokes the session the
|
|
697
|
+
// server last confirmed. All of it resets per socket so a frame from one
|
|
698
|
+
// connection never decides how a later connection's close is read.
|
|
699
|
+
let lastAuthError = null;
|
|
700
|
+
let pendingAuthSessions = [];
|
|
701
|
+
let confirmedSession;
|
|
686
702
|
// Handler storage: channel -> set of handlers (supports multiple subscribers per channel)
|
|
687
703
|
const handlers = new Map();
|
|
688
704
|
// Subscribe-time Snapshot frame handlers, keyed by the RAW subscribed
|
|
@@ -732,6 +748,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
732
748
|
return;
|
|
733
749
|
const timestamp = Date.now();
|
|
734
750
|
const signature = signMessage(hexToBytes(session.privateKey), composeWsSigningString(session.publicKey, timestamp));
|
|
751
|
+
pendingAuthSessions.push(session);
|
|
735
752
|
ws.send(JSON.stringify({
|
|
736
753
|
type: "Authenticate",
|
|
737
754
|
public_key: session.publicKey,
|
|
@@ -831,8 +848,8 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
831
848
|
};
|
|
832
849
|
const routeMessage = (msg) => {
|
|
833
850
|
if (msg.type === "Error") {
|
|
834
|
-
//
|
|
835
|
-
//
|
|
851
|
+
// An Error frame matches no subscription even when it names a channel,
|
|
852
|
+
// so without this it reached no consumer at all and a
|
|
836
853
|
// failed subscribe / invalidated session / unreadable subscribe-time
|
|
837
854
|
// snapshot was silent. `code` is passed through whatever its value —
|
|
838
855
|
// a code added server-side after this SDK shipped must still reach the
|
|
@@ -841,10 +858,31 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
841
858
|
// and synthesizing the missing half would send the app down a branch on
|
|
842
859
|
// data the server never sent.
|
|
843
860
|
if (typeof msg.code === "string" && typeof msg.message === "string") {
|
|
844
|
-
|
|
861
|
+
// The server sends the reason a connection is ending as an Error
|
|
862
|
+
// frame just before the close; the close event decides from it.
|
|
863
|
+
// Recorded before any application code runs, so a throwing onError
|
|
864
|
+
// cannot make the close look transient.
|
|
865
|
+
if (msg.code === "AUTH_FAILED") {
|
|
866
|
+
lastAuthError = { code: msg.code, message: msg.message, session: pendingAuthSessions.shift() };
|
|
867
|
+
}
|
|
868
|
+
else if (msg.code === "SESSION_INVALID") {
|
|
869
|
+
lastAuthError = { code: msg.code, message: msg.message, session: confirmedSession ?? pendingAuthSessions[0] };
|
|
870
|
+
}
|
|
871
|
+
// A per-channel failure names the channel it concerns; the key is
|
|
872
|
+
// left off entirely otherwise, so a connection-wide failure reaches
|
|
873
|
+
// the app exactly as the server sent it. A non-string channel is
|
|
874
|
+
// dropped rather than the frame: the code and message are still
|
|
875
|
+
// actionable without it. (The frame is untyped JSON, so the declared
|
|
876
|
+
// `string` type of `channel` is not trusted here.)
|
|
877
|
+
const channel = msg.channel;
|
|
878
|
+
options.onError?.(typeof channel === "string" ? { code: msg.code, message: msg.message, channel } : { code: msg.code, message: msg.message });
|
|
845
879
|
}
|
|
846
880
|
return;
|
|
847
881
|
}
|
|
882
|
+
if (msg.type === "Authenticated") {
|
|
883
|
+
confirmedSession = pendingAuthSessions.shift();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
848
886
|
if (!msg.channel)
|
|
849
887
|
return;
|
|
850
888
|
if (msg.type === "Snapshot") {
|
|
@@ -876,6 +914,9 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
876
914
|
try {
|
|
877
915
|
const socket = new WebSocket(getUrl());
|
|
878
916
|
ws = socket;
|
|
917
|
+
lastAuthError = null;
|
|
918
|
+
pendingAuthSessions = [];
|
|
919
|
+
confirmedSession = undefined;
|
|
879
920
|
const timeout = setTimeout(() => {
|
|
880
921
|
if (ws !== socket) {
|
|
881
922
|
resolve();
|
|
@@ -947,6 +988,56 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
947
988
|
}
|
|
948
989
|
ws = null;
|
|
949
990
|
stopHeartbeat();
|
|
991
|
+
// Terminal authentication loss: the server revoked the session
|
|
992
|
+
// (SESSION_INVALID, then 1008) or rejected this connection's
|
|
993
|
+
// handshake (AUTH_FAILED, then a close). Reconnecting would replay
|
|
994
|
+
// the same rejected credentials into the same rejection forever, so
|
|
995
|
+
// drop them, skip the reconnect, and let the app supply a fresh
|
|
996
|
+
// session. Handlers stay registered so that session resubscribes
|
|
997
|
+
// them. A 1008 without SESSION_INVALID (the message-rate cap) is a
|
|
998
|
+
// back-off-and-retry and falls through to the reconnect below, as
|
|
999
|
+
// does a close after the app already replaced the rejected session
|
|
1000
|
+
// (e.g. re-authenticating from onError): the fresh session is what
|
|
1001
|
+
// the reconnect uses.
|
|
1002
|
+
const authError = lastAuthError;
|
|
1003
|
+
const authLoss = authError !== null && authError.session !== undefined && (authError.code === "AUTH_FAILED" || event.code === 1008);
|
|
1004
|
+
const terminalAuthLoss = authLoss && session === authError.session;
|
|
1005
|
+
if (terminalAuthLoss) {
|
|
1006
|
+
session = undefined;
|
|
1007
|
+
// The reopen with a fresh session still owes the app a resync —
|
|
1008
|
+
// nothing was delivered while the session was dead — whether or
|
|
1009
|
+
// not automatic reconnect is on, since that reopen is driven by
|
|
1010
|
+
// the new session, not by the reconnect loop.
|
|
1011
|
+
if (pendingResyncCode === null) {
|
|
1012
|
+
pendingResyncCode = event.code;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
options.onStatusChange?.("disconnected");
|
|
1016
|
+
}
|
|
1017
|
+
finally {
|
|
1018
|
+
// Recovery must not hinge on the status observer not throwing.
|
|
1019
|
+
options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
// The app already handed over a replacement session on this socket.
|
|
1024
|
+
// Had it arrived after the close, setSessionKeypair would have
|
|
1025
|
+
// connected with it; honour that even with automatic reconnect off
|
|
1026
|
+
// (with it on, the reconnect below uses the new session).
|
|
1027
|
+
if (authLoss && session !== undefined && !autoReconnect) {
|
|
1028
|
+
if (pendingResyncCode === null) {
|
|
1029
|
+
pendingResyncCode = event.code;
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
options.onStatusChange?.("disconnected");
|
|
1033
|
+
}
|
|
1034
|
+
finally {
|
|
1035
|
+
connect().catch((err) => {
|
|
1036
|
+
console.warn("WebSocket: Failed to connect with the replacement session:", err);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
950
1041
|
// Reconnect on abnormal close. 1013 is the server's slow-client
|
|
951
1042
|
// shed: the stream has a gap, so the reopen owes the app a resync.
|
|
952
1043
|
// Keep the first owed code (upgrading to 1013 if it arrives later):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
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.
|
|
23
|
-
"@0xmonaco/types": "1.0.
|
|
22
|
+
"@0xmonaco/contracts": "1.0.66",
|
|
23
|
+
"@0xmonaco/types": "1.0.66",
|
|
24
24
|
"@noble/curves": "^1.9.1",
|
|
25
25
|
"@noble/hashes": "^2.4.0",
|
|
26
26
|
"http-status-codes": "^2.3.0"
|