@lifi/perps-sdk-provider-hyperliquid 1.1.0 → 1.2.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/dist/cjs/types/ws.d.ts +33 -0
- package/dist/cjs/types/ws.d.ts.map +1 -1
- package/dist/cjs/utils/decodeFastAssetCtxs.d.ts +1 -0
- package/dist/cjs/utils/decodeFastAssetCtxs.d.ts.map +1 -1
- package/dist/cjs/utils/decodeFastAssetCtxs.js +5 -1
- package/dist/cjs/utils/decodeFastAssetCtxs.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +2 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +13 -0
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.js +206 -22
- package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/esm/types/ws.d.ts +37 -0
- package/dist/esm/types/ws.d.ts.map +1 -1
- package/dist/esm/utils/decodeFastAssetCtxs.d.ts +1 -0
- package/dist/esm/utils/decodeFastAssetCtxs.d.ts.map +1 -1
- package/dist/esm/utils/decodeFastAssetCtxs.js +9 -6
- package/dist/esm/utils/decodeFastAssetCtxs.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts +13 -0
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.js +208 -27
- package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/types/types/ws.d.ts +37 -0
- package/dist/types/types/ws.d.ts.map +1 -1
- package/dist/types/utils/decodeFastAssetCtxs.d.ts +1 -0
- package/dist/types/utils/decodeFastAssetCtxs.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/websocket/HyperliquidWsProvider.d.ts +13 -0
- package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/types/ws.ts +35 -0
- package/src/utils/decodeFastAssetCtxs.ts +10 -6
- package/src/utils/index.ts +4 -1
- package/src/websocket/HyperliquidWsProvider.ts +271 -29
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
type MarketContext,
|
|
16
16
|
type OpenOrder,
|
|
17
|
+
type OrderbookLevel,
|
|
18
|
+
type OrderbookResponse,
|
|
17
19
|
OrderSide,
|
|
18
20
|
OrderType,
|
|
19
21
|
type Subscription,
|
|
@@ -27,14 +29,18 @@ import type {
|
|
|
27
29
|
HlWsAllDexsAssetCtxsData,
|
|
28
30
|
HlWsAllDexsClearinghouseStateData,
|
|
29
31
|
HlWsCandleData,
|
|
32
|
+
HlWsCompressedL2Data,
|
|
30
33
|
HlWsFastAssetCtx,
|
|
31
34
|
HlWsL2BookData,
|
|
35
|
+
HlWsL2Data,
|
|
32
36
|
HlWsMessage,
|
|
33
37
|
HlWsPerpAssetCtx,
|
|
34
38
|
HlWsSpotStateData,
|
|
39
|
+
HlWsTrade,
|
|
35
40
|
HlWsUserFillsData,
|
|
36
41
|
} from '../types/index.js'
|
|
37
42
|
import {
|
|
43
|
+
decodeCompressedJson,
|
|
38
44
|
decodeFastAssetCtxs,
|
|
39
45
|
isOpenAssetPosition,
|
|
40
46
|
isTriggerOrder,
|
|
@@ -49,7 +55,7 @@ import {
|
|
|
49
55
|
spotPriceById,
|
|
50
56
|
} from '../utils/index.js'
|
|
51
57
|
|
|
52
|
-
/** HL's `
|
|
58
|
+
/** HL's compact `l2` snapshot carries 20 levels per side. */
|
|
53
59
|
const HL_L2_BOOK_MAX_LEVELS_PER_SIDE = 20
|
|
54
60
|
|
|
55
61
|
/**
|
|
@@ -85,6 +91,8 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
85
91
|
private readonly client: PerpsSDKClient | undefined
|
|
86
92
|
private readonly registry: MarketRegistry | undefined
|
|
87
93
|
private perpCtxBySubDex = new Map<string, Record<string, HlWsPerpAssetCtx>>()
|
|
94
|
+
private orderbookKeysByMarketId = new Map<string, Set<string>>()
|
|
95
|
+
private latestOrderbookByMarketId = new Map<string, OrderbookResponse>()
|
|
88
96
|
// Latest mid/mark per `Market.id` from the high-frequency `fastAssetCtxs`
|
|
89
97
|
// feed (all dexes, incl. builder/sub-dex coins). Merged incrementally —
|
|
90
98
|
// frames carry only changed coins — and overlaid onto the slower
|
|
@@ -93,6 +101,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
93
101
|
// `fastAssetCtxs` payloads are base64 + raw-DEFLATE; decode is async, so
|
|
94
102
|
// chain decodes to apply incremental frames in arrival order.
|
|
95
103
|
private fastDecodeChain: Promise<void> = Promise.resolve()
|
|
104
|
+
private orderbookDecodeChain: Promise<void> = Promise.resolve()
|
|
96
105
|
|
|
97
106
|
constructor(wsUrl: string, providerKey: string, client?: PerpsSDKClient) {
|
|
98
107
|
super(
|
|
@@ -159,8 +168,14 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
159
168
|
|
|
160
169
|
// All other channels: single WS subscription per key
|
|
161
170
|
const key = this.toKey(sub)
|
|
171
|
+
if (sub.channel === 'orderbook') {
|
|
172
|
+
this.claimOrderbookKey(sub.marketId, key)
|
|
173
|
+
}
|
|
162
174
|
const payload = this.toHlPayload(sub)
|
|
163
175
|
await this.registerSub(key, payload)
|
|
176
|
+
if (sub.channel === 'orderbook') {
|
|
177
|
+
this.registerOrderbook(sub.marketId, key)
|
|
178
|
+
}
|
|
164
179
|
|
|
165
180
|
await this.rws.ready()
|
|
166
181
|
|
|
@@ -170,8 +185,14 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
170
185
|
this.orderUpdatesKey = undefined
|
|
171
186
|
}
|
|
172
187
|
this.rws.send(
|
|
173
|
-
JSON.stringify({
|
|
188
|
+
JSON.stringify({
|
|
189
|
+
method: 'unsubscribe',
|
|
190
|
+
subscription: payload,
|
|
191
|
+
})
|
|
174
192
|
)
|
|
193
|
+
if (sub.channel === 'orderbook') {
|
|
194
|
+
this.releaseOrderbook(sub.marketId, key)
|
|
195
|
+
}
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
|
|
@@ -198,6 +219,25 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
198
219
|
this.orderUpdatesKey = key
|
|
199
220
|
}
|
|
200
221
|
|
|
222
|
+
private claimOrderbookKey(marketId: string, key: string): void {
|
|
223
|
+
const activeKeys = this.orderbookKeysByMarketId.get(marketId)
|
|
224
|
+
if (activeKeys === undefined) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const activeKey of [...activeKeys]) {
|
|
229
|
+
if (activeKey === key) {
|
|
230
|
+
continue
|
|
231
|
+
}
|
|
232
|
+
if (!this.closeChannelIfIdle(activeKey)) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Hyperliquid supports one orderbook aggregation per market per ` +
|
|
235
|
+
`provider instance. Unsubscribe ${activeKey} before subscribing ${key}.`
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
201
241
|
/**
|
|
202
242
|
* Sub-key + payload pairs for the markets-context aggregation: the all-dexs
|
|
203
243
|
* perp asset-context feed (oracle/funding/OI/metadata) plus the compressed
|
|
@@ -213,9 +253,35 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
213
253
|
]
|
|
214
254
|
}
|
|
215
255
|
|
|
256
|
+
private registerOrderbook(marketId: string, orderbookKey: string): void {
|
|
257
|
+
let activeKeys = this.orderbookKeysByMarketId.get(marketId)
|
|
258
|
+
if (activeKeys === undefined) {
|
|
259
|
+
activeKeys = new Set()
|
|
260
|
+
this.orderbookKeysByMarketId.set(marketId, activeKeys)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
activeKeys.add(orderbookKey)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private releaseOrderbook(marketId: string, orderbookKey: string): void {
|
|
267
|
+
const activeKeys = this.orderbookKeysByMarketId.get(marketId)
|
|
268
|
+
if (activeKeys === undefined) {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
activeKeys.delete(orderbookKey)
|
|
273
|
+
if (activeKeys.size > 0) {
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.orderbookKeysByMarketId.delete(marketId)
|
|
278
|
+
}
|
|
279
|
+
|
|
216
280
|
protected override onClose(): void {
|
|
217
281
|
this.perpCtxBySubDex.clear()
|
|
218
282
|
this.fastCtxByMarketId = {}
|
|
283
|
+
this.orderbookKeysByMarketId.clear()
|
|
284
|
+
this.latestOrderbookByMarketId.clear()
|
|
219
285
|
this.orderUpdatesKey = undefined
|
|
220
286
|
}
|
|
221
287
|
|
|
@@ -230,9 +296,14 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
230
296
|
case 'marketsContext':
|
|
231
297
|
return 'marketsContext'
|
|
232
298
|
case 'orderbook':
|
|
233
|
-
return `
|
|
299
|
+
return `l2:${sub.marketId}:${this.orderbookAggregationKey(
|
|
300
|
+
sub.marketId,
|
|
301
|
+
sub.priceStep
|
|
302
|
+
)}`
|
|
234
303
|
case 'candle':
|
|
235
304
|
return `candle:${sub.marketId}:${sub.interval}`
|
|
305
|
+
case 'trades':
|
|
306
|
+
return `trades:${sub.marketId}`
|
|
236
307
|
case 'orderUpdates':
|
|
237
308
|
return `orderUpdates:${sub.address.toLowerCase()}`
|
|
238
309
|
case 'fills':
|
|
@@ -244,32 +315,61 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
244
315
|
}
|
|
245
316
|
}
|
|
246
317
|
|
|
318
|
+
private orderbookAggregation(
|
|
319
|
+
marketId: string,
|
|
320
|
+
priceStep: number | undefined
|
|
321
|
+
): { nSigFigs?: number; mantissa?: number } {
|
|
322
|
+
if (priceStep === undefined) {
|
|
323
|
+
return {}
|
|
324
|
+
}
|
|
325
|
+
return priceStepToAggregation(
|
|
326
|
+
priceStep,
|
|
327
|
+
this.orderbookReferencePrice(marketId)
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private orderbookAggregationKey(
|
|
332
|
+
marketId: string,
|
|
333
|
+
priceStep: number | undefined
|
|
334
|
+
): string {
|
|
335
|
+
const aggregation = this.orderbookAggregation(marketId, priceStep)
|
|
336
|
+
const nSigFigs = aggregation.nSigFigs ?? null
|
|
337
|
+
const mantissa = aggregation.mantissa ?? null
|
|
338
|
+
if (nSigFigs === null && mantissa === null) {
|
|
339
|
+
return 'full'
|
|
340
|
+
}
|
|
341
|
+
return `s:${nSigFigs}:m:${mantissa}`
|
|
342
|
+
}
|
|
343
|
+
|
|
247
344
|
private toHlPayload(sub: Subscription): object {
|
|
248
345
|
switch (sub.channel) {
|
|
249
346
|
case 'marketsContext':
|
|
250
347
|
// Handled via getMarketsContextSubEntries in openChannel; never reaches
|
|
251
348
|
// toHlPayload, but TS requires an exhaustive switch.
|
|
252
349
|
return { type: 'allDexsAssetCtxs' }
|
|
253
|
-
case 'orderbook':
|
|
350
|
+
case 'orderbook': {
|
|
351
|
+
const aggregation = this.orderbookAggregation(
|
|
352
|
+
sub.marketId,
|
|
353
|
+
sub.priceStep
|
|
354
|
+
)
|
|
254
355
|
return {
|
|
255
|
-
type: '
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// valid only when nSigFigs === 5).
|
|
260
|
-
...(sub.priceStep !== undefined
|
|
261
|
-
? priceStepToAggregation(
|
|
262
|
-
sub.priceStep,
|
|
263
|
-
this.mergedMids().get(sub.marketId) ?? Number.NaN
|
|
264
|
-
)
|
|
265
|
-
: {}),
|
|
356
|
+
type: 'l2',
|
|
357
|
+
c: sub.marketId,
|
|
358
|
+
s: aggregation.nSigFigs ?? null,
|
|
359
|
+
m: aggregation.mantissa ?? null,
|
|
266
360
|
}
|
|
361
|
+
}
|
|
267
362
|
case 'candle':
|
|
268
363
|
return {
|
|
269
364
|
type: 'candle',
|
|
270
365
|
coin: sub.marketId,
|
|
271
366
|
interval: sub.interval,
|
|
272
367
|
}
|
|
368
|
+
case 'trades':
|
|
369
|
+
return {
|
|
370
|
+
type: 'trades',
|
|
371
|
+
coin: sub.marketId,
|
|
372
|
+
}
|
|
273
373
|
case 'orderUpdates':
|
|
274
374
|
return { type: 'orderUpdates', user: sub.address }
|
|
275
375
|
case 'fills':
|
|
@@ -315,12 +415,18 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
315
415
|
case 'fastAssetCtxs':
|
|
316
416
|
this.handleFastAssetCtxs(msg.data as string)
|
|
317
417
|
break
|
|
418
|
+
case 'l2':
|
|
419
|
+
this.handleL2(msg.data as HlWsL2Data)
|
|
420
|
+
break
|
|
318
421
|
case 'l2Book':
|
|
319
422
|
this.handleL2Book(msg.data as HlWsL2BookData)
|
|
320
423
|
break
|
|
321
424
|
case 'candle':
|
|
322
425
|
this.handleCandle(msg.data as HlWsCandleData)
|
|
323
426
|
break
|
|
427
|
+
case 'trades':
|
|
428
|
+
this.handleTrades(msg.data as HlWsTrade[])
|
|
429
|
+
break
|
|
324
430
|
case 'orderUpdates':
|
|
325
431
|
this.handleOrderUpdates(msg.data as HlOrderDetail[])
|
|
326
432
|
break
|
|
@@ -427,21 +533,93 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
427
533
|
return map
|
|
428
534
|
}
|
|
429
535
|
|
|
536
|
+
private orderbookReferencePrice(marketId: string): number {
|
|
537
|
+
const mid = this.mergedMids().get(marketId)
|
|
538
|
+
if (mid !== undefined) {
|
|
539
|
+
return mid
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const book = this.latestOrderbookByMarketId.get(marketId)
|
|
543
|
+
const bid = book?.bids[0]?.price
|
|
544
|
+
const ask = book?.asks[0]?.price
|
|
545
|
+
if (bid !== undefined && ask !== undefined) {
|
|
546
|
+
return (Number(bid) + Number(ask)) / 2
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return Number.NaN
|
|
550
|
+
}
|
|
551
|
+
|
|
430
552
|
private handleL2Book(data: HlWsL2BookData) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
553
|
+
const book = {
|
|
554
|
+
provider: this.providerKey,
|
|
555
|
+
marketId: data.coin,
|
|
556
|
+
bids: data.levels[0]
|
|
557
|
+
.slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE)
|
|
558
|
+
.map((l) => ({ price: l.px, size: l.sz })),
|
|
559
|
+
asks: data.levels[1]
|
|
560
|
+
.slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE)
|
|
561
|
+
.map((l) => ({ price: l.px, size: l.sz })),
|
|
562
|
+
timestamp: data.time,
|
|
563
|
+
}
|
|
564
|
+
this.latestOrderbookByMarketId.set(data.coin, book)
|
|
565
|
+
this.emitOrderbook(data.coin, book)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private handleL2(data: HlWsL2Data) {
|
|
569
|
+
if (data.s !== undefined) {
|
|
570
|
+
this.handleL2Book(data.s)
|
|
571
|
+
}
|
|
572
|
+
if (data.u !== undefined) {
|
|
573
|
+
this.handleCompactL2Delta(data.u)
|
|
574
|
+
}
|
|
575
|
+
if (data.c === undefined) {
|
|
576
|
+
return
|
|
577
|
+
}
|
|
578
|
+
const compressed = data.c
|
|
579
|
+
this.orderbookDecodeChain = this.orderbookDecodeChain
|
|
580
|
+
.then(async () => {
|
|
581
|
+
this.handleCompactL2Delta(
|
|
582
|
+
await decodeCompressedJson<HlWsCompressedL2Data>(compressed)
|
|
583
|
+
)
|
|
584
|
+
})
|
|
585
|
+
.catch((error) => wsLog.handlerFailure(this.providerKey, error))
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private handleCompactL2Delta(delta: HlWsCompressedL2Data) {
|
|
589
|
+
const previous = this.latestOrderbookByMarketId.get(delta.c)
|
|
590
|
+
if (previous === undefined) {
|
|
591
|
+
return
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const book: OrderbookResponse = {
|
|
595
|
+
provider: this.providerKey,
|
|
596
|
+
marketId: delta.c,
|
|
597
|
+
bids: applyCompressedL2Side(
|
|
598
|
+
previous.bids,
|
|
599
|
+
delta.l[0],
|
|
600
|
+
delta.r?.[0] ?? [],
|
|
601
|
+
'bid'
|
|
602
|
+
),
|
|
603
|
+
asks: applyCompressedL2Side(
|
|
604
|
+
previous.asks,
|
|
605
|
+
delta.l[1],
|
|
606
|
+
delta.r?.[1] ?? [],
|
|
607
|
+
'ask'
|
|
608
|
+
),
|
|
609
|
+
timestamp: delta.t,
|
|
610
|
+
}
|
|
611
|
+
this.latestOrderbookByMarketId.set(delta.c, book)
|
|
612
|
+
this.emitOrderbook(delta.c, book)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private emitOrderbook(marketId: string, book: OrderbookResponse) {
|
|
616
|
+
const keys = this.orderbookKeysByMarketId.get(marketId)
|
|
617
|
+
if (keys === undefined) {
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
for (const key of keys) {
|
|
621
|
+
this.emit(key, { channel: 'orderbook', data: book })
|
|
622
|
+
}
|
|
445
623
|
}
|
|
446
624
|
|
|
447
625
|
private handleCandle(data: HlWsCandleData) {
|
|
@@ -458,6 +636,25 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
458
636
|
})
|
|
459
637
|
}
|
|
460
638
|
|
|
639
|
+
private handleTrades(data: HlWsTrade[]) {
|
|
640
|
+
for (const trade of data) {
|
|
641
|
+
this.emit(`trades:${trade.coin}`, {
|
|
642
|
+
channel: 'trades',
|
|
643
|
+
data: [
|
|
644
|
+
{
|
|
645
|
+
provider: this.providerKey,
|
|
646
|
+
marketId: trade.coin,
|
|
647
|
+
price: trade.px,
|
|
648
|
+
size: trade.sz,
|
|
649
|
+
timestamp: trade.time,
|
|
650
|
+
side: trade.side === 'B' ? 'buy' : 'sell',
|
|
651
|
+
id: trade.tid !== undefined ? String(trade.tid) : trade.hash,
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
})
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
461
658
|
private handleOrderUpdates(data: HlOrderDetail[]) {
|
|
462
659
|
// Untagged frame: attributable only via the single-address invariant.
|
|
463
660
|
const key = this.orderUpdatesKey
|
|
@@ -563,6 +760,47 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
563
760
|
const isObject = (v: unknown): v is Record<string, unknown> =>
|
|
564
761
|
typeof v === 'object' && v !== null
|
|
565
762
|
|
|
763
|
+
function compactRemovalPrice(
|
|
764
|
+
levels: OrderbookLevel[],
|
|
765
|
+
value: number | string | { p: string }
|
|
766
|
+
): string | undefined {
|
|
767
|
+
if (typeof value === 'number') {
|
|
768
|
+
return levels[value]?.price
|
|
769
|
+
}
|
|
770
|
+
return typeof value === 'string' ? value : value.p
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function applyCompressedL2Side(
|
|
774
|
+
previous: OrderbookLevel[],
|
|
775
|
+
updates: HlWsCompressedL2Data['l'][number],
|
|
776
|
+
removals: NonNullable<HlWsCompressedL2Data['r']>[number],
|
|
777
|
+
side: 'bid' | 'ask'
|
|
778
|
+
): OrderbookLevel[] {
|
|
779
|
+
const byPrice = new Map(previous.map((level) => [level.price, level.size]))
|
|
780
|
+
for (const removal of removals) {
|
|
781
|
+
const price = compactRemovalPrice(previous, removal)
|
|
782
|
+
if (price !== undefined) {
|
|
783
|
+
byPrice.delete(price)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
for (const update of updates) {
|
|
787
|
+
if (Number(update.s) === 0) {
|
|
788
|
+
byPrice.delete(update.p)
|
|
789
|
+
} else {
|
|
790
|
+
byPrice.set(update.p, update.s)
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return [...byPrice.entries()]
|
|
795
|
+
.map(([price, size]) => ({ price, size }))
|
|
796
|
+
.sort((a, b) =>
|
|
797
|
+
side === 'bid'
|
|
798
|
+
? Number(b.price) - Number(a.price)
|
|
799
|
+
: Number(a.price) - Number(b.price)
|
|
800
|
+
)
|
|
801
|
+
.slice(0, HL_L2_BOOK_MAX_LEVELS_PER_SIDE)
|
|
802
|
+
}
|
|
803
|
+
|
|
566
804
|
/**
|
|
567
805
|
* Minimal presence/type check of the channel-discriminating and required
|
|
568
806
|
* fields the matching handler dereferences without its own guard. A frame
|
|
@@ -589,8 +827,12 @@ function isValidHlFrame(channel: string, data: unknown): boolean {
|
|
|
589
827
|
Array.isArray(data.levels[1]) &&
|
|
590
828
|
typeof data.time === 'number'
|
|
591
829
|
)
|
|
830
|
+
case 'l2':
|
|
831
|
+
return typeof data.c === 'string' || isObject(data.s) || isObject(data.u)
|
|
592
832
|
case 'candle':
|
|
593
833
|
return typeof data.s === 'string' && typeof data.i === 'string'
|
|
834
|
+
case 'trades':
|
|
835
|
+
return Array.isArray(data)
|
|
594
836
|
case 'orderUpdates':
|
|
595
837
|
return Array.isArray(data)
|
|
596
838
|
case 'userFills':
|