@lifi/perps-sdk-provider-hyperliquid 24.0.0 → 26.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 (241) hide show
  1. package/dist/cjs/accountSummary.d.ts.map +1 -1
  2. package/dist/cjs/accountSummary.js +8 -14
  3. package/dist/cjs/accountSummary.js.map +1 -1
  4. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  5. package/dist/cjs/services/getAccount.js +19 -16
  6. package/dist/cjs/services/getAccount.js.map +1 -1
  7. package/dist/cjs/services/getFills.d.ts.map +1 -1
  8. package/dist/cjs/services/getFills.js +2 -1
  9. package/dist/cjs/services/getFills.js.map +1 -1
  10. package/dist/cjs/services/getMarketSettings.d.ts.map +1 -1
  11. package/dist/cjs/services/getMarketSettings.js +1 -1
  12. package/dist/cjs/services/getMarketSettings.js.map +1 -1
  13. package/dist/cjs/services/getOrder.d.ts.map +1 -1
  14. package/dist/cjs/services/getOrder.js +14 -4
  15. package/dist/cjs/services/getOrder.js.map +1 -1
  16. package/dist/cjs/services/getOrders.d.ts.map +1 -1
  17. package/dist/cjs/services/getOrders.js +3 -1
  18. package/dist/cjs/services/getOrders.js.map +1 -1
  19. package/dist/cjs/services/getPortfolioHistory.d.ts.map +1 -1
  20. package/dist/cjs/services/getPortfolioHistory.js +15 -6
  21. package/dist/cjs/services/getPortfolioHistory.js.map +1 -1
  22. package/dist/cjs/services/getPositions.d.ts.map +1 -1
  23. package/dist/cjs/services/getPositions.js +4 -1
  24. package/dist/cjs/services/getPositions.js.map +1 -1
  25. package/dist/cjs/types/ws.d.ts +3 -2
  26. package/dist/cjs/types/ws.d.ts.map +1 -1
  27. package/dist/cjs/utils/liquidation.d.ts +1 -1
  28. package/dist/cjs/utils/liquidation.d.ts.map +1 -1
  29. package/dist/cjs/utils/liquidation.js +3 -4
  30. package/dist/cjs/utils/liquidation.js.map +1 -1
  31. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  32. package/dist/cjs/utils/mapActivity.js +32 -23
  33. package/dist/cjs/utils/mapActivity.js.map +1 -1
  34. package/dist/cjs/utils/mapFill.d.ts +1 -1
  35. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  36. package/dist/cjs/utils/mapFill.js +14 -6
  37. package/dist/cjs/utils/mapFill.js.map +1 -1
  38. package/dist/cjs/utils/mapMarket.d.ts.map +1 -1
  39. package/dist/cjs/utils/mapMarket.js +8 -12
  40. package/dist/cjs/utils/mapMarket.js.map +1 -1
  41. package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
  42. package/dist/cjs/utils/mapMarketContext.js +3 -12
  43. package/dist/cjs/utils/mapMarketContext.js.map +1 -1
  44. package/dist/cjs/utils/mapOrder.d.ts +1 -1
  45. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  46. package/dist/cjs/utils/mapOrder.js +60 -18
  47. package/dist/cjs/utils/mapOrder.js.map +1 -1
  48. package/dist/cjs/utils/mapPosition.d.ts +1 -1
  49. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  50. package/dist/cjs/utils/mapPosition.js +40 -14
  51. package/dist/cjs/utils/mapPosition.js.map +1 -1
  52. package/dist/cjs/utils/orderFormatting.d.ts.map +1 -1
  53. package/dist/cjs/utils/orderFormatting.js +4 -5
  54. package/dist/cjs/utils/orderFormatting.js.map +1 -1
  55. package/dist/cjs/utils/rowTimestamp.d.ts +2 -0
  56. package/dist/cjs/utils/rowTimestamp.d.ts.map +1 -0
  57. package/dist/cjs/utils/rowTimestamp.js +14 -0
  58. package/dist/cjs/utils/rowTimestamp.js.map +1 -0
  59. package/dist/cjs/utils/spotBalance.d.ts +3 -3
  60. package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
  61. package/dist/cjs/utils/spotBalance.js +6 -5
  62. package/dist/cjs/utils/spotBalance.js.map +1 -1
  63. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  64. package/dist/cjs/utils/spotCollateral.js +3 -2
  65. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  66. package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
  67. package/dist/cjs/utils/transferMargin.js +15 -23
  68. package/dist/cjs/utils/transferMargin.js.map +1 -1
  69. package/dist/cjs/utils/venueTotals.d.ts +5 -5
  70. package/dist/cjs/utils/venueTotals.d.ts.map +1 -1
  71. package/dist/cjs/utils/venueTotals.js +10 -11
  72. package/dist/cjs/utils/venueTotals.js.map +1 -1
  73. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  74. package/dist/cjs/utils/withdrawableBalances.js +10 -10
  75. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  76. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  77. package/dist/cjs/websocket/HyperliquidWsProvider.js +109 -89
  78. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  79. package/dist/esm/accountSummary.d.ts +2 -1
  80. package/dist/esm/accountSummary.d.ts.map +1 -1
  81. package/dist/esm/accountSummary.js +11 -13
  82. package/dist/esm/accountSummary.js.map +1 -1
  83. package/dist/esm/services/getAccount.d.ts.map +1 -1
  84. package/dist/esm/services/getAccount.js +24 -21
  85. package/dist/esm/services/getAccount.js.map +1 -1
  86. package/dist/esm/services/getFills.d.ts.map +1 -1
  87. package/dist/esm/services/getFills.js +2 -1
  88. package/dist/esm/services/getFills.js.map +1 -1
  89. package/dist/esm/services/getMarketSettings.d.ts.map +1 -1
  90. package/dist/esm/services/getMarketSettings.js +2 -2
  91. package/dist/esm/services/getMarketSettings.js.map +1 -1
  92. package/dist/esm/services/getOrder.d.ts.map +1 -1
  93. package/dist/esm/services/getOrder.js +14 -4
  94. package/dist/esm/services/getOrder.js.map +1 -1
  95. package/dist/esm/services/getOrders.d.ts.map +1 -1
  96. package/dist/esm/services/getOrders.js +5 -2
  97. package/dist/esm/services/getOrders.js.map +1 -1
  98. package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -1
  99. package/dist/esm/services/getPortfolioHistory.js +17 -7
  100. package/dist/esm/services/getPortfolioHistory.js.map +1 -1
  101. package/dist/esm/services/getPositions.d.ts.map +1 -1
  102. package/dist/esm/services/getPositions.js +4 -1
  103. package/dist/esm/services/getPositions.js.map +1 -1
  104. package/dist/esm/services/getWithdrawableBalances.d.ts +1 -1
  105. package/dist/esm/services/getWithdrawableBalances.js +1 -1
  106. package/dist/esm/types/ws.d.ts +3 -2
  107. package/dist/esm/types/ws.d.ts.map +1 -1
  108. package/dist/esm/utils/liquidation.d.ts +1 -1
  109. package/dist/esm/utils/liquidation.d.ts.map +1 -1
  110. package/dist/esm/utils/liquidation.js +4 -5
  111. package/dist/esm/utils/liquidation.js.map +1 -1
  112. package/dist/esm/utils/mapActivity.d.ts +2 -1
  113. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  114. package/dist/esm/utils/mapActivity.js +35 -22
  115. package/dist/esm/utils/mapActivity.js.map +1 -1
  116. package/dist/esm/utils/mapFill.d.ts +3 -2
  117. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  118. package/dist/esm/utils/mapFill.js +18 -9
  119. package/dist/esm/utils/mapFill.js.map +1 -1
  120. package/dist/esm/utils/mapMarket.d.ts +2 -2
  121. package/dist/esm/utils/mapMarket.d.ts.map +1 -1
  122. package/dist/esm/utils/mapMarket.js +10 -11
  123. package/dist/esm/utils/mapMarket.js.map +1 -1
  124. package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
  125. package/dist/esm/utils/mapMarketContext.js +3 -9
  126. package/dist/esm/utils/mapMarketContext.js.map +1 -1
  127. package/dist/esm/utils/mapOrder.d.ts +5 -2
  128. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  129. package/dist/esm/utils/mapOrder.js +65 -17
  130. package/dist/esm/utils/mapOrder.js.map +1 -1
  131. package/dist/esm/utils/mapPosition.d.ts +4 -2
  132. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  133. package/dist/esm/utils/mapPosition.js +43 -12
  134. package/dist/esm/utils/mapPosition.js.map +1 -1
  135. package/dist/esm/utils/orderFormatting.d.ts.map +1 -1
  136. package/dist/esm/utils/orderFormatting.js +6 -6
  137. package/dist/esm/utils/orderFormatting.js.map +1 -1
  138. package/dist/esm/utils/rowTimestamp.d.ts +6 -0
  139. package/dist/esm/utils/rowTimestamp.d.ts.map +1 -0
  140. package/dist/esm/utils/rowTimestamp.js +14 -0
  141. package/dist/esm/utils/rowTimestamp.js.map +1 -0
  142. package/dist/esm/utils/spotBalance.d.ts +5 -5
  143. package/dist/esm/utils/spotBalance.d.ts.map +1 -1
  144. package/dist/esm/utils/spotBalance.js +9 -8
  145. package/dist/esm/utils/spotBalance.js.map +1 -1
  146. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  147. package/dist/esm/utils/spotCollateral.js +3 -2
  148. package/dist/esm/utils/spotCollateral.js.map +1 -1
  149. package/dist/esm/utils/transferMargin.d.ts +2 -1
  150. package/dist/esm/utils/transferMargin.d.ts.map +1 -1
  151. package/dist/esm/utils/transferMargin.js +18 -22
  152. package/dist/esm/utils/transferMargin.js.map +1 -1
  153. package/dist/esm/utils/venueTotals.d.ts +10 -5
  154. package/dist/esm/utils/venueTotals.d.ts.map +1 -1
  155. package/dist/esm/utils/venueTotals.js +13 -7
  156. package/dist/esm/utils/venueTotals.js.map +1 -1
  157. package/dist/esm/utils/withdrawableBalances.d.ts +6 -4
  158. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  159. package/dist/esm/utils/withdrawableBalances.js +17 -15
  160. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  161. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +2 -0
  162. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  163. package/dist/esm/websocket/HyperliquidWsProvider.js +119 -88
  164. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  165. package/dist/types/accountSummary.d.ts +2 -1
  166. package/dist/types/accountSummary.d.ts.map +1 -1
  167. package/dist/types/services/getAccount.d.ts.map +1 -1
  168. package/dist/types/services/getFills.d.ts.map +1 -1
  169. package/dist/types/services/getMarketSettings.d.ts.map +1 -1
  170. package/dist/types/services/getOrder.d.ts.map +1 -1
  171. package/dist/types/services/getOrders.d.ts.map +1 -1
  172. package/dist/types/services/getPortfolioHistory.d.ts.map +1 -1
  173. package/dist/types/services/getPositions.d.ts.map +1 -1
  174. package/dist/types/services/getWithdrawableBalances.d.ts +1 -1
  175. package/dist/types/types/ws.d.ts +3 -2
  176. package/dist/types/types/ws.d.ts.map +1 -1
  177. package/dist/types/utils/liquidation.d.ts +1 -1
  178. package/dist/types/utils/liquidation.d.ts.map +1 -1
  179. package/dist/types/utils/mapActivity.d.ts +2 -1
  180. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  181. package/dist/types/utils/mapFill.d.ts +3 -2
  182. package/dist/types/utils/mapFill.d.ts.map +1 -1
  183. package/dist/types/utils/mapMarket.d.ts +2 -2
  184. package/dist/types/utils/mapMarket.d.ts.map +1 -1
  185. package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
  186. package/dist/types/utils/mapOrder.d.ts +5 -2
  187. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  188. package/dist/types/utils/mapPosition.d.ts +4 -2
  189. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  190. package/dist/types/utils/orderFormatting.d.ts.map +1 -1
  191. package/dist/types/utils/rowTimestamp.d.ts +6 -0
  192. package/dist/types/utils/rowTimestamp.d.ts.map +1 -0
  193. package/dist/types/utils/spotBalance.d.ts +5 -5
  194. package/dist/types/utils/spotBalance.d.ts.map +1 -1
  195. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  196. package/dist/types/utils/transferMargin.d.ts +2 -1
  197. package/dist/types/utils/transferMargin.d.ts.map +1 -1
  198. package/dist/types/utils/venueTotals.d.ts +10 -5
  199. package/dist/types/utils/venueTotals.d.ts.map +1 -1
  200. package/dist/types/utils/withdrawableBalances.d.ts +6 -4
  201. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  202. package/dist/types/websocket/HyperliquidWsProvider.d.ts +2 -0
  203. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  204. package/package.json +3 -3
  205. package/src/HyperliquidProvider.ts +1 -1
  206. package/src/accountSummary.ts +35 -23
  207. package/src/services/getAccount.ts +50 -33
  208. package/src/services/getFills.ts +2 -1
  209. package/src/services/getMarketSettings.ts +6 -2
  210. package/src/services/getOrder.ts +18 -7
  211. package/src/services/getOrders.ts +6 -3
  212. package/src/services/getPortfolioHistory.ts +38 -7
  213. package/src/services/getPositions.ts +4 -3
  214. package/src/services/getWithdrawableBalances.ts +1 -1
  215. package/src/types/ws.ts +3 -2
  216. package/src/utils/liquidation.ts +14 -8
  217. package/src/utils/mapActivity.ts +42 -20
  218. package/src/utils/mapFill.ts +22 -9
  219. package/src/utils/mapMarket.ts +12 -10
  220. package/src/utils/mapMarketContext.ts +6 -8
  221. package/src/utils/mapOrder.ts +81 -20
  222. package/src/utils/mapPosition.ts +67 -13
  223. package/src/utils/orderFormatting.ts +9 -8
  224. package/src/utils/rowTimestamp.ts +15 -0
  225. package/src/utils/spotBalance.ts +19 -16
  226. package/src/utils/spotCollateral.ts +15 -2
  227. package/src/utils/transferMargin.ts +36 -20
  228. package/src/utils/venueTotals.ts +54 -13
  229. package/src/utils/withdrawableBalances.ts +33 -15
  230. package/src/websocket/HyperliquidWsProvider.ts +169 -102
  231. package/dist/cjs/utils/decimal.d.ts +0 -3
  232. package/dist/cjs/utils/decimal.d.ts.map +0 -1
  233. package/dist/cjs/utils/decimal.js +0 -19
  234. package/dist/cjs/utils/decimal.js.map +0 -1
  235. package/dist/esm/utils/decimal.d.ts +0 -10
  236. package/dist/esm/utils/decimal.d.ts.map +0 -1
  237. package/dist/esm/utils/decimal.js +0 -19
  238. package/dist/esm/utils/decimal.js.map +0 -1
  239. package/dist/types/utils/decimal.d.ts +0 -10
  240. package/dist/types/utils/decimal.d.ts.map +0 -1
  241. package/src/utils/decimal.ts +0 -21
