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