bybit-api 3.7.1-beta.0 → 3.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +34 -6
  2. package/lib/constants/enum.d.ts +4 -1
  3. package/lib/constants/enum.js +4 -1
  4. package/lib/constants/enum.js.map +1 -1
  5. package/lib/rest-client-v5.d.ts +3 -3
  6. package/lib/rest-client-v5.js.map +1 -1
  7. package/lib/types/request/v5-trade.d.ts +2 -2
  8. package/lib/types/response/v5-market.d.ts +4 -1
  9. package/package.json +1 -13
  10. package/src/account-asset-client-v3.ts +0 -310
  11. package/src/account-asset-client.ts +0 -149
  12. package/src/constants/enum.ts +0 -105
  13. package/src/contract-client.ts +0 -360
  14. package/src/copy-trading-client.ts +0 -159
  15. package/src/index.ts +0 -18
  16. package/src/inverse-client.ts +0 -337
  17. package/src/inverse-futures-client.ts +0 -403
  18. package/src/linear-client.ts +0 -387
  19. package/src/rest-client-v5.ts +0 -1638
  20. package/src/spot-client-v3.ts +0 -303
  21. package/src/spot-client.ts +0 -180
  22. package/src/types/index.ts +0 -5
  23. package/src/types/request/account-asset.ts +0 -258
  24. package/src/types/request/contract.ts +0 -139
  25. package/src/types/request/copy-trading.ts +0 -53
  26. package/src/types/request/index.ts +0 -18
  27. package/src/types/request/inverse.ts +0 -135
  28. package/src/types/request/linear.ts +0 -201
  29. package/src/types/request/spot.ts +0 -89
  30. package/src/types/request/unified-margin.ts +0 -247
  31. package/src/types/request/usdc-options.ts +0 -134
  32. package/src/types/request/usdc-perp.ts +0 -100
  33. package/src/types/request/usdc-shared.ts +0 -39
  34. package/src/types/request/v5-account.ts +0 -40
  35. package/src/types/request/v5-asset.ts +0 -133
  36. package/src/types/request/v5-market.ts +0 -127
  37. package/src/types/request/v5-position.ts +0 -107
  38. package/src/types/request/v5-pre-upgrade.ts +0 -37
  39. package/src/types/request/v5-spot-leverage-token.ts +0 -23
  40. package/src/types/request/v5-trade.ts +0 -133
  41. package/src/types/request/v5-user.ts +0 -21
  42. package/src/types/response/account-asset.ts +0 -236
  43. package/src/types/response/contract.ts +0 -74
  44. package/src/types/response/index.ts +0 -13
  45. package/src/types/response/shared.ts +0 -19
  46. package/src/types/response/spot.ts +0 -17
  47. package/src/types/response/unified-margin.ts +0 -71
  48. package/src/types/response/usdt-perp.ts +0 -58
  49. package/src/types/response/v5-account.ts +0 -122
  50. package/src/types/response/v5-asset.ts +0 -170
  51. package/src/types/response/v5-market.ts +0 -271
  52. package/src/types/response/v5-position.ts +0 -118
  53. package/src/types/response/v5-spot-leverage-token.ts +0 -70
  54. package/src/types/response/v5-trade.ts +0 -103
  55. package/src/types/response/v5-user.ts +0 -58
  56. package/src/types/shared.ts +0 -175
  57. package/src/types/v5-shared.ts +0 -213
  58. package/src/types/websockets.ts +0 -115
  59. package/src/types/ws-events/failed-topic-subscription-confirmation.ts +0 -6
  60. package/src/types/ws-events/succeeded-topic-subscription-confirmation.ts +0 -6
  61. package/src/types/ws-events/topic-subscription-confirmation.ts +0 -7
  62. package/src/unified-margin-client.ts +0 -400
  63. package/src/usdc-option-client.ts +0 -333
  64. package/src/usdc-perpetual-client.ts +0 -312
  65. package/src/util/BaseRestClient.ts +0 -502
  66. package/src/util/WsStore.ts +0 -160
  67. package/src/util/browser-support.ts +0 -29
  68. package/src/util/index.ts +0 -5
  69. package/src/util/logger.ts +0 -26
  70. package/src/util/node-support.ts +0 -9
  71. package/src/util/requestUtils.ts +0 -171
  72. package/src/util/websocket-util.ts +0 -550
  73. package/src/websocket-client.ts +0 -1262
