@lifi/perps-sdk-provider-hyperliquid 12.0.0 → 13.0.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 (112) hide show
  1. package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
  2. package/dist/cjs/HyperliquidProvider.js +2 -0
  3. package/dist/cjs/HyperliquidProvider.js.map +1 -1
  4. package/dist/cjs/index.d.ts +2 -0
  5. package/dist/cjs/index.d.ts.map +1 -1
  6. package/dist/cjs/index.js +3 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  9. package/dist/cjs/services/getAccount.js +9 -6
  10. package/dist/cjs/services/getAccount.js.map +1 -1
  11. package/dist/cjs/services/getPortfolioHistory.d.ts +10 -0
  12. package/dist/cjs/services/getPortfolioHistory.d.ts.map +1 -0
  13. package/dist/cjs/services/getPortfolioHistory.js +59 -0
  14. package/dist/cjs/services/getPortfolioHistory.js.map +1 -0
  15. package/dist/cjs/types/index.d.ts +1 -0
  16. package/dist/cjs/types/index.d.ts.map +1 -1
  17. package/dist/cjs/types/index.js +1 -0
  18. package/dist/cjs/types/index.js.map +1 -1
  19. package/dist/cjs/types/portfolio.d.ts +9 -0
  20. package/dist/cjs/types/portfolio.d.ts.map +1 -0
  21. package/dist/cjs/types/portfolio.js +3 -0
  22. package/dist/cjs/types/portfolio.js.map +1 -0
  23. package/dist/cjs/utils/abstractionMode.d.ts +3 -0
  24. package/dist/cjs/utils/abstractionMode.d.ts.map +1 -0
  25. package/dist/cjs/utils/abstractionMode.js +8 -0
  26. package/dist/cjs/utils/abstractionMode.js.map +1 -0
  27. package/dist/cjs/utils/decimal.d.ts +3 -0
  28. package/dist/cjs/utils/decimal.d.ts.map +1 -0
  29. package/dist/cjs/utils/decimal.js +19 -0
  30. package/dist/cjs/utils/decimal.js.map +1 -0
  31. package/dist/cjs/utils/mapMarketContext.d.ts +2 -2
  32. package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
  33. package/dist/cjs/utils/mapMarketContext.js +21 -4
  34. package/dist/cjs/utils/mapMarketContext.js.map +1 -1
  35. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  36. package/dist/cjs/utils/spotCollateral.js +4 -0
  37. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  38. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  39. package/dist/cjs/websocket/HyperliquidWsProvider.js +8 -10
  40. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  41. package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
  42. package/dist/esm/HyperliquidProvider.js +2 -0
  43. package/dist/esm/HyperliquidProvider.js.map +1 -1
  44. package/dist/esm/index.d.ts +2 -0
  45. package/dist/esm/index.d.ts.map +1 -1
  46. package/dist/esm/index.js +1 -0
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/services/getAccount.d.ts.map +1 -1
  49. package/dist/esm/services/getAccount.js +9 -6
  50. package/dist/esm/services/getAccount.js.map +1 -1
  51. package/dist/esm/services/getPortfolioHistory.d.ts +18 -0
  52. package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -0
  53. package/dist/esm/services/getPortfolioHistory.js +71 -0
  54. package/dist/esm/services/getPortfolioHistory.js.map +1 -0
  55. package/dist/esm/types/index.d.ts +1 -0
  56. package/dist/esm/types/index.d.ts.map +1 -1
  57. package/dist/esm/types/index.js +1 -0
  58. package/dist/esm/types/index.js.map +1 -1
  59. package/dist/esm/types/portfolio.d.ts +17 -0
  60. package/dist/esm/types/portfolio.d.ts.map +1 -0
  61. package/dist/esm/types/portfolio.js +3 -0
  62. package/dist/esm/types/portfolio.js.map +1 -0
  63. package/dist/esm/utils/abstractionMode.d.ts +8 -0
  64. package/dist/esm/utils/abstractionMode.d.ts.map +1 -0
  65. package/dist/esm/utils/abstractionMode.js +9 -0
  66. package/dist/esm/utils/abstractionMode.js.map +1 -0
  67. package/dist/esm/utils/decimal.d.ts +10 -0
  68. package/dist/esm/utils/decimal.d.ts.map +1 -0
  69. package/dist/esm/utils/decimal.js +19 -0
  70. package/dist/esm/utils/decimal.js.map +1 -0
  71. package/dist/esm/utils/mapMarketContext.d.ts +7 -4
  72. package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
  73. package/dist/esm/utils/mapMarketContext.js +27 -6
  74. package/dist/esm/utils/mapMarketContext.js.map +1 -1
  75. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  76. package/dist/esm/utils/spotCollateral.js +4 -0
  77. package/dist/esm/utils/spotCollateral.js.map +1 -1
  78. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +1 -1
  79. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  80. package/dist/esm/websocket/HyperliquidWsProvider.js +9 -11
  81. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  82. package/dist/types/HyperliquidProvider.d.ts.map +1 -1
  83. package/dist/types/index.d.ts +2 -0
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/types/services/getAccount.d.ts.map +1 -1
  86. package/dist/types/services/getPortfolioHistory.d.ts +18 -0
  87. package/dist/types/services/getPortfolioHistory.d.ts.map +1 -0
  88. package/dist/types/types/index.d.ts +1 -0
  89. package/dist/types/types/index.d.ts.map +1 -1
  90. package/dist/types/types/portfolio.d.ts +17 -0
  91. package/dist/types/types/portfolio.d.ts.map +1 -0
  92. package/dist/types/utils/abstractionMode.d.ts +8 -0
  93. package/dist/types/utils/abstractionMode.d.ts.map +1 -0
  94. package/dist/types/utils/decimal.d.ts +10 -0
  95. package/dist/types/utils/decimal.d.ts.map +1 -0
  96. package/dist/types/utils/mapMarketContext.d.ts +7 -4
  97. package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
  98. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  99. package/dist/types/websocket/HyperliquidWsProvider.d.ts +1 -1
  100. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  101. package/package.json +3 -3
  102. package/src/HyperliquidProvider.ts +13 -0
  103. package/src/index.ts +2 -0
  104. package/src/services/getAccount.ts +11 -9
  105. package/src/services/getPortfolioHistory.ts +118 -0
  106. package/src/types/index.ts +1 -0
  107. package/src/types/portfolio.ts +29 -0
  108. package/src/utils/abstractionMode.ts +12 -0
  109. package/src/utils/decimal.ts +21 -0
  110. package/src/utils/mapMarketContext.ts +32 -7
  111. package/src/utils/spotCollateral.ts +4 -0
  112. package/src/websocket/HyperliquidWsProvider.ts +22 -15
