ccxt 4.3.52 → 4.3.53

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.
@@ -0,0 +1,979 @@
1
+ // ----------------------------------------------------------------------------
2
+
3
+ // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
4
+ // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
5
+ // EDIT THE CORRESPONDENT .ts FILE INSTEAD
6
+
7
+ // ----------------------------------------------------------------------------
8
+ import vertexRest from '../vertex.js';
9
+ import { AuthenticationError, NotSupported, ArgumentsRequired } from '../base/errors.js';
10
+ import { ArrayCacheBySymbolById, ArrayCache, ArrayCacheBySymbolBySide } from '../base/ws/Cache.js';
11
+ import { Precise } from '../base/Precise.js';
12
+ // ----------------------------------------------------------------------------
13
+ export default class vertex extends vertexRest {
14
+ describe() {
15
+ return this.deepExtend(super.describe(), {
16
+ 'has': {
17
+ 'ws': true,
18
+ 'watchBalance': false,
19
+ 'watchMyTrades': true,
20
+ 'watchOHLCV': false,
21
+ 'watchOrderBook': true,
22
+ 'watchOrders': true,
23
+ 'watchTicker': true,
24
+ 'watchTickers': false,
25
+ 'watchTrades': true,
26
+ 'watchPositions': true,
27
+ },
28
+ 'urls': {
29
+ 'api': {
30
+ 'ws': 'wss://gateway.prod.vertexprotocol.com/v1/subscribe',
31
+ },
32
+ 'test': {
33
+ 'ws': 'wss://gateway.sepolia-test.vertexprotocol.com/v1/subscribe',
34
+ },
35
+ },
36
+ 'requiredCredentials': {
37
+ 'apiKey': false,
38
+ 'secret': false,
39
+ 'walletAddress': true,
40
+ 'privateKey': true,
41
+ },
42
+ 'options': {
43
+ 'tradesLimit': 1000,
44
+ 'ordersLimit': 1000,
45
+ 'requestId': {},
46
+ 'watchPositions': {
47
+ 'fetchPositionsSnapshot': true,
48
+ 'awaitPositionsSnapshot': true, // whether to wait for the positions snapshot before providing updates
49
+ },
50
+ },
51
+ 'streaming': {
52
+ // 'ping': this.ping,
53
+ 'keepAlive': 30000,
54
+ },
55
+ 'exceptions': {
56
+ 'ws': {
57
+ 'exact': {
58
+ 'Auth is needed.': AuthenticationError,
59
+ },
60
+ },
61
+ },
62
+ });
63
+ }
64
+ requestId(url) {
65
+ const options = this.safeDict(this.options, 'requestId', {});
66
+ const previousValue = this.safeInteger(options, url, 0);
67
+ const newValue = this.sum(previousValue, 1);
68
+ this.options['requestId'][url] = newValue;
69
+ return newValue;
70
+ }
71
+ async watchPublic(messageHash, message) {
72
+ const url = this.urls['api']['ws'];
73
+ const requestId = this.requestId(url);
74
+ const subscribe = {
75
+ 'id': requestId,
76
+ };
77
+ const request = this.extend(subscribe, message);
78
+ return await this.watch(url, messageHash, request, messageHash, subscribe);
79
+ }
80
+ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
81
+ /**
82
+ * @method
83
+ * @name vertex#watchTrades
84
+ * @description watches information on multiple trades made in a market
85
+ * @see https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
86
+ * @param {string} symbol unified market symbol of the market trades were made in
87
+ * @param {int} [since] the earliest time in ms to fetch trades for
88
+ * @param {int} [limit] the maximum number of trade structures to retrieve
89
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
90
+ * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure
91
+ */
92
+ await this.loadMarkets();
93
+ const market = this.market(symbol);
94
+ const name = 'trade';
95
+ const topic = market['id'] + '@' + name;
96
+ const request = {
97
+ 'method': 'subscribe',
98
+ 'stream': {
99
+ 'type': name,
100
+ 'product_id': this.parseToNumeric(market['id']),
101
+ },
102
+ };
103
+ const message = this.extend(request, params);
104
+ const trades = await this.watchPublic(topic, message);
105
+ if (this.newUpdates) {
106
+ limit = trades.getLimit(market['symbol'], limit);
107
+ }
108
+ return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
109
+ }
110
+ handleTrade(client, message) {
111
+ //
112
+ // {
113
+ // "type": "trade",
114
+ // "timestamp": "1676151190656903000", // timestamp of the event in nanoseconds
115
+ // "product_id": 1,
116
+ // "price": "1000", // price the trade happened at, multiplied by 1e18
117
+ // // both taker_qty and maker_qty have the same value;
118
+ // // set to filled amount (min amount of taker and maker) when matching against book
119
+ // // set to matched amm base amount when matching against amm
120
+ // "taker_qty": "1000",
121
+ // "maker_qty": "1000",
122
+ // "is_taker_buyer": true,
123
+ // "is_maker_amm": true // true when maker is amm
124
+ // }
125
+ //
126
+ const topic = this.safeString(message, 'type');
127
+ const marketId = this.safeString(message, 'product_id');
128
+ const trade = this.parseWsTrade(message);
129
+ const symbol = trade['symbol'];
130
+ if (!(symbol in this.trades)) {
131
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
132
+ const stored = new ArrayCache(limit);
133
+ this.trades[symbol] = stored;
134
+ }
135
+ const trades = this.trades[symbol];
136
+ trades.append(trade);
137
+ this.trades[symbol] = trades;
138
+ client.resolve(trades, marketId + '@' + topic);
139
+ }
140
+ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
141
+ /**
142
+ * @method
143
+ * @name vertex#watchMyTrades
144
+ * @description watches information on multiple trades made by the user
145
+ * @see https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
146
+ * @param {string} symbol unified market symbol of the market orders were made in
147
+ * @param {int} [since] the earliest time in ms to fetch orders for
148
+ * @param {int} [limit] the maximum number of order structures to retrieve
149
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
150
+ * @param {string} [params.user] user address, will default to this.walletAddress if not provided
151
+ * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure
152
+ */
153
+ if (symbol === undefined) {
154
+ throw new ArgumentsRequired(this.id + ' watchMyTrades requires a symbol.');
155
+ }
156
+ await this.loadMarkets();
157
+ let userAddress = undefined;
158
+ [userAddress, params] = this.handlePublicAddress('watchMyTrades', params);
159
+ const market = this.market(symbol);
160
+ const name = 'fill';
161
+ const topic = market['id'] + '@' + name;
162
+ const request = {
163
+ 'method': 'subscribe',
164
+ 'stream': {
165
+ 'type': name,
166
+ 'product_id': this.parseToNumeric(market['id']),
167
+ 'subaccount': this.convertAddressToSender(userAddress),
168
+ },
169
+ };
170
+ const message = this.extend(request, params);
171
+ const trades = await this.watchPublic(topic, message);
172
+ if (this.newUpdates) {
173
+ limit = trades.getLimit(symbol, limit);
174
+ }
175
+ return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
176
+ }
177
+ handleMyTrades(client, message) {
178
+ //
179
+ // {
180
+ // "type": "fill",
181
+ // "timestamp": "1676151190656903000", // timestamp of the event in nanoseconds
182
+ // "product_id": 1,
183
+ // // the subaccount that placed this order
184
+ // "subaccount": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
185
+ // // hash of the order that uniquely identifies it
186
+ // "order_digest": "0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
187
+ // // the amount filled, multiplied by 1e18
188
+ // "filled_qty": "1000",
189
+ // // the amount outstanding unfilled, multiplied by 1e18
190
+ // "remaining_qty": "2000",
191
+ // // the original order amount, multiplied by 1e18
192
+ // "original_qty": "3000",
193
+ // // fill price
194
+ // "price": "24991000000000000000000",
195
+ // // true for `taker`, false for `maker`
196
+ // "is_taker": true,
197
+ // "is_bid": true,
198
+ // // true when matching against amm
199
+ // "is_against_amm": true,
200
+ // // an optional `order id` that can be provided when placing an order
201
+ // "id": 100
202
+ // }
203
+ //
204
+ const topic = this.safeString(message, 'type');
205
+ const marketId = this.safeString(message, 'product_id');
206
+ if (this.myTrades === undefined) {
207
+ const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
208
+ this.myTrades = new ArrayCacheBySymbolById(limit);
209
+ }
210
+ const trades = this.myTrades;
211
+ const parsed = this.parseWsTrade(message);
212
+ trades.append(parsed);
213
+ client.resolve(trades, marketId + '@' + topic);
214
+ }
215
+ parseWsTrade(trade, market = undefined) {
216
+ //
217
+ // watchTrades
218
+ // {
219
+ // "type": "trade",
220
+ // "timestamp": "1676151190656903000", // timestamp of the event in nanoseconds
221
+ // "product_id": 1,
222
+ // "price": "1000", // price the trade happened at, multiplied by 1e18
223
+ // // both taker_qty and maker_qty have the same value;
224
+ // // set to filled amount (min amount of taker and maker) when matching against book
225
+ // // set to matched amm base amount when matching against amm
226
+ // "taker_qty": "1000",
227
+ // "maker_qty": "1000",
228
+ // "is_taker_buyer": true,
229
+ // "is_maker_amm": true // true when maker is amm
230
+ // }
231
+ // watchMyTrades
232
+ // {
233
+ // "type": "fill",
234
+ // "timestamp": "1676151190656903000", // timestamp of the event in nanoseconds
235
+ // "product_id": 1,
236
+ // // the subaccount that placed this order
237
+ // "subaccount": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
238
+ // // hash of the order that uniquely identifies it
239
+ // "order_digest": "0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
240
+ // // the amount filled, multiplied by 1e18
241
+ // "filled_qty": "1000",
242
+ // // the amount outstanding unfilled, multiplied by 1e18
243
+ // "remaining_qty": "2000",
244
+ // // the original order amount, multiplied by 1e18
245
+ // "original_qty": "3000",
246
+ // // fill price
247
+ // "price": "24991000000000000000000",
248
+ // // true for `taker`, false for `maker`
249
+ // "is_taker": true,
250
+ // "is_bid": true,
251
+ // // true when matching against amm
252
+ // "is_against_amm": true,
253
+ // // an optional `order id` that can be provided when placing an order
254
+ // "id": 100
255
+ // }
256
+ //
257
+ const marketId = this.safeString(trade, 'product_id');
258
+ market = this.safeMarket(marketId, market);
259
+ const symbol = market['symbol'];
260
+ const price = this.convertFromX18(this.safeString(trade, 'price'));
261
+ const amount = this.convertFromX18(this.safeString2(trade, 'taker_qty', 'filled_qty'));
262
+ const cost = Precise.stringMul(price, amount);
263
+ const timestamp = Precise.stringDiv(this.safeString(trade, 'timestamp'), '1000000');
264
+ let takerOrMaker = undefined;
265
+ const isTaker = this.safeBool(trade, 'is_taker');
266
+ if (isTaker !== undefined) {
267
+ takerOrMaker = (isTaker) ? 'taker' : 'maker';
268
+ }
269
+ let side = undefined;
270
+ const isBid = this.safeBool(trade, 'is_bid');
271
+ if (isBid !== undefined) {
272
+ side = (isBid) ? 'buy' : 'sell';
273
+ }
274
+ return this.safeTrade({
275
+ 'id': undefined,
276
+ 'timestamp': timestamp,
277
+ 'datetime': this.iso8601(timestamp),
278
+ 'symbol': symbol,
279
+ 'side': side,
280
+ 'price': price,
281
+ 'amount': amount,
282
+ 'cost': cost,
283
+ 'order': this.safeString2(trade, 'digest', 'id'),
284
+ 'takerOrMaker': takerOrMaker,
285
+ 'type': undefined,
286
+ 'fee': undefined,
287
+ 'info': trade,
288
+ }, market);
289
+ }
290
+ async watchTicker(symbol, params = {}) {
291
+ /**
292
+ * @method
293
+ * @name vertex#watchTicker
294
+ * @see https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
295
+ * @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
296
+ * @param {string} symbol unified symbol of the market to fetch the ticker for
297
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
298
+ * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
299
+ */
300
+ await this.loadMarkets();
301
+ const name = 'best_bid_offer';
302
+ const market = this.market(symbol);
303
+ const topic = market['id'] + '@' + name;
304
+ const request = {
305
+ 'method': 'subscribe',
306
+ 'stream': {
307
+ 'type': name,
308
+ 'product_id': this.parseToNumeric(market['id']),
309
+ },
310
+ };
311
+ const message = this.extend(request, params);
312
+ return await this.watchPublic(topic, message);
313
+ }
314
+ parseWsTicker(ticker, market = undefined) {
315
+ //
316
+ // {
317
+ // "type": "best_bid_offer",
318
+ // "timestamp": "1676151190656903000", // timestamp of the event in nanoseconds
319
+ // "product_id": 1,
320
+ // "bid_price": "1000", // the highest bid price, multiplied by 1e18
321
+ // "bid_qty": "1000", // quantity at the huighest bid, multiplied by 1e18.
322
+ // // i.e. if this is USDC with 6 decimals, one USDC
323
+ // // would be 1e12
324
+ // "ask_price": "1000", // lowest ask price
325
+ // "ask_qty": "1000" // quantity at the lowest ask
326
+ // }
327
+ //
328
+ const timestamp = Precise.stringDiv(this.safeString(ticker, 'timestamp'), '1000000');
329
+ return this.safeTicker({
330
+ 'symbol': this.safeSymbol(undefined, market),
331
+ 'timestamp': timestamp,
332
+ 'datetime': this.iso8601(timestamp),
333
+ 'high': this.safeString(ticker, 'high'),
334
+ 'low': this.safeString(ticker, 'low'),
335
+ 'bid': this.convertFromX18(this.safeString(ticker, 'bid_price')),
336
+ 'bidVolume': this.convertFromX18(this.safeString(ticker, 'bid_qty')),
337
+ 'ask': this.convertFromX18(this.safeString(ticker, 'ask_price')),
338
+ 'askVolume': this.convertFromX18(this.safeString(ticker, 'ask_qty')),
339
+ 'vwap': undefined,
340
+ 'open': undefined,
341
+ 'close': undefined,
342
+ 'last': undefined,
343
+ 'previousClose': undefined,
344
+ 'change': undefined,
345
+ 'percentage': undefined,
346
+ 'average': undefined,
347
+ 'baseVolume': undefined,
348
+ 'quoteVolume': undefined,
349
+ 'info': ticker,
350
+ }, market);
351
+ }
352
+ handleTicker(client, message) {
353
+ //
354
+ // {
355
+ // "type": "best_bid_offer",
356
+ // "timestamp": "1676151190656903000", // timestamp of the event in nanoseconds
357
+ // "product_id": 1,
358
+ // "bid_price": "1000", // the highest bid price, multiplied by 1e18
359
+ // "bid_qty": "1000", // quantity at the huighest bid, multiplied by 1e18.
360
+ // // i.e. if this is USDC with 6 decimals, one USDC
361
+ // // would be 1e12
362
+ // "ask_price": "1000", // lowest ask price
363
+ // "ask_qty": "1000" // quantity at the lowest ask
364
+ // }
365
+ //
366
+ const marketId = this.safeString(message, 'product_id');
367
+ const market = this.safeMarket(marketId);
368
+ const ticker = this.parseWsTicker(message, market);
369
+ ticker['symbol'] = market['symbol'];
370
+ this.tickers[market['symbol']] = ticker;
371
+ client.resolve(ticker, marketId + '@best_bid_offer');
372
+ return message;
373
+ }
374
+ async watchOrderBook(symbol, limit = undefined, params = {}) {
375
+ /**
376
+ * @method
377
+ * @name vertex#watchOrderBook
378
+ * @see https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
379
+ * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
380
+ * @param {string} symbol unified symbol of the market to fetch the order book for
381
+ * @param {int} [limit] the maximum amount of order book entries to return.
382
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
383
+ * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
384
+ */
385
+ await this.loadMarkets();
386
+ const name = 'book_depth';
387
+ const market = this.market(symbol);
388
+ const messageHash = market['id'] + '@' + name;
389
+ const url = this.urls['api']['ws'];
390
+ const requestId = this.requestId(url);
391
+ const request = {
392
+ 'id': requestId,
393
+ 'method': 'subscribe',
394
+ 'stream': {
395
+ 'type': name,
396
+ 'product_id': this.parseToNumeric(market['id']),
397
+ },
398
+ };
399
+ const subscription = {
400
+ 'id': requestId.toString(),
401
+ 'name': name,
402
+ 'symbol': symbol,
403
+ 'method': this.handleOrderBookSubscription,
404
+ 'limit': limit,
405
+ 'params': params,
406
+ };
407
+ const message = this.extend(request, params);
408
+ const orderbook = await this.watch(url, messageHash, message, messageHash, subscription);
409
+ return orderbook.limit();
410
+ }
411
+ handleOrderBookSubscription(client, message, subscription) {
412
+ const defaultLimit = this.safeInteger(this.options, 'watchOrderBookLimit', 1000);
413
+ const limit = this.safeInteger(subscription, 'limit', defaultLimit);
414
+ const symbol = this.safeString(subscription, 'symbol'); // watchOrderBook
415
+ if (symbol in this.orderbooks) {
416
+ delete this.orderbooks[symbol];
417
+ }
418
+ this.orderbooks[symbol] = this.orderBook({}, limit);
419
+ this.spawn(this.fetchOrderBookSnapshot, client, message, subscription);
420
+ }
421
+ async fetchOrderBookSnapshot(client, message, subscription) {
422
+ const symbol = this.safeString(subscription, 'symbol');
423
+ const market = this.market(symbol);
424
+ const messageHash = market['id'] + '@book_depth';
425
+ try {
426
+ const defaultLimit = this.safeInteger(this.options, 'watchOrderBookLimit', 1000);
427
+ const limit = this.safeInteger(subscription, 'limit', defaultLimit);
428
+ const params = this.safeValue(subscription, 'params');
429
+ const snapshot = await this.fetchRestOrderBookSafe(symbol, limit, params);
430
+ if (this.safeValue(this.orderbooks, symbol) === undefined) {
431
+ // if the orderbook is dropped before the snapshot is received
432
+ return;
433
+ }
434
+ const orderbook = this.orderbooks[symbol];
435
+ orderbook.reset(snapshot);
436
+ const messages = orderbook.cache;
437
+ for (let i = 0; i < messages.length; i++) {
438
+ const messageItem = messages[i];
439
+ const lastTimestamp = this.parseToInt(Precise.stringDiv(this.safeString(messageItem, 'last_max_timestamp'), '1000000'));
440
+ if (lastTimestamp < orderbook['timestamp']) {
441
+ continue;
442
+ }
443
+ else {
444
+ this.handleOrderBookMessage(client, messageItem, orderbook);
445
+ }
446
+ }
447
+ this.orderbooks[symbol] = orderbook;
448
+ client.resolve(orderbook, messageHash);
449
+ }
450
+ catch (e) {
451
+ delete client.subscriptions[messageHash];
452
+ client.reject(e, messageHash);
453
+ }
454
+ }
455
+ handleOrderBook(client, message) {
456
+ //
457
+ //
458
+ // the feed does not include a snapshot, just the deltas
459
+ //
460
+ // {
461
+ // "type":"book_depth",
462
+ // // book depth aggregates a number of events once every 50ms
463
+ // // these are the minimum and maximum timestamps from
464
+ // // events that contributed to this response
465
+ // "min_timestamp": "1683805381879572835",
466
+ // "max_timestamp": "1683805381879572835",
467
+ // // the max_timestamp of the last book_depth event for this product
468
+ // "last_max_timestamp": "1683805381771464799",
469
+ // "product_id":1,
470
+ // // changes to the bid side of the book in the form of [[price, new_qty]]
471
+ // "bids":[["21594490000000000000000","51007390115411548"]],
472
+ // // changes to the ask side of the book in the form of [[price, new_qty]]
473
+ // "asks":[["21694490000000000000000","0"],["21695050000000000000000","0"]]
474
+ // }
475
+ //
476
+ const marketId = this.safeString(message, 'product_id');
477
+ const market = this.safeMarket(marketId);
478
+ const symbol = market['symbol'];
479
+ if (!(symbol in this.orderbooks)) {
480
+ this.orderbooks[symbol] = this.orderBook();
481
+ }
482
+ const orderbook = this.orderbooks[symbol];
483
+ const timestamp = this.safeInteger(orderbook, 'timestamp');
484
+ if (timestamp === undefined) {
485
+ // Buffer the events you receive from the stream.
486
+ orderbook.cache.push(message);
487
+ }
488
+ else {
489
+ const lastTimestamp = this.parseToInt(Precise.stringDiv(this.safeString(message, 'last_max_timestamp'), '1000000'));
490
+ if (lastTimestamp > timestamp) {
491
+ this.handleOrderBookMessage(client, message, orderbook);
492
+ client.resolve(orderbook, marketId + '@book_depth');
493
+ }
494
+ }
495
+ }
496
+ handleOrderBookMessage(client, message, orderbook) {
497
+ const timestamp = this.parseToInt(Precise.stringDiv(this.safeString(message, 'last_max_timestamp'), '1000000'));
498
+ // convert from X18
499
+ const data = {
500
+ 'bids': [],
501
+ 'asks': [],
502
+ };
503
+ const bids = this.safeList(message, 'bids', []);
504
+ for (let i = 0; i < bids.length; i++) {
505
+ const bid = bids[i];
506
+ data['bids'].push([
507
+ this.convertFromX18(bid[0]),
508
+ this.convertFromX18(bid[1]),
509
+ ]);
510
+ }
511
+ const asks = this.safeList(message, 'asks', []);
512
+ for (let i = 0; i < asks.length; i++) {
513
+ const ask = asks[i];
514
+ data['asks'].push([
515
+ this.convertFromX18(ask[0]),
516
+ this.convertFromX18(ask[1]),
517
+ ]);
518
+ }
519
+ this.handleDeltas(orderbook['asks'], this.safeList(data, 'asks', []));
520
+ this.handleDeltas(orderbook['bids'], this.safeList(data, 'bids', []));
521
+ orderbook['timestamp'] = timestamp;
522
+ orderbook['datetime'] = this.iso8601(timestamp);
523
+ return orderbook;
524
+ }
525
+ handleDelta(bookside, delta) {
526
+ const price = this.safeFloat(delta, 0);
527
+ const amount = this.safeFloat(delta, 1);
528
+ bookside.store(price, amount);
529
+ }
530
+ handleDeltas(bookside, deltas) {
531
+ for (let i = 0; i < deltas.length; i++) {
532
+ this.handleDelta(bookside, deltas[i]);
533
+ }
534
+ }
535
+ handleSubscriptionStatus(client, message) {
536
+ //
537
+ // {
538
+ // "result": null,
539
+ // "id": 1574649734450
540
+ // }
541
+ //
542
+ const id = this.safeString(message, 'id');
543
+ const subscriptionsById = this.indexBy(client.subscriptions, 'id');
544
+ const subscription = this.safeValue(subscriptionsById, id, {});
545
+ const method = this.safeValue(subscription, 'method');
546
+ if (method !== undefined) {
547
+ method.call(this, client, message, subscription);
548
+ }
549
+ return message;
550
+ }
551
+ async watchPositions(symbols = undefined, since = undefined, limit = undefined, params = {}) {
552
+ /**
553
+ * @method
554
+ * @name vertex#watchPositions
555
+ * @see https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
556
+ * @description watch all open positions
557
+ * @param {string[]|undefined} symbols list of unified market symbols
558
+ * @param {object} params extra parameters specific to the exchange API endpoint
559
+ * @param {string} [params.user] user address, will default to this.walletAddress if not provided
560
+ * @returns {object[]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
561
+ */
562
+ await this.loadMarkets();
563
+ symbols = this.marketSymbols(symbols);
564
+ if (!this.isEmpty(symbols)) {
565
+ if (symbols.length > 1) {
566
+ throw new NotSupported(this.id + ' watchPositions require only one symbol.');
567
+ }
568
+ }
569
+ else {
570
+ throw new ArgumentsRequired(this.id + ' watchPositions require one symbol.');
571
+ }
572
+ let userAddress = undefined;
573
+ [userAddress, params] = this.handlePublicAddress('watchPositions', params);
574
+ const url = this.urls['api']['ws'];
575
+ const client = this.client(url);
576
+ this.setPositionsCache(client, symbols, params);
577
+ const fetchPositionsSnapshot = this.handleOption('watchPositions', 'fetchPositionsSnapshot', true);
578
+ const awaitPositionsSnapshot = this.safeBool('watchPositions', 'awaitPositionsSnapshot', true);
579
+ if (fetchPositionsSnapshot && awaitPositionsSnapshot && this.positions === undefined) {
580
+ const snapshot = await client.future('fetchPositionsSnapshot');
581
+ return this.filterBySymbolsSinceLimit(snapshot, symbols, since, limit, true);
582
+ }
583
+ const name = 'position_change';
584
+ const market = this.market(symbols[0]);
585
+ const topic = market['id'] + '@' + name;
586
+ const request = {
587
+ 'method': 'subscribe',
588
+ 'stream': {
589
+ 'type': name,
590
+ 'product_id': this.parseToNumeric(market['id']),
591
+ 'subaccount': this.convertAddressToSender(userAddress),
592
+ },
593
+ };
594
+ const message = this.extend(request, params);
595
+ const newPositions = await this.watchPublic(topic, message);
596
+ if (this.newUpdates) {
597
+ limit = newPositions.getLimit(symbols[0], limit);
598
+ }
599
+ return this.filterBySymbolsSinceLimit(this.positions, symbols, since, limit, true);
600
+ }
601
+ setPositionsCache(client, symbols = undefined, params = {}) {
602
+ const fetchPositionsSnapshot = this.handleOption('watchPositions', 'fetchPositionsSnapshot', false);
603
+ if (fetchPositionsSnapshot) {
604
+ const messageHash = 'fetchPositionsSnapshot';
605
+ if (!(messageHash in client.futures)) {
606
+ client.future(messageHash);
607
+ this.spawn(this.loadPositionsSnapshot, client, messageHash, symbols, params);
608
+ }
609
+ }
610
+ else {
611
+ this.positions = new ArrayCacheBySymbolBySide();
612
+ }
613
+ }
614
+ async loadPositionsSnapshot(client, messageHash, symbols, params) {
615
+ const positions = await this.fetchPositions(symbols, params);
616
+ this.positions = new ArrayCacheBySymbolBySide();
617
+ const cache = this.positions;
618
+ for (let i = 0; i < positions.length; i++) {
619
+ const position = positions[i];
620
+ cache.append(position);
621
+ }
622
+ // don't remove the future from the .futures cache
623
+ const future = client.futures[messageHash];
624
+ future.resolve(cache);
625
+ client.resolve(cache, 'positions');
626
+ }
627
+ handlePositions(client, message) {
628
+ //
629
+ // {
630
+ // "type":"position_change",
631
+ // "timestamp": "1676151190656903000", // timestamp of event in nanoseconds
632
+ // "product_id":1,
633
+ // // whether this is a position change for the LP token for this product
634
+ // "is_lp":false,
635
+ // // subaccount who's position changed
636
+ // "subaccount":"0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43706d00000000000000000000",
637
+ // // new amount for this product
638
+ // "amount":"51007390115411548",
639
+ // // new quote balance for this product; zero for everything except non lp perps
640
+ // // the negative of the entry cost of the perp
641
+ // "v_quote_amount":"0"
642
+ // }
643
+ //
644
+ if (this.positions === undefined) {
645
+ this.positions = new ArrayCacheBySymbolBySide();
646
+ }
647
+ const cache = this.positions;
648
+ const topic = this.safeString(message, 'type');
649
+ const marketId = this.safeString(message, 'product_id');
650
+ const market = this.safeMarket(marketId);
651
+ const position = this.parseWsPosition(message, market);
652
+ cache.append(position);
653
+ client.resolve(position, marketId + '@' + topic);
654
+ }
655
+ parseWsPosition(position, market = undefined) {
656
+ //
657
+ // {
658
+ // "type":"position_change",
659
+ // "timestamp": "1676151190656903000", // timestamp of event in nanoseconds
660
+ // "product_id":1,
661
+ // // whether this is a position change for the LP token for this product
662
+ // "is_lp":false,
663
+ // // subaccount who's position changed
664
+ // "subaccount":"0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43706d00000000000000000000",
665
+ // // new amount for this product
666
+ // "amount":"51007390115411548",
667
+ // // new quote balance for this product; zero for everything except non lp perps
668
+ // // the negative of the entry cost of the perp
669
+ // "v_quote_amount":"0"
670
+ // }
671
+ //
672
+ const marketId = this.safeString(position, 'product_id');
673
+ market = this.safeMarket(marketId);
674
+ const contractSize = this.convertFromX18(this.safeString(position, 'amount'));
675
+ let side = 'buy';
676
+ if (Precise.stringLt(contractSize, '1')) {
677
+ side = 'sell';
678
+ }
679
+ const timestamp = this.parseToInt(Precise.stringDiv(this.safeString(position, 'timestamp'), '1000000'));
680
+ return this.safePosition({
681
+ 'info': position,
682
+ 'id': undefined,
683
+ 'symbol': this.safeString(market, 'symbol'),
684
+ 'timestamp': timestamp,
685
+ 'datetime': this.iso8601(timestamp),
686
+ 'lastUpdateTimestamp': undefined,
687
+ 'initialMargin': undefined,
688
+ 'initialMarginPercentage': undefined,
689
+ 'maintenanceMargin': undefined,
690
+ 'maintenanceMarginPercentage': undefined,
691
+ 'entryPrice': undefined,
692
+ 'notional': undefined,
693
+ 'leverage': undefined,
694
+ 'unrealizedPnl': undefined,
695
+ 'contracts': undefined,
696
+ 'contractSize': this.parseNumber(contractSize),
697
+ 'marginRatio': undefined,
698
+ 'liquidationPrice': undefined,
699
+ 'markPrice': undefined,
700
+ 'lastPrice': undefined,
701
+ 'collateral': undefined,
702
+ 'marginMode': 'cross',
703
+ 'marginType': undefined,
704
+ 'side': side,
705
+ 'percentage': undefined,
706
+ 'hedged': undefined,
707
+ 'stopLossPrice': undefined,
708
+ 'takeProfitPrice': undefined,
709
+ });
710
+ }
711
+ handleAuth(client, message) {
712
+ //
713
+ // { result: null, id: 1 }
714
+ //
715
+ const messageHash = 'authenticated';
716
+ const error = this.safeString(message, 'error');
717
+ if (error === undefined) {
718
+ // client.resolve (message, messageHash);
719
+ const future = this.safeValue(client.futures, 'authenticated');
720
+ future.resolve(true);
721
+ }
722
+ else {
723
+ const authError = new AuthenticationError(this.json(message));
724
+ client.reject(authError, messageHash);
725
+ // allows further authentication attempts
726
+ if (messageHash in client.subscriptions) {
727
+ delete client.subscriptions['authenticated'];
728
+ }
729
+ }
730
+ }
731
+ buildWsAuthenticationSig(message, chainId, verifyingContractAddress) {
732
+ const messageTypes = {
733
+ 'StreamAuthentication': [
734
+ { 'name': 'sender', 'type': 'bytes32' },
735
+ { 'name': 'expiration', 'type': 'uint64' },
736
+ ],
737
+ };
738
+ return this.buildSig(chainId, messageTypes, message, verifyingContractAddress);
739
+ }
740
+ async authenticate(params = {}) {
741
+ this.checkRequiredCredentials();
742
+ const url = this.urls['api']['ws'];
743
+ const client = this.client(url);
744
+ const messageHash = 'authenticated';
745
+ const future = client.future(messageHash);
746
+ const authenticated = this.safeValue(client.subscriptions, messageHash);
747
+ if (authenticated === undefined) {
748
+ const requestId = this.requestId(url);
749
+ const contracts = await this.queryContracts();
750
+ const chainId = this.safeString(contracts, 'chain_id');
751
+ const verifyingContractAddress = this.safeString(contracts, 'endpoint_addr');
752
+ const now = this.nonce();
753
+ const nonce = now + 90000;
754
+ const authentication = {
755
+ 'sender': this.convertAddressToSender(this.walletAddress),
756
+ 'expiration': nonce,
757
+ };
758
+ const request = {
759
+ 'id': requestId,
760
+ 'method': 'authenticate',
761
+ 'tx': {
762
+ 'sender': authentication['sender'],
763
+ 'expiration': this.numberToString(authentication['expiration']),
764
+ },
765
+ 'signature': this.buildWsAuthenticationSig(authentication, chainId, verifyingContractAddress),
766
+ };
767
+ const message = this.extend(request, params);
768
+ this.watch(url, messageHash, message, messageHash);
769
+ }
770
+ return await future;
771
+ }
772
+ async watchPrivate(messageHash, message, params = {}) {
773
+ await this.authenticate(params);
774
+ const url = this.urls['api']['ws'];
775
+ const requestId = this.requestId(url);
776
+ const subscribe = {
777
+ 'id': requestId,
778
+ };
779
+ const request = this.extend(subscribe, message);
780
+ return await this.watch(url, messageHash, request, messageHash, subscribe);
781
+ }
782
+ async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
783
+ /**
784
+ * @method
785
+ * @name vertex#watchOrders
786
+ * @description watches information on multiple orders made by the user
787
+ * @see https://docs.vertexprotocol.com/developer-resources/api/subscriptions/streams
788
+ * @param {string} symbol unified market symbol of the market orders were made in
789
+ * @param {int} [since] the earliest time in ms to fetch orders for
790
+ * @param {int} [limit] the maximum number of order structures to retrieve
791
+ * @param {object} [params] extra parameters specific to the exchange API endpoint
792
+ * @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
793
+ */
794
+ if (symbol === undefined) {
795
+ throw new ArgumentsRequired(this.id + ' watchOrders requires a symbol.');
796
+ }
797
+ this.checkRequiredCredentials();
798
+ await this.loadMarkets();
799
+ const name = 'order_update';
800
+ const market = this.market(symbol);
801
+ const topic = market['id'] + '@' + name;
802
+ const request = {
803
+ 'method': 'subscribe',
804
+ 'stream': {
805
+ 'type': name,
806
+ 'subaccount': this.convertAddressToSender(this.walletAddress),
807
+ 'product_id': this.parseToNumeric(market['id']),
808
+ },
809
+ };
810
+ const message = this.extend(request, params);
811
+ const orders = await this.watchPrivate(topic, message);
812
+ if (this.newUpdates) {
813
+ limit = orders.getLimit(symbol, limit);
814
+ }
815
+ return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
816
+ }
817
+ parseWsOrderStatus(status) {
818
+ if (status !== undefined) {
819
+ const statuses = {
820
+ 'filled': 'open',
821
+ 'placed': 'open',
822
+ 'cancelled': 'canceled',
823
+ };
824
+ return this.safeString(statuses, status, status);
825
+ }
826
+ return status;
827
+ }
828
+ parseWsOrder(order, market = undefined) {
829
+ //
830
+ // {
831
+ // "type": "order_update",
832
+ // // timestamp of the event in nanoseconds
833
+ // "timestamp": "1695081920633151000",
834
+ // "product_id": 1,
835
+ // // order digest
836
+ // "digest": "0xf7712b63ccf70358db8f201e9bf33977423e7a63f6a16f6dab180bdd580f7c6c",
837
+ // // remaining amount to be filled.
838
+ // // will be `0` if the order is either fully filled or cancelled.
839
+ // "amount": "82000000000000000",
840
+ // // any of: "filled", "cancelled", "placed"
841
+ // "reason": "filled"
842
+ // // an optional `order id` that can be provided when placing an order
843
+ // "id": 100
844
+ // }
845
+ //
846
+ const marketId = this.safeString(order, 'product_id');
847
+ const timestamp = this.parseToInt(Precise.stringDiv(this.safeString(order, 'timestamp'), '1000000'));
848
+ const remaining = this.parseToNumeric(this.convertFromX18(this.safeString(order, 'amount')));
849
+ let status = this.parseWsOrderStatus(this.safeString(order, 'reason'));
850
+ if (remaining === 0 && status === 'open') {
851
+ status = 'closed';
852
+ }
853
+ market = this.safeMarket(marketId, market);
854
+ const symbol = market['symbol'];
855
+ return this.safeOrder({
856
+ 'info': order,
857
+ 'id': this.safeString2(order, 'digest', 'id'),
858
+ 'clientOrderId': undefined,
859
+ 'timestamp': timestamp,
860
+ 'datetime': this.iso8601(timestamp),
861
+ 'lastTradeTimestamp': undefined,
862
+ 'lastUpdateTimestamp': undefined,
863
+ 'symbol': symbol,
864
+ 'type': undefined,
865
+ 'timeInForce': undefined,
866
+ 'postOnly': undefined,
867
+ 'reduceOnly': undefined,
868
+ 'side': undefined,
869
+ 'price': undefined,
870
+ 'triggerPrice': undefined,
871
+ 'amount': undefined,
872
+ 'cost': undefined,
873
+ 'average': undefined,
874
+ 'filled': undefined,
875
+ 'remaining': remaining,
876
+ 'status': status,
877
+ 'fee': undefined,
878
+ 'trades': undefined,
879
+ }, market);
880
+ }
881
+ handleOrderUpdate(client, message) {
882
+ //
883
+ // {
884
+ // "type": "order_update",
885
+ // // timestamp of the event in nanoseconds
886
+ // "timestamp": "1695081920633151000",
887
+ // "product_id": 1,
888
+ // // order digest
889
+ // "digest": "0xf7712b63ccf70358db8f201e9bf33977423e7a63f6a16f6dab180bdd580f7c6c",
890
+ // // remaining amount to be filled.
891
+ // // will be `0` if the order is either fully filled or cancelled.
892
+ // "amount": "82000000000000000",
893
+ // // any of: "filled", "cancelled", "placed"
894
+ // "reason": "filled"
895
+ // // an optional `order id` that can be provided when placing an order
896
+ // "id": 100
897
+ // }
898
+ //
899
+ const topic = this.safeString(message, 'type');
900
+ const marketId = this.safeString(message, 'product_id');
901
+ const parsed = this.parseWsOrder(message);
902
+ const symbol = this.safeString(parsed, 'symbol');
903
+ const orderId = this.safeString(parsed, 'id');
904
+ if (symbol !== undefined) {
905
+ if (this.orders === undefined) {
906
+ const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
907
+ this.orders = new ArrayCacheBySymbolById(limit);
908
+ }
909
+ const cachedOrders = this.orders;
910
+ const orders = this.safeDict(cachedOrders.hashmap, symbol, {});
911
+ const order = this.safeDict(orders, orderId);
912
+ if (order !== undefined) {
913
+ parsed['timestamp'] = this.safeInteger(order, 'timestamp');
914
+ parsed['datetime'] = this.safeString(order, 'datetime');
915
+ }
916
+ cachedOrders.append(parsed);
917
+ client.resolve(this.orders, marketId + '@' + topic);
918
+ }
919
+ }
920
+ handleErrorMessage(client, message) {
921
+ //
922
+ // {
923
+ // result: null,
924
+ // error: 'error parsing request: missing field `expiration`',
925
+ // id: 0
926
+ // }
927
+ //
928
+ const errorMessage = this.safeString(message, 'error');
929
+ try {
930
+ if (errorMessage !== undefined) {
931
+ const feedback = this.id + ' ' + this.json(message);
932
+ this.throwExactlyMatchedException(this.exceptions['exact'], errorMessage, feedback);
933
+ }
934
+ return false;
935
+ }
936
+ catch (error) {
937
+ if (error instanceof AuthenticationError) {
938
+ const messageHash = 'authenticated';
939
+ client.reject(error, messageHash);
940
+ if (messageHash in client.subscriptions) {
941
+ delete client.subscriptions[messageHash];
942
+ }
943
+ }
944
+ else {
945
+ client.reject(error);
946
+ }
947
+ return true;
948
+ }
949
+ }
950
+ handleMessage(client, message) {
951
+ if (this.handleErrorMessage(client, message)) {
952
+ return;
953
+ }
954
+ const methods = {
955
+ 'trade': this.handleTrade,
956
+ 'best_bid_offer': this.handleTicker,
957
+ 'book_depth': this.handleOrderBook,
958
+ 'fill': this.handleMyTrades,
959
+ 'position_change': this.handlePositions,
960
+ 'order_update': this.handleOrderUpdate,
961
+ };
962
+ const event = this.safeString(message, 'type');
963
+ const method = this.safeValue(methods, event);
964
+ if (method !== undefined) {
965
+ method.call(this, client, message);
966
+ return;
967
+ }
968
+ const requestId = this.safeString(message, 'id');
969
+ if (requestId !== undefined) {
970
+ this.handleSubscriptionStatus(client, message);
971
+ return;
972
+ }
973
+ // check whether it's authentication
974
+ const auth = this.safeValue(client.futures, 'authenticated');
975
+ if (auth !== undefined) {
976
+ this.handleAuth(client, message);
977
+ }
978
+ }
979
+ }