@@ -1,1262 +0,0 @@
1
- /* eslint-disable max-len */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- import { EventEmitter } from 'events';
4
- import WebSocket from 'isomorphic-ws';
5
-
6
- import { InverseClient } from './inverse-client';
7
- import { LinearClient } from './linear-client';
8
- import { SpotClientV3 } from './spot-client-v3';
9
- import { SpotClient } from './spot-client';
10
- import { USDCOptionClient } from './usdc-option-client';
11
- import { USDCPerpetualClient } from './usdc-perpetual-client';
12
- import { UnifiedMarginClient } from './unified-margin-client';
13
- import { ContractClient } from './contract-client';
14
-
15
- import { signMessage } from './util/node-support';
16
- import WsStore from './util/WsStore';
17
-
18
- import {
19
- APIMarket,
20
- CategoryV5,
21
- KlineInterval,
22
- RESTClient,
23
- WSClientConfigurableOptions,
24
- WebsocketClientOptions,
25
- WsKey,
26
- WsTopic,
27
- } from './types';
28
-
29
- import {
30
- DefaultLogger,
31
- PUBLIC_WS_KEYS,
32
- WS_AUTH_ON_CONNECT_KEYS,
33
- WS_KEY_MAP,
34
- WsConnectionStateEnum,
35
- getMaxTopicsPerSubscribeEvent,
36
- getWsKeyForTopic,
37
- getWsUrl,
38
- isPrivateWsTopic,
39
- isTopicSubscriptionConfirmation,
40
- isTopicSubscriptionSuccess,
41
- isWsPong,
42
- neverGuard,
43
- serializeParams,
44
- } from './util';
45
- import { RestClientV5 } from './rest-client-v5';
46
- import { WebsocketTopicSubscriptionConfirmationEvent } from './types/ws-events/topic-subscription-confirmation';
47
-
48
- const loggerCategory = { category: 'bybit-ws' };
49
-
50
- export type WsClientEvent =
51
- | 'open'
52
- | 'update'
53
- | 'close'
54
- | 'error'
55
- | 'reconnect'
56
- | 'reconnected'
57
- | 'response';
58
-
59
- interface WebsocketClientEvents {
60
- /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */
61
- open: (evt: { wsKey: WsKey; event: any }) => void;
62
- /** Reconnecting a dropped connection */
63
- reconnect: (evt: { wsKey: WsKey; event: any }) => void;
64
- /** Successfully reconnected a connection that dropped */
65
- reconnected: (evt: { wsKey: WsKey; event: any }) => void;
66
- /** Connection closed */
67
- close: (evt: { wsKey: WsKey; event: any }) => void;
68
- /** Received reply to websocket command (e.g. after subscribing to topics) */
69
- response: (response: any) => void;
70
- /** Received data for topic */
71
- update: (response: any) => void;
72
- /** Exception from ws client OR custom listeners */
73
- error: (response: any) => void;
74
- }
75
-
76
- type TopicsPendingSubscriptionsResolver = () => void;
77
- type TopicsPendingSubscriptionsRejector = (reason: string) => void;
78
-
79
- interface TopicsPendingSubscriptions {
80
- wsKey: string;
81
- failedTopicsSubscriptions: Set<string>;
82
- pendingTopicsSubscriptions: Set<string>;
83
- resolver: TopicsPendingSubscriptionsResolver;
84
- rejector: TopicsPendingSubscriptionsRejector;
85
- }
86
-
87
- // Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837
88
- export declare interface WebsocketClient {
89
- on<U extends keyof WebsocketClientEvents>(
90
- event: U,
91
- listener: WebsocketClientEvents[U],
92
- ): this;
93
-
94
- emit<U extends keyof WebsocketClientEvents>(
95
- event: U,
96
- ...args: Parameters<WebsocketClientEvents[U]>
97
- ): boolean;
98
- }
99
-
100
- // eslint-disable-next-line no-redeclare
101
- export class WebsocketClient extends EventEmitter {
102
- private logger: typeof DefaultLogger;
103
-
104
- private restClient?: RESTClient;
105
-
106
- private options: WebsocketClientOptions;
107
-
108
- private wsStore: WsStore;
109
-
110
- private pendingTopicsSubscriptions: TopicsPendingSubscriptions[] = [];
111
-
112
- constructor(
113
- options: WSClientConfigurableOptions,
114
- logger?: typeof DefaultLogger,
115
- ) {
116
- super();
117
-
118
- this.logger = logger || DefaultLogger;
119
- this.wsStore = new WsStore(this.logger);
120
-
121
- this.options = {
122
- testnet: false,
123
- pongTimeout: 1000,
124
- pingInterval: 10000,
125
- reconnectTimeout: 500,
126
- fetchTimeOffsetBeforeAuth: false,
127
- ...options,
128
- };
129
- this.options.restOptions = {
130
- ...this.options.restOptions,
131
- testnet: this.options.testnet,
132
- };
133
-
134
- this.prepareRESTClient();
135
-
136
- // add default error handling so this doesn't crash node (if the user didn't set a handler)
137
- // eslint-disable-next-line @typescript-eslint/no-empty-function
138
- this.on('error', () => {});
139
- }
140
-
141
- /** Get the WsStore that tracks websockets & topics */
142
- public getWsStore(): WsStore {
143
- return this.wsStore;
144
- }
145
-
146
- public isTestnet(): boolean {
147
- return this.options.testnet === true;
148
- }
149
-
150
- /**
151
- * Subscribe to V5 topics & track/persist them.
152
- * @param wsTopics - topic or list of topics
153
- * @param category - the API category this topic is for (e.g. "linear"). The value is only important when connecting to public topics and will be ignored for private topics.
154
- * @param isPrivateTopic - optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
155
- */
156
- public subscribeV5(
157
- wsTopics: WsTopic[] | WsTopic,
158
- category: CategoryV5,
159
- isPrivateTopic?: boolean,
160
- ) {
161
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
162
-
163
- return new Promise<void>((resolver, rejector) => {
164
- topics.forEach((topic) => {
165
- const wsKey = getWsKeyForTopic(
166
- this.options.market,
167
- topic,
168
- isPrivateTopic,
169
- category,
170
- );
171
-
172
- // Persist topic for reconnects
173
- this.wsStore.addTopic(wsKey, topic);
174
- this.upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector);
175
-
176
- // if connected, send subscription request
177
- if (
178
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
179
- ) {
180
- return this.requestSubscribeTopics(wsKey, [topic]);
181
- }
182
-
183
- // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
184
- if (
185
- !this.wsStore.isConnectionState(
186
- wsKey,
187
- WsConnectionStateEnum.CONNECTING,
188
- ) &&
189
- !this.wsStore.isConnectionState(
190
- wsKey,
191
- WsConnectionStateEnum.RECONNECTING,
192
- )
193
- ) {
194
- return this.connect(wsKey);
195
- }
196
- });
197
- });
198
- }
199
-
200
- /**
201
- * Subscribe to V1-V3 topics & track/persist them.
202
- *
203
- * Note: for public V5 topics use the `subscribeV5()` method.
204
- *
205
- * Topics will be automatically resubscribed to if the connection resets/drops/reconnects.
206
- * @param wsTopics - topic or list of topics
207
- * @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
208
- */
209
- public subscribe(
210
- wsTopics: WsTopic[] | WsTopic,
211
- isPrivateTopic?: boolean,
212
- ): Promise<void> {
213
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
214
- if (this.options.market === 'v5') {
215
- topics.forEach((topic) => {
216
- if (!isPrivateWsTopic(topic)) {
217
- throw new Error(
218
- 'For public "v5" websocket topics, use the subscribeV5() method & provide the category parameter',
219
- );
220
- }
221
- });
222
- }
223
-
224
- return new Promise<void>((resolver, rejector) => {
225
- topics.forEach((topic) => {
226
- const wsKey = getWsKeyForTopic(
227
- this.options.market,
228
- topic,
229
- isPrivateTopic,
230
- );
231
-
232
- // Persist topic for reconnects
233
- this.wsStore.addTopic(wsKey, topic);
234
- this.upsertPendingTopicsSubscriptions(wsKey, topic, resolver, rejector);
235
-
236
- // if connected, send subscription request
237
- if (
238
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
239
- ) {
240
- return this.requestSubscribeTopics(wsKey, [topic]);
241
- }
242
-
243
- // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect
244
- if (
245
- !this.wsStore.isConnectionState(
246
- wsKey,
247
- WsConnectionStateEnum.CONNECTING,
248
- ) &&
249
- !this.wsStore.isConnectionState(
250
- wsKey,
251
- WsConnectionStateEnum.RECONNECTING,
252
- )
253
- ) {
254
- return this.connect(wsKey);
255
- }
256
- });
257
- });
258
- }
259
-
260
- private upsertPendingTopicsSubscriptions(
261
- wsKey: string,
262
- topic: string,
263
- resolver: TopicsPendingSubscriptionsResolver,
264
- rejector: TopicsPendingSubscriptionsRejector,
265
- ) {
266
- const existingWsKeyPendingSubscriptions =
267
- this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
268
- if (!existingWsKeyPendingSubscriptions) {
269
- this.pendingTopicsSubscriptions.push({
270
- wsKey,
271
- resolver,
272
- rejector,
273
- failedTopicsSubscriptions: new Set(),
274
- pendingTopicsSubscriptions: new Set([topic]),
275
- });
276
- return;
277
- }
278
-
279
- existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.add(topic);
280
- }
281
-
282
- /**
283
- * Unsubscribe from V5 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
284
- * @param wsTopics - topic or list of topics
285
- * @param category - the API category this topic is for (e.g. "linear"). The value is only important when connecting to public topics and will be ignored for private topics.
286
- * @param isPrivateTopic - optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
287
- */
288
- public unsubscribeV5(
289
- wsTopics: WsTopic[] | WsTopic,
290
- category: CategoryV5,
291
- isPrivateTopic?: boolean,
292
- ) {
293
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
294
- topics.forEach((topic) => {
295
- const wsKey = getWsKeyForTopic(
296
- this.options.market,
297
- topic,
298
- isPrivateTopic,
299
- category,
300
- );
301
-
302
- // Remove topic from persistence for reconnects
303
- this.wsStore.deleteTopic(wsKey, topic);
304
- this.removeTopicPendingSubscription(wsKey, topic);
305
-
306
- // unsubscribe request only necessary if active connection exists
307
- if (
308
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
309
- ) {
310
- this.requestUnsubscribeTopics(wsKey, [topic]);
311
- }
312
- });
313
- }
314
-
315
- private removeTopicPendingSubscription(wsKey: string, topic: string) {
316
- const existingWsKeyPendingSubscriptions =
317
- this.pendingTopicsSubscriptions.find((s) => s.wsKey === wsKey);
318
- if (existingWsKeyPendingSubscriptions) {
319
- existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.delete(
320
- topic,
321
- );
322
- if (!existingWsKeyPendingSubscriptions.pendingTopicsSubscriptions.size) {
323
- this.pendingTopicsSubscriptions =
324
- this.pendingTopicsSubscriptions.filter((s) => s.wsKey !== wsKey);
325
- }
326
- }
327
- }
328
-
329
- private clearTopicsPendingSubscriptions(wsKey: string) {
330
- this.pendingTopicsSubscriptions = this.pendingTopicsSubscriptions.filter(
331
- (s) => s.wsKey !== wsKey,
332
- );
333
- }
334
-
335
- /**
336
- * Unsubscribe from V1-V3 topics & remove them from memory. They won't be re-subscribed to if the connection reconnects.
337
- *
338
- * Note: For public V5 topics, use `unsubscribeV5()` instead!
339
- *
340
- * @param wsTopics topic or list of topics
341
- * @param isPrivateTopic optional - the library will try to detect private topics, you can use this to mark a topic as private (if the topic isn't recognised yet)
342
- */
343
- public unsubscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) {
344
- const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics];
345
- if (this.options.market === 'v5') {
346
- topics.forEach((topic) => {
347
- if (!isPrivateWsTopic(topic)) {
348
- throw new Error(
349
- 'For public "v5" websocket topics, use the unsubscribeV5() method & provide the category parameter',
350
- );
351
- }
352
- });
353
- }
354
-
355
- topics.forEach((topic) => {
356
- const wsKey = getWsKeyForTopic(
357
- this.options.market,
358
- topic,
359
- isPrivateTopic,
360
- );
361
-
362
- // Remove topic from persistence for reconnects
363
- this.wsStore.deleteTopic(wsKey, topic);
364
- this.removeTopicPendingSubscription(wsKey, topic);
365
-
366
- // unsubscribe request only necessary if active connection exists
367
- if (
368
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED)
369
- ) {
370
- this.requestUnsubscribeTopics(wsKey, [topic]);
371
- }
372
- });
373
- }
374
-
375
- /**
376
- * @private Only used if we fetch exchange time before attempting auth. Disabled by default.
377
- * I've removed this for ftx and it's working great, tempted to remove this here
378
- */
379
- private prepareRESTClient(): void {
380
- switch (this.options.market) {
381
- case 'inverse': {
382
- this.restClient = new InverseClient(
383
- this.options.restOptions,
384
- this.options.requestOptions,
385
- );
386
- break;
387
- }
388
- case 'linear': {
389
- this.restClient = new LinearClient(
390
- this.options.restOptions,
391
- this.options.requestOptions,
392
- );
393
- break;
394
- }
395
- case 'spot': {
396
- this.restClient = new SpotClient(
397
- this.options.restOptions,
398
- this.options.requestOptions,
399
- );
400
- this.connectPublic();
401
- break;
402
- }
403
- case 'spotv3': {
404
- this.restClient = new SpotClientV3(
405
- this.options.restOptions,
406
- this.options.requestOptions,
407
- );
408
- break;
409
- }
410
- case 'usdcOption': {
411
- this.restClient = new USDCOptionClient(
412
- this.options.restOptions,
413
- this.options.requestOptions,
414
- );
415
- break;
416
- }
417
- case 'usdcPerp': {
418
- this.restClient = new USDCPerpetualClient(
419
- this.options.restOptions,
420
- this.options.requestOptions,
421
- );
422
- break;
423
- }
424
- case 'unifiedOption':
425
- case 'unifiedPerp': {
426
- this.restClient = new UnifiedMarginClient(
427
- this.options.restOptions,
428
- this.options.requestOptions,
429
- );
430
- break;
431
- }
432
- case 'contractInverse':
433
- case 'contractUSDT': {
434
- this.restClient = new ContractClient(
435
- this.options.restOptions,
436
- this.options.requestOptions,
437
- );
438
- break;
439
- }
440
- case 'v5': {
441
- this.restClient = new RestClientV5(
442
- this.options.restOptions,
443
- this.options.requestOptions,
444
- );
445
- break;
446
- }
447
- default: {
448
- throw neverGuard(
449
- this.options.market,
450
- 'prepareRESTClient(): Unhandled market',
451
- );
452
- }
453
- }
454
- }
455
-
456
- public close(wsKey: WsKey, force?: boolean) {
457
- this.logger.info('Closing connection', { ...loggerCategory, wsKey });
458
- this.setWsState(wsKey, WsConnectionStateEnum.CLOSING);
459
- this.clearTimers(wsKey);
460
-
461
- const ws = this.getWs(wsKey);
462
- ws?.close();
463
- if (force) {
464
- ws?.terminate();
465
- }
466
- }
467
-
468
- public closeAll(force?: boolean) {
469
- const keys = this.wsStore.getKeys();
470
- this.logger.info(`Closing all ws connections: ${keys}`);
471
- keys.forEach((key) => {
472
- this.close(key, force);
473
- });
474
- }
475
-
476
- /**
477
- * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library
478
- */
479
- public connectAll(): Promise<WebSocket | undefined>[] {
480
- switch (this.options.market) {
481
- case 'inverse': {
482
- // only one for inverse
483
- return [...this.connectPublic()];
484
- }
485
- // these all have separate public & private ws endpoints
486
- case 'linear':
487
- case 'spot':
488
- case 'spotv3':
489
- case 'usdcOption':
490
- case 'usdcPerp':
491
- case 'unifiedPerp':
492
- case 'unifiedOption':
493
- case 'contractUSDT':
494
- case 'contractInverse': {
495
- return [...this.connectPublic(), this.connectPrivate()];
496
- }
497
- case 'v5': {
498
- return [this.connectPrivate()];
499
- }
500
- default: {
501
- throw neverGuard(this.options.market, 'connectAll(): Unhandled market');
502
- }
503
- }
504
- }
505
-
506
- public connectPublic(): Promise<WebSocket | undefined>[] {
507
- switch (this.options.market) {
508
- case 'inverse': {
509
- return [this.connect(WS_KEY_MAP.inverse)];
510
- }
511
- case 'linear': {
512
- return [this.connect(WS_KEY_MAP.linearPublic)];
513
- }
514
- case 'spot': {
515
- return [this.connect(WS_KEY_MAP.spotPublic)];
516
- }
517
- case 'spotv3': {
518
- return [this.connect(WS_KEY_MAP.spotV3Public)];
519
- }
520
- case 'usdcOption': {
521
- return [this.connect(WS_KEY_MAP.usdcOptionPublic)];
522
- }
523
- case 'usdcPerp': {
524
- return [this.connect(WS_KEY_MAP.usdcPerpPublic)];
525
- }
526
- case 'unifiedOption': {
527
- return [this.connect(WS_KEY_MAP.unifiedOptionPublic)];
528
- }
529
- case 'unifiedPerp': {
530
- return [
531
- this.connect(WS_KEY_MAP.unifiedPerpUSDTPublic),
532
- this.connect(WS_KEY_MAP.unifiedPerpUSDCPublic),
533
- ];
534
- }
535
- case 'contractUSDT':
536
- return [this.connect(WS_KEY_MAP.contractUSDTPublic)];
537
- case 'contractInverse':
538
- return [this.connect(WS_KEY_MAP.contractInversePublic)];
539
- case 'v5': {
540
- return [
541
- this.connect(WS_KEY_MAP.v5SpotPublic),
542
- this.connect(WS_KEY_MAP.v5LinearPublic),
543
- this.connect(WS_KEY_MAP.v5InversePublic),
544
- this.connect(WS_KEY_MAP.v5OptionPublic),
545
- ];
546
- }
547
- default: {
548
- throw neverGuard(
549
- this.options.market,
550
- 'connectPublic(): Unhandled market',
551
- );
552
- }
553
- }
554
- }
555
-
556
- public connectPrivate(): Promise<WebSocket | undefined> {
557
- switch (this.options.market) {
558
- case 'inverse': {
559
- return this.connect(WS_KEY_MAP.inverse);
560
- }
561
- case 'linear': {
562
- return this.connect(WS_KEY_MAP.linearPrivate);
563
- }
564
- case 'spot': {
565
- return this.connect(WS_KEY_MAP.spotPrivate);
566
- }
567
- case 'spotv3': {
568
- return this.connect(WS_KEY_MAP.spotV3Private);
569
- }
570
- case 'usdcOption': {
571
- return this.connect(WS_KEY_MAP.usdcOptionPrivate);
572
- }
573
- case 'usdcPerp': {
574
- return this.connect(WS_KEY_MAP.usdcPerpPrivate);
575
- }
576
- case 'unifiedPerp':
577
- case 'unifiedOption': {
578
- return this.connect(WS_KEY_MAP.unifiedPrivate);
579
- }
580
- case 'contractUSDT':
581
- return this.connect(WS_KEY_MAP.contractUSDTPrivate);
582
- case 'contractInverse':
583
- return this.connect(WS_KEY_MAP.contractInversePrivate);
584
- case 'v5': {
585
- return this.connect(WS_KEY_MAP.v5Private);
586
- }
587
- default: {
588
- throw neverGuard(
589
- this.options.market,
590
- 'connectPrivate(): Unhandled market',
591
- );
592
- }
593
- }
594
- }
595
-
596
- private async connect(wsKey: WsKey): Promise<WebSocket | undefined> {
597
- try {
598
- if (this.wsStore.isWsOpen(wsKey)) {
599
- this.logger.error(
600
- 'Refused to connect to ws with existing active connection',
601
- { ...loggerCategory, wsKey },
602
- );
603
- return this.wsStore.getWs(wsKey);
604
- }
605
-
606
- if (
607
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTING)
608
- ) {
609
- this.logger.error(
610
- 'Refused to connect to ws, connection attempt already active',
611
- { ...loggerCategory, wsKey },
612
- );
613
- return;
614
- }
615
-
616
- if (
617
- !this.wsStore.getConnectionState(wsKey) ||
618
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.INITIAL)
619
- ) {
620
- this.setWsState(wsKey, WsConnectionStateEnum.CONNECTING);
621
- }
622
-
623
- const authParams = await this.getAuthParams(wsKey);
624
- const url = getWsUrl(
625
- wsKey,
626
- this.options.wsUrl,
627
- this.isTestnet(),
628
- this.logger,
629
- );
630
- const ws = this.connectToWsUrl(url + authParams, wsKey);
631
-
632
- return this.wsStore.setWs(wsKey, ws);
633
- } catch (err) {
634
- this.parseWsError('Connection failed', err, wsKey);
635
- this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
636
- }
637
- }
638
-
639
- private parseWsError(context: string, error: any, wsKey: WsKey) {
640
- if (!error.message) {
641
- this.logger.error(`${context} due to unexpected error: `, error);
642
- this.emit('error', error);
643
- return;
644
- }
645
-
646
- switch (error.message) {
647
- case 'Unexpected server response: 401':
648
- this.logger.error(`${context} due to 401 authorization failure.`, {
649
- ...loggerCategory,
650
- wsKey,
651
- });
652
- break;
653
-
654
- default:
655
- if (
656
- this.wsStore.getConnectionState(wsKey) !==
657
- WsConnectionStateEnum.CLOSING
658
- ) {
659
- this.logger.error(
660
- `${context} due to unexpected response error: "${
661
- error?.msg || error?.message || error
662
- }"`,
663
- { ...loggerCategory, wsKey, error },
664
- );
665
- this.executeReconnectableClose(wsKey, 'unhandled onWsError');
666
- } else {
667
- this.logger.info(
668
- `${wsKey} socket forcefully closed. Will not reconnect.`,
669
- );
670
- }
671
- break;
672
- }
673
- this.emit('error', error);
674
- }
675
-
676
- /**
677
- * Return params required to make authorized request
678
- */
679
- private async getAuthParams(wsKey: WsKey): Promise<string> {
680
- if (PUBLIC_WS_KEYS.includes(wsKey)) {
681
- this.logger.debug('Starting public only websocket client.', {
682
- ...loggerCategory,
683
- wsKey,
684
- });
685
- return '';
686
- }
687
-
688
- try {
689
- const { signature, expiresAt } = await this.getWsAuthSignature(wsKey);
690
-
691
- const authParams = {
692
- api_key: this.options.key,
693
- expires: expiresAt,
694
- signature,
695
- };
696
-
697
- return '?' + serializeParams(authParams);
698
- } catch (e) {
699
- this.logger.error(e, { ...loggerCategory, wsKey });
700
- return '';
701
- }
702
- }
703
-
704
- private async sendAuthRequest(wsKey: WsKey): Promise<void> {
705
- try {
706
- const { signature, expiresAt } = await this.getWsAuthSignature(wsKey);
707
-
708
- const request = {
709
- op: 'auth',
710
- args: [this.options.key, expiresAt, signature],
711
- req_id: `${wsKey}-auth`,
712
- };
713
-
714
- return this.tryWsSend(wsKey, JSON.stringify(request));
715
- } catch (e) {
716
- this.logger.error(e, { ...loggerCategory, wsKey });
717
- }
718
- }
719
-
720
- private async getWsAuthSignature(
721
- wsKey: WsKey,
722
- ): Promise<{ expiresAt: number; signature: string }> {
723
- const { key, secret } = this.options;
724
-
725
- if (!key || !secret) {
726
- this.logger.warning(
727
- 'Cannot authenticate websocket, either api or private keys missing.',
728
- { ...loggerCategory, wsKey },
729
- );
730
- throw new Error('Cannot auth - missing api or secret in config');
731
- }
732
-
733
- this.logger.debug("Getting auth'd request params", {
734
- ...loggerCategory,
735
- wsKey,
736
- });
737
-
738
- const timeOffset = this.options.fetchTimeOffsetBeforeAuth
739
- ? (await this.restClient?.fetchTimeOffset()) || 0
740
- : 0;
741
-
742
- const signatureExpiresAt = Date.now() + timeOffset + 5000;
743
-
744
- const signature = await signMessage(
745
- 'GET/realtime' + signatureExpiresAt,
746
- secret,
747
- );
748
-
749
- return {
750
- expiresAt: signatureExpiresAt,
751
- signature,
752
- };
753
- }
754
-
755
- private reconnectWithDelay(wsKey: WsKey, connectionDelayMs: number) {
756
- this.clearTimers(wsKey);
757
-
758
- if (
759
- this.wsStore.getConnectionState(wsKey) !==
760
- WsConnectionStateEnum.CONNECTING
761
- ) {
762
- this.setWsState(wsKey, WsConnectionStateEnum.RECONNECTING);
763
- }
764
-
765
- if (this.wsStore.get(wsKey)?.activeReconnectTimer) {
766
- this.clearReconnectTimer(wsKey);
767
- }
768
-
769
- this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
770
- this.logger.info('Reconnecting to websocket', {
771
- ...loggerCategory,
772
- wsKey,
773
- });
774
- this.clearReconnectTimer(wsKey);
775
- this.connect(wsKey);
776
- }, connectionDelayMs);
777
- }
778
-
779
- private ping(wsKey: WsKey) {
780
- if (this.wsStore.get(wsKey, true).activePongTimer) {
781
- return;
782
- }
783
-
784
- this.clearPongTimer(wsKey);
785
-
786
- this.logger.silly('Sending ping', { ...loggerCategory, wsKey });
787
- this.tryWsSend(wsKey, JSON.stringify({ op: 'ping' }));
788
-
789
- this.wsStore.get(wsKey, true).activePongTimer = setTimeout(
790
- () => this.executeReconnectableClose(wsKey, 'Pong timeout'),
791
- this.options.pongTimeout,
792
- );
793
- }
794
-
795
- /**
796
- * Closes a connection, if it's even open. If open, this will trigger a reconnect asynchronously.
797
- * If closed, trigger a reconnect immediately
798
- */
799
- private executeReconnectableClose(wsKey: WsKey, reason: string) {
800
- this.logger.info(`${reason} - closing socket to reconnect`, {
801
- ...loggerCategory,
802
- wsKey,
803
- reason,
804
- });
805
-
806
- const wasOpen = this.wsStore.isWsOpen(wsKey);
807
-
808
- this.getWs(wsKey)?.terminate();
809
- delete this.wsStore.get(wsKey, true).activePongTimer;
810
- this.clearPingTimer(wsKey);
811
- this.clearPongTimer(wsKey);
812
-
813
- if (!wasOpen) {
814
- this.logger.info(
815
- `${reason} - socket already closed - trigger immediate reconnect`,
816
- {
817
- ...loggerCategory,
818
- wsKey,
819
- reason,
820
- },
821
- );
822
- this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
823
- }
824
- }
825
-
826
- private clearTimers(wsKey: WsKey) {
827
- this.clearPingTimer(wsKey);
828
- this.clearPongTimer(wsKey);
829
- this.clearReconnectTimer(wsKey);
830
- }
831
-
832
- // Send a ping at intervals
833
- private clearPingTimer(wsKey: WsKey) {
834
- const wsState = this.wsStore.get(wsKey);
835
- if (wsState?.activePingTimer) {
836
- clearInterval(wsState.activePingTimer);
837
- wsState.activePingTimer = undefined;
838
- }
839
- }
840
-
841
- // Expect a pong within a time limit
842
- private clearPongTimer(wsKey: WsKey) {
843
- const wsState = this.wsStore.get(wsKey);
844
- if (wsState?.activePongTimer) {
845
- clearTimeout(wsState.activePongTimer);
846
- wsState.activePongTimer = undefined;
847
- }
848
- }
849
-
850
- private clearReconnectTimer(wsKey: WsKey) {
851
- const wsState = this.wsStore.get(wsKey);
852
- if (wsState?.activeReconnectTimer) {
853
- clearTimeout(wsState.activeReconnectTimer);
854
- wsState.activeReconnectTimer = undefined;
855
- }
856
- }
857
-
858
- /**
859
- * @private Use the `subscribe(topics)` method to subscribe to topics. Send WS message to subscribe to topics.
860
- */
861
- private requestSubscribeTopics(wsKey: WsKey, topics: string[]) {
862
- if (!topics.length) {
863
- return;
864
- }
865
-
866
- const maxTopicsPerEvent = getMaxTopicsPerSubscribeEvent(
867
- this.options.market,
868
- wsKey,
869
- );
870
- if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) {
871
- this.logger.silly(
872
- `Subscribing to topics in batches of ${maxTopicsPerEvent}`,
873
- );
874
- for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
875
- const batch = topics.slice(i, i + maxTopicsPerEvent);
876
- this.logger.silly(`Subscribing to batch of ${batch.length}`);
877
- this.requestSubscribeTopics(wsKey, batch);
878
- }
879
- this.logger.silly(
880
- `Finished batch subscribing to ${topics.length} topics`,
881
- );
882
- return;
883
- }
884
-
885
- const wsMessage = JSON.stringify({
886
- req_id: topics.join(','),
887
- op: 'subscribe',
888
- args: topics,
889
- });
890
-
891
- this.tryWsSend(wsKey, wsMessage);
892
- }
893
-
894
- /**
895
- * @private Use the `unsubscribe(topics)` method to unsubscribe from topics. Send WS message to unsubscribe from topics.
896
- */
897
- private requestUnsubscribeTopics(wsKey: WsKey, topics: string[]) {
898
- if (!topics.length) {
899
- return;
900
- }
901
-
902
- const maxTopicsPerEvent = getMaxTopicsPerSubscribeEvent(
903
- this.options.market,
904
- wsKey,
905
- );
906
- if (maxTopicsPerEvent && topics.length > maxTopicsPerEvent) {
907
- this.logger.silly(
908
- `Unsubscribing to topics in batches of ${maxTopicsPerEvent}`,
909
- );
910
- for (let i = 0; i < topics.length; i += maxTopicsPerEvent) {
911
- const batch = topics.slice(i, i + maxTopicsPerEvent);
912
- this.logger.silly(`Unsubscribing to batch of ${batch.length}`);
913
- this.requestUnsubscribeTopics(wsKey, batch);
914
- }
915
- this.logger.silly(
916
- `Finished batch unsubscribing to ${topics.length} topics`,
917
- );
918
- return;
919
- }
920
-
921
- const wsMessage = JSON.stringify({
922
- op: 'unsubscribe',
923
- args: topics,
924
- });
925
-
926
- this.tryWsSend(wsKey, wsMessage);
927
- }
928
-
929
- public tryWsSend(wsKey: WsKey, wsMessage: string) {
930
- try {
931
- this.logger.silly('Sending upstream ws message: ', {
932
- ...loggerCategory,
933
- wsMessage,
934
- wsKey,
935
- });
936
- if (!wsKey) {
937
- throw new Error(
938
- 'Cannot send message due to no known websocket for this wsKey',
939
- );
940
- }
941
- const ws = this.getWs(wsKey);
942
- if (!ws) {
943
- throw new Error(
944
- `${wsKey} socket not connected yet, call "connect(${wsKey}) first then try again when the "open" event arrives`,
945
- );
946
- }
947
- ws.send(wsMessage);
948
- } catch (e) {
949
- this.logger.error('Failed to send WS message', {
950
- ...loggerCategory,
951
- wsMessage,
952
- wsKey,
953
- exception: e,
954
- });
955
- }
956
- }
957
-
958
- private connectToWsUrl(url: string, wsKey: WsKey): WebSocket {
959
- this.logger.silly(`Opening WS connection to URL: ${url}`, {
960
- ...loggerCategory,
961
- wsKey,
962
- });
963
-
964
- const agent = this.options.requestOptions?.agent;
965
- const ws = new WebSocket(url, undefined, agent ? { agent } : undefined);
966
- ws.onopen = (event) => this.onWsOpen(event, wsKey);
967
- ws.onmessage = (event) => this.onWsMessage(event, wsKey);
968
- ws.onerror = (event) =>
969
- this.parseWsError('Websocket onWsError', event, wsKey);
970
- ws.onclose = (event) => this.onWsClose(event, wsKey);
971
-
972
- return ws;
973
- }
974
-
975
- private async onWsOpen(event, wsKey: WsKey) {
976
- if (
977
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTING)
978
- ) {
979
- this.logger.info('Websocket connected', {
980
- ...loggerCategory,
981
- wsKey,
982
- testnet: this.isTestnet(),
983
- market: this.options.market,
984
- });
985
- this.emit('open', { wsKey, event });
986
- } else if (
987
- this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.RECONNECTING)
988
- ) {
989
- this.logger.info('Websocket reconnected', { ...loggerCategory, wsKey });
990
- this.emit('reconnected', { wsKey, event });
991
- }
992
-
993
- this.setWsState(wsKey, WsConnectionStateEnum.CONNECTED);
994
-
995
- // Some websockets require an auth packet to be sent after opening the connection
996
- if (WS_AUTH_ON_CONNECT_KEYS.includes(wsKey)) {
997
- this.logger.info('Sending auth request...');
998
- await this.sendAuthRequest(wsKey);
999
- }
1000
-
1001
- // TODO: persistence not working yet for spot v1 topics
1002
- if (wsKey !== WS_KEY_MAP.spotPublic && wsKey !== WS_KEY_MAP.spotPrivate) {
1003
- const topics = [...this.wsStore.getTopics(wsKey)];
1004
- this.logger.info('Subscribing to topics', {
1005
- ...loggerCategory,
1006
- wsKey,
1007
- topics,
1008
- });
1009
- this.requestSubscribeTopics(wsKey, topics);
1010
- }
1011
-
1012
- this.wsStore.get(wsKey, true)!.activePingTimer = setInterval(
1013
- () => this.ping(wsKey),
1014
- this.options.pingInterval,
1015
- );
1016
- }
1017
-
1018
- private onWsMessage(event, wsKey: WsKey) {
1019
- try {
1020
- // any message can clear the pong timer - wouldn't get a message if the ws dropped
1021
- this.clearPongTimer(wsKey);
1022
-
1023
- const msg = JSON.parse((event && event.data) || event);
1024
- // this.logger.silly('Received event', {
1025
- // ...loggerCategory,
1026
- // wsKey,
1027
- // msg: JSON.stringify(msg),
1028
- // });
1029
-
1030
- if (isTopicSubscriptionConfirmation(msg)) {
1031
- this.updatePendingTopicSubscriptionStatus(wsKey, msg);
1032
- }
1033
-
1034
- // TODO: cleanme
1035
- if (msg['success'] || msg?.pong || isWsPong(msg)) {
1036
- if (isWsPong(msg)) {
1037
- this.logger.silly('Received pong', { ...loggerCategory, wsKey });
1038
- } else {
1039
- this.emit('response', { ...msg, wsKey });
1040
- }
1041
- return;
1042
- }
1043
-
1044
- if (msg['finalFragment']) {
1045
- return this.emit('response', { ...msg, wsKey });
1046
- }
1047
- if (msg?.topic) {
1048
- return this.emit('update', { ...msg, wsKey });
1049
- }
1050
-
1051
- if (
1052
- // spot v1
1053
- msg?.code ||
1054
- // spot v3
1055
- msg?.type === 'error' ||
1056
- // usdc options
1057
- msg?.success === false
1058
- ) {
1059
- return this.emit('error', { ...msg, wsKey });
1060
- }
1061
-
1062
- this.logger.warning('Unhandled/unrecognised ws event message', {
1063
- ...loggerCategory,
1064
- message: msg,
1065
- event,
1066
- wsKey,
1067
- });
1068
- } catch (e) {
1069
- this.logger.error('Failed to parse ws event message', {
1070
- ...loggerCategory,
1071
- error: e,
1072
- event,
1073
- wsKey,
1074
- });
1075
- }
1076
- }
1077
-
1078
- private updatePendingTopicSubscriptionStatus(
1079
- wsKey: string,
1080
- msg: WebsocketTopicSubscriptionConfirmationEvent,
1081
- ) {
1082
- const requestsIds = msg.req_id as string;
1083
- const pendingTopicsSubscriptions = this.pendingTopicsSubscriptions.find(
1084
- (s) => s.wsKey === wsKey,
1085
- );
1086
-
1087
- if (!pendingTopicsSubscriptions) return;
1088
-
1089
- const splitRequestsIds = requestsIds.split(',');
1090
- if (!isTopicSubscriptionSuccess(msg)) {
1091
- splitRequestsIds.forEach((req_id) =>
1092
- pendingTopicsSubscriptions.failedTopicsSubscriptions.add(req_id),
1093
- );
1094
- }
1095
-
1096
- splitRequestsIds.forEach((req_id) => {
1097
- this.removeTopicPendingSubscription(wsKey, req_id);
1098
-
1099
- if (
1100
- !pendingTopicsSubscriptions.pendingTopicsSubscriptions.size &&
1101
- !pendingTopicsSubscriptions.failedTopicsSubscriptions.size
1102
- ) {
1103
- // all topics have been subscribed successfully, so we can resolve the subscription request
1104
- pendingTopicsSubscriptions.resolver();
1105
- this.clearTopicsPendingSubscriptions(wsKey);
1106
- }
1107
-
1108
- if (
1109
- !pendingTopicsSubscriptions.pendingTopicsSubscriptions.size &&
1110
- pendingTopicsSubscriptions.failedTopicsSubscriptions.size
1111
- ) {
1112
- // not all topics have been subscribed successfully, so we reject the subscription request
1113
- // and let the caller handle the situation by providing the list of failed subscriptions requests
1114
- const failedSubscriptionsMessage = `(${[
1115
- ...pendingTopicsSubscriptions.failedTopicsSubscriptions,
1116
- ].toString()}) failed to subscribe`;
1117
- pendingTopicsSubscriptions.rejector(failedSubscriptionsMessage);
1118
- this.clearTopicsPendingSubscriptions(wsKey);
1119
- }
1120
- });
1121
- }
1122
-
1123
- private onWsClose(event, wsKey: WsKey) {
1124
- this.logger.info('Websocket connection closed', {
1125
- ...loggerCategory,
1126
- wsKey,
1127
- });
1128
-
1129
- if (
1130
- this.wsStore.getConnectionState(wsKey) !== WsConnectionStateEnum.CLOSING
1131
- ) {
1132
- this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
1133
- this.emit('reconnect', { wsKey, event });
1134
- } else {
1135
- this.setWsState(wsKey, WsConnectionStateEnum.INITIAL);
1136
- this.emit('close', { wsKey, event });
1137
- }
1138
- }
1139
-
1140
- private getWs(wsKey: WsKey) {
1141
- return this.wsStore.getWs(wsKey);
1142
- }
1143
-
1144
- private setWsState(wsKey: WsKey, state: WsConnectionStateEnum) {
1145
- this.wsStore.setConnectionState(wsKey, state);
1146
- }
1147
-
1148
- private wrongMarketError(market: APIMarket) {
1149
- return new Error(
1150
- `This WS client was instanced for the ${this.options.market} market. Make another WebsocketClient instance with "market: '${market}'" to listen to ${market} topics`,
1151
- );
1152
- }
1153
-
1154
- /** @deprecated use "market: 'spotv3" client */
1155
- public subscribePublicSpotTrades(symbol: string, binary?: boolean) {
1156
- if (this.options.market !== 'spot') {
1157
- throw this.wrongMarketError('spot');
1158
- }
1159
-
1160
- return this.tryWsSend(
1161
- WS_KEY_MAP.spotPublic,
1162
- JSON.stringify({
1163
- topic: 'trade',
1164
- event: 'sub',
1165
- symbol,
1166
- params: {
1167
- binary: !!binary,
1168
- },
1169
- }),
1170
- );
1171
- }
1172
-
1173
- /** @deprecated use "market: 'spotv3" client */
1174
- public subscribePublicSpotTradingPair(symbol: string, binary?: boolean) {
1175
- if (this.options.market !== 'spot') {
1176
- throw this.wrongMarketError('spot');
1177
- }
1178
-
1179
- return this.tryWsSend(
1180
- WS_KEY_MAP.spotPublic,
1181
- JSON.stringify({
1182
- symbol,
1183
- topic: 'realtimes',
1184
- event: 'sub',
1185
- params: {
1186
- binary: !!binary,
1187
- },
1188
- }),
1189
- );
1190
- }
1191
-
1192
- /** @deprecated use "market: 'spotv3" client */
1193
- public subscribePublicSpotV1Kline(
1194
- symbol: string,
1195
- candleSize: KlineInterval,
1196
- binary?: boolean,
1197
- ) {
1198
- if (this.options.market !== 'spot') {
1199
- throw this.wrongMarketError('spot');
1200
- }
1201
-
1202
- return this.tryWsSend(
1203
- WS_KEY_MAP.spotPublic,
1204
- JSON.stringify({
1205
- symbol,
1206
- topic: 'kline_' + candleSize,
1207
- event: 'sub',
1208
- params: {
1209
- binary: !!binary,
1210
- },
1211
- }),
1212
- );
1213
- }
1214
-
1215
- //ws.send('{"symbol":"BTCUSDT","topic":"depth","event":"sub","params":{"binary":false}}');
1216
- //ws.send('{"symbol":"BTCUSDT","topic":"mergedDepth","event":"sub","params":{"binary":false,"dumpScale":1}}');
1217
- //ws.send('{"symbol":"BTCUSDT","topic":"diffDepth","event":"sub","params":{"binary":false}}');
1218
-
1219
- /** @deprecated use "market: 'spotv3" client */
1220
- public subscribePublicSpotOrderbook(
1221
- symbol: string,
1222
- depth: 'full' | 'merge' | 'delta',
1223
- dumpScale?: number,
1224
- binary?: boolean,
1225
- ) {
1226
- if (this.options.market !== 'spot') {
1227
- throw this.wrongMarketError('spot');
1228
- }
1229
-
1230
- let topic: string;
1231
- switch (depth) {
1232
- case 'full': {
1233
- topic = 'depth';
1234
- break;
1235
- }
1236
- case 'merge': {
1237
- topic = 'mergedDepth';
1238
- if (!dumpScale) {
1239
- throw new Error('Dumpscale must be provided for merged orderbooks');
1240
- }
1241
- break;
1242
- }
1243
- case 'delta': {
1244
- topic = 'diffDepth';
1245
- break;
1246
- }
1247
- }
1248
-
1249
- const msg: any = {
1250
- symbol,
1251
- topic,
1252
- event: 'sub',
1253
- params: {
1254
- binary: !!binary,
1255
- },
1256
- };
1257
- if (dumpScale) {
1258
- msg.params.dumpScale = dumpScale;
1259
- }
1260
- return this.tryWsSend(WS_KEY_MAP.spotPublic, JSON.stringify(msg));
1261
- }
1262
- }