@lifi/perps-sdk-provider-hyperliquid 1.5.5 → 1.6.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/accountConfig.d.ts.map +1 -1
- package/dist/cjs/accountConfig.js +1 -0
- package/dist/cjs/accountConfig.js.map +1 -1
- package/dist/cjs/utils/decodeFastAssetCtxs.d.ts.map +1 -1
- package/dist/cjs/utils/decodeFastAssetCtxs.js +11 -6
- package/dist/cjs/utils/decodeFastAssetCtxs.js.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +5 -4
- package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/HyperliquidWsProvider.js +18 -24
- package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/cjs/websocket/decodeChain.d.ts +11 -0
- package/dist/cjs/websocket/decodeChain.d.ts.map +1 -0
- package/dist/cjs/websocket/decodeChain.js +37 -0
- package/dist/cjs/websocket/decodeChain.js.map +1 -0
- package/dist/esm/accountConfig.d.ts.map +1 -1
- package/dist/esm/accountConfig.js +1 -0
- package/dist/esm/accountConfig.js.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 +14 -6
- package/dist/esm/utils/decodeFastAssetCtxs.js.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts +7 -5
- package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/HyperliquidWsProvider.js +25 -27
- package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
- package/dist/esm/websocket/decodeChain.d.ts +30 -0
- package/dist/esm/websocket/decodeChain.d.ts.map +1 -0
- package/dist/esm/websocket/decodeChain.js +44 -0
- package/dist/esm/websocket/decodeChain.js.map +1 -0
- package/dist/types/accountConfig.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/websocket/HyperliquidWsProvider.d.ts +7 -5
- package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
- package/dist/types/websocket/decodeChain.d.ts +30 -0
- package/dist/types/websocket/decodeChain.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/accountConfig.ts +1 -0
- package/src/utils/decodeFastAssetCtxs.ts +14 -6
- package/src/websocket/HyperliquidWsProvider.ts +67 -56
- package/src/websocket/decodeChain.ts +54 -0
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
spotBalance,
|
|
62
62
|
spotPriceById,
|
|
63
63
|
} from '../utils/index.js'
|
|
64
|
+
import { DecodeChain } from './decodeChain.js'
|
|
64
65
|
|
|
65
66
|
/** HL's compact `l2` snapshot carries 20 levels per side. */
|
|
66
67
|
const HL_L2_BOOK_MAX_LEVELS_PER_SIDE = 20
|
|
@@ -107,12 +108,29 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
107
108
|
// frames carry only changed coins — and overlaid onto asset contexts that
|
|
108
109
|
// carry oracle/funding/OI/metadata.
|
|
109
110
|
private fastCtxByMarketId: Record<string, HlWsFastAssetCtx> = {}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
private readonly reportDecodeFailure = (error: unknown) =>
|
|
112
|
+
wsLog.handlerFailure(this.providerKey, error)
|
|
113
|
+
// Compressed payloads (base64 + raw-DEFLATE) decode async, so each channel
|
|
114
|
+
// chains decodes to apply frames in arrival order. Only the fast feed
|
|
115
|
+
// coalesces a stall backlog to its newest frame: a coin's dropped tick is
|
|
116
|
+
// refreshed by its next one. pac/sac field-merges and l2 deltas depend on
|
|
117
|
+
// every frame, so they must not coalesce.
|
|
118
|
+
private readonly fastDecodeChain = new DecodeChain(
|
|
119
|
+
'latest',
|
|
120
|
+
this.reportDecodeFailure
|
|
121
|
+
)
|
|
122
|
+
private readonly pacDecodeChain = new DecodeChain(
|
|
123
|
+
'every',
|
|
124
|
+
this.reportDecodeFailure
|
|
125
|
+
)
|
|
126
|
+
private readonly sacDecodeChain = new DecodeChain(
|
|
127
|
+
'every',
|
|
128
|
+
this.reportDecodeFailure
|
|
129
|
+
)
|
|
130
|
+
private readonly orderbookDecodeChain = new DecodeChain(
|
|
131
|
+
'every',
|
|
132
|
+
this.reportDecodeFailure
|
|
133
|
+
)
|
|
116
134
|
|
|
117
135
|
constructor(wsUrl: string, providerKey: string, client?: PerpsSDKClient) {
|
|
118
136
|
super(
|
|
@@ -297,10 +315,10 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
297
315
|
this.marketsContextByMarketId = {}
|
|
298
316
|
this.orderbookKeysByMarketId.clear()
|
|
299
317
|
this.latestOrderbookByMarketId.clear()
|
|
300
|
-
this.fastDecodeChain
|
|
301
|
-
this.pacDecodeChain
|
|
302
|
-
this.sacDecodeChain
|
|
303
|
-
this.orderbookDecodeChain
|
|
318
|
+
this.fastDecodeChain.reset()
|
|
319
|
+
this.pacDecodeChain.reset()
|
|
320
|
+
this.sacDecodeChain.reset()
|
|
321
|
+
this.orderbookDecodeChain.reset()
|
|
304
322
|
this.orderUpdatesKey = undefined
|
|
305
323
|
}
|
|
306
324
|
|
|
@@ -492,15 +510,13 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
492
510
|
}
|
|
493
511
|
|
|
494
512
|
private handlePac(base64: string) {
|
|
495
|
-
this.pacDecodeChain
|
|
496
|
-
.
|
|
497
|
-
this.
|
|
498
|
-
|
|
499
|
-
await decodeCompressedJson<HlWsPacData>(base64)
|
|
500
|
-
)
|
|
513
|
+
this.pacDecodeChain.push(async () => {
|
|
514
|
+
this.emitMarketsContext(
|
|
515
|
+
this.mergePerpAssetCtxEntries(
|
|
516
|
+
await decodeCompressedJson<HlWsPacData>(base64)
|
|
501
517
|
)
|
|
502
|
-
|
|
503
|
-
|
|
518
|
+
)
|
|
519
|
+
})
|
|
504
520
|
}
|
|
505
521
|
|
|
506
522
|
/** Merge perp asset contexts in place; returns the marketIds this frame touched. */
|
|
@@ -530,25 +546,23 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
530
546
|
}
|
|
531
547
|
|
|
532
548
|
private handleSac(base64: string) {
|
|
533
|
-
this.sacDecodeChain
|
|
534
|
-
.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
continue
|
|
542
|
-
}
|
|
543
|
-
this.spotCtxByMarketId[marketId] = {
|
|
544
|
-
...this.spotCtxByMarketId[marketId],
|
|
545
|
-
...ctx,
|
|
546
|
-
}
|
|
547
|
-
touched.add(marketId)
|
|
549
|
+
this.sacDecodeChain.push(async () => {
|
|
550
|
+
const spotMarketIdBySacKey = this.spotMarketIdBySacKey()
|
|
551
|
+
const ctxs = await decodeCompressedJson<HlWsSacData>(base64)
|
|
552
|
+
const touched = new Set<string>()
|
|
553
|
+
for (const [sacKey, ctx] of Object.entries(ctxs)) {
|
|
554
|
+
const marketId = spotMarketIdBySacKey.get(sacKey)
|
|
555
|
+
if (marketId === undefined) {
|
|
556
|
+
continue
|
|
548
557
|
}
|
|
549
|
-
this.
|
|
550
|
-
|
|
551
|
-
|
|
558
|
+
this.spotCtxByMarketId[marketId] = {
|
|
559
|
+
...this.spotCtxByMarketId[marketId],
|
|
560
|
+
...ctx,
|
|
561
|
+
}
|
|
562
|
+
touched.add(marketId)
|
|
563
|
+
}
|
|
564
|
+
this.emitMarketsContext(touched)
|
|
565
|
+
})
|
|
552
566
|
}
|
|
553
567
|
|
|
554
568
|
private spotMarketIdBySacKey(): Map<string, string> {
|
|
@@ -570,22 +584,21 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
570
584
|
* Decode a `fastAssetCtxs` frame (base64 + raw-DEFLATE) and merge its changed
|
|
571
585
|
* coins into the per-market fast-context store. Frames are incremental, so a
|
|
572
586
|
* field absent from this frame keeps its prior value. Decodes are chained to
|
|
573
|
-
* preserve arrival order across the async boundary
|
|
587
|
+
* preserve arrival order across the async boundary; a backlog coalesces to
|
|
588
|
+
* its newest frame (see the chain's mode).
|
|
574
589
|
*/
|
|
575
590
|
private handleFastAssetCtxs(base64: string) {
|
|
576
|
-
this.fastDecodeChain
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
midPx: 'midPx' in ctx ? ctx.midPx : prev?.midPx,
|
|
584
|
-
}
|
|
591
|
+
this.fastDecodeChain.push(async () => {
|
|
592
|
+
const ctxs = await decodeFastAssetCtxs(base64)
|
|
593
|
+
for (const [marketId, ctx] of Object.entries(ctxs)) {
|
|
594
|
+
const prev = this.fastCtxByMarketId[marketId]
|
|
595
|
+
this.fastCtxByMarketId[marketId] = {
|
|
596
|
+
markPx: 'markPx' in ctx ? ctx.markPx : prev?.markPx,
|
|
597
|
+
midPx: 'midPx' in ctx ? ctx.midPx : prev?.midPx,
|
|
585
598
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
599
|
+
}
|
|
600
|
+
this.emitMarketsContext(Object.keys(ctxs))
|
|
601
|
+
})
|
|
589
602
|
}
|
|
590
603
|
|
|
591
604
|
/**
|
|
@@ -735,13 +748,11 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
|
|
|
735
748
|
return
|
|
736
749
|
}
|
|
737
750
|
const compressed = data.c
|
|
738
|
-
this.orderbookDecodeChain
|
|
739
|
-
.
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
})
|
|
744
|
-
.catch((error) => wsLog.handlerFailure(this.providerKey, error))
|
|
751
|
+
this.orderbookDecodeChain.push(async () => {
|
|
752
|
+
this.handleCompactL2Delta(
|
|
753
|
+
await decodeCompressedJson<HlWsCompressedL2Data>(compressed)
|
|
754
|
+
)
|
|
755
|
+
})
|
|
745
756
|
}
|
|
746
757
|
|
|
747
758
|
private handleCompactL2Delta(delta: HlWsCompressedL2Data) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame semantics of a {@link DecodeChain}:
|
|
3
|
+
* - `'every'`: every frame decodes and applies in arrival order — required for
|
|
4
|
+
* delta-carrying channels, where dropping a frame corrupts downstream state.
|
|
5
|
+
* - `'latest'`: frames queued behind an in-flight decode coalesce to the
|
|
6
|
+
* newest — for snapshot-like channels, where a newer frame supersedes
|
|
7
|
+
* anything still waiting.
|
|
8
|
+
*/
|
|
9
|
+
export type DecodeChainMode = 'every' | 'latest'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Serializes async WS frame decodes so results apply in arrival order.
|
|
13
|
+
* In `'latest'` mode a backlog (e.g. queued behind a main-thread stall)
|
|
14
|
+
* collapses to its newest frame instead of replaying every stale decode.
|
|
15
|
+
* Decode failures are reported to `onError` and never break the chain.
|
|
16
|
+
*/
|
|
17
|
+
export class DecodeChain {
|
|
18
|
+
private tail: Promise<void> = Promise.resolve()
|
|
19
|
+
private queued: (() => Promise<void>) | undefined
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly mode: DecodeChainMode,
|
|
23
|
+
private readonly onError: (error: unknown) => void
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
push(decode: () => Promise<void>): void {
|
|
27
|
+
if (this.mode === 'every') {
|
|
28
|
+
this.tail = this.tail.then(decode).catch(this.onError)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
const drainScheduled = this.queued !== undefined
|
|
32
|
+
this.queued = decode
|
|
33
|
+
if (drainScheduled) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
this.tail = this.tail
|
|
37
|
+
.then(() => {
|
|
38
|
+
const next = this.queued
|
|
39
|
+
this.queued = undefined
|
|
40
|
+
return next?.()
|
|
41
|
+
})
|
|
42
|
+
.catch(this.onError)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detach on connection teardown: drops the queued latest-mode decode and
|
|
47
|
+
* starts subsequent pushes on a fresh chain. A decode already in flight
|
|
48
|
+
* still settles.
|
|
49
|
+
*/
|
|
50
|
+
reset(): void {
|
|
51
|
+
this.tail = Promise.resolve()
|
|
52
|
+
this.queued = undefined
|
|
53
|
+
}
|
|
54
|
+
}
|