@lifi/perps-sdk-provider-hyperliquid 1.5.4 → 1.5.5

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 (34) hide show
  1. package/dist/cjs/services/getFills.d.ts.map +1 -1
  2. package/dist/cjs/services/getFills.js +13 -6
  3. package/dist/cjs/services/getFills.js.map +1 -1
  4. package/dist/cjs/services/getOrders.js +1 -1
  5. package/dist/cjs/services/getOrders.js.map +1 -1
  6. package/dist/cjs/types/order.d.ts +1 -1
  7. package/dist/cjs/types/order.d.ts.map +1 -1
  8. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +2 -0
  9. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  10. package/dist/cjs/websocket/HyperliquidWsProvider.js +59 -38
  11. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  12. package/dist/esm/services/getFills.d.ts +4 -4
  13. package/dist/esm/services/getFills.d.ts.map +1 -1
  14. package/dist/esm/services/getFills.js +19 -9
  15. package/dist/esm/services/getFills.js.map +1 -1
  16. package/dist/esm/services/getOrders.js +1 -1
  17. package/dist/esm/services/getOrders.js.map +1 -1
  18. package/dist/esm/types/order.d.ts +1 -1
  19. package/dist/esm/types/order.d.ts.map +1 -1
  20. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +13 -2
  21. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  22. package/dist/esm/websocket/HyperliquidWsProvider.js +75 -44
  23. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  24. package/dist/types/services/getFills.d.ts +4 -4
  25. package/dist/types/services/getFills.d.ts.map +1 -1
  26. package/dist/types/types/order.d.ts +1 -1
  27. package/dist/types/types/order.d.ts.map +1 -1
  28. package/dist/types/websocket/HyperliquidWsProvider.d.ts +13 -2
  29. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  30. package/package.json +2 -2
  31. package/src/services/getFills.ts +37 -11
  32. package/src/services/getOrders.ts +1 -1
  33. package/src/types/order.ts +1 -1
  34. package/src/websocket/HyperliquidWsProvider.ts +79 -47
@@ -99,6 +99,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
99
99
  private readonly registry: MarketRegistry | undefined
100
100
  private perpCtxBySubDex = new Map<string, Record<string, HlWsPerpAssetCtx>>()
101
101
  private spotCtxByMarketId: Record<string, HlWsSpotAssetCtx> = {}
102
+ private marketsContextByMarketId: Record<string, MarketContext> = {}
102
103
  private orderbookKeysByMarketId = new Map<string, Set<string>>()
103
104
  private latestOrderbookByMarketId = new Map<string, OrderbookResponse>()
104
105
  // Latest mid/mark per `Market.id` from the high-frequency `fastAssetCtxs`
@@ -173,6 +174,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
173
174
  this.perpCtxBySubDex.clear()
174
175
  this.spotCtxByMarketId = {}
175
176
  this.fastCtxByMarketId = {}
177
+ this.marketsContextByMarketId = {}
176
178
  }
177
179
  }
178
180
 
@@ -292,6 +294,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
292
294
  this.perpCtxBySubDex.clear()
293
295
  this.spotCtxByMarketId = {}
294
296
  this.fastCtxByMarketId = {}
297
+ this.marketsContextByMarketId = {}
295
298
  this.orderbookKeysByMarketId.clear()
296
299
  this.latestOrderbookByMarketId.clear()
297
300
  this.fastDecodeChain = Promise.resolve()
@@ -483,41 +486,47 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
483
486
  }
484
487
 
485
488
  private handleAllDexsAssetCtxs(data: HlWsAllDexsAssetCtxsData) {
486
- this.mergePerpAssetCtxEntries(allDexsAssetCtxEntries(data))
487
- this.emitMarketsContext()
489
+ this.emitMarketsContext(
490
+ this.mergePerpAssetCtxEntries(allDexsAssetCtxEntries(data))
491
+ )
488
492
  }
489
493
 