@@ -14,6 +14,7 @@ import {
14
14
  type ProviderGetMarketSettingsParams,
15
15
  type ProviderGetOrderParams,
16
16
  type ProviderGetOrdersParams,
17
+ type ProviderGetPortfolioHistoryParams,
17
18
  type ProviderGetPositionsParams,
18
19
  type ProviderGetQuoteParams,
19
20
  type ProviderGetRunningTwapsParams,
@@ -38,6 +39,7 @@ import {
38
39
  PerpsErrorCode,
39
40
  type PerpsMarket,
40
41
  PerpsSigner,
42
+ type PortfolioHistoryResponse,
41
43
  type Position,
42
44
  type PositionsResponse,
43
45
  type ProviderAction,
@@ -63,6 +65,7 @@ import { getFills } from './services/getFills.js'
63
65
  import { getMarketSettings } from './services/getMarketSettings.js'
64
66
  import { getOrder } from './services/getOrder.js'
65
67
  import { getOrders } from './services/getOrders.js'
68
+ import { getPortfolioHistory } from './services/getPortfolioHistory.js'
66
69
  import { getPositions } from './services/getPositions.js'
67
70
  import { getRunningTwaps } from './services/getRunningTwaps.js'
68
71
  import {
@@ -387,6 +390,16 @@ export function hyperliquidProvider(
387
390
  opts
388
391
  ),
389
392
 
393
+ getPortfolioHistory: (
394
+ params: ProviderGetPortfolioHistoryParams,
395
+ opts?: SDKRequestOptions
396
+ ): Promise<PortfolioHistoryResponse> =>
397
+ getPortfolioHistory(
398
+ contextRef.require(),
399
+ { address: params.address, range: params.range },
400
+ opts
401
+ ),
402
+
390
403
  getQuote: (
391
404
  params: ProviderGetQuoteParams,
392
405
  opts?: SDKRequestOptions
package/src/index.ts CHANGED
@@ -35,6 +35,8 @@ export type { GetOrderParams } from './services/getOrder.js'
35
35
  export { getOrder } from './services/getOrder.js'
36
36
  export type { GetOrdersParams } from './services/getOrders.js'
37
37
  export { getOrders } from './services/getOrders.js'
38
+ export type { GetPortfolioHistoryParams } from './services/getPortfolioHistory.js'
39
+ export { getPortfolioHistory } from './services/getPortfolioHistory.js'
38
40
  export type { GetPositionsParams } from './services/getPositions.js'
39
41
  export { getPositions } from './services/getPositions.js'
40
42
  export type { GetRunningTwapsParams } from './services/getRunningTwaps.js'
@@ -13,6 +13,7 @@ import type {
13
13
  HyperliquidAccountConfig,
14
14
  Position,
15
15
  } from '@lifi/perps-types'
16
+ import type Big from 'big.js'
16
17
  import { PROVIDER_KEY } from '../constants.js'
17
18
  import type { HyperliquidContext } from '../context.js'
18
19
  import {
@@ -22,6 +23,8 @@ import {
22
23
  type HlSpotClearinghouseState,
23
24
  type HlUserFees,
24
25
  } from '../types/index.js'
26
+ import { isUnifiedAbstraction } from '../utils/abstractionMode.js'
27
+ import { toWireBig } from '../utils/decimal.js'
25
28
  import {
26
29
  partitionSpotBalances,
27
30
  perpsDexNames,
@@ -42,8 +45,8 @@ export type GetAccountParams = ProviderGetAccountParams
42
45
  // `marginSummary` covers the whole account (cross AND isolated positions);
43
46
  // `crossMarginSummary` is the cross-only subset and would drop isolated
44
47
  // equity/margin.
45
- const getAccountValue = (state: HlClearinghouseState): number =>
46
- Number.parseFloat(state.marginSummary.accountValue)
48
+ const getAccountValue = (state: HlClearinghouseState): Big =>
49
+ toWireBig(state.marginSummary.accountValue, 'marginSummary.accountValue')
47
50
 
48
51
  const getTotalMarginUsed = (state: HlClearinghouseState): number =>
49
52
  Number.parseFloat(state.marginSummary.totalMarginUsed)
@@ -81,10 +84,6 @@ const getMarginUsed = (
81
84
  return getTotalMarginUsed(mainState).toString()
82
85
  }
83
86
 
84
- const isUnifiedMode = (abstraction: HlAbstractionMode | null): boolean =>
85
- abstraction === HlAbstractionMode.UNIFIED_ACCOUNT ||
86
- abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
87
-
88
87
  interface BalancePartition {
89
88
  balances: Balance[]
90
89
  collateralBalances: Balance[]
@@ -110,17 +109,20 @@ const buildBalances = (
110
109
  // double-count. Only disabled/dexAbstraction carry separate venue collateral.
111
110
  // `accountValue` is the dex's TOTAL equity: locked margin and unrealized
112
111
  // PnL are already included, so summaries must not add them on top.
113
- if (!isUnifiedMode(abstraction)) {
112
+ if (!isUnifiedAbstraction(abstraction)) {
114
113
  for (const [dex, state] of stateByDex) {
115
114
  const categoryId = dex || PROVIDER_KEY
116
115
  const value = getAccountValue(state)
116
+ if (!value.gt(0)) {
117
+ continue
118
+ }
117
119
  collateralBalances.push({
118
120
  categoryId,
119
121
  // Always present: every dex in `stateByDex` derives from `markets`,
120
122
  // which is what populates `quoteAssetByCategory`.
121
123
  asset: quoteAssetByCategory.get(categoryId)!,
122
- units: value.toString(),
123
- valueUsd: value.toString(),
124
+ units: value.toFixed(),
125
+ valueUsd: value.toFixed(),
124
126
  })
125
127
  }
126
128
  }
@@ -0,0 +1,118 @@
1
+ import { PerpsError, type SDKRequestOptions } from '@lifi/perps-sdk'
2
+ import {
3
+ PerpsErrorCode,
4
+ type PortfolioHistoryPoint,
5
+ type PortfolioHistoryRange,
6
+ type PortfolioHistoryResponse,
7
+ } from '@lifi/perps-types'
8
+ import type { Address } from 'viem'
9
+ import type { HyperliquidContext } from '../context.js'
10
+ import type {
11
+ HlAbstractionMode,
12
+ HlPortfolio,
13
+ HlPortfolioPeriod,
14
+ HlPortfolioWindow,
15
+ } from '../types/index.js'
16
+ import { isUnifiedAbstraction } from '../utils/abstractionMode.js'
17
+ import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
18
+
19
+ /** Parameters for {@link getPortfolioHistory}. @public */
20
+ export interface GetPortfolioHistoryParams {
21
+ address: Address
22
+ range: PortfolioHistoryRange
23
+ }
24
+
25
+ const COMBINED_PERIOD_BY_RANGE: Record<
26
+ PortfolioHistoryRange,
27
+ HlPortfolioPeriod
28
+ > = {
29
+ '24h': 'day',
30
+ '7d': 'week',
31
+ '30d': 'month',
32
+ all: 'allTime',
33
+ }
34
+
35
+ const PERP_PERIOD_BY_RANGE: Record<PortfolioHistoryRange, HlPortfolioPeriod> = {
36
+ '24h': 'perpDay',
37
+ '7d': 'perpWeek',
38
+ '30d': 'perpMonth',
39
+ all: 'perpAllTime',
40
+ }
41
+
42
+ /**
43
+ * Join the window's two series on their timestamps. `pnlHistory` is cumulative
44
+ * from the window's start, so a point with no PnL sample carries the last
45
+ * sampled value — `'0'` before the first one.
46
+ */
47
+ const joinWindow = (
48
+ window: HlPortfolioWindow,
49
+ period: HlPortfolioPeriod
50
+ ): PortfolioHistoryPoint[] => {
51
+ const pnlByTimestamp = new Map(window.pnlHistory)
52
+ let matched = 0
53
+ let cumulative = '0'
54
+ const points = window.accountValueHistory.map(([timestamp, accountValue]) => {
55
+ const sample = pnlByTimestamp.get(timestamp)
56
+ if (sample !== undefined) {
57
+ matched += 1
58
+ cumulative = sample
59
+ }
60
+ return { timestamp, accountValue, pnl: cumulative }
61
+ })
62
+ if (points.length > 0 && window.pnlHistory.length > 0 && matched === 0) {
63
+ throw new PerpsError(
64
+ PerpsErrorCode.ThirdPartyError,
65
+ `Hyperliquid portfolio window '${period}' samples PnL on no ` +
66
+ 'account-value timestamp.'
67
+ )
68
+ }
69
+ return points
70
+ }
71
+
72
+ /**
73
+ * Read the account's portfolio value and cumulative PnL for `params.range`
74
+ * from one `portfolio` info request. An account whose collateral lives
75
+ * per-dex reads the perps-only window; a unified or portfolio-margin account
76
+ * holds its collateral in spot, so it reads the combined window.
77
+ * @public
78
+ */
79
+ export async function getPortfolioHistory(
80
+ ctx: HyperliquidContext,
81
+ params: GetPortfolioHistoryParams,
82
+ options?: SDKRequestOptions
83
+ ): Promise<PortfolioHistoryResponse> {
84
+ const { client, apiUrl } = ctx
85
+ const infoOpts = hlInfoOptions(client, options)
86
+ const [portfolio, abstraction] = await Promise.all([
87
+ infoRequest<HlPortfolio>(
88
+ apiUrl,
89
+ { type: 'portfolio', user: params.address },
90
+ infoOpts
91
+ ),
92
+ // "Never set abstraction" is a successful 200 `null` body, not an error —
93
+ // so a fetch failure must propagate, never be coerced to `null` (which
94
+ // would silently read the combined window for a perps-only account).
95
+ infoRequest<HlAbstractionMode | null>(
96
+ apiUrl,
97
+ { type: 'userAbstraction', user: params.address },
98
+ infoOpts
99
+ ),
100
+ ])
101
+ const period = isUnifiedAbstraction(abstraction)
102
+ ? COMBINED_PERIOD_BY_RANGE[params.range]
103
+ : PERP_PERIOD_BY_RANGE[params.range]
104
+ const window = portfolio.find(([key]) => key === period)?.[1]
105
+ if (window === undefined) {
106
+ throw new PerpsError(
107
+ PerpsErrorCode.ThirdPartyError,
108
+ `Hyperliquid portfolio response has no '${period}' window.`
109
+ )
110
+ }
111
+
112
+ return {
113
+ range: params.range,
114
+ points: joinWindow(window, period),
115
+ volume: window.vlm,
116
+ totalPnl: window.pnlHistory.at(-1)?.[1],
117
+ }
118
+ }
@@ -8,4 +8,5 @@ export * from './asset.js'
8
8
  export * from './fill.js'
9
9
  export * from './ledger.js'
10
10
  export * from './order.js'
11
+ export * from './portfolio.js'
11
12
  export * from './ws.js'
@@ -0,0 +1,29 @@
1
+ // `portfolio` info response shapes.
2
+
3
+ /** `[Unix milliseconds, decimal string]` sample of one portfolio series. @public */
4
+ export type HlPortfolioSample = [number, string]
5
+
6
+ /**
7
+ * One window of the Hyperliquid `portfolio` info response. `vlm` is the
8
+ * traded volume in the window; `pnlHistory` is cumulative from its start.
9
+ * @public
10
+ */
11
+ export type HlPortfolioWindow = {
12
+ accountValueHistory: HlPortfolioSample[]
13
+ pnlHistory: HlPortfolioSample[]
14
+ vlm: string
15
+ }
16
+
17
+ /** Hyperliquid `portfolio` window keys. @public */
18
+ export type HlPortfolioPeriod =
19
+ | 'day'
20
+ | 'week'
21
+ | 'month'
22
+ | 'allTime'
23
+ | 'perpDay'
24
+ | 'perpWeek'
25
+ | 'perpMonth'
26
+ | 'perpAllTime'
27
+
28
+ /** Hyperliquid `portfolio` info response: a list of `[period, window]` pairs. @public */
29
+ export type HlPortfolio = [HlPortfolioPeriod, HlPortfolioWindow][]
@@ -0,0 +1,12 @@
1
+ import { HlAbstractionMode } from '../types/account.js'
2
+
3
+ /**
4
+ * Whether the mode holds the account's collateral in spot. Unified and
5
+ * portfolio-margin accounts do; every other mode keeps perps collateral
6
+ * per-dex, apart from the spot balances.
7
+ */
8
+ export const isUnifiedAbstraction = (
9
+ abstraction: HlAbstractionMode | null
10
+ ): boolean =>
11
+ abstraction === HlAbstractionMode.UNIFIED_ACCOUNT ||
12
+ abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
@@ -0,0 +1,21 @@
1
+ import { PerpsError } from '@lifi/perps-sdk'
2
+ import { PerpsErrorCode } from '@lifi/perps-types'
3
+ import Big from 'big.js'
4
+
5
+ /**
6
+ * Exact-decimal parse of a Hyperliquid wire decimal string. `field` names the
7
+ * offending field on failure, so a venue contract breach is diagnosable
8
+ * instead of surfacing as an anonymous big.js throw or an `NaN` unit.
9
+ *
10
+ * @throws {PerpsError} `SDKError` when `value` is not a decimal numeric string.
11
+ */
12
+ export const toWireBig = (value: string, field: string): Big => {
13
+ try {
14
+ return new Big(value)
15
+ } catch {
16
+ throw new PerpsError(
17
+ PerpsErrorCode.SDKError,
18
+ `Hyperliquid field \`${field}\` is not a valid decimal: '${value}'`
19
+ )
20
+ }
21
+ }
@@ -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
@@ -13,6 +13,7 @@ import {
13
13
  toPerpsMarketDisplay,
14
14
  WsProviderBase,
15
15
  type WsProviderFactory,
16
+ type WsProviderFactoryParams,
16
17
  type WsStatusListener,
17
18
  wsLog,
18
19
  } from '@lifi/perps-sdk'
@@ -58,6 +59,7 @@ import type {
58
59
  HlWsUserFillsData,
59
60
  } from '../types/index.js'
60
61
  import { HlAbstractionMode } from '../types/index.js'
62
+ import { toWireBig } from '../utils/decimal.js'
61
63
  import {
62
64
  decodeCompressedJson,
63
65
  decodeFastAssetCtxs,
@@ -94,13 +96,15 @@ const normalizeHlAddress = (address: string): string =>
94
96
  *
95
97
  * @public
96
98
  */
97
- export const hyperliquidWsProvider =
98
- (): WsProviderFactory =>
99
- ({ provider, wsUrl, client }) =>
100
- new HyperliquidWsProvider(wsUrl, provider, client)
99
+ export const hyperliquidWsProvider = (): WsProviderFactory =>
100
+ Object.assign(
101
+ ({ provider, wsUrl, client }: WsProviderFactoryParams) =>
102
+ new HyperliquidWsProvider(wsUrl, provider, client),
103
+ { streamsCandles: true }
104
+ )
101
105
 
102
106
  /**
103
- * Hyperliquid realtime {@link WsProvider}: multiplexes markets context,
107
+ * Hyperliquid WebSocket {@link WsProvider}: multiplexes markets context,
104
108
  * positions, orders, fills and spot balances over a single
105
109
  * {@link ReconnectingWebSocket}.
106
110
  * Construct via {@link hyperliquidWsProvider}.
@@ -786,12 +790,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
786
790
  perpCtx = byMarketId[marketId] ?? perpCtx
787
791
  }
788
792
  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
- }
793
+ return mapMarketContext(marketId, perpCtx, fast)
795
794
  }
796
795
  const midPrice = fast?.midPx ?? fast?.markPx
797
796
  const markPrice = fast?.markPx ?? fast?.midPx
@@ -1254,10 +1253,18 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1254
1253
  const user = data.user.toLowerCase()
1255
1254
  const markets = this.registry?.activeMarkets ?? []
1256
1255
  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
- }))
1256
+ const rows = data.spotState.balances
1257
+ .filter((balance) =>
1258
+ toWireBig(balance.total, 'spotState.balances.total').gt(0)
1259
+ )
1260
+ .map((balance) => ({
1261
+ balance: spotBalance(
1262
+ spotAssetFromToken(balance),
1263
+ balance.total,
1264
+ priceById
1265
+ ),
1266
+ hold: balance.hold,
1267
+ }))
1261
1268
  this.emit(`spotState:${user}`, {
1262
1269
  channel: 'spotBalances',
1263
1270
  data: rows.map(({ balance, hold }) => ({ ...balance, locked: hold })),