@lifi/perps-sdk-provider-hyperliquid 11.0.0 → 12.1.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.
Files changed (57) hide show
  1. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  2. package/dist/cjs/services/getAccount.js +7 -3
  3. package/dist/cjs/services/getAccount.js.map +1 -1
  4. package/dist/cjs/utils/decimal.d.ts +3 -0
  5. package/dist/cjs/utils/decimal.d.ts.map +1 -0
  6. package/dist/cjs/utils/decimal.js +19 -0
  7. package/dist/cjs/utils/decimal.js.map +1 -0
  8. package/dist/cjs/utils/infoClient.d.ts.map +1 -1
  9. package/dist/cjs/utils/infoClient.js +5 -1
  10. package/dist/cjs/utils/infoClient.js.map +1 -1
  11. package/dist/cjs/utils/mapMarketContext.d.ts +2 -2
  12. package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
  13. package/dist/cjs/utils/mapMarketContext.js +21 -4
  14. package/dist/cjs/utils/mapMarketContext.js.map +1 -1
  15. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  16. package/dist/cjs/utils/spotCollateral.js +4 -0
  17. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  18. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  19. package/dist/cjs/websocket/HyperliquidWsProvider.js +7 -9
  20. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  21. package/dist/esm/services/getAccount.d.ts.map +1 -1
  22. package/dist/esm/services/getAccount.js +7 -3
  23. package/dist/esm/services/getAccount.js.map +1 -1
  24. package/dist/esm/utils/decimal.d.ts +10 -0
  25. package/dist/esm/utils/decimal.d.ts.map +1 -0
  26. package/dist/esm/utils/decimal.js +19 -0
  27. package/dist/esm/utils/decimal.js.map +1 -0
  28. package/dist/esm/utils/infoClient.d.ts +4 -1
  29. package/dist/esm/utils/infoClient.d.ts.map +1 -1
  30. package/dist/esm/utils/infoClient.js +10 -3
  31. package/dist/esm/utils/infoClient.js.map +1 -1
  32. package/dist/esm/utils/mapMarketContext.d.ts +7 -4
  33. package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
  34. package/dist/esm/utils/mapMarketContext.js +27 -6
  35. package/dist/esm/utils/mapMarketContext.js.map +1 -1
  36. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  37. package/dist/esm/utils/spotCollateral.js +4 -0
  38. package/dist/esm/utils/spotCollateral.js.map +1 -1
  39. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  40. package/dist/esm/websocket/HyperliquidWsProvider.js +7 -9
  41. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  42. package/dist/types/services/getAccount.d.ts.map +1 -1
  43. package/dist/types/utils/decimal.d.ts +10 -0
  44. package/dist/types/utils/decimal.d.ts.map +1 -0
  45. package/dist/types/utils/infoClient.d.ts +4 -1
  46. package/dist/types/utils/infoClient.d.ts.map +1 -1
  47. package/dist/types/utils/mapMarketContext.d.ts +7 -4
  48. package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
  49. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  50. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  51. package/package.json +3 -3
  52. package/src/services/getAccount.ts +9 -4
  53. package/src/utils/decimal.ts +21 -0
  54. package/src/utils/infoClient.ts +15 -2
  55. package/src/utils/mapMarketContext.ts +32 -7
  56. package/src/utils/spotCollateral.ts +4 -0
  57. package/src/websocket/HyperliquidWsProvider.ts +14 -10
