lighter-ts-sdk 1.0.1 → 1.0.2
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/README.md +53 -40
- package/dist/api/transaction-api.d.ts +5 -1
- package/dist/api/transaction-api.d.ts.map +1 -1
- package/dist/api/transaction-api.js.map +1 -1
- package/dist/api/ws-order-client.d.ts +82 -0
- package/dist/api/ws-order-client.d.ts.map +1 -0
- package/dist/api/ws-order-client.js +281 -0
- package/dist/api/ws-order-client.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/signer/signer-client.d.ts.map +1 -1
- package/dist/signer/signer-client.js +5 -3
- package/dist/signer/signer-client.js.map +1 -1
- package/dist/signer/wasm-signer-client.d.ts +104 -10
- package/dist/signer/wasm-signer-client.d.ts.map +1 -1
- package/dist/signer/wasm-signer-client.js +643 -356
- package/dist/signer/wasm-signer-client.js.map +1 -1
- package/dist/utils/advanced-cache.d.ts +66 -0
- package/dist/utils/advanced-cache.d.ts.map +1 -0
- package/dist/utils/advanced-cache.js +204 -0
- package/dist/utils/advanced-cache.js.map +1 -0
- package/dist/utils/exceptions.d.ts +24 -0
- package/dist/utils/exceptions.d.ts.map +1 -1
- package/dist/utils/exceptions.js +48 -1
- package/dist/utils/exceptions.js.map +1 -1
- package/dist/utils/logger.d.ts +35 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +96 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/memory-pool.d.ts +98 -0
- package/dist/utils/memory-pool.d.ts.map +1 -0
- package/dist/utils/memory-pool.js +190 -0
- package/dist/utils/memory-pool.js.map +1 -0
- package/dist/utils/node-wasm-signer.d.ts +8 -0
- package/dist/utils/node-wasm-signer.d.ts.map +1 -1
- package/dist/utils/node-wasm-signer.js +84 -29
- package/dist/utils/node-wasm-signer.js.map +1 -1
- package/dist/utils/nonce-cache.d.ts +29 -0
- package/dist/utils/nonce-cache.d.ts.map +1 -0
- package/dist/utils/nonce-cache.js +111 -0
- package/dist/utils/nonce-cache.js.map +1 -0
- package/dist/utils/optimized-http-client.d.ts +29 -0
- package/dist/utils/optimized-http-client.d.ts.map +1 -0
- package/dist/utils/optimized-http-client.js +142 -0
- package/dist/utils/optimized-http-client.js.map +1 -0
- package/dist/utils/performance-monitor.d.ts +41 -0
- package/dist/utils/performance-monitor.d.ts.map +1 -0
- package/dist/utils/performance-monitor.js +183 -0
- package/dist/utils/performance-monitor.js.map +1 -0
- package/dist/utils/request-batcher.d.ts +45 -0
- package/dist/utils/request-batcher.d.ts.map +1 -0
- package/dist/utils/request-batcher.js +177 -0
- package/dist/utils/request-batcher.js.map +1 -0
- package/dist/utils/wasm-manager.d.ts +30 -0
- package/dist/utils/wasm-manager.d.ts.map +1 -0
- package/dist/utils/wasm-manager.js +115 -0
- package/dist/utils/wasm-manager.js.map +1 -0
- package/docs/AccountApi.md +1 -1
- package/docs/GettingStarted.md +8 -8
- package/docs/OrderApi.md +1 -1
- package/docs/SignerClient.md +1 -1
- package/docs/TransactionApi.md +1 -1
- package/docs/WsClient.md +1 -1
- package/docs/types/Account.md +1 -1
- package/docs/types/ApiKeyPair.md +1 -1
- package/docs/types/CreateOrderParams.md +1 -1
- package/docs/types/MarketOrderParams.md +1 -1
- package/docs/types/SignerConfig.md +1 -1
- package/docs/types/WasmSignerConfig.md +1 -1
- package/examples/README.md +1 -1
- package/examples/account_info.ts +5 -2
- package/examples/cancel_all_orders.ts +77 -0
- package/examples/close_all_positions.ts +69 -0
- package/examples/create_cancel_order.ts +48 -8
- package/examples/create_market_order.ts +42 -1
- package/examples/create_market_order_if_slippage.ts +69 -0
- package/examples/create_market_order_max_slippage.ts +43 -43
- package/examples/create_sl_tp.ts +114 -29
- package/examples/create_with_multiple_keys.ts +77 -58
- package/examples/get_info.ts +1 -1
- package/examples/market_data_json.ts +80 -0
- package/examples/wait_for_transaction.ts +101 -0
- package/examples/ws.ts +64 -24
- package/examples/ws_async.ts +40 -39
- package/package.json +70 -70
- package/dist/utils/signer.d.ts +0 -15
- package/dist/utils/signer.d.ts.map +0 -1
- package/dist/utils/signer.js +0 -68
- package/dist/utils/signer.js.map +0 -1
|
@@ -3,35 +3,136 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SignerClient = void 0;
|
|
4
4
|
const api_client_1 = require("../api/api-client");
|
|
5
5
|
const transaction_api_1 = require("../api/transaction-api");
|
|
6
|
-
const
|
|
6
|
+
const account_api_1 = require("../api/account-api");
|
|
7
7
|
const wasm_signer_1 = require("../utils/wasm-signer");
|
|
8
8
|
const node_wasm_signer_1 = require("../utils/node-wasm-signer");
|
|
9
9
|
const root_api_1 = require("../api/root-api");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
const exceptions_1 = require("../utils/exceptions");
|
|
12
|
+
const wasm_manager_1 = require("../utils/wasm-manager");
|
|
13
|
+
const nonce_cache_1 = require("../utils/nonce-cache");
|
|
14
|
+
const performance_monitor_1 = require("../utils/performance-monitor");
|
|
15
|
+
// import { poolManager } from '../utils/memory-pool';
|
|
16
|
+
// import { cacheManager } from '../utils/advanced-cache';
|
|
17
|
+
const request_batcher_1 = require("../utils/request-batcher");
|
|
18
|
+
const ws_order_client_1 = require("../api/ws-order-client");
|
|
10
19
|
class SignerClient {
|
|
11
20
|
constructor(config) {
|
|
12
21
|
this.clientCreated = false;
|
|
22
|
+
this.nonceCache = null;
|
|
23
|
+
this.wsOrderClient = null;
|
|
24
|
+
this.orderBatcher = null;
|
|
25
|
+
// Validate configuration
|
|
26
|
+
this.validateConfig(config);
|
|
13
27
|
this.config = config;
|
|
14
28
|
this.apiClient = new api_client_1.ApiClient({ host: config.url });
|
|
15
29
|
this.transactionApi = new transaction_api_1.TransactionApi(this.apiClient);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
this.accountApi = new account_api_1.AccountApi(this.apiClient);
|
|
31
|
+
// Initialize logging based on Python SDK patterns
|
|
32
|
+
if (config.logLevel !== undefined) {
|
|
33
|
+
logger_1.logger.setLevel(config.logLevel);
|
|
34
|
+
}
|
|
35
|
+
// Initialize WASM signer using manager
|
|
36
|
+
if (config.wasmConfig) {
|
|
37
|
+
const wasmManager = wasm_manager_1.WasmManager.getInstance();
|
|
38
|
+
const clientType = typeof window !== 'undefined' ? 'browser' : 'node';
|
|
39
|
+
// Use pre-initialized WASM client if available
|
|
40
|
+
if (wasmManager.isReady()) {
|
|
41
|
+
this.wallet = wasmManager.getWasmClient();
|
|
42
|
+
this.signerType = clientType === 'browser' ? 'wasm' : 'node-wasm';
|
|
26
43
|
}
|
|
27
44
|
else {
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
// Fallback to direct initialization
|
|
46
|
+
if (typeof window !== 'undefined') {
|
|
47
|
+
this.wallet = (0, wasm_signer_1.createWasmSignerClient)(config.wasmConfig);
|
|
48
|
+
this.signerType = 'wasm';
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.wallet = (0, node_wasm_signer_1.createNodeWasmSignerClient)(config.wasmConfig);
|
|
52
|
+
this.signerType = 'node-wasm';
|
|
53
|
+
}
|
|
30
54
|
}
|
|
31
55
|
}
|
|
32
56
|
else {
|
|
33
|
-
throw new Error('
|
|
57
|
+
throw new Error('wasmConfig must be provided.');
|
|
58
|
+
}
|
|
59
|
+
// Initialize optimization components
|
|
60
|
+
this.initializeOptimizations();
|
|
61
|
+
}
|
|
62
|
+
initializeOptimizations() {
|
|
63
|
+
// Initialize nonce cache first
|
|
64
|
+
this.nonceCache = new nonce_cache_1.NonceCache(async (apiKeyIndex, count) => {
|
|
65
|
+
// Get a single nonce and then calculate sequential nonces
|
|
66
|
+
const firstNonceResult = await this.transactionApi.getNextNonce(this.config.accountIndex, apiKeyIndex);
|
|
67
|
+
const nonces = [];
|
|
68
|
+
for (let i = 0; i < count; i++) {
|
|
69
|
+
nonces.push(firstNonceResult.nonce + i);
|
|
70
|
+
}
|
|
71
|
+
return nonces;
|
|
72
|
+
});
|
|
73
|
+
// Initialize WebSocket order client if enabled
|
|
74
|
+
if (this.config.enableWebSocket) {
|
|
75
|
+
this.wsOrderClient = new ws_order_client_1.WebSocketOrderClient({
|
|
76
|
+
url: this.config.url,
|
|
77
|
+
reconnectInterval: 5000,
|
|
78
|
+
maxReconnectAttempts: 10,
|
|
79
|
+
heartbeatInterval: 30000,
|
|
80
|
+
timeout: 10000
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Initialize request batcher if enabled
|
|
84
|
+
if (this.config.enableBatching) {
|
|
85
|
+
this.orderBatcher = new request_batcher_1.RequestBatcher(async (requests) => {
|
|
86
|
+
// Batch processor implementation
|
|
87
|
+
const results = [];
|
|
88
|
+
for (const request of requests) {
|
|
89
|
+
try {
|
|
90
|
+
let result;
|
|
91
|
+
if (request.type === 'CREATE_ORDER') {
|
|
92
|
+
result = await this.processOrderRequest(request.params);
|
|
93
|
+
}
|
|
94
|
+
else if (request.type === 'CANCEL_ORDER') {
|
|
95
|
+
result = await this.processCancelRequest(request.params);
|
|
96
|
+
}
|
|
97
|
+
results.push({
|
|
98
|
+
id: request.id,
|
|
99
|
+
success: true,
|
|
100
|
+
result,
|
|
101
|
+
timestamp: Date.now()
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
results.push({
|
|
106
|
+
id: request.id,
|
|
107
|
+
success: false,
|
|
108
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
109
|
+
timestamp: Date.now()
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}, {
|
|
115
|
+
maxBatchSize: 10,
|
|
116
|
+
maxWaitTime: 50,
|
|
117
|
+
flushInterval: 25
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async processOrderRequest(params) {
|
|
122
|
+
// Process individual order request
|
|
123
|
+
const [, txHash, createErr] = await this.createOrderOptimized(params);
|
|
124
|
+
if (createErr) {
|
|
125
|
+
throw new Error(createErr);
|
|
126
|
+
}
|
|
127
|
+
return { txHash };
|
|
128
|
+
}
|
|
129
|
+
async processCancelRequest(params) {
|
|
130
|
+
// Process individual cancel request
|
|
131
|
+
const [, txHash, cancelErr] = await this.cancelOrder(params.marketIndex);
|
|
132
|
+
if (cancelErr) {
|
|
133
|
+
throw new Error(cancelErr);
|
|
34
134
|
}
|
|
135
|
+
return { txHash };
|
|
35
136
|
}
|
|
36
137
|
/**
|
|
37
138
|
* Initialize the signer (required for WASM signers)
|
|
@@ -105,6 +206,27 @@ class SignerClient {
|
|
|
105
206
|
});
|
|
106
207
|
this.clientCreated = true;
|
|
107
208
|
}
|
|
209
|
+
validateConfig(config) {
|
|
210
|
+
if (!config.url || typeof config.url !== 'string') {
|
|
211
|
+
throw new Error('URL is required and must be a string');
|
|
212
|
+
}
|
|
213
|
+
if (!config.privateKey || typeof config.privateKey !== 'string') {
|
|
214
|
+
throw new Error('Private key is required and must be a string');
|
|
215
|
+
}
|
|
216
|
+
// Validate private key format (should be hex string)
|
|
217
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(config.privateKey)) {
|
|
218
|
+
throw new Error('Private key must be a valid 64-character hex string with 0x prefix');
|
|
219
|
+
}
|
|
220
|
+
if (typeof config.accountIndex !== 'number' || config.accountIndex < 0) {
|
|
221
|
+
throw new Error('Account index must be a non-negative number');
|
|
222
|
+
}
|
|
223
|
+
if (typeof config.apiKeyIndex !== 'number' || config.apiKeyIndex < 0) {
|
|
224
|
+
throw new Error('API key index must be a non-negative number');
|
|
225
|
+
}
|
|
226
|
+
if (!config.wasmConfig || !config.wasmConfig.wasmPath) {
|
|
227
|
+
throw new Error('WASM configuration with wasmPath is required');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
108
230
|
checkClient() {
|
|
109
231
|
// Basic validation
|
|
110
232
|
if (!this.config.privateKey) {
|
|
@@ -119,196 +241,253 @@ class SignerClient {
|
|
|
119
241
|
return null;
|
|
120
242
|
}
|
|
121
243
|
async createOrder(params) {
|
|
244
|
+
const endTimer = performance_monitor_1.performanceMonitor.startTimer('create_order', {
|
|
245
|
+
orderType: (params.orderType || SignerClient.ORDER_TYPE_LIMIT).toString(),
|
|
246
|
+
marketIndex: params.marketIndex.toString()
|
|
247
|
+
});
|
|
122
248
|
try {
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
249
|
+
// Try WebSocket first if enabled and connected
|
|
250
|
+
if (this.config.enableWebSocket && this.wsOrderClient?.isReady()) {
|
|
251
|
+
try {
|
|
252
|
+
// Get next nonce
|
|
253
|
+
const nonceResult = await this.getNextNonce();
|
|
254
|
+
const nonce = nonceResult.nonce;
|
|
255
|
+
// Sign the order using WASM - use the existing method signature
|
|
256
|
+
const wasmParams = {
|
|
257
|
+
marketIndex: params.marketIndex,
|
|
258
|
+
clientOrderIndex: params.clientOrderIndex,
|
|
259
|
+
baseAmount: params.baseAmount,
|
|
260
|
+
price: params.price,
|
|
261
|
+
isAsk: params.isAsk ? 1 : 0,
|
|
262
|
+
orderType: params.orderType || SignerClient.ORDER_TYPE_LIMIT,
|
|
263
|
+
timeInForce: params.timeInForce || SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
|
|
264
|
+
reduceOnly: params.reduceOnly ? 1 : 0,
|
|
265
|
+
triggerPrice: params.triggerPrice || SignerClient.NIL_TRIGGER_PRICE,
|
|
266
|
+
orderExpiry: params.orderExpiry || SignerClient.DEFAULT_IOC_EXPIRY,
|
|
267
|
+
nonce
|
|
268
|
+
};
|
|
269
|
+
const txInfoStr = await this.wallet.signCreateOrder(wasmParams);
|
|
270
|
+
const txInfo = JSON.parse(txInfoStr);
|
|
271
|
+
// Send via WebSocket using official Lighter API
|
|
272
|
+
const wsTransaction = await this.wsOrderClient.sendTransaction(SignerClient.TX_TYPE_CREATE_ORDER, JSON.stringify(txInfo));
|
|
273
|
+
return [txInfo, wsTransaction.hash, null];
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
logger_1.logger.warning('WebSocket order failed, falling back to HTTP', { error: error instanceof Error ? error.message : String(error) });
|
|
146
277
|
}
|
|
147
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, orderTx);
|
|
148
|
-
const txInfo = { ...orderTx, Sig: signature };
|
|
149
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CREATE_ORDER, JSON.stringify(txInfo));
|
|
150
|
-
return [orderTx, txHash.hash, null];
|
|
151
278
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
0 : orderExpiry;
|
|
157
|
-
const wasmParams = {
|
|
158
|
-
marketIndex: params.marketIndex,
|
|
159
|
-
clientOrderIndex: params.clientOrderIndex,
|
|
160
|
-
baseAmount: params.baseAmount,
|
|
161
|
-
price: params.price,
|
|
162
|
-
isAsk: params.isAsk ? 1 : 0,
|
|
163
|
-
orderType: params.orderType,
|
|
164
|
-
timeInForce: params.timeInForce,
|
|
165
|
-
reduceOnly: params.reduceOnly ? 1 : 0,
|
|
166
|
-
triggerPrice: params.triggerPrice,
|
|
167
|
-
orderExpiry: wasmOrderExpiry,
|
|
168
|
-
nonce: nextNonce.nonce
|
|
169
|
-
};
|
|
170
|
-
const txInfoStr = await this.wallet.signCreateOrder(wasmParams);
|
|
171
|
-
// Send exactly what WASM produced, using urlencoded form like Python/Go
|
|
172
|
-
console.log('WASM signCreateOrder result:', txInfoStr);
|
|
173
|
-
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_CREATE_ORDER, txInfoStr, this.config.accountIndex, this.config.apiKeyIndex);
|
|
174
|
-
return [JSON.parse(txInfoStr), txHash.hash, null];
|
|
279
|
+
// Try batching if enabled
|
|
280
|
+
if (this.config.enableBatching && this.orderBatcher) {
|
|
281
|
+
const result = await this.orderBatcher.addRequest('CREATE_ORDER', params);
|
|
282
|
+
return [result, result.txHash || '', null];
|
|
175
283
|
}
|
|
284
|
+
// Fallback to optimized HTTP method
|
|
285
|
+
return await this.createOrderOptimized(params);
|
|
176
286
|
}
|
|
177
287
|
catch (error) {
|
|
178
|
-
|
|
179
|
-
|
|
288
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
289
|
+
performance_monitor_1.performanceMonitor.recordCounter('create_order_error', 1, {
|
|
290
|
+
error: errorMessage.substring(0, 50)
|
|
291
|
+
});
|
|
292
|
+
return [null, '', errorMessage];
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
endTimer();
|
|
180
296
|
}
|
|
181
297
|
}
|
|
298
|
+
async createOrderOptimized(params) {
|
|
299
|
+
// Get next nonce (with caching)
|
|
300
|
+
const nextNonce = await this.getNextNonce();
|
|
301
|
+
// Handle order expiry - use real timestamp for GTT orders (milliseconds)
|
|
302
|
+
const orderExpiry = params.orderExpiry ?? SignerClient.DEFAULT_28_DAY_ORDER_EXPIRY;
|
|
303
|
+
// Use WASM signer
|
|
304
|
+
// For IOC orders, use NilOrderExpiry (0)
|
|
305
|
+
const wasmOrderExpiry = params.timeInForce === SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL ?
|
|
306
|
+
0 : orderExpiry;
|
|
307
|
+
const wasmParams = {
|
|
308
|
+
marketIndex: params.marketIndex,
|
|
309
|
+
clientOrderIndex: params.clientOrderIndex,
|
|
310
|
+
baseAmount: params.baseAmount,
|
|
311
|
+
price: params.price,
|
|
312
|
+
isAsk: params.isAsk ? 1 : 0,
|
|
313
|
+
orderType: params.orderType || SignerClient.ORDER_TYPE_LIMIT,
|
|
314
|
+
timeInForce: params.timeInForce || SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
|
|
315
|
+
reduceOnly: (params.reduceOnly || false) ? 1 : 0,
|
|
316
|
+
triggerPrice: params.triggerPrice || SignerClient.NIL_TRIGGER_PRICE,
|
|
317
|
+
orderExpiry: wasmOrderExpiry,
|
|
318
|
+
nonce: nextNonce.nonce
|
|
319
|
+
};
|
|
320
|
+
const txInfoStr = await this.wallet.signCreateOrder(wasmParams);
|
|
321
|
+
// Send exactly what WASM produced, using urlencoded form like Python/Go
|
|
322
|
+
// Note: This may contain sensitive information - remove in production
|
|
323
|
+
if (process.env["NODE_ENV"] === 'development') {
|
|
324
|
+
console.log('WASM signCreateOrder result:', txInfoStr);
|
|
325
|
+
}
|
|
326
|
+
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_CREATE_ORDER, txInfoStr, this.config.accountIndex, this.config.apiKeyIndex);
|
|
327
|
+
return [JSON.parse(txInfoStr), txHash.tx_hash || txHash.hash || '', null];
|
|
328
|
+
}
|
|
329
|
+
async getNextNonce() {
|
|
330
|
+
// Use the pre-initialized nonce cache
|
|
331
|
+
if (!this.nonceCache) {
|
|
332
|
+
throw new Error('Nonce cache not initialized');
|
|
333
|
+
}
|
|
334
|
+
const nonce = await this.nonceCache.getNextNonce(this.config.apiKeyIndex);
|
|
335
|
+
return { nonce };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Pre-warm the nonce cache for better performance
|
|
339
|
+
*/
|
|
340
|
+
async preWarmNonceCache() {
|
|
341
|
+
if (this.nonceCache) {
|
|
342
|
+
await this.nonceCache.preWarmCache([this.config.apiKeyIndex]);
|
|
343
|
+
logger_1.logger.info('Nonce cache pre-warmed', { apiKeyIndex: this.config.apiKeyIndex });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get nonce cache statistics for monitoring
|
|
348
|
+
*/
|
|
349
|
+
getNonceCacheStats() {
|
|
350
|
+
return this.nonceCache ? this.nonceCache.getCacheStats() : null;
|
|
351
|
+
}
|
|
182
352
|
async createMarketOrder(params) {
|
|
353
|
+
const endTimer = performance_monitor_1.performanceMonitor.startTimer('create_market_order', {
|
|
354
|
+
marketIndex: params.marketIndex.toString()
|
|
355
|
+
});
|
|
183
356
|
try {
|
|
184
|
-
// Get next nonce
|
|
185
|
-
const nextNonce = await this.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Use WASM signer
|
|
209
|
-
const wasmParams = {
|
|
210
|
-
marketIndex: params.marketIndex,
|
|
211
|
-
clientOrderIndex: params.clientOrderIndex,
|
|
212
|
-
baseAmount: params.baseAmount,
|
|
213
|
-
price: params.avgExecutionPrice,
|
|
214
|
-
isAsk: params.isAsk ? 1 : 0,
|
|
215
|
-
orderType: SignerClient.ORDER_TYPE_MARKET,
|
|
216
|
-
timeInForce: SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
|
|
217
|
-
reduceOnly: 0,
|
|
218
|
-
triggerPrice: SignerClient.NIL_TRIGGER_PRICE,
|
|
219
|
-
orderExpiry: 0, // NilOrderExpiry for market orders
|
|
220
|
-
nonce: nextNonce.nonce
|
|
221
|
-
};
|
|
222
|
-
const txInfoStr = await this.wallet.signCreateOrder(wasmParams);
|
|
223
|
-
// Debug: Log the transaction info string to see what WASM is producing
|
|
224
|
-
console.log('WASM signCreateOrder result:', txInfoStr);
|
|
225
|
-
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_CREATE_ORDER, txInfoStr, this.config.accountIndex, this.config.apiKeyIndex);
|
|
226
|
-
return [JSON.parse(txInfoStr), txHash.hash, null];
|
|
357
|
+
// Get next nonce (with caching)
|
|
358
|
+
const nextNonce = await this.getNextNonce();
|
|
359
|
+
// Use WASM signer
|
|
360
|
+
const wasmParams = {
|
|
361
|
+
marketIndex: params.marketIndex,
|
|
362
|
+
clientOrderIndex: params.clientOrderIndex,
|
|
363
|
+
baseAmount: params.baseAmount,
|
|
364
|
+
price: params.avgExecutionPrice,
|
|
365
|
+
isAsk: params.isAsk ? 1 : 0,
|
|
366
|
+
orderType: SignerClient.ORDER_TYPE_MARKET,
|
|
367
|
+
timeInForce: SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
|
|
368
|
+
reduceOnly: params.reduceOnly ? 1 : 0,
|
|
369
|
+
triggerPrice: SignerClient.NIL_TRIGGER_PRICE,
|
|
370
|
+
orderExpiry: 0, // NilOrderExpiry for market orders
|
|
371
|
+
nonce: nextNonce.nonce
|
|
372
|
+
};
|
|
373
|
+
const txInfoStr = await this.wallet.signCreateOrder(wasmParams);
|
|
374
|
+
// Debug: Log the transaction info string to see what WASM is producing
|
|
375
|
+
// Note: This may contain sensitive information - remove in production
|
|
376
|
+
if (process.env["NODE_ENV"] === 'development') {
|
|
377
|
+
// Note: This may contain sensitive information - remove in production
|
|
378
|
+
if (process.env["NODE_ENV"] === 'development') {
|
|
379
|
+
console.log('WASM signCreateOrder result:', txInfoStr);
|
|
380
|
+
}
|
|
227
381
|
}
|
|
382
|
+
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_CREATE_ORDER, txInfoStr, this.config.accountIndex, this.config.apiKeyIndex);
|
|
383
|
+
return [JSON.parse(txInfoStr), txHash.tx_hash || txHash.hash || '', null];
|
|
228
384
|
}
|
|
229
385
|
catch (error) {
|
|
230
|
-
|
|
231
|
-
|
|
386
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
387
|
+
performance_monitor_1.performanceMonitor.recordCounter('create_market_order_error', 1, {
|
|
388
|
+
error: errorMessage.substring(0, 50)
|
|
389
|
+
});
|
|
390
|
+
return [null, '', errorMessage];
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
endTimer();
|
|
232
394
|
}
|
|
233
395
|
}
|
|
234
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Create market order with maximum slippage limit
|
|
398
|
+
* Will only execute the amount such that slippage is limited to the value provided
|
|
399
|
+
*/
|
|
400
|
+
async createMarketOrder_maxSlippage(params) {
|
|
235
401
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (
|
|
239
|
-
// Use
|
|
240
|
-
|
|
241
|
-
AccountIndex: this.config.accountIndex,
|
|
242
|
-
ApiKeyIndex: this.config.apiKeyIndex,
|
|
243
|
-
MarketIndex: params.marketIndex,
|
|
244
|
-
Index: params.orderIndex,
|
|
245
|
-
Nonce: nextNonce.nonce
|
|
246
|
-
};
|
|
247
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, cancelTx);
|
|
248
|
-
const txInfo = { ...cancelTx, Sig: signature };
|
|
249
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CANCEL_ORDER, JSON.stringify(txInfo));
|
|
250
|
-
return [cancelTx, txHash.hash, null];
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
// Use WASM signer
|
|
254
|
-
const wasmParams = {
|
|
255
|
-
marketIndex: params.marketIndex,
|
|
256
|
-
orderIndex: params.orderIndex,
|
|
257
|
-
nonce: nextNonce.nonce
|
|
258
|
-
};
|
|
259
|
-
const txInfoStr = await this.wallet.signCancelOrder(wasmParams);
|
|
260
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CANCEL_ORDER, txInfoStr);
|
|
261
|
-
return [JSON.parse(txInfoStr), txHash.hash, null];
|
|
402
|
+
let idealPrice = params.idealPrice;
|
|
403
|
+
// Get ideal price from order book if not provided
|
|
404
|
+
if (idealPrice === undefined) {
|
|
405
|
+
// Use a default price for now (can be improved later with proper order book integration)
|
|
406
|
+
idealPrice = 4000; // Default ETH price
|
|
262
407
|
}
|
|
408
|
+
// Calculate acceptable execution price based on max slippage
|
|
409
|
+
const acceptableExecutionPrice = Math.round(idealPrice * (1 + params.maxSlippage * (params.isAsk ? -1 : 1)));
|
|
410
|
+
// Create market order with price limit
|
|
411
|
+
return await this.createMarketOrder({
|
|
412
|
+
marketIndex: params.marketIndex,
|
|
413
|
+
clientOrderIndex: params.clientOrderIndex,
|
|
414
|
+
baseAmount: params.baseAmount,
|
|
415
|
+
avgExecutionPrice: acceptableExecutionPrice,
|
|
416
|
+
isAsk: params.isAsk,
|
|
417
|
+
reduceOnly: params.reduceOnly || false
|
|
418
|
+
});
|
|
263
419
|
}
|
|
264
420
|
catch (error) {
|
|
265
|
-
|
|
266
|
-
|
|
421
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
422
|
+
console.error('Error creating market order with max slippage:', errorMessage);
|
|
423
|
+
return [null, '', errorMessage];
|
|
267
424
|
}
|
|
268
425
|
}
|
|
269
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Create market order only if slippage is acceptable
|
|
428
|
+
* Will only execute if slippage <= max_slippage
|
|
429
|
+
*/
|
|
430
|
+
async createMarketOrder_ifSlippage(params) {
|
|
270
431
|
try {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
const changeTx = {
|
|
276
|
-
AccountIndex: this.config.accountIndex,
|
|
277
|
-
ApiKeyIndex: this.config.apiKeyIndex,
|
|
278
|
-
PubKey: params.newPubkey,
|
|
279
|
-
Nonce: nextNonce.nonce
|
|
280
|
-
};
|
|
281
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, changeTx);
|
|
282
|
-
const txInfo = { ...changeTx, Sig: signature };
|
|
283
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CHANGE_PUB_KEY, JSON.stringify(txInfo));
|
|
284
|
-
return [changeTx, txHash.hash, null];
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
// WASM signer doesn't support changeApiKey yet
|
|
288
|
-
throw new Error('changeApiKey not supported with WASM signer. Use signer server instead.');
|
|
432
|
+
let idealPrice = params.idealPrice;
|
|
433
|
+
if (idealPrice === undefined) {
|
|
434
|
+
// Use a default price for now (can be improved later)
|
|
435
|
+
idealPrice = 4000; // Default ETH price
|
|
289
436
|
}
|
|
437
|
+
// For now, just use the ideal price with slippage calculation
|
|
438
|
+
// In a full implementation, you would match through the order book
|
|
439
|
+
const acceptableExecutionPrice = idealPrice * (1 + params.maxSlippage * (params.isAsk ? -1 : 1));
|
|
440
|
+
// Create market order with acceptable price limit
|
|
441
|
+
return await this.createMarketOrder({
|
|
442
|
+
marketIndex: params.marketIndex,
|
|
443
|
+
clientOrderIndex: params.clientOrderIndex,
|
|
444
|
+
baseAmount: params.baseAmount,
|
|
445
|
+
avgExecutionPrice: Math.round(acceptableExecutionPrice),
|
|
446
|
+
isAsk: params.isAsk,
|
|
447
|
+
reduceOnly: params.reduceOnly || false
|
|
448
|
+
});
|
|
290
449
|
}
|
|
291
450
|
catch (error) {
|
|
292
|
-
|
|
293
|
-
|
|
451
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
452
|
+
console.error('Error creating market order if slippage:', errorMessage);
|
|
453
|
+
return [null, '', errorMessage];
|
|
294
454
|
}
|
|
295
455
|
}
|
|
456
|
+
async cancelOrder(params) {
|
|
457
|
+
try {
|
|
458
|
+
// Get next nonce (with caching)
|
|
459
|
+
const nextNonce = await this.getNextNonce();
|
|
460
|
+
// Use WASM signer
|
|
461
|
+
const wasmParams = {
|
|
462
|
+
marketIndex: params.marketIndex,
|
|
463
|
+
orderIndex: params.orderIndex,
|
|
464
|
+
nonce: nextNonce.nonce
|
|
465
|
+
};
|
|
466
|
+
const txInfoStr = await this.wallet.signCancelOrder(wasmParams);
|
|
467
|
+
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CANCEL_ORDER, txInfoStr);
|
|
468
|
+
return [JSON.parse(txInfoStr), txHash.tx_hash || txHash.hash || '', null];
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
472
|
+
console.error('Error canceling order:', errorMessage);
|
|
473
|
+
return [null, '', errorMessage];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async changeApiKey(_params) {
|
|
477
|
+
// WASM signer doesn't support changeApiKey yet
|
|
478
|
+
throw new Error('changeApiKey not supported with WASM signer.');
|
|
479
|
+
}
|
|
296
480
|
async createAuthTokenWithExpiry(expirySeconds = SignerClient.DEFAULT_10_MIN_AUTH_EXPIRY) {
|
|
297
481
|
try {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
else {
|
|
303
|
-
// Use WASM signer
|
|
304
|
-
const deadline = expirySeconds === SignerClient.DEFAULT_10_MIN_AUTH_EXPIRY ?
|
|
305
|
-
undefined : Math.floor(Date.now() / 1000) + expirySeconds;
|
|
306
|
-
return await this.wallet.createAuthToken(deadline);
|
|
307
|
-
}
|
|
482
|
+
// Use WASM signer
|
|
483
|
+
const deadline = expirySeconds === SignerClient.DEFAULT_10_MIN_AUTH_EXPIRY ?
|
|
484
|
+
undefined : Math.floor(Date.now() / 1000) + expirySeconds;
|
|
485
|
+
return await this.wallet.createAuthToken(deadline);
|
|
308
486
|
}
|
|
309
487
|
catch (error) {
|
|
310
|
-
|
|
311
|
-
|
|
488
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
489
|
+
console.error('Error creating auth token:', errorMessage);
|
|
490
|
+
throw new Error(errorMessage);
|
|
312
491
|
}
|
|
313
492
|
}
|
|
314
493
|
/**
|
|
@@ -316,203 +495,222 @@ class SignerClient {
|
|
|
316
495
|
*/
|
|
317
496
|
async generateAPIKey(seed) {
|
|
318
497
|
try {
|
|
319
|
-
|
|
320
|
-
throw new Error('generateAPIKey not supported with server signer. Use WASM signer instead.');
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
return await this.wallet.generateAPIKey(seed);
|
|
324
|
-
}
|
|
498
|
+
return await this.wallet.generateAPIKey(seed);
|
|
325
499
|
}
|
|
326
500
|
catch (error) {
|
|
327
|
-
|
|
501
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
502
|
+
console.error('Error generating API key:', errorMessage);
|
|
328
503
|
return null;
|
|
329
504
|
}
|
|
330
505
|
}
|
|
331
506
|
/**
|
|
332
507
|
* Withdraw USDC from account
|
|
333
508
|
*/
|
|
334
|
-
async withdraw(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const nextNonce = nonce === -1 ?
|
|
338
|
-
await this.transactionApi.getNextNonce(this.config.accountIndex, this.config.apiKeyIndex) :
|
|
339
|
-
{ nonce };
|
|
340
|
-
const scaledAmount = Math.floor(usdcAmount * SignerClient.USDC_TICKER_SCALE);
|
|
341
|
-
if (this.signerType === 'server') {
|
|
342
|
-
// Use server signer
|
|
343
|
-
const withdrawTx = {
|
|
344
|
-
AccountIndex: this.config.accountIndex,
|
|
345
|
-
ApiKeyIndex: this.config.apiKeyIndex,
|
|
346
|
-
Amount: scaledAmount,
|
|
347
|
-
Nonce: nextNonce.nonce
|
|
348
|
-
};
|
|
349
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, withdrawTx);
|
|
350
|
-
const txInfo = { ...withdrawTx, Sig: signature };
|
|
351
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_WITHDRAW, JSON.stringify(txInfo));
|
|
352
|
-
return [withdrawTx, txHash.hash, null];
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
// WASM signer doesn't support withdraw yet
|
|
356
|
-
throw new Error('withdraw not supported with WASM signer. Use signer server instead.');
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
catch (error) {
|
|
360
|
-
console.error('Error withdrawing:', error);
|
|
361
|
-
return [null, '', error instanceof Error ? error.message : 'Unknown error'];
|
|
362
|
-
}
|
|
509
|
+
async withdraw(_usdcAmount, _nonce = -1) {
|
|
510
|
+
// WASM signer doesn't support withdraw yet
|
|
511
|
+
throw new Error('withdraw not supported with WASM signer.');
|
|
363
512
|
}
|
|
364
513
|
/**
|
|
365
514
|
* Create a sub account
|
|
366
515
|
*/
|
|
367
|
-
async createSubAccount(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const nextNonce = nonce === -1 ?
|
|
371
|
-
await this.transactionApi.getNextNonce(this.config.accountIndex, this.config.apiKeyIndex) :
|
|
372
|
-
{ nonce };
|
|
373
|
-
if (this.signerType === 'server') {
|
|
374
|
-
// Use server signer
|
|
375
|
-
const createSubAccountTx = {
|
|
376
|
-
AccountIndex: this.config.accountIndex,
|
|
377
|
-
ApiKeyIndex: this.config.apiKeyIndex,
|
|
378
|
-
Nonce: nextNonce.nonce
|
|
379
|
-
};
|
|
380
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, createSubAccountTx);
|
|
381
|
-
const txInfo = { ...createSubAccountTx, Sig: signature };
|
|
382
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CREATE_SUB_ACCOUNT, JSON.stringify(txInfo));
|
|
383
|
-
return [txHash.hash, null];
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// WASM signer doesn't support createSubAccount yet
|
|
387
|
-
throw new Error('createSubAccount not supported with WASM signer. Use signer server instead.');
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
console.error('Error creating sub account:', error);
|
|
392
|
-
return [null, error instanceof Error ? error.message : 'Unknown error'];
|
|
393
|
-
}
|
|
516
|
+
async createSubAccount(_nonce = -1) {
|
|
517
|
+
// WASM signer doesn't support createSubAccount yet
|
|
518
|
+
throw new Error('createSubAccount not supported with WASM signer.');
|
|
394
519
|
}
|
|
395
520
|
/**
|
|
396
521
|
* Cancel all orders
|
|
397
522
|
*/
|
|
398
523
|
async cancelAllOrders(timeInForce, time, nonce = -1) {
|
|
399
524
|
try {
|
|
400
|
-
// Get next nonce if not provided
|
|
525
|
+
// Get next nonce if not provided (with caching)
|
|
401
526
|
const nextNonce = nonce === -1 ?
|
|
402
|
-
await this.
|
|
527
|
+
await this.getNextNonce() :
|
|
403
528
|
{ nonce };
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
};
|
|
413
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, cancelAllTx);
|
|
414
|
-
const txInfo = { ...cancelAllTx, Sig: signature };
|
|
415
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_CANCEL_ALL_ORDERS, JSON.stringify(txInfo));
|
|
416
|
-
return [txHash.hash, null];
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
// Use WASM signer
|
|
420
|
-
const txInfo = await this.wallet.signCancelAllOrders({
|
|
421
|
-
timeInForce,
|
|
422
|
-
time,
|
|
423
|
-
nonce: nextNonce.nonce
|
|
424
|
-
});
|
|
425
|
-
if (txInfo.error) {
|
|
426
|
-
return [null, txInfo.error];
|
|
427
|
-
}
|
|
428
|
-
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_CANCEL_ALL_ORDERS, txInfo.txInfo, this.config.accountIndex, this.config.apiKeyIndex);
|
|
429
|
-
return [txHash.hash, null];
|
|
529
|
+
// Use WASM signer
|
|
530
|
+
const result = await this.wallet.signCancelAllOrders({
|
|
531
|
+
timeInForce,
|
|
532
|
+
time,
|
|
533
|
+
nonce: nextNonce.nonce
|
|
534
|
+
});
|
|
535
|
+
if (result.error) {
|
|
536
|
+
return [null, null, result.error];
|
|
430
537
|
}
|
|
538
|
+
const txInfo = JSON.parse(result.txInfo);
|
|
539
|
+
const apiResponse = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_CANCEL_ALL_ORDERS, result.txInfo, this.config.accountIndex, this.config.apiKeyIndex);
|
|
540
|
+
return [txInfo, apiResponse, null];
|
|
431
541
|
}
|
|
432
542
|
catch (error) {
|
|
433
|
-
|
|
434
|
-
|
|
543
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
544
|
+
console.error('Error canceling all orders:', errorMessage);
|
|
545
|
+
return [null, null, errorMessage];
|
|
435
546
|
}
|
|
436
547
|
}
|
|
437
548
|
/**
|
|
438
|
-
*
|
|
549
|
+
* Close all positions by creating opposite market orders
|
|
550
|
+
* This method gets all open positions and creates market orders to close them
|
|
439
551
|
*/
|
|
440
|
-
async
|
|
552
|
+
async closeAllPositions() {
|
|
441
553
|
try {
|
|
442
|
-
// Get
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
ApiKeyIndex: this.config.apiKeyIndex,
|
|
451
|
-
MarketIndex: marketIndex,
|
|
452
|
-
OrderIndex: orderIndex,
|
|
453
|
-
BaseAmount: baseAmount,
|
|
454
|
-
Price: price,
|
|
455
|
-
TriggerPrice: triggerPrice,
|
|
456
|
-
Nonce: nextNonce.nonce
|
|
457
|
-
};
|
|
458
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, modifyTx);
|
|
459
|
-
const txInfo = { ...modifyTx, Sig: signature };
|
|
460
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_MODIFY_ORDER, JSON.stringify(txInfo));
|
|
461
|
-
return [modifyTx, txHash.hash, null];
|
|
554
|
+
// Get account data to retrieve open positions
|
|
555
|
+
const account = await this.accountApi.getAccount({
|
|
556
|
+
by: 'index',
|
|
557
|
+
value: this.config.accountIndex.toString()
|
|
558
|
+
});
|
|
559
|
+
// Check if account has positions data
|
|
560
|
+
if (!account.positions || !Array.isArray(account.positions)) {
|
|
561
|
+
return [[], [], []]; // No positions data available
|
|
462
562
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
563
|
+
const openPositions = account.positions.filter(pos => parseFloat(pos.size) > 0);
|
|
564
|
+
if (openPositions.length === 0) {
|
|
565
|
+
return [[], [], []]; // No positions to close
|
|
466
566
|
}
|
|
567
|
+
const closedTransactions = [];
|
|
568
|
+
const closedResponses = [];
|
|
569
|
+
const errors = [];
|
|
570
|
+
// Close each position by creating opposite market orders
|
|
571
|
+
for (const position of openPositions) {
|
|
572
|
+
try {
|
|
573
|
+
const isLong = position.side === 'long';
|
|
574
|
+
const positionSize = Math.floor(parseFloat(position.size));
|
|
575
|
+
// Create market order in opposite direction to close position
|
|
576
|
+
const [tx, apiResponse, err] = await this.createMarketOrder({
|
|
577
|
+
marketIndex: position.market_id,
|
|
578
|
+
clientOrderIndex: Date.now() + Math.random() * 1000, // Unique index
|
|
579
|
+
baseAmount: positionSize,
|
|
580
|
+
avgExecutionPrice: Math.floor(parseFloat(position.mark_price)), // Use mark price as reference
|
|
581
|
+
isAsk: isLong, // If long position, sell to close; if short, buy to close
|
|
582
|
+
reduceOnly: true // This is a position-closing order
|
|
583
|
+
});
|
|
584
|
+
if (err) {
|
|
585
|
+
errors.push(`Failed to close position in market ${position.market_id}: ${err}`);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
closedTransactions.push(tx);
|
|
589
|
+
closedResponses.push(apiResponse);
|
|
590
|
+
console.log(`✅ Position closed in market ${position.market_id}: ${isLong ? 'Long' : 'Short'} ${positionSize} units`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (positionError) {
|
|
594
|
+
errors.push(`Error closing position in market ${position.market_id}: ${positionError instanceof Error ? positionError.message : 'Unknown error'}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return [closedTransactions, closedResponses, errors];
|
|
467
598
|
}
|
|
468
599
|
catch (error) {
|
|
469
|
-
|
|
470
|
-
|
|
600
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
601
|
+
console.error('Error closing all positions:', errorMessage);
|
|
602
|
+
return [[], [], [errorMessage]];
|
|
471
603
|
}
|
|
472
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Create a Take Profit order (market order when trigger price is reached)
|
|
607
|
+
*/
|
|
608
|
+
async createTpOrder(marketIndex, clientOrderIndex, baseAmount, triggerPrice, price, isAsk, reduceOnly = false, nonce = -1) {
|
|
609
|
+
return await this.createOrder({
|
|
610
|
+
marketIndex,
|
|
611
|
+
clientOrderIndex,
|
|
612
|
+
baseAmount,
|
|
613
|
+
price,
|
|
614
|
+
isAsk,
|
|
615
|
+
orderType: SignerClient.ORDER_TYPE_TAKE_PROFIT,
|
|
616
|
+
timeInForce: SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
|
|
617
|
+
reduceOnly,
|
|
618
|
+
triggerPrice,
|
|
619
|
+
orderExpiry: SignerClient.DEFAULT_28_DAY_ORDER_EXPIRY,
|
|
620
|
+
nonce
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Create a Take Profit Limit order (limit order when trigger price is reached)
|
|
625
|
+
*/
|
|
626
|
+
async createTpLimitOrder(marketIndex, clientOrderIndex, baseAmount, triggerPrice, price, isAsk, reduceOnly = false, nonce = -1) {
|
|
627
|
+
return await this.createOrder({
|
|
628
|
+
marketIndex,
|
|
629
|
+
clientOrderIndex,
|
|
630
|
+
baseAmount,
|
|
631
|
+
price,
|
|
632
|
+
isAsk,
|
|
633
|
+
orderType: SignerClient.ORDER_TYPE_TAKE_PROFIT_LIMIT,
|
|
634
|
+
timeInForce: SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
|
|
635
|
+
reduceOnly,
|
|
636
|
+
triggerPrice,
|
|
637
|
+
orderExpiry: SignerClient.DEFAULT_28_DAY_ORDER_EXPIRY,
|
|
638
|
+
nonce
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Create a Stop Loss order (market order when trigger price is reached)
|
|
643
|
+
*/
|
|
644
|
+
async createSlOrder(marketIndex, clientOrderIndex, baseAmount, triggerPrice, price = 0, isAsk, reduceOnly = false, nonce = -1) {
|
|
645
|
+
// For Stop Loss orders, use trigger price as execution price if price is 0 or too low
|
|
646
|
+
const executionPrice = price <= 1 ? triggerPrice : price;
|
|
647
|
+
return await this.createOrder({
|
|
648
|
+
marketIndex,
|
|
649
|
+
clientOrderIndex,
|
|
650
|
+
baseAmount,
|
|
651
|
+
price: executionPrice,
|
|
652
|
+
isAsk,
|
|
653
|
+
orderType: SignerClient.ORDER_TYPE_STOP_LOSS,
|
|
654
|
+
timeInForce: SignerClient.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
|
|
655
|
+
reduceOnly,
|
|
656
|
+
triggerPrice,
|
|
657
|
+
orderExpiry: SignerClient.DEFAULT_28_DAY_ORDER_EXPIRY, // Use same expiry as other orders
|
|
658
|
+
nonce
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Create a Stop Loss Limit order (limit order when trigger price is reached)
|
|
663
|
+
*/
|
|
664
|
+
async createSlLimitOrder(marketIndex, clientOrderIndex, baseAmount, triggerPrice, price, isAsk, reduceOnly = false, nonce = -1) {
|
|
665
|
+
return await this.createOrder({
|
|
666
|
+
marketIndex,
|
|
667
|
+
clientOrderIndex,
|
|
668
|
+
baseAmount,
|
|
669
|
+
price,
|
|
670
|
+
isAsk,
|
|
671
|
+
orderType: SignerClient.ORDER_TYPE_STOP_LOSS_LIMIT,
|
|
672
|
+
timeInForce: SignerClient.ORDER_TIME_IN_FORCE_GOOD_TILL_TIME,
|
|
673
|
+
reduceOnly,
|
|
674
|
+
triggerPrice,
|
|
675
|
+
orderExpiry: SignerClient.DEFAULT_28_DAY_ORDER_EXPIRY,
|
|
676
|
+
nonce
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Modify an existing order
|
|
681
|
+
*/
|
|
682
|
+
async modifyOrder(_marketIndex, _orderIndex, _baseAmount, _price, _triggerPrice, _nonce = -1) {
|
|
683
|
+
// WASM signer doesn't support modifyOrder yet
|
|
684
|
+
throw new Error('modifyOrder not supported with WASM signer.');
|
|
685
|
+
}
|
|
473
686
|
/**
|
|
474
687
|
* Transfer USDC to another account
|
|
475
688
|
*/
|
|
476
689
|
async transfer(toAccountIndex, usdcAmount, nonce = -1) {
|
|
477
690
|
try {
|
|
478
|
-
// Get next nonce if not provided
|
|
691
|
+
// Get next nonce if not provided (with caching)
|
|
479
692
|
const nextNonce = nonce === -1 ?
|
|
480
|
-
await this.
|
|
693
|
+
await this.getNextNonce() :
|
|
481
694
|
{ nonce };
|
|
482
695
|
const scaledAmount = Math.floor(usdcAmount * SignerClient.USDC_TICKER_SCALE);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const txInfo = { ...transferTx, Sig: signature };
|
|
494
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_TRANSFER, JSON.stringify(txInfo));
|
|
495
|
-
return [transferTx, txHash.hash, null];
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
// Use WASM signer
|
|
499
|
-
const txInfo = await this.wallet.signTransfer({
|
|
500
|
-
toAccountIndex,
|
|
501
|
-
usdcAmount: scaledAmount,
|
|
502
|
-
fee: 0, // fee - should be calculated separately
|
|
503
|
-
memo: 'a'.repeat(32), // memo - 32 bytes required
|
|
504
|
-
nonce: nextNonce.nonce
|
|
505
|
-
});
|
|
506
|
-
if (txInfo.error) {
|
|
507
|
-
return [null, '', txInfo.error];
|
|
508
|
-
}
|
|
509
|
-
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_TRANSFER, txInfo.txInfo, this.config.accountIndex, this.config.apiKeyIndex);
|
|
510
|
-
return [JSON.parse(txInfo.txInfo), txHash.hash, null];
|
|
696
|
+
// Use WASM signer
|
|
697
|
+
const txInfo = await this.wallet.signTransfer({
|
|
698
|
+
toAccountIndex,
|
|
699
|
+
usdcAmount: scaledAmount,
|
|
700
|
+
fee: 0, // fee - should be calculated separately
|
|
701
|
+
memo: 'a'.repeat(32), // memo - 32 bytes required
|
|
702
|
+
nonce: nextNonce.nonce
|
|
703
|
+
});
|
|
704
|
+
if (txInfo.error) {
|
|
705
|
+
return [null, '', txInfo.error];
|
|
511
706
|
}
|
|
707
|
+
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_TRANSFER, txInfo.txInfo, this.config.accountIndex, this.config.apiKeyIndex);
|
|
708
|
+
return [JSON.parse(txInfo.txInfo), txHash.tx_hash || txHash.hash || '', null];
|
|
512
709
|
}
|
|
513
710
|
catch (error) {
|
|
514
|
-
|
|
515
|
-
|
|
711
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
712
|
+
console.error('Error transferring:', errorMessage);
|
|
713
|
+
return [null, '', errorMessage];
|
|
516
714
|
}
|
|
517
715
|
}
|
|
518
716
|
/**
|
|
@@ -520,44 +718,133 @@ class SignerClient {
|
|
|
520
718
|
*/
|
|
521
719
|
async updateLeverage(marketIndex, marginMode, initialMarginFraction, nonce = -1) {
|
|
522
720
|
try {
|
|
523
|
-
// Get next nonce if not provided
|
|
721
|
+
// Get next nonce if not provided (with caching)
|
|
524
722
|
const nextNonce = nonce === -1 ?
|
|
525
|
-
await this.
|
|
723
|
+
await this.getNextNonce() :
|
|
526
724
|
{ nonce };
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
};
|
|
537
|
-
const signature = await this.wallet.signTransaction(this.config.privateKey, leverageTx);
|
|
538
|
-
const txInfo = { ...leverageTx, Sig: signature };
|
|
539
|
-
const txHash = await this.transactionApi.sendTx(SignerClient.TX_TYPE_UPDATE_LEVERAGE, JSON.stringify(txInfo));
|
|
540
|
-
return [leverageTx, txHash.hash, null];
|
|
725
|
+
// Use WASM signer
|
|
726
|
+
const txInfo = await this.wallet.signUpdateLeverage({
|
|
727
|
+
marketIndex,
|
|
728
|
+
fraction: initialMarginFraction,
|
|
729
|
+
marginMode,
|
|
730
|
+
nonce: nextNonce.nonce
|
|
731
|
+
});
|
|
732
|
+
if (txInfo.error) {
|
|
733
|
+
return [null, '', txInfo.error];
|
|
541
734
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
735
|
+
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_UPDATE_LEVERAGE, txInfo.txInfo, this.config.accountIndex, this.config.apiKeyIndex);
|
|
736
|
+
return [JSON.parse(txInfo.txInfo), txHash.tx_hash || txHash.hash || '', null];
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
740
|
+
console.error('Error updating leverage:', errorMessage);
|
|
741
|
+
return [null, '', errorMessage];
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Wait for a transaction to be confirmed
|
|
746
|
+
* @param txHash - Transaction hash to wait for
|
|
747
|
+
* @param maxWaitTime - Maximum time to wait in milliseconds (default: 60000 = 1 minute)
|
|
748
|
+
* @param pollInterval - Polling interval in milliseconds (default: 2000 = 2 seconds)
|
|
749
|
+
* @returns Promise<Transaction> - The confirmed transaction
|
|
750
|
+
*/
|
|
751
|
+
async waitForTransaction(txHash, maxWaitTime = 60000, pollInterval = 2000) {
|
|
752
|
+
const startTime = Date.now();
|
|
753
|
+
let dots = '';
|
|
754
|
+
let animationInterval = null;
|
|
755
|
+
// Start rotating dots animation
|
|
756
|
+
const startAnimation = () => {
|
|
757
|
+
animationInterval = setInterval(() => {
|
|
758
|
+
dots = dots.length >= 3 ? '' : dots + '.';
|
|
759
|
+
process.stdout.write(`\r⏳ Transaction ${txHash.substring(0, 16)}${dots} `);
|
|
760
|
+
}, 500);
|
|
761
|
+
};
|
|
762
|
+
// Stop animation and clear line
|
|
763
|
+
const stopAnimation = () => {
|
|
764
|
+
if (animationInterval) {
|
|
765
|
+
clearInterval(animationInterval);
|
|
766
|
+
animationInterval = null;
|
|
767
|
+
}
|
|
768
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r'); // Clear the line
|
|
769
|
+
};
|
|
770
|
+
try {
|
|
771
|
+
startAnimation();
|
|
772
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
773
|
+
try {
|
|
774
|
+
const transaction = await this.transactionApi.getTransaction({
|
|
775
|
+
by: 'hash',
|
|
776
|
+
value: txHash
|
|
777
|
+
});
|
|
778
|
+
// Check if transaction is confirmed
|
|
779
|
+
if (transaction.status === 'confirmed') {
|
|
780
|
+
stopAnimation();
|
|
781
|
+
console.log(`✅ Transaction ${txHash.substring(0, 16)} confirmed`);
|
|
782
|
+
return transaction;
|
|
783
|
+
}
|
|
784
|
+
else if (transaction.status === 'failed') {
|
|
785
|
+
stopAnimation();
|
|
786
|
+
console.log(`❌ Transaction ${txHash.substring(0, 16)} failed`);
|
|
787
|
+
throw new exceptions_1.TransactionException(`Transaction ${txHash} failed with status: ${transaction.status}`, 'waitForTransaction', transaction);
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
// Transaction is still processing (pending or unknown status)
|
|
791
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
// If transaction not found yet, continue polling
|
|
796
|
+
if (error instanceof Error && (error.message.includes('not found') ||
|
|
797
|
+
error.message.includes('404') ||
|
|
798
|
+
error.message.includes('No transaction found'))) {
|
|
799
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
// For other errors, continue trying
|
|
803
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
552
804
|
}
|
|
553
|
-
const txHash = await this.transactionApi.sendTxWithIndices(SignerClient.TX_TYPE_UPDATE_LEVERAGE, txInfo.txInfo, this.config.accountIndex, this.config.apiKeyIndex);
|
|
554
|
-
return [JSON.parse(txInfo.txInfo), txHash.hash, null];
|
|
555
805
|
}
|
|
806
|
+
stopAnimation();
|
|
807
|
+
throw new Error(`Transaction ${txHash} did not confirm within ${maxWaitTime}ms`);
|
|
556
808
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
809
|
+
finally {
|
|
810
|
+
stopAnimation();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Wait for order confirmation by checking if the order appears in the order book
|
|
815
|
+
* @param marketIndex - Market index
|
|
816
|
+
* @param clientOrderIndex - Client order index
|
|
817
|
+
* @param maxWaitTime - Maximum time to wait in milliseconds (default: 30000 = 30 seconds)
|
|
818
|
+
* @param pollInterval - Polling interval in milliseconds (default: 1000 = 1 second)
|
|
819
|
+
* @returns Promise<boolean> - True if order is confirmed
|
|
820
|
+
*/
|
|
821
|
+
async waitForOrderConfirmation(marketIndex, clientOrderIndex, maxWaitTime = 30000, pollInterval = 1000) {
|
|
822
|
+
const startTime = Date.now();
|
|
823
|
+
console.log(`⏳ Waiting for order ${clientOrderIndex} in market ${marketIndex} to be confirmed...`);
|
|
824
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
825
|
+
try {
|
|
826
|
+
// This would need to be implemented based on the order API
|
|
827
|
+
// For now, we'll just wait for the transaction to be confirmed
|
|
828
|
+
console.log(`⏳ Waiting for order confirmation... (${marketIndex}:${clientOrderIndex})`);
|
|
829
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
830
|
+
// TODO: Implement actual order book checking
|
|
831
|
+
// const orderBook = await this.orderApi.getOrderBooks(marketIndex);
|
|
832
|
+
// const orderExists = orderBook.orders.some(order => order.client_order_index === clientOrderIndex);
|
|
833
|
+
// if (orderExists) {
|
|
834
|
+
// console.log(`✅ Order ${clientOrderIndex} confirmed in order book`);
|
|
835
|
+
// return true;
|
|
836
|
+
// }
|
|
837
|
+
// For now, just return true after a short wait to demonstrate the functionality
|
|
838
|
+
console.log(`✅ Order ${clientOrderIndex} confirmation simulated (placeholder)`);
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
console.log(`⏳ Order confirmation check failed, retrying...`);
|
|
843
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
844
|
+
}
|
|
560
845
|
}
|
|
846
|
+
console.log(`⚠️ Order ${clientOrderIndex} confirmation timeout after ${maxWaitTime}ms`);
|
|
847
|
+
return false;
|
|
561
848
|
}
|
|
562
849
|
/**
|
|
563
850
|
* Close the API client connection
|