490
494
  private handlePac(base64: string) {
491
495
  this.pacDecodeChain = this.pacDecodeChain
492
496
  .then(async () => {
493
- this.mergePerpAssetCtxEntries(
494
- await decodeCompressedJson<HlWsPacData>(base64)
497
+ this.emitMarketsContext(
498
+ this.mergePerpAssetCtxEntries(
499
+ await decodeCompressedJson<HlWsPacData>(base64)
500
+ )
495
501
  )
496
- this.emitMarketsContext()
497
502
  })
498
503
  .catch((error) => wsLog.handlerFailure(this.providerKey, error))
499
504
  }
500
505
 
506
+ /** Merge perp asset contexts in place; returns the marketIds this frame touched. */
501
507
  private mergePerpAssetCtxEntries(
502
508
  entries: [string, HlWsPerpAssetCtxPayload[]][]
503
- ) {
509
+ ): Set<string> {
510
+ const touched = new Set<string>()
504
511
  for (const [dex, ctxs] of entries) {
505
- const previous = this.perpCtxBySubDex.get(dex || 'default')
506
- const byMarketId: Record<string, HlWsPerpAssetCtx> = {
507
- ...(previous ?? {}),
512
+ let byMarketId = this.perpCtxBySubDex.get(dex || 'default')
513
+ if (byMarketId === undefined) {
514
+ byMarketId = {}
515
+ this.perpCtxBySubDex.set(dex || 'default', byMarketId)
508
516
  }
509
517
  for (const ctx of ctxs) {
510
518
  const marketId = ctx.coin
511
519
  if (marketId === undefined) {
512
520
  continue
513
521
  }
514
- const merged = mergePerpAssetCtx(previous?.[marketId], marketId, ctx)
522
+ const merged = mergePerpAssetCtx(byMarketId[marketId], marketId, ctx)
515
523
  if (merged !== undefined) {
516
524
  byMarketId[marketId] = merged
525
+ touched.add(marketId)
517
526
  }
518
527
  }
519
- this.perpCtxBySubDex.set(dex || 'default', byMarketId)
520
528
  }
529
+ return touched
521
530
  }
522
531
 
523
532
  private handleSac(base64: string) {
@@ -525,6 +534,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
525
534
  .then(async () => {
526
535
  const spotMarketIdBySacKey = this.spotMarketIdBySacKey()
527
536
  const ctxs = await decodeCompressedJson<HlWsSacData>(base64)
537
+ const touched = new Set<string>()
528
538
  for (const [sacKey, ctx] of Object.entries(ctxs)) {
529
539
  const marketId = spotMarketIdBySacKey.get(sacKey)
530
540
  if (marketId === undefined) {
@@ -534,8 +544,9 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
534
544
  ...this.spotCtxByMarketId[marketId],
535
545
  ...ctx,
536
546
  }
547
+ touched.add(marketId)
537
548
  }
538
- this.emitMarketsContext()
549
+ this.emitMarketsContext(touched)
539
550
  })
540
551
  .catch((error) => wsLog.handlerFailure(this.providerKey, error))
541
552
  }
@@ -572,52 +583,66 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
572
583
  midPx: 'midPx' in ctx ? ctx.midPx : prev?.midPx,
573
584
  }
574
585
  }
575
- this.emitMarketsContext()
586
+ this.emitMarketsContext(Object.keys(ctxs))
576
587
  })
577
588
  .catch((error) => wsLog.handlerFailure(this.providerKey, error))
578
589
  }
579
590
 
580
591
  /**
581
- * Build the all-markets context map from perp + spot asset contexts, with mid
582
- * + mark overlaid from `fastAssetCtxs`.
592
+ * Emit the all-markets context map, re-mapping only the markets the incoming
593
+ * frame touched. Each emission is a fresh snapshot object that carries
594
+ * untouched entries over by reference, so a listener holding an earlier
595
+ * snapshot never observes later-frame mutation.
583
596
  */