@@ -1,4 +1,5 @@
1
1
  import {
2
+ errorCodeFromStatus,
2
3
  fetchWithRetry,
3
4
  PerpsError,
4
5
  type PerpsSDKClient,
@@ -28,6 +29,11 @@ const normalizeInfoValue = (value: unknown): unknown => {
28
29
  return value
29
30
  }
30
31
 
32
+ const HYPERLIQUID_STATUS_ERROR_CODES = {
33
+ 401: PerpsErrorCode.Unauthorized,
34
+ 403: PerpsErrorCode.AgentUnauthorized,
35
+ } as const
36
+
31
37
  /** @internal */
32
38
  export const HYPERLIQUID_RETRY_DEFAULTS: ResolvedRetryPolicy = {
33
39
  enabled: true,
@@ -88,7 +94,10 @@ export const hlInfoOptions = (
88
94
  * upstream-controlled and normalise into `@lifi/perps-types` shapes before
89
95
  * surfacing.
90
96
  *
91
- * Non-2xx responses raise a {@link PerpsError} with the Hyperliquid provider key.
97
+ * Non-2xx responses raise a {@link PerpsError} tagged with the Hyperliquid
98
+ * provider key, carrying the code the status resolves to: `RateLimitExceeded`
99
+ * for a 429, `Unauthorized` for a 401, `AgentUnauthorized` for a 403, and
100
+ * `ThirdPartyError` for every other status.
92
101
  * @public
93
102
  */
94
103
  export async function infoRequest<T>(
@@ -124,7 +133,11 @@ export async function infoRequest<T>(
124
133
 
125
134
  if (!response.ok) {
126
135
  const err = new PerpsError(
127
- PerpsErrorCode.ThirdPartyError,
136
+ errorCodeFromStatus(
137
+ response.status,
138
+ PerpsErrorCode.ThirdPartyError,
139
+ HYPERLIQUID_STATUS_ERROR_CODES
140
+ ),
128
141
  `Hyperliquid info request failed: ${response.status}`
129
142
  )
130
143
  err.tool = PROVIDER_KEY
@@ -1,30 +1,55 @@
1
1
  import type { MarketContext } from '@lifi/perps-types'
2
- import type { HlWsPerpAssetCtx } from '../types/index.js'
2
+ import Big from 'big.js'
3
+ import type { HlWsFastAssetCtx, HlWsPerpAssetCtx } from '../types/index.js'
3
4
 
4
5
  const NEXT_FUNDING_INTERVAL_MS = 60 * 60 * 1000 // 1 hour
5
6
 
7
+ /**
8
+ * Convert venue base-unit open interest to a quote-asset notional. A mark that
9
+ * is not a positive number leaves the notional unknown rather than zero.
10
+ */
11
+ const toOpenInterestNotional = (
12
+ openInterest: string,
13
+ markPrice: string
14
+ ): string | undefined => {
15
+ try {
16
+ const mark = new Big(markPrice)
17
+ if (mark.lte(0)) {
18
+ return undefined
19
+ }
20
+ return new Big(openInterest).times(mark).toFixed()
21
+ } catch {
22
+ return undefined
23
+ }
24
+ }
25
+
6
26
  /**
7
27
  * Map a Hyperliquid perp asset context from the `allDexsAssetCtxs` feed to a
8
- * {@link MarketContext}. `midPx` is null when the book is empty; mid then falls
9
- * back to the mark so the context always carries a usable mid.
28
+ * {@link MarketContext}, with mid and mark overlaid from a `fastAssetCtxs`
29
+ * context where one is given. `midPx` is null when the book is empty; mid then
30
+ * falls back to the mark so the context always carries a usable mid. The
31
+ * open-interest notional follows the emitted mark, so it never mixes the two
32
+ * feeds' prices.
10
33
  * @public
11
34
  */
12
35
  export const mapMarketContext = (
13
36
  marketId: string,
14
- ctx: HlWsPerpAssetCtx
37
+ ctx: HlWsPerpAssetCtx,
38
+ fast?: HlWsFastAssetCtx
15
39
  ): MarketContext => {
16
40
  const now = Date.now()
17
41
  const nextFundingTime =
18
42
  Math.ceil(now / NEXT_FUNDING_INTERVAL_MS) * NEXT_FUNDING_INTERVAL_MS
43
+ const markPrice = fast?.markPx != null ? fast.markPx : ctx.markPx
19
44
 
20
45
  return {
21
46
  marketId,
22
- midPrice: ctx.midPx ?? ctx.markPx,
23
- markPrice: ctx.markPx,
47
+ midPrice: fast?.midPx != null ? fast.midPx : (ctx.midPx ?? ctx.markPx),
48
+ markPrice,
24
49
  oraclePrice: ctx.oraclePx,
25
50
  prevDayPrice: ctx.prevDayPx,
26
51
  volume24h: ctx.dayNtlVlm,
27
- openInterest: ctx.openInterest,
52
+ openInterest: toOpenInterestNotional(ctx.openInterest, markPrice),
28
53
  funding: {
29
54
  rate: ctx.funding,
30
55
  nextFundingTime,
@@ -1,4 +1,5 @@
1
1
  import type { Balance } from '@lifi/perps-types'
2
+ import { toWireBig } from './decimal.js'
2
3
 
3
4
  /**
4
5
  * Portfolio-margin collateral beyond the category quote assets, keyed by spot
@@ -37,6 +38,9 @@ export const partitionSpotBalances = (
37
38
  const collateralBalances: Balance[] = []
38
39
  const balances: Balance[] = []
39
40
  for (const balance of spotBalances) {
41
+ if (!toWireBig(balance.units, 'spotBalance.units').gt(0)) {
42
+ continue
43
+ }
40
44
  if (quoteAssetIds.has(balance.asset.id)) {
41
45
  collateralBalances.push(balance)
42
46
  continue
@@ -58,6 +58,7 @@ import type {
58
58
  HlWsUserFillsData,
59
59
  } from '../types/index.js'
60
60
  import { HlAbstractionMode } from '../types/index.js'
61
+ import { toWireBig } from '../utils/decimal.js'
61
62
  import {
62
63
  decodeCompressedJson,
63
64
  decodeFastAssetCtxs,
@@ -786,12 +787,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
786
787
  perpCtx = byMarketId[marketId] ?? perpCtx
787
788
  }
788
789
  if (perpCtx !== undefined) {
789
- const base = mapMarketContext(marketId, perpCtx)
790
- return {
791
- ...base,
792
- midPrice: fast?.midPx != null ? fast.midPx : base.midPrice,
793
- markPrice: fast?.markPx != null ? fast.markPx : base.markPrice,
794
- }
790
+ return mapMarketContext(marketId, perpCtx, fast)
795
791
  }
796
792
  const midPrice = fast?.midPx ?? fast?.markPx
797
793
  const markPrice = fast?.markPx ?? fast?.midPx
@@ -1254,10 +1250,18 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1254
1250
  const user = data.user.toLowerCase()
1255
1251
  const markets = this.registry?.activeMarkets ?? []
1256
1252
  const priceById = spotPriceById(markets, this.mergedMids())
1257
- const rows = data.spotState.balances.map((b) => ({
1258
- balance: spotBalance(spotAssetFromToken(b), b.total, priceById),
1259
- hold: b.hold,
1260
- }))
1253
+ const rows = data.spotState.balances
1254
+ .filter((balance) =>
1255
+ toWireBig(balance.total, 'spotState.balances.total').gt(0)
1256
+ )
1257
+ .map((balance) => ({
1258
+ balance: spotBalance(
1259
+ spotAssetFromToken(balance),
1260
+ balance.total,
1261
+ priceById
1262
+ ),
1263
+ hold: balance.hold,
1264
+ }))
1261
1265
  this.emit(`spotState:${user}`, {
1262
1266
  channel: 'spotBalances',
1263
1267
  data: rows.map(({ balance, hold }) => ({ ...balance, locked: hold })),