hollaex-node-lib 1.1.0 → 2.12.0

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/kit.js ADDED
@@ -0,0 +1,896 @@
1
+ 'use strict';
2
+
3
+ const WebSocket = require('ws');
4
+ const moment = require('moment');
5
+ const { createRequest, createSignature, generateHeaders, isDatetime, sanitizeDate } = require('./utils');
6
+ const { setWsHeartbeat } = require('ws-heartbeat/client');
7
+ const { each, union, isNumber, isString, isPlainObject, isBoolean } = require('lodash');
8
+ class HollaExKit {
9
+ constructor(
10
+ opts = {
11
+ apiURL: 'https://api.hollaex.com',
12
+ baseURL: '/v2',
13
+ apiKey: '',
14
+ apiSecret: '',
15
+ apiExpiresAfter: 60
16
+ }
17
+ ) {
18
+ this.apiUrl = opts.apiURL || 'https://api.hollaex.com';
19
+ this.baseUrl = opts.baseURL || '/v2';
20
+ this.apiKey = opts.apiKey;
21
+ this.apiSecret = opts.apiSecret;
22
+ this.apiExpiresAfter = opts.apiExpiresAfter || 60;
23
+ this.headers = {
24
+ 'content-type': 'application/json',
25
+ Accept: 'application/json',
26
+ 'api-key': opts.apiKey
27
+ };
28
+ this.ws = null;
29
+ const [protocol, endpoint] = this.apiUrl.split('://');
30
+ this.wsUrl =
31
+ protocol === 'https'
32
+ ? `wss://${endpoint}/stream`
33
+ : `ws://${endpoint}/stream`;
34
+ this.wsEvents = [];
35
+ this.wsReconnect = true;
36
+ this.wsReconnectInterval = 5000;
37
+ this.wsEventListeners = null;
38
+ this.wsConnected = () => this.ws && this.ws.readyState === WebSocket.OPEN;
39
+ }
40
+
41
+ /* Public Endpoints*/
42
+
43
+ /**
44
+ * Get exchange information
45
+ * @return {object} A json object with the exchange information
46
+ */
47
+ getKit() {
48
+ return createRequest(
49
+ 'GET',
50
+ `${this.apiUrl}${this.baseUrl}/kit`,
51
+ this.headers
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Retrieve last, high, low, open and close price and volume within last 24 hours for a symbol
57
+ * @param {string} symbol - The currency pair symbol e.g. 'hex-usdt'
58
+ * @return {object} A JSON object with keys high(number), low(number), open(number), close(number), volume(number), last(number)
59
+ */
60
+ getTicker(symbol = '') {
61
+ return createRequest(
62
+ 'GET',
63
+ `${this.apiUrl}${this.baseUrl}/ticker?symbol=${symbol}`,
64
+ this.headers
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Retrieve last, high, low, open and close price and volume within last 24 hours for all symbols
70
+ * @return {object} A JSON object with symbols as keys which contain high(number), low(number), open(number), close(number), volume(number), last(number)
71
+ */
72
+ getTickers() {
73
+ return createRequest(
74
+ 'GET',
75
+ `${this.apiUrl}${this.baseUrl}/tickers`,
76
+ this.headers
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Retrieve orderbook containing lists of up to the last 20 bids and asks for a symbol
82
+ * @param {string} symbol - The currency pair symbol e.g. 'hex-usdt'
83
+ * @return {object} A JSON object with keys bids(array of active buy orders), asks(array of active sell orders), and timestamp(string)
84
+ */
85
+ getOrderbook(symbol = '') {
86
+ return createRequest(
87
+ 'GET',
88
+ `${this.apiUrl}${this.baseUrl}/orderbook?symbol=${symbol}`,
89
+ this.headers
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Retrieve orderbook containing lists of up to the last 20 bids and asks for all symbols
95
+ * @return {object} A JSON object with the symbol-pairs as keys where the values are objects with keys bids(array of active buy orders), asks(array of active sell orders), and timestamp(string)
96
+ */
97
+ getOrderbooks() {
98
+ return createRequest(
99
+ 'GET',
100
+ `${this.apiUrl}${this.baseUrl}/orderbooks`,
101
+ this.headers
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Retrieve list of up to the last 50 trades
107
+ * @param {object} opts - Optional parameters
108
+ * @param {string} opts.symbol - The currency pair symbol e.g. 'hex-usdt'
109
+ * @return {object} A JSON object with the symbol-pairs as keys where the values are arrays of objects with keys size(number), price(number), side(string), and timestamp(string)
110
+ */
111
+ getTrades(opts = { symbol: null }) {
112
+ let path = `${this.apiUrl}${this.baseUrl}/trades`;
113
+
114
+ if (isString(opts.symbol)) {
115
+ path += `?symbol=${opts.symbol}`;
116
+ }
117
+
118
+ return createRequest('GET', path, this.headers);
119
+ }
120
+
121
+ /**
122
+ * Retrieve tick size, min price, max price, min size, and max size of each symbol-pair
123
+ * @return {object} A JSON object with the keys pairs(information on each symbol-pair such as tick_size, min/max price, and min/max size) and currencies(array of all currencies involved in hollaEx)
124
+ */
125
+ getConstants() {
126
+ return createRequest(
127
+ 'GET',
128
+ `${this.apiUrl}${this.baseUrl}/constants`,
129
+ this.headers
130
+ );
131
+ }
132
+
133
+ /* Private Endpoints*/
134
+
135
+ /**
136
+ * Retrieve user's personal information
137
+ * @return {string} A JSON object showing user's information such as id, email, bank_account, crypto_wallet, balance, etc
138
+ */
139
+ getUser() {
140
+ const verb = 'GET';
141
+ const path = `${this.baseUrl}/user`;
142
+ const headers = generateHeaders(
143
+ this.headers,
144
+ this.apiSecret,
145
+ verb,
146
+ path,
147
+ this.apiExpiresAfter
148
+ );
149
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
150
+ }
151
+
152
+ /**
153
+ * Retrieve user's wallet balance
154
+ * @return {object} A JSON object with the keys updated_at(string), usdt_balance(number), usdt_pending(number), usdt_available(number), hex_balance, hex_pending, hex_available, eth_balance, eth_pending, eth_available, bch_balance, bch_pending, bch_available
155
+ */
156
+ getBalance() {
157
+ const verb = 'GET';
158
+ const path = `${this.baseUrl}/user/balance`;
159
+ const headers = generateHeaders(
160
+ this.headers,
161
+ this.apiSecret,
162
+ verb,
163
+ path,
164
+ this.apiExpiresAfter
165
+ );
166
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
167
+ }
168
+
169
+ /**
170
+ * Retrieve list of the user's deposits
171
+ * @param {object} opts - Optional parameters
172
+ * @param {string} opts.currency - The currency to filter by, pass undefined to receive data on all currencies
173
+ * @param {boolean} opts.status - Confirmed status of the deposits to get. Leave blank to get all confirmed and unconfirmed deposits
174
+ * @param {boolean} opts.dismissed - Dismissed status of the deposits to get. Leave blank to get all dismissed and undismissed deposits
175
+ * @param {boolean} opts.rejected - Rejected status of the deposits to get. Leave blank to get all rejected and unrejected deposits
176
+ * @param {boolean} opts.processing - Processing status of the deposits to get. Leave blank to get all processing and unprocessing deposits
177
+ * @param {boolean} opts.waiting - Waiting status of the deposits to get. Leave blank to get all waiting and unwaiting deposits
178
+ * @param {number} opts.limit - Amount of trades per page. Maximum: 50. Default: 50
179
+ * @param {number} opts.page - Page of trades data. Default: 1
180
+ * @param {string} opts.orderBy - The field to order data by e.g. amount, id.
181
+ * @param {string} opts.order - Ascending (asc) or descending (desc).
182
+ * @param {string} opts.startDate - Start date of query in ISO8601 format.
183
+ * @param {string} opts.endDate - End date of query in ISO8601 format.
184
+ * @param {string} opts.transactionId - Deposits with specific transaction ID.
185
+ * @param {string} opts.address - Deposits with specific address.
186
+ * @return {object} A JSON object with the keys count(total number of user's deposits) and data(array of deposits as objects with keys id(number), type(string), amount(number), transaction_id(string), currency(string), created_at(string), status(boolean), fee(number), dismissed(boolean), rejected(boolean), description(string))
187
+ */
188
+ getDeposits(
189
+ opts = {
190
+ currency: null,
191
+ status: null,
192
+ dismissed: null,
193
+ rejected: null,
194
+ processing: null,
195
+ waiting: null,
196
+ limit: null,
197
+ page: null,
198
+ orderBy: null,
199
+ order: null,
200
+ startDate: null,
201
+ endDate: null,
202
+ transactionId: null,
203
+ address: null
204
+ }
205
+ ) {
206
+ const verb = 'GET';
207
+ let path = `${this.baseUrl}/user/deposits?`;
208
+
209
+ if (isString(opts.currency)) {
210
+ path += `&currency=${opts.currency}`;
211
+ }
212
+
213
+ if (isNumber(opts.limit)) {
214
+ path += `&limit=${opts.limit}`;
215
+ }
216
+
217
+ if (isNumber(opts.page)) {
218
+ path += `&page=${opts.page}`;
219
+ }
220
+
221
+ if (isString(opts.orderBy)) {
222
+ path += `&order_by=${opts.orderBy}`;
223
+ }
224
+
225
+ if (isString(opts.order)) {
226
+ path += `&order=${opts.order}`;
227
+ }
228
+
229
+ if (isDatetime(opts.startDate)) {
230
+ path += `&start_date=${sanitizeDate(opts.startDate)}`;
231
+ }
232
+
233
+ if (isDatetime(opts.endDate)) {
234
+ path += `&end_date=${sanitizeDate(opts.endDate)}`;
235
+ }
236
+
237
+ if (isString(opts.address)) {
238
+ path += `&address=${opts.address}`;
239
+ }
240
+
241
+ if (isString(opts.transactionId)) {
242
+ path += `&transaction_id=${opts.transactionId}`;
243
+ }
244
+
245
+ if (isBoolean(opts.status)) {
246
+ path += `&status=${opts.status}`;
247
+ }
248
+
249
+ if (isBoolean(opts.dismissed)) {
250
+ path += `&dismissed=${opts.dismissed}`;
251
+ }
252
+
253
+ if (isBoolean(opts.rejected)) {
254
+ path += `&rejected=${opts.rejected}`;
255
+ }
256
+
257
+ if (isBoolean(opts.processing)) {
258
+ path += `&processing=${opts.processing}`;
259
+ }
260
+
261
+ if (isBoolean(opts.waiting)) {
262
+ path += `&waiting=${opts.waiting}`;
263
+ }
264
+
265
+ const headers = generateHeaders(
266
+ this.headers,
267
+ this.apiSecret,
268
+ verb,
269
+ path,
270
+ this.apiExpiresAfter
271
+ );
272
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
273
+ }
274
+
275
+ /****** Withdrawals ******/
276
+ /**
277
+ * Retrieve list of the user's withdrawals
278
+ * @param {object} opts - Optional parameters
279
+ * @param {string} opts.currency - The currency to filter by, pass undefined to receive data on all currencies
280
+ * @param {boolean} opts.status - Confirmed status of the withdrawals to get. Leave blank to get all confirmed and unconfirmed withdrawals
281
+ * @param {boolean} opts.dismissed - Dismissed status of the withdrawals to get. Leave blank to get all dismissed and undismissed withdrawals
282
+ * @param {boolean} opts.rejected - Rejected status of the withdrawals to get. Leave blank to get all rejected and unrejected withdrawals
283
+ * @param {boolean} opts.processing - Processing status of the withdrawals to get. Leave blank to get all processing and unprocessing withdrawals
284
+ * @param {boolean} opts.waiting - Waiting status of the withdrawals to get. Leave blank to get all waiting and unwaiting withdrawals
285
+ * @param {number} opts.limit - Amount of trades per page. Maximum: 50. Default: 50
286
+ * @param {number} opts.page - Page of trades data. Default: 1
287
+ * @param {string} opts.orderBy - The field to order data by e.g. amount, id.
288
+ * @param {string} opts.order - Ascending (asc) or descending (desc).
289
+ * @param {string} opts.startDate - Start date of query in ISO8601 format.
290
+ * @param {string} opts.endDate - End date of query in ISO8601 format.
291
+ * @param {string} opts.transactionId - Withdrawals with specific transaction ID.
292
+ * @param {string} opts.address - Withdrawals with specific address.
293
+ * @return {object} A JSON object with the keys count(total number of user's withdrawals) and data(array of withdrawals as objects with keys id(number), type(string), amount(number), transaction_id(string), currency(string), created_at(string), status(boolean), fee(number), dismissed(boolean), rejected(boolean), description(string))
294
+ */
295
+ getWithdrawals(
296
+ opts = {
297
+ currency: null,
298
+ status: null,
299
+ dismissed: null,
300
+ rejected: null,
301
+ processing: null,
302
+ waiting: null,
303
+ limit: null,
304
+ page: null,
305
+ orderBy: null,
306
+ order: null,
307
+ startDate: null,
308
+ endDate: null,
309
+ transactionId: null,
310
+ address: null
311
+ }
312
+ ) {
313
+ const verb = 'GET';
314
+ let path = `${this.baseUrl}/user/withdrawals?`;
315
+
316
+ if (isString(opts.currency)) {
317
+ path += `&currency=${opts.currency}`;
318
+ }
319
+
320
+ if (isNumber(opts.limit)) {
321
+ path += `&limit=${opts.limit}`;
322
+ }
323
+
324
+ if (isNumber(opts.page)) {
325
+ path += `&page=${opts.page}`;
326
+ }
327
+
328
+ if (isString(opts.orderBy)) {
329
+ path += `&order_by=${opts.orderBy}`;
330
+ }
331
+
332
+ if (isString(opts.order)) {
333
+ path += `&order=${opts.order}`;
334
+ }
335
+
336
+ if (isDatetime(opts.startDate)) {
337
+ path += `&start_date=${sanitizeDate(opts.startDate)}`;
338
+ }
339
+
340
+ if (isDatetime(opts.endDate)) {
341
+ path += `&end_date=${sanitizeDate(opts.endDate)}`;
342
+ }
343
+
344
+ if (isString(opts.address)) {
345
+ path += `&address=${opts.address}`;
346
+ }
347
+
348
+ if (isString(opts.transactionId)) {
349
+ path += `&transaction_id=${opts.transactionId}`;
350
+ }
351
+
352
+ if (isBoolean(opts.status)) {
353
+ path += `&status=${opts.status}`;
354
+ }
355
+
356
+ if (isBoolean(opts.dismissed)) {
357
+ path += `&dismissed=${opts.dismissed}`;
358
+ }
359
+
360
+ if (isBoolean(opts.rejected)) {
361
+ path += `&rejected=${opts.rejected}`;
362
+ }
363
+
364
+ if (isBoolean(opts.processing)) {
365
+ path += `&processing=${opts.processing}`;
366
+ }
367
+
368
+ if (isBoolean(opts.waiting)) {
369
+ path += `&waiting=${opts.waiting}`;
370
+ }
371
+
372
+ const headers = generateHeaders(
373
+ this.headers,
374
+ this.apiSecret,
375
+ verb,
376
+ path,
377
+ this.apiExpiresAfter
378
+ );
379
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
380
+ }
381
+
382
+ /**
383
+ * Make a withdrawal request
384
+ * @param {string} currency - The currency to withdrawal
385
+ * @param {number} amount - The amount of currency to withdrawal
386
+ * @param {string} address - The recipient's wallet address
387
+ * @param {object} opts - Optional parameters.
388
+ * @param {string} opts.network - Crypto network of currency being withdrawn.
389
+ * @param {string} opts.otpCode - Otp code for user if otp is enabled.
390
+ * @return {object} A JSON object {message:"Success"}
391
+ */
392
+ requestWithdrawal(currency, amount, address, opts = {
393
+ network: null,
394
+ otpCode: null
395
+ }) {
396
+ const verb = 'POST';
397
+ const path = `${this.baseUrl}/user/request-withdrawal`;
398
+ const data = {
399
+ currency,
400
+ amount,
401
+ address
402
+ };
403
+
404
+ if (opts.network) {
405
+ data.otp_code = opts.otpCode;
406
+ }
407
+
408
+ if (opts.network) {
409
+ data.network = opts.network;
410
+ }
411
+
412
+ const headers = generateHeaders(
413
+ this.headers,
414
+ this.apiSecret,
415
+ verb,
416
+ path,
417
+ this.apiExpiresAfter,
418
+ data
419
+ );
420
+ return createRequest(verb, `${this.apiUrl}${path}`, headers, { data });
421
+ }
422
+
423
+ /**
424
+ * Retrieve list of the user's completed trades
425
+ * @param {object} opts - Optional parameters
426
+ * @param {string} opts.symbol - The symbol-pair to filter by, pass undefined to receive data on all currencies
427
+ * @param {number} opts.limit - Amount of trades per page
428
+ * @param {number} opts.page - Page of trades data
429
+ * @param {string} opts.orderBy - The field to order data by e.g. amount, id. Default: id
430
+ * @param {string} opts.order - Ascending (asc) or descending (desc). Default: desc
431
+ * @param {string} opts.startDate - Start date of query in ISO8601 format
432
+ * @param {string} opts.endDate - End date of query in ISO8601 format
433
+ * @param {string} opts.format - Custom format of data set. Enum: ['all', 'csv']
434
+ * @return {object} A JSON object with the keys count(total number of user's completed trades) and data(array of up to the user's last 50 completed trades as objects with keys side(string), symbol(string), size(number), price(number), timestamp(string), and fee(number))
435
+ */
436
+ getUserTrades(
437
+ opts = {
438
+ symbol: null,
439
+ limit: null,
440
+ page: null,
441
+ orderBy: null,
442
+ order: null,
443
+ startDate: null,
444
+ endDate: null,
445
+ format: null
446
+ }
447
+ ) {
448
+ const verb = 'GET';
449
+ let path = `${this.baseUrl}/user/trades?`;
450
+
451
+ if (isString(opts.symbol)) {
452
+ path += `&symbol=${opts.symbol}`;
453
+ }
454
+
455
+ if (isNumber(opts.limit)) {
456
+ path += `&limit=${opts.limit}`;
457
+ }
458
+
459
+ if (isNumber(opts.page)) {
460
+ path += `&page=${opts.page}`;
461
+ }
462
+
463
+ if (isString(opts.orderBy)) {
464
+ path += `&order_by=${opts.orderBy}`;
465
+ }
466
+
467
+ if (isString(opts.order)) {
468
+ path += `&order=${opts.order}`;
469
+ }
470
+
471
+ if (isDatetime(opts.startDate)) {
472
+ path += `&start_date=${sanitizeDate(opts.startDate)}`;
473
+ }
474
+
475
+ if (isDatetime(opts.endDate)) {
476
+ path += `&end_date=${sanitizeDate(opts.endDate)}`;
477
+ }
478
+
479
+ if (isString(opts.format)) {
480
+ path += `&format=${opts.format}`;
481
+ }
482
+
483
+ const headers = generateHeaders(
484
+ this.headers,
485
+ this.apiSecret,
486
+ verb,
487
+ path,
488
+ this.apiExpiresAfter
489
+ );
490
+
491
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
492
+ }
493
+
494
+ /****** Orders ******/
495
+ /**
496
+ * Retrieve information of a user's specific order
497
+ * @param {string} orderId - The id of the desired order
498
+ * @return {object} The selected order as a JSON object with keys created_at(string), title(string), symbol(string), side(string), size(number), type(string), price(number), id(string), created_by(number), filled(number)
499
+ */
500
+ getOrder(orderId) {
501
+ const verb = 'GET';
502
+ const path = `${this.baseUrl}/order?order_id=${orderId}`;
503
+ const headers = generateHeaders(
504
+ this.headers,
505
+ this.apiSecret,
506
+ verb,
507
+ path,
508
+ this.apiExpiresAfter
509
+ );
510
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
511
+ }
512
+
513
+ /**
514
+ * Retrieve information of all the user's active orders
515
+ * @param {object} opts - Optional parameters
516
+ * @param {string} opts.symbol - The currency pair symbol to filter by e.g. 'hex-usdt', leave empty to retrieve information of orders of all symbols
517
+ * @param {number} opts.limit - Amount of trades per page. Maximum: 50. Default: 50
518
+ * @param {number} opts.page - Page of trades data. Default: 1
519
+ * @param {string} opts.orderBy - The field to order data by e.g. amount, id.
520
+ * @param {string} opts.order - Ascending (asc) or descending (desc).
521
+ * @param {string} opts.startDate - Start date of query in ISO8601 format.
522
+ * @param {string} opts.endDate - End date of query in ISO8601 format.
523
+ * @return {object} A JSON array of objects containing the user's active orders
524
+ */
525
+ getOrders(
526
+ opts = {
527
+ symbol: null,
528
+ side: null,
529
+ status: null,
530
+ open: null,
531
+ limit: null,
532
+ page: null,
533
+ orderBy: null,
534
+ order: null,
535
+ startDate: null,
536
+ endDate: null
537
+ }
538
+ ) {
539
+ const verb = 'GET';
540
+ let path = `${this.baseUrl}/orders?`;
541
+
542
+ if (isString(opts.symbol)) {
543
+ path += `&symbol=${opts.symbol}`;
544
+ }
545
+
546
+ if (isString(opts.side) && (opts.side.toLowerCase() === 'buy' || opts.side.toLowerCase() === 'sell')) {
547
+ path += `&side=${opts.side}`;
548
+ }
549
+
550
+ if (isString(opts.status)) {
551
+ path += `&status=${opts.status}`;
552
+ }
553
+
554
+ if (isBoolean(opts.open)) {
555
+ path += `&open=${opts.open}`;
556
+ }
557
+
558
+ if (isNumber(opts.limit)) {
559
+ path += `&limit=${opts.limit}`;
560
+ }
561
+
562
+ if (isNumber(opts.page)) {
563
+ path += `&page=${opts.page}`;
564
+ }
565
+
566
+ if (isString(opts.orderBy)) {
567
+ path += `&order_by=${opts.orderBy}`;
568
+ }
569
+
570
+ if (isString(opts.order)) {
571
+ path += `&order=${opts.order}`;
572
+ }
573
+
574
+ if (isDatetime(opts.startDate)) {
575
+ path += `&start_date=${sanitizeDate(opts.startDate)}`;
576
+ }
577
+
578
+ if (isDatetime(opts.endDate)) {
579
+ path += `&end_date=${sanitizeDate(opts.endDate)}`;
580
+ }
581
+
582
+ const headers = generateHeaders(
583
+ this.headers,
584
+ this.apiSecret,
585
+ verb,
586
+ path,
587
+ this.apiExpiresAfter
588
+ );
589
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
590
+ }
591
+
592
+ /**
593
+ * Create a new order
594
+ * @param {string} symbol - The currency pair symbol e.g. 'hex-usdt'
595
+ * @param {string} side - The side of the order e.g. 'buy', 'sell'
596
+ * @param {number} size - The amount of currency to order
597
+ * @param {string} type - The type of order to create e.g. 'market', 'limit'
598
+ * @param {number} price - The price at which to order (only required if type is 'limit')
599
+ * @param {object} opts - Optional parameters
600
+ * @param {number} opts.stop - Stop order price
601
+ * @param {object} opts.meta - Additional meta parameters in an object
602
+ * @param {boolean} opts.meta.post_only - Whether or not the order should only be made if market maker.
603
+ * @param {string} opts.meta.note - Additional note to add to order data.
604
+ * @return {object} The new order as a JSON object with keys symbol(string), side(string), size(number), type(string), price(number), id(string), created_by(number), and filled(number)
605
+ */
606
+ createOrder(
607
+ symbol,
608
+ side,
609
+ size,
610
+ type,
611
+ price = 0,
612
+ opts = {
613
+ stop: null,
614
+ meta: null
615
+ }
616
+ ) {
617
+ const verb = 'POST';
618
+ const path = `${this.baseUrl}/order`;
619
+ const data = { symbol, side, size, type, price };
620
+
621
+ if (isPlainObject(opts.meta)) {
622
+ data.meta = opts.meta;
623
+ }
624
+
625
+ if (isNumber(opts.stop)) {
626
+ data.stop = opts.stop;
627
+ }
628
+
629
+ const headers = generateHeaders(
630
+ this.headers,
631
+ this.apiSecret,
632
+ verb,
633
+ path,
634
+ this.apiExpiresAfter,
635
+ data
636
+ );
637
+ return createRequest(verb, `${this.apiUrl}${path}`, headers, { data });
638
+ }
639
+
640
+ /**
641
+ * Cancel a user's specific order
642
+ * @param {string} orderId - The id of the order to be cancelled
643
+ * @return {object} The cancelled order as a JSON object with keys symbol(string), side(string), size(number), type(string), price(number), id(string), created_by(number), and filled(number)
644
+ */
645
+ cancelOrder(orderId) {
646
+ const verb = 'DELETE';
647
+ const path = `${this.baseUrl}/order?order_id=${orderId}`;
648
+ const headers = generateHeaders(
649
+ this.headers,
650
+ this.apiSecret,
651
+ verb,
652
+ path,
653
+ this.apiExpiresAfter
654
+ );
655
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
656
+ }
657
+
658
+ /**
659
+ * Cancel all the user's active orders, can filter by currency pair symbol
660
+ * @param {object} opts - Optional parameters
661
+ * @param {string} opts.symbol - The currency pair symbol to filter by e.g. 'hex-usdt', leave empty to cancel orders of all symbols
662
+ * @return {array} A JSON array of objects containing the cancelled orders
663
+ */
664
+ cancelAllOrders(opts = { symbol: null }) {
665
+ const verb = 'DELETE';
666
+ let path = `${this.baseUrl}/order/all`;
667
+
668
+ if (isString(opts.symbol)) {
669
+ path += `?symbol=${opts.symbol}`;
670
+ }
671
+
672
+ const headers = generateHeaders(
673
+ this.headers,
674
+ this.apiSecret,
675
+ verb,
676
+ path,
677
+ this.apiExpiresAfter
678
+ );
679
+ return createRequest(verb, `${this.apiUrl}${path}`, headers);
680
+ }
681
+
682
+ /**
683
+ * Connect to hollaEx websocket and listen to an event
684
+ * @param {array} events - The events to listen to
685
+ */
686
+ connect(events = []) {
687
+ this.wsReconnect = true;
688
+ this.wsEvents = events;
689
+ this.initialConnection = true;
690
+ let url = this.wsUrl;
691
+ if (this.apiKey && this.apiSecret) {
692
+ const apiExpires = moment().unix() + this.apiExpiresAfter;
693
+ const signature = createSignature(
694
+ this.apiSecret,
695
+ 'CONNECT',
696
+ '/stream',
697
+ apiExpires
698
+ );
699
+ url = `${url}?api-key=${
700
+ this.apiKey
701
+ }&api-signature=${signature}&api-expires=${apiExpires}`;
702
+ }
703
+
704
+ this.ws = new WebSocket(url);
705
+
706
+ if (this.wsEventListeners) {
707
+ this.ws._events = this.wsEventListeners;
708
+ } else {
709
+ this.ws.on('unexpected-response', () => {
710
+ if (this.ws.readyState !== WebSocket.CLOSING) {
711
+ if (this.ws.readyState === WebSocket.OPEN) {
712
+ this.ws.close();
713
+ } else if (this.wsReconnect) {
714
+ this.wsEventListeners = this.ws._events;
715
+ this.ws = null;
716
+ setTimeout(() => {
717
+ this.connect(this.wsEvents);
718
+ }, this.wsReconnectInterval);
719
+ } else {
720
+ this.wsEventListeners = null;
721
+ this.ws = null;
722
+ }
723
+ }
724
+ });
725
+
726
+ this.ws.on('error', () => {
727
+ if (this.ws.readyState !== WebSocket.CLOSING) {
728
+ if (this.ws.readyState === WebSocket.OPEN) {
729
+ this.ws.close();
730
+ } else if (this.wsReconnect) {
731
+ this.wsEventListeners = this.ws._events;
732
+ this.ws = null;
733
+ setTimeout(() => {
734
+ this.connect(this.wsEvents);
735
+ }, this.wsReconnectInterval);
736
+ } else {
737
+ this.wsEventListeners = null;
738
+ this.ws = null;
739
+ }
740
+ }
741
+ });
742
+
743
+ this.ws.on('close', () => {
744
+ if (this.wsReconnect) {
745
+ this.wsEventListeners = this.ws._events;
746
+ this.ws = null;
747
+ setTimeout(() => {
748
+ this.connect(this.wsEvents);
749
+ }, this.wsReconnectInterval);
750
+ } else {
751
+ this.wsEventListeners = null;
752
+ this.ws = null;
753
+ }
754
+ });
755
+
756
+ this.ws.on('open', () => {
757
+ if (this.wsEvents.length > 0) {
758
+ this.subscribe(this.wsEvents);
759
+ }
760
+
761
+ this.initialConnection = false;
762
+
763
+ setWsHeartbeat(this.ws, JSON.stringify({ op: 'ping' }), {
764
+ pingTimeout: 60000,
765
+ pingInterval: 25000
766
+ });
767
+ });
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Disconnect from hollaEx websocket
773
+ */
774
+ disconnect() {
775
+ if (this.wsConnected()) {
776
+ this.wsReconnect = false;
777
+ this.ws.close();
778
+ } else {
779
+ throw new Error('Websocket not connected');
780
+ }
781
+ }
782
+
783
+ /**
784
+ * Subscribe to hollaEx websocket events
785
+ * @param {array} events - The events to listen to
786
+ */
787
+ subscribe(events = []) {
788
+ if (this.wsConnected()) {
789
+ each(events, (event) => {
790
+ if (!this.wsEvents.includes(event) || this.initialConnection) {
791
+ const [topic, symbol] = event.split(':');
792
+ switch (topic) {
793
+ case 'orderbook':
794
+ case 'trade':
795
+ if (symbol) {
796
+ if (!this.wsEvents.includes(topic)) {
797
+ this.ws.send(
798
+ JSON.stringify({
799
+ op: 'subscribe',
800
+ args: [`${topic}:${symbol}`]
801
+ })
802
+ );
803
+ if (!this.initialConnection) {
804
+ this.wsEvents = union(this.wsEvents, [event]);
805
+ }
806
+ }
807
+ } else {
808
+ this.ws.send(
809
+ JSON.stringify({
810
+ op: 'subscribe',
811
+ args: [topic]
812
+ })
813
+ );
814
+ if (!this.initialConnection) {
815
+ this.wsEvents = this.wsEvents.filter(
816
+ (e) => !e.includes(`${topic}:`)
817
+ );
818
+ this.wsEvents = union(this.wsEvents, [event]);
819
+ }
820
+ }
821
+ break;
822
+ case 'order':
823
+ case 'wallet':
824
+ case 'deposit':
825
+ this.ws.send(
826
+ JSON.stringify({
827
+ op: 'subscribe',
828
+ args: [topic]
829
+ })
830
+ );
831
+ if (!this.initialConnection) {
832
+ this.wsEvents = union(this.wsEvents, [event]);
833
+ }
834
+ break;
835
+ default:
836
+ break;
837
+ }
838
+ }
839
+ });
840
+ } else {
841
+ throw new Error('Websocket not connected');
842
+ }
843
+ }
844
+
845
+ /**
846
+ * Unsubscribe to hollaEx websocket events
847
+ * @param {array} events - The events to unsub from
848
+ */
849
+ unsubscribe(events = []) {
850
+ if (this.wsConnected()) {
851
+ each(events, (event) => {
852
+ if (this.wsEvents.includes(event)) {
853
+ const [topic, symbol] = event.split(':');
854
+ switch (topic) {
855
+ case 'orderbook':
856
+ case 'trade':
857
+ if (symbol) {
858
+ this.ws.send(
859
+ JSON.stringify({
860
+ op: 'unsubscribe',
861
+ args: [`${topic}:${symbol}`]
862
+ })
863
+ );
864
+ } else {
865
+ this.ws.send(
866
+ JSON.stringify({
867
+ op: 'unsubscribe',
868
+ args: [topic]
869
+ })
870
+ );
871
+ }
872
+ this.wsEvents = this.wsEvents.filter((e) => e !== event);
873
+ break;
874
+ case 'order':
875
+ case 'wallet':
876
+ case 'deposit':
877
+ this.ws.send(
878
+ JSON.stringify({
879
+ op: 'unsubscribe',
880
+ args: [topic]
881
+ })
882
+ );
883
+ this.wsEvents = this.wsEvents.filter((e) => e !== event);
884
+ break;
885
+ default:
886
+ break;
887
+ }
888
+ }
889
+ });
890
+ } else {
891
+ throw new Error('Websocket not connected');
892
+ }
893
+ }
894
+ }
895
+
896
+ module.exports = HollaExKit;