@@ -2,20 +2,22 @@ import {
2
2
  calculateTransferable,
3
3
  getMarketRegistry,
4
4
  getMarketsContext,
5
+ isDecimalStringGreaterThan,
5
6
  type ProviderGetAccountParams,
6
- parseDecimal,
7
7
  type SDKRequestOptions,
8
+ subtractDecimalString,
8
9
  toPerpsMarketDisplay,
10
+ unknownToDecimalString,
9
11
  } from '@lifi/perps-sdk'
10
12
  import type {
11
13
  AccountResponse,
12
14
  Asset,
13
15
  Balance,
16
+ DecimalString,
14
17
  HyperliquidAccountConfig,
15
18
  HyperliquidDexAccountState,
16
19
  Position,
17
20
  } from '@lifi/perps-types'
18
- import type Big from 'big.js'
19
21
  import { PROVIDER_KEY } from '../constants.js'
20
22
  import type { HyperliquidContext } from '../context.js'
21
23
  import type {
@@ -26,19 +28,17 @@ import type {
26
28
  HlUserFees,
27
29
  } from '../types/index.js'
28
30
  import { isUnifiedAbstraction } from '../utils/abstractionMode.js'
29
- import { toWireBig } from '../utils/decimal.js'
30
31
  import {
31
32
  assetIsOutcome,
32
33
  partitionSpotBalances,
33
34
  perpsDexNames,
34
- perpsTotals,
35
35
  spotAssetFromToken,
36
36
  spotBalance,
37
37
  spotPriceById,
38
- sumUnrealizedPnl,
39
38
  } from '../utils/index.js'
40
39
  import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
41
40
  import { isOpenAssetPosition, mapPosition } from '../utils/mapPosition.js'
41
+ import { safeSumDecimalStrings } from '../utils/venueTotals.js'
42
42
  import { requireAccountExists } from './getAccountExists.js'
43
43
 
44
44
  /**
@@ -48,12 +48,6 @@ import { requireAccountExists } from './getAccountExists.js'
48
48
  */
49
49
  export type GetAccountParams = ProviderGetAccountParams
50
50
 
51
- // `marginSummary` covers the whole account (cross AND isolated positions);
52
- // `crossMarginSummary` is the cross-only subset and would drop isolated
53
- // equity/margin.
54
- const getAccountValue = (state: HlClearinghouseState): Big =>
55
- toWireBig(state.marginSummary.accountValue, 'marginSummary.accountValue')
56
-
57
51
  /** Venue buying power for the quote asset the perps dex settles in. */
58
52
  const getAvailableAfterMaintenance = (
59
53
  spotState: HlSpotClearinghouseState,
@@ -73,21 +67,28 @@ const buildBalances = (
73
67
  spotState: HlSpotClearinghouseState,
74
68
  stateByDex: Map<string, HlClearinghouseState>,
75
69
  quoteAssetIds: ReadonlySet<string>,
76
- priceById: Map<string, number>,
70
+ priceById: ReadonlyMap<string, DecimalString>,
77
71
  quoteAssetByCategory: Map<string, Asset>
78
72
  ): BalancePartition => {
79
73
  const { balances, collateralBalances } = partitionSpotBalances(
80
74
  spotState.balances
81
75
  .filter((b) => !assetIsOutcome(b.coin))
82
76
  .map((b) => {
83
- const total = toWireBig(b.total, 'spotClearinghouseState.total')
77
+ const total = unknownToDecimalString(
78
+ b.total,
79
+ 'spotClearinghouseState.total',
80
+ PROVIDER_KEY
81
+ )
82
+ const hold = unknownToDecimalString(
83
+ b.hold,
84
+ 'spotClearinghouseState.hold',
85
+ PROVIDER_KEY
86
+ )
84
87
  return {
85
- ...spotBalance(spotAssetFromToken(b), b.total, priceById),
88
+ ...spotBalance(spotAssetFromToken(b), total, priceById),
86
89
  transferable: calculateTransferable(
90
+ subtractDecimalString(total, hold),
87
91
  total
88
- .minus(toWireBig(b.hold, 'spotClearinghouseState.hold'))
89
- .toFixed(),
90
- total.toFixed()
91
92
  ),
92
93
  }
93
94
  }),
@@ -101,8 +102,15 @@ const buildBalances = (
101
102
  if (!isUnifiedAbstraction(abstraction)) {
102
103
  for (const [dex, state] of stateByDex) {
103
104
  const categoryId = dex || PROVIDER_KEY
104
- const value = getAccountValue(state)
105
- if (!value.gt(0)) {
105
+ // `marginSummary` covers the whole account (cross AND isolated positions);
106
+ // `crossMarginSummary` is the cross-only subset and would drop isolated
107
+ // equity/margin.
108
+ const value = unknownToDecimalString(
109
+ state.marginSummary.accountValue,
110
+ 'marginSummary.accountValue',
111
+ PROVIDER_KEY
112
+ )
113
+ if (!isDecimalStringGreaterThan(value, '0')) {
106
114
  continue
107
115
  }
108
116
  collateralBalances.push({
@@ -110,15 +118,16 @@ const buildBalances = (
110
118
  // Always present: every dex in `stateByDex` derives from `markets`,
111
119
  // which is what populates `quoteAssetByCategory`.
112
120
  asset: quoteAssetByCategory.get(categoryId)!,
113
- units: value.toFixed(),
114
- valueUsd: value.toFixed(),
121
+ units: value,
122
+ valueUsd: value,
115
123
  price: '1',
116
124
  transferable: calculateTransferable(
117
- toWireBig(
125
+ unknownToDecimalString(
118
126
  state.withdrawable,
119
- 'clearinghouseState.withdrawable'
120
- ).toFixed(),
121
- value.toFixed()
127
+ 'clearinghouseState.withdrawable',
128
+ PROVIDER_KEY
129
+ ),
130
+ value
122
131
  ),
123
132
  })
124
133
  }
@@ -209,9 +218,7 @@ export const getAccount = async (
209
218
 
210
219
  const priceById = spotPriceById(
211
220
  markets,
212
- new Map(
213
- prices.map((p) => [p.marketId, parseDecimal(p.markPrice) ?? Number.NaN])
214
- )
221
+ new Map(prices.map((p) => [p.marketId, p.markPrice]))
215
222
  )
216
223
 
217
224
  const positions: Position[] = stateResults.flatMap((state) =>
@@ -219,12 +226,13 @@ export const getAccount = async (
219
226
  .filter(
220
227
  (ap) => !assetIsOutcome(ap.position.coin) && isOpenAssetPosition(ap)
221
228
  )
222
- .map((ap) =>
223
- mapPosition(
229
+ .flatMap((ap) => {
230
+ const position = mapPosition(
224
231
  ap,
225
232
  toPerpsMarketDisplay(registry.require(ap.position.coin))
226
233
  )
227
- )
234
+ return position === undefined ? [] : [position]
235
+ })
228
236
  )
229
237
 
230
238
  const stateByDex = new Map<string, HlClearinghouseState>()
@@ -266,14 +274,23 @@ export const getAccount = async (
266
274
  quoteAssetByCategory
267
275
  )
268
276
 
277
+ const marginUsed = safeSumDecimalStrings(
278
+ dexStates.flatMap(({ marginSummary }) =>
279
+ marginSummary === undefined ? [] : [marginSummary.totalMarginUsed]
280
+ )
281
+ )
282
+ const unrealizedPnl = safeSumDecimalStrings(
283
+ positions.map((position) => position.unrealizedPnl)
284
+ )
285
+
269
286
  return {
270
287
  provider: PROVIDER_KEY,
271
288
  address: params.address,
272
289
  balances,
273
290
  collateralBalances,
274
291
  positions,
275
- marginUsed: perpsTotals(dexStates).marginUsed.toFixed(),
276
- unrealizedPnl: sumUnrealizedPnl(positions).toFixed(),
292
+ ...(marginUsed === undefined ? {} : { marginUsed }),
293
+ ...(unrealizedPnl === undefined ? {} : { unrealizedPnl }),
277
294
  feeTier: {
278
295
  maker: feesResult.userAddRate ?? '0',
279
296
  taker: feesResult.userCrossRate ?? '0',
@@ -67,7 +67,8 @@ export const getFills = async (
67
67
  return []
68
68
  }
69
69
  const market = registry.get(fill.coin)
70
- return market === undefined ? [] : [mapFill(fill, market)]
70
+ const mapped = market && mapFill(fill, market)
71
+ return mapped ? [mapped] : []
71
72
  })
72
73
  return { items, saturated: false }
73
74
  },
@@ -1,4 +1,8 @@
1
- import { PerpsError, type SDKRequestOptions } from '@lifi/perps-sdk'
1
+ import {
2
+ numberToDecimalString,
3
+ PerpsError,
4
+ type SDKRequestOptions,
5
+ } from '@lifi/perps-sdk'
2
6
  import {
3
7
  MarginMode,
4
8
  type MarketRef,
@@ -66,6 +70,6 @@ export const getMarketSettings = async (
66
70
  return {
67
71
  marginMode:
68
72
  leverage.type === 'isolated' ? MarginMode.ISOLATED : MarginMode.CROSS,
69
- leverage: leverage.value,
73
+ leverage: numberToDecimalString(leverage.value),
70
74
  }
71
75
  }
@@ -24,6 +24,15 @@ import { requireAccountExists } from './getAccountExists.js'
24
24
  */
25
25
  export type GetOrderParams = ProviderGetOrderParams
26
26
 
27
+ const orderNotFound = (id: string): PerpsError => {
28
+ const err = new PerpsError(
29
+ PerpsErrorCode.OrderNotFound,
30
+ `Order not found: ${id}`
31
+ )
32
+ err.tool = PROVIDER_KEY
33
+ return err
34
+ }
35
+
27
36
  /**
28
37
  * Read getOrder direct from the Hyperliquid REST API.
29
38
  *
@@ -70,17 +79,16 @@ export const getOrder = async (
70
79
  if (twap !== undefined) {
71
80
  const registry = getMarketRegistry(client, PROVIDER_KEY)
72
81
  await registry.sync()
73
- return mapOrder(twap, registry.require(twap.state.coin))
82
+ const order = mapOrder(twap, registry.require(twap.state.coin))
83
+ if (order === undefined) {
84
+ throw orderNotFound(params.id)
85
+ }
86
+ return order
74
87
  }
75
88
  }
76
89
 
77
90
  if (status.status !== 'order' || assetIsOutcome(status.order.order.coin)) {
78
- const err = new PerpsError(
79
- PerpsErrorCode.OrderNotFound,
80
- `Order not found: ${params.id}`
81
- )
82
- err.tool = PROVIDER_KEY
83
- throw err
91
+ throw orderNotFound(params.id)
84
92
  }
85
93
 
86
94
  const registry = getMarketRegistry(client, PROVIDER_KEY)
@@ -89,6 +97,9 @@ export const getOrder = async (
89
97
  status.order,
90
98
  registry.require(status.order.order.coin)
91
99
  )
100
+ if (order === undefined) {
101
+ throw orderNotFound(params.id)
102
+ }
92
103
  const [linked] = await withExplorerLinks([order], params.address, infoOpts)
93
104
  return linked
94
105
  }
@@ -39,7 +39,7 @@ const warnOnce = (key: string): void => {
39
39
  const mapRow = (
40
40
  coin: string,
41
41
  registry: MarketRegistry,
42
- map: (market: MarketDisplay) => Order
42
+ map: (market: MarketDisplay) => Order | undefined
43
43
  ): Order | undefined => {
44
44
  if (assetIsOutcome(coin)) {
45
45
  return undefined
@@ -114,14 +114,17 @@ export const getOrders = async (
114
114
  rows.set(key, order)
115
115
  }
116
116
  }
117
+ const seenHistorical = new Set<string>()
117
118
  for (const detail of historical) {
118
119
  const key = String(detail.order.oid)
119
120
  // The feed carries one row per lifecycle transition, newest first, and a
120
121
  // terminal row shares its timestamp with the `open` row beneath it, so
121
- // only the first row of an id states that order's current status.
122
- if (rows.has(key)) {
122
+ // only the first row of an id states that order's current status, even
123
+ // when the mapper skips that row.
124
+ if (seenHistorical.has(key)) {
123
125
  continue
124
126
  }
127
+ seenHistorical.add(key)
125
128
  keep(
126
129
  key,
127
130
  mapRow(detail.order.coin, registry, (market) => mapOrder(detail, market))
@@ -1,11 +1,17 @@
1
- import { PerpsError, type SDKRequestOptions } from '@lifi/perps-sdk'
2
1
  import {
2
+ PerpsError,
3
+ type SDKRequestOptions,
4
+ warnSkippedVenueRow,
5
+ } from '@lifi/perps-sdk'
6
+ import {
7
+ type DecimalString,
3
8
  PerpsErrorCode,
4
9
  type PortfolioHistoryPoint,
5
10
  type PortfolioHistoryRange,
6
11
  type PortfolioHistoryResponse,
7
12
  } from '@lifi/perps-types'
8
13
  import type { Address } from 'viem'
14
+ import { PROVIDER_KEY } from '../constants.js'
9
15
  import type { HyperliquidContext } from '../context.js'
10
16
  import type {
11
17
  HlAbstractionMode,
@@ -40,19 +46,39 @@ const PERP_PERIOD_BY_RANGE: Record<PortfolioHistoryRange, HlPortfolioPeriod> = {
40
46
  all: 'perpAllTime',
41
47
  }
42
48
 
49
+ /** The samples with a finite timestamp; each other sample is skipped. */
50
+ const validSamples = (
51
+ samples: HlPortfolioWindow['pnlHistory'],
52
+ row: string
53
+ ): [number, DecimalString][] =>
54
+ samples.flatMap(([timestamp, value]): [number, DecimalString][] => {
55
+ if (!Number.isFinite(timestamp)) {
56
+ warnSkippedVenueRow(
57
+ PROVIDER_KEY,
58
+ row,
59
+ 'timestamp',
60
+ timestamp,
61
+ 'timestamp'
62
+ )
63
+ return []
64
+ }
65
+ return [[timestamp, value]]
66
+ })
67
+
43
68
  /**
44
69
  * Join the window's two series on their timestamps. `pnlHistory` is cumulative
45
70
  * from the window's start, so a point with no PnL sample carries the last
46
71
  * sampled value — `'0'` before the first one.
47
72
  */
48
73
  const joinWindow = (
49
- window: HlPortfolioWindow,
74
+ accountValueHistory: [number, DecimalString][],
75
+ pnlHistory: [number, DecimalString][],
50
76
  period: HlPortfolioPeriod
51
77
  ): PortfolioHistoryPoint[] => {
52
- const pnlByTimestamp = new Map(window.pnlHistory)
78
+ const pnlByTimestamp = new Map(pnlHistory)
53
79
  let matched = 0
54
80
  let cumulative = '0'
55
- const points = window.accountValueHistory.map(([timestamp, accountValue]) => {
81
+ const points = accountValueHistory.map(([timestamp, accountValue]) => {
56
82
  const sample = pnlByTimestamp.get(timestamp)
57
83
  if (sample !== undefined) {
58
84
  matched += 1
@@ -60,7 +86,7 @@ const joinWindow = (
60
86
  }
61
87
  return { timestamp, accountValue, pnl: cumulative }
62
88
  })
63
- if (points.length > 0 && window.pnlHistory.length > 0 && matched === 0) {
89
+ if (points.length > 0 && pnlHistory.length > 0 && matched === 0) {
64
90
  throw new PerpsError(
65
91
  PerpsErrorCode.ThirdPartyError,
66
92
  `Hyperliquid portfolio window '${period}' samples PnL on no ` +
@@ -111,10 +137,15 @@ export async function getPortfolioHistory(
111
137
  )
112
138
  }
113
139
 
140
+ const pnlHistory = validSamples(window.pnlHistory, 'portfolio PnL sample')
114
141
  return {
115
142
  range: params.range,
116
- points: joinWindow(window, period),
143
+ points: joinWindow(
144
+ validSamples(window.accountValueHistory, 'portfolio value sample'),
145
+ pnlHistory,
146
+ period
147
+ ),
117
148
  volume: window.vlm,
118
- totalPnl: window.pnlHistory.at(-1)?.[1],
149
+ totalPnl: pnlHistory.at(-1)?.[1],
119
150
  }
120
151
  }
@@ -67,12 +67,13 @@ export const getPositions = async (
67
67
  .filter(
68
68
  (ap) => !assetIsOutcome(ap.position.coin) && isOpenAssetPosition(ap)
69
69
  )
70
- .map((ap) =>
71
- mapPosition(
70
+ .flatMap((ap) => {
71
+ const position = mapPosition(
72
72
  ap,
73
73
  toPerpsMarketDisplay(registry.require(ap.position.coin))
74
74
  )
75
- )
75
+ return position === undefined ? [] : [position]
76
+ })
76
77
  )
77
78
 
78
79
  if (params.marketId !== undefined) {
@@ -27,7 +27,7 @@ export type GetWithdrawableBalancesParams =
27
27
  ProviderGetWithdrawableBalancesParams
28
28
 
29
29
  /**
30
- * The `(asset, route)` pairs `params.address` can withdraw from Hyperliquid,
30
+ * The `(asset, category)` pairs `params.address` can withdraw from Hyperliquid,
31
31
  * read from the venue's own `withdrawable` and spot `total`/`hold` figures.
32
32
  * Hyperliquid charges its flat withdrawal fee in USDC, so the backend's
33
33
  * `withdrawalFeeUsd` is the fee in quote-asset units on every USDC row.
package/src/types/ws.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Hyperliquid WebSocket incoming message types.
2
2
 
3
+ import type { DecimalString } from '@lifi/perps-types'
3
4
  import type { HlClearinghouseState, HlSpotBalance } from './account.js'
4
5
  import type { HlCandle, HlL2Book } from './asset.js'
5
6
  import type { HlUserFill } from './fill.js'
@@ -77,8 +78,8 @@ export type HlWsMessage = {
77
78
  * @public
78
79
  */
79
80
  export type HlWsFastAssetCtx = {
80
- markPx?: string
81
- midPx?: string | null
81
+ markPx?: DecimalString
82
+ midPx?: DecimalString | null
82
83
  }
83
84
 
84
85
  /**
@@ -12,7 +12,11 @@
12
12
  * - margin_available = isolated_margin - maintenance_margin_required
13
13
  */
14
14
 
15
- import { estimateLiquidationPrice } from '@lifi/perps-sdk'
15
+ import {
16
+ divideDecimalString,
17
+ numberToDecimalString,
18
+ safeEstimateLiquidationPrice,
19
+ } from '@lifi/perps-sdk'
16
20
 
17
21
  /**
18
22
  * Calculate the maintenance margin fraction for a Hyperliquid asset.
@@ -56,19 +60,21 @@ export function calculateMaintenanceMarginRate(
56
60
  * @public
57
61
  */
58
62
  export function calculateLiquidationPrice(
59
- entryPrice: number,
60
- leverage: number,
63
+ entryPrice: string,
64
+ leverage: string,
61
65
  isLong: boolean,
62
66
  maxLeverage: number
63
- ): number | undefined {
64
- const mmr = calculateMaintenanceMarginRate(maxLeverage)
65
- if (mmr === undefined) {
67
+ ): string | undefined {
68
+ if (maxLeverage === 0) {
66
69
  return undefined
67
70
  }
68
- return estimateLiquidationPrice({
71
+ return safeEstimateLiquidationPrice({
69
72
  entryPrice,
70
73
  leverage,
71
74
  isLong,
72
- maintenanceMarginRate: mmr,
75
+ maintenanceMarginRate: divideDecimalString(
76
+ '1',
77
+ numberToDecimalString(2 * maxLeverage)
78
+ ),
73
79
  })
74
80
  }
@@ -3,6 +3,10 @@ import {
3
3
  ExplorerChainId,
4
4
  explorerTxUrl,
5
5
  PerpsError,
6
+ safeAddDecimalString,
7
+ safeMultiplyDecimalString,
8
+ safeSubtractDecimalString,
9
+ warnSkippedVenueRow,
6
10
  } from '@lifi/perps-sdk'
7
11
  import type {
8
12
  ActivityItem,
@@ -16,7 +20,6 @@ import type {
16
20
  WithdrawalActivity,
17
21
  } from '@lifi/perps-types'
18
22
  import { ActivityType, PerpsErrorCode } from '@lifi/perps-types'
19
- import Big from 'big.js'
20
23
  import { zeroHash } from 'viem'
21
24
  import type {
22
25
  HlBorrowLendOperation,
@@ -35,6 +38,7 @@ import {
35
38
  isVaultTransferDelta,
36
39
  isWithdrawDelta,
37
40
  } from '../types/index.js'
41
+ import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
38
42
 
39
43
  /**
40
44
  * Hyperliquid settles every perp deposit and withdrawal in USDC, and charges
@@ -96,11 +100,12 @@ export const mapLedgerEntry = (
96
100
  resolveMarket: (coin: string) => MarketDisplay | undefined
97
101
  ): ActivityItem | null => {
98
102
  const { delta } = entry
99
- const base = {
100
- id: entry.hash,
101
- provider: providerKey,
102
- timestamp: new Date(entry.time).toISOString(),
103
+ const timestamp = rowTimestampToIsoStringOrUndefined(entry.time)
104
+ if (timestamp === undefined) {
105
+ warnSkippedVenueRow(providerKey, 'ledger', 'time', entry.time, 'timestamp')
106
+ return null
103
107
  }
108
+ const base = { id: entry.hash, provider: providerKey, timestamp }
104
109
 
105
110
  if (
106
111
  isSpotTransferDelta(delta) ||
@@ -315,7 +320,11 @@ export const mapFundingActivity = (
315
320
  if (market === undefined) {
316
321
  return null
317
322
  }
318
- const timestamp = new Date(entry.time).toISOString()
323
+ const timestamp = rowTimestampToIsoStringOrUndefined(entry.time)
324
+ if (timestamp === undefined) {
325
+ warnSkippedVenueRow(providerKey, 'funding', 'time', entry.time, 'timestamp')
326
+ return null
327
+ }
319
328
  return {
320
329
  id: `funding:${entry.delta.coin}:${timestamp}`,
321
330
  provider: providerKey,
@@ -334,7 +343,8 @@ export const mapFundingActivity = (
334
343
  * parties, so only fills whose `liquidatedUser` is the queried address count.
335
344
  * A fill whose hash a ledger liquidation row already carries is the same
336
345
  * event and is skipped. The size sign follows the closed position: a sell
337
- * fill closes a long. Groups whose market does not resolve are dropped.
346
+ * fill closes a long. Groups whose market or time does not resolve are
347
+ * dropped; an invalid fill size or price leaves the derived field absent.
338
348
  * @public
339
349
  */
340
350
  export const mapLiquidationFills = (
@@ -363,26 +373,38 @@ export const mapLiquidationFills = (
363
373
  if (market === undefined) {
364
374
  return []
365
375
  }
366
- let size = new Big(0)
367
- let notional = new Big(0)
368
- let time = first.time
369
- for (const fill of group) {
370
- size = size.plus(fill.sz)
371
- notional = notional.plus(new Big(fill.px).times(fill.sz))
372
- time = Math.max(time, fill.time)
376
+ const size = group.reduce<string | undefined>(
377
+ (sum, fill) =>
378
+ sum === undefined ? undefined : safeAddDecimalString(sum, fill.sz),
379
+ '0'
380
+ )
381
+ const notional = group.reduce<string | undefined>((sum, fill) => {
382
+ const fillNotional = safeMultiplyDecimalString(fill.px, fill.sz)
383
+ return sum === undefined || fillNotional === undefined
384
+ ? undefined
385
+ : safeAddDecimalString(sum, fillNotional)
386
+ }, '0')
387
+ const time = Math.max(...group.map((fill) => fill.time))
388
+ const timestamp = rowTimestampToIsoStringOrUndefined(time)
389
+ if (timestamp === undefined) {
390
+ warnSkippedVenueRow(providerKey, 'liquidation', 'time', time, 'timestamp')
391
+ return []
373
392
  }
393
+ const closedSize =
394
+ size === undefined || first.side === 'A'
395
+ ? size
396
+ : safeSubtractDecimalString('0', size)
374
397
  return [
375
398
  {
376
399
  id: `liquidation:${oid}`,
377
400
  provider: providerKey,
378
- timestamp: new Date(time).toISOString(),
401
+ timestamp,
379
402
  type: ActivityType.LIQUIDATION,
380
- liquidatedNotionalPosition: notional.toFixed(),
403
+ ...(notional === undefined
404
+ ? {}
405
+ : { liquidatedNotionalPosition: notional }),
381
406
  liquidatedPositions: [
382
- {
383
- market,
384
- size: (first.side === 'A' ? size : size.neg()).toFixed(),
385
- },
407
+ { market, ...(closedSize === undefined ? {} : { size: closedSize }) },
386
408
  ],
387
409
  } satisfies LiquidationActivity,
388
410
  ]
@@ -2,6 +2,7 @@ import {
2
2
  classifyFillFromPosition,
3
3
  ExplorerChainId,
4
4
  explorerTxUrl,
5
+ warnSkippedVenueRow,
5
6
  } from '@lifi/perps-sdk'
6
7
  import type { Fill, MarketDisplay } from '@lifi/perps-types'
7
8
  import {
@@ -10,20 +11,32 @@ import {
10
11
  OrderSide,
11
12
  OrderType,
12
13
  } from '@lifi/perps-types'
13
- import { SPOT_MARKET_ID } from '../constants.js'
14
+ import { PROVIDER_KEY, SPOT_MARKET_ID } from '../constants.js'
14
15
  import type { HlUserFill } from '../types/index.js'
16
+ import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
15
17
 
16
18
  /** Re-export the shared fill-position classifier used by Hyperliquid mappings. @public */
17
19
  export { classifyFillFromPosition }
18
20
 
19
21
  /**
20
22
  * Spot-ness comes from the resolved market's category, not the coin string —
21
- * HL's canonical spot pair 0 is addressed as `PURR/USDC`, not `@0`.
23
+ * HL's canonical spot pair 0 is addressed as `PURR/USDC`, not `@0`. A fill with
24
+ * an invalid time gives `undefined`.
22
25
  * @public
23
26
  */
24
- export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => {
27
+ export const mapFill = (
28
+ fill: HlUserFill,
29
+ market: MarketDisplay
30
+ ): Fill | undefined => {
31
+ const size = fill.sz
32
+ const createdAt = rowTimestampToIsoStringOrUndefined(fill.time)
33
+ if (createdAt === undefined) {
34
+ warnSkippedVenueRow(PROVIDER_KEY, 'fill', 'time', fill.time, 'timestamp')
35
+ return undefined
36
+ }
25
37
  // HL charges the builder portion in the same token as the total fee.
26
38
  const feeAsset = fill.feeToken ?? market.quoteAsset.displaySymbol
39
+ const side = fill.side === 'B' ? OrderSide.BUY : OrderSide.SELL
27
40
 
28
41
  return {
29
42
  ...(market.categoryId === SPOT_MARKET_ID
@@ -33,16 +46,16 @@ export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => {
33
46
  orderId: String(fill.oid),
34
47
  clientOrderId: fill.cloid,
35
48
  market,
36
- side: fill.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
49
+ side,
37
50
  // HL fills don't carry the originating order type. A maker fill (crossed:
38
51
  // false) can only come from a resting order, so it's necessarily a limit;
39
52
  // a taker fill (crossed: true) may be a market OR an aggressive limit order,
40
53
  // which the payload can't distinguish, so the type is left undefined.
41
54
  type: fill.crossed ? undefined : OrderType.LIMIT,
42
- size: fill.sz,
55
+ size,
43
56
  price: fill.px,
44
57
  liquidity: fill.crossed ? LiquidityRole.TAKER : LiquidityRole.MAKER,
45
- filledSize: fill.sz,
58
+ filledSize: size,
46
59
  fee: {
47
60
  amount: fill.fee,
48
61
  asset: feeAsset,
@@ -55,10 +68,10 @@ export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => {
55
68
  explorerLink: explorerTxUrl(ExplorerChainId.HYPERLIQUID, fill.hash),
56
69
  classification:
57
70
  market.categoryId === SPOT_MARKET_ID
58
- ? fill.side === 'B'
71
+ ? side === OrderSide.BUY
59
72
  ? FillClassification.SPOT_BUY
60
73
  : FillClassification.SPOT_SELL
61
- : classifyFillFromPosition(fill.startPosition, fill.side, fill.sz),
62
- createdAt: new Date(fill.time).toISOString(),
74
+ : classifyFillFromPosition(fill.startPosition, side, size),
75
+ createdAt,
63
76
  }
64
77
  }