584
- private emitMarketsContext() {
585
- const data: Record<string, MarketContext> = {}
586
- for (const byMarketId of this.perpCtxBySubDex.values()) {
587
- for (const [marketId, ctx] of Object.entries(byMarketId)) {
588
- const base = mapMarketContext(marketId, ctx)
589
- const fast = this.fastCtxByMarketId[marketId]
590
- data[marketId] = {
591
- ...base,
592
- midPrice: fast?.midPx != null ? fast.midPx : base.midPrice,
593
- markPrice: fast?.markPx != null ? fast.markPx : base.markPrice,
594
- }
595
- }
597
+ private emitMarketsContext(touchedMarketIds: Iterable<string>) {
598
+ const data: Record<string, MarketContext> = {
599
+ ...this.marketsContextByMarketId,
596
600
  }
597
- for (const [marketId, ctx] of Object.entries(this.spotCtxByMarketId)) {
598
- const context = mapSpotMarketContext(
599
- marketId,
600
- ctx,
601
- this.fastCtxByMarketId[marketId]
602
- )
601
+ for (const marketId of touchedMarketIds) {
602
+ const context = this.computeMarketContext(marketId)
603
603
  if (context === undefined) {
604
- continue
604
+ delete data[marketId]
605
+ } else {
606
+ data[marketId] = context
605
607
  }
606
- data[marketId] = context
607
608
  }
608
- for (const [marketId, fast] of Object.entries(this.fastCtxByMarketId)) {
609
- if (data[marketId] !== undefined) {
610
- continue
609
+ this.marketsContextByMarketId = data
610
+ this.emit('marketsContext', { channel: 'marketsContext', data })
611
+ }
612
+
613
+ /**
614
+ * Context for one market from the cached feeds: spot asset context when it
615
+ * maps, else perp asset context with mid + mark overlaid from
616
+ * `fastAssetCtxs`, else the fast feed alone — each price standing in for the
617
+ * other where it carries only one of mid/mark.
618
+ */
619
+ private computeMarketContext(marketId: string): MarketContext | undefined {
620
+ const fast = this.fastCtxByMarketId[marketId]
621
+ const spotCtx = this.spotCtxByMarketId[marketId]
622
+ if (spotCtx !== undefined) {
623
+ const context = mapSpotMarketContext(marketId, spotCtx, fast)
624
+ if (context !== undefined) {
625
+ return context
611
626
  }
612
- // No asset-context frame for this market yet — each price stands in for
613
- // the other where the fast feed carries only one of mid/mark.
614
- const midPrice = fast.midPx ?? fast.markPx
615
- const markPrice = fast.markPx ?? fast.midPx
616
- if (midPrice != null && markPrice != null) {
617
- data[marketId] = { marketId, midPrice, markPrice }
627
+ }
628
+ let perpCtx: HlWsPerpAssetCtx | undefined
629
+ for (const byMarketId of this.perpCtxBySubDex.values()) {
630
+ perpCtx = byMarketId[marketId] ?? perpCtx
631
+ }
632
+ if (perpCtx !== undefined) {
633
+ const base = mapMarketContext(marketId, perpCtx)
634
+ return {
635
+ ...base,
636
+ midPrice: fast?.midPx != null ? fast.midPx : base.midPrice,
637
+ markPrice: fast?.markPx != null ? fast.markPx : base.markPrice,
618
638
  }
619
639
  }
620
- this.emit('marketsContext', { channel: 'marketsContext', data })
640
+ const midPrice = fast?.midPx ?? fast?.markPx
641
+ const markPrice = fast?.markPx ?? fast?.midPx
642
+ if (midPrice != null && markPrice != null) {
643
+ return { marketId, midPrice, markPrice }
644
+ }
645
+ return undefined
621
646
  }
622
647
 
623
648
  private handleActiveAssetCtx(data: HlWsActiveAssetCtxData, raw: string) {
@@ -725,6 +750,14 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
725
750
  return
726
751
  }
727
752
 
753
+ // Compact deltas decode asynchronously via `orderbookDecodeChain`, so one
754
+ // can resolve after a newer frame (sync snapshot/update, or a later delta)
755
+ // has already been applied. A delta no newer than the maintained book is
756
+ // stale; applying it would overwrite fresher state with older levels.
757
+ if (delta.t <= previous.timestamp) {
758
+ return
759
+ }
760
+
728
761
  const book: OrderbookResponse = {
729
762
  provider: this.providerKey,
730
763
  marketId: delta.c,
@@ -806,8 +839,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
806
839
  continue
807
840
  }
808
841
  const type = mapOrderType(o.orderType)
809
- // A miss warns once and schedules a cooldown-gated registry refetch
810
- // (the id may have listed after the snapshot); skip just this item.
842
+ // Unknown market id (absent from the synced snapshot); skip just this item.
811
843
  const market = this.registry?.get(o.coin)
812
844
  if (!market) {
813
845
  continue