@lifi/perps-sdk-provider-hyperliquid 25.0.0 → 27.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 (245) 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 +18 -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 +8 -28
  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 +17 -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/index.d.ts +1 -1
  28. package/dist/cjs/utils/index.d.ts.map +1 -1
  29. package/dist/cjs/utils/index.js +3 -1
  30. package/dist/cjs/utils/index.js.map +1 -1
  31. package/dist/cjs/utils/liquidation.d.ts +5 -2
  32. package/dist/cjs/utils/liquidation.d.ts.map +1 -1
  33. package/dist/cjs/utils/liquidation.js +8 -8
  34. package/dist/cjs/utils/liquidation.js.map +1 -1
  35. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  36. package/dist/cjs/utils/mapActivity.js +38 -23
  37. package/dist/cjs/utils/mapActivity.js.map +1 -1
  38. package/dist/cjs/utils/mapFill.d.ts +1 -1
  39. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  40. package/dist/cjs/utils/mapFill.js +17 -6
  41. package/dist/cjs/utils/mapFill.js.map +1 -1
  42. package/dist/cjs/utils/mapMarket.d.ts.map +1 -1
  43. package/dist/cjs/utils/mapMarket.js +9 -13
  44. package/dist/cjs/utils/mapMarket.js.map +1 -1
  45. package/dist/cjs/utils/mapMarketContext.d.ts.map +1 -1
  46. package/dist/cjs/utils/mapMarketContext.js +3 -12
  47. package/dist/cjs/utils/mapMarketContext.js.map +1 -1
  48. package/dist/cjs/utils/mapOrder.d.ts +3 -1
  49. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  50. package/dist/cjs/utils/mapOrder.js +73 -19
  51. package/dist/cjs/utils/mapOrder.js.map +1 -1
  52. package/dist/cjs/utils/mapPosition.d.ts +1 -1
  53. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  54. package/dist/cjs/utils/mapPosition.js +41 -15
  55. package/dist/cjs/utils/mapPosition.js.map +1 -1
  56. package/dist/cjs/utils/rowTimestamp.d.ts +2 -0
  57. package/dist/cjs/utils/rowTimestamp.d.ts.map +1 -0
  58. package/dist/cjs/utils/rowTimestamp.js +14 -0
  59. package/dist/cjs/utils/rowTimestamp.js.map +1 -0
  60. package/dist/cjs/utils/spotBalance.d.ts +3 -3
  61. package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
  62. package/dist/cjs/utils/spotBalance.js +6 -5
  63. package/dist/cjs/utils/spotBalance.js.map +1 -1
  64. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  65. package/dist/cjs/utils/spotCollateral.js +3 -2
  66. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  67. package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
  68. package/dist/cjs/utils/transferMargin.js +16 -19
  69. package/dist/cjs/utils/transferMargin.js.map +1 -1
  70. package/dist/cjs/utils/venueTotals.d.ts +4 -5
  71. package/dist/cjs/utils/venueTotals.d.ts.map +1 -1
  72. package/dist/cjs/utils/venueTotals.js +18 -10
  73. package/dist/cjs/utils/venueTotals.js.map +1 -1
  74. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  75. package/dist/cjs/utils/withdrawableBalances.js +10 -10
  76. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  77. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +1 -0
  78. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  79. package/dist/cjs/websocket/HyperliquidWsProvider.js +144 -115
  80. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  81. package/dist/esm/accountSummary.d.ts +2 -1
  82. package/dist/esm/accountSummary.d.ts.map +1 -1
  83. package/dist/esm/accountSummary.js +11 -13
  84. package/dist/esm/accountSummary.js.map +1 -1
  85. package/dist/esm/services/getAccount.d.ts.map +1 -1
  86. package/dist/esm/services/getAccount.js +23 -21
  87. package/dist/esm/services/getAccount.js.map +1 -1
  88. package/dist/esm/services/getFills.d.ts.map +1 -1
  89. package/dist/esm/services/getFills.js +2 -1
  90. package/dist/esm/services/getFills.js.map +1 -1
  91. package/dist/esm/services/getMarketSettings.d.ts.map +1 -1
  92. package/dist/esm/services/getMarketSettings.js +2 -2
  93. package/dist/esm/services/getMarketSettings.js.map +1 -1
  94. package/dist/esm/services/getOrder.d.ts.map +1 -1
  95. package/dist/esm/services/getOrder.js +14 -4
  96. package/dist/esm/services/getOrder.js.map +1 -1
  97. package/dist/esm/services/getOrders.d.ts.map +1 -1
  98. package/dist/esm/services/getOrders.js +12 -37
  99. package/dist/esm/services/getOrders.js.map +1 -1
  100. package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -1
  101. package/dist/esm/services/getPortfolioHistory.js +19 -7
  102. package/dist/esm/services/getPortfolioHistory.js.map +1 -1
  103. package/dist/esm/services/getPositions.d.ts.map +1 -1
  104. package/dist/esm/services/getPositions.js +4 -1
  105. package/dist/esm/services/getPositions.js.map +1 -1
  106. package/dist/esm/services/getWithdrawableBalances.d.ts +1 -1
  107. package/dist/esm/services/getWithdrawableBalances.js +1 -1
  108. package/dist/esm/types/ws.d.ts +3 -2
  109. package/dist/esm/types/ws.d.ts.map +1 -1
  110. package/dist/esm/utils/index.d.ts +1 -1
  111. package/dist/esm/utils/index.d.ts.map +1 -1
  112. package/dist/esm/utils/index.js +1 -1
  113. package/dist/esm/utils/index.js.map +1 -1
  114. package/dist/esm/utils/liquidation.d.ts +23 -24
  115. package/dist/esm/utils/liquidation.d.ts.map +1 -1
  116. package/dist/esm/utils/liquidation.js +26 -31
  117. package/dist/esm/utils/liquidation.js.map +1 -1
  118. package/dist/esm/utils/mapActivity.d.ts +2 -1
  119. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  120. package/dist/esm/utils/mapActivity.js +41 -22
  121. package/dist/esm/utils/mapActivity.js.map +1 -1
  122. package/dist/esm/utils/mapFill.d.ts +3 -2
  123. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  124. package/dist/esm/utils/mapFill.js +21 -9
  125. package/dist/esm/utils/mapFill.js.map +1 -1
  126. package/dist/esm/utils/mapMarket.d.ts +2 -2
  127. package/dist/esm/utils/mapMarket.d.ts.map +1 -1
  128. package/dist/esm/utils/mapMarket.js +12 -13
  129. package/dist/esm/utils/mapMarket.js.map +1 -1
  130. package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
  131. package/dist/esm/utils/mapMarketContext.js +3 -9
  132. package/dist/esm/utils/mapMarketContext.js.map +1 -1
  133. package/dist/esm/utils/mapOrder.d.ts +12 -2
  134. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  135. package/dist/esm/utils/mapOrder.js +81 -17
  136. package/dist/esm/utils/mapOrder.js.map +1 -1
  137. package/dist/esm/utils/mapPosition.d.ts +4 -2
  138. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  139. package/dist/esm/utils/mapPosition.js +44 -13
  140. package/dist/esm/utils/mapPosition.js.map +1 -1
  141. package/dist/esm/utils/rowTimestamp.d.ts +6 -0
  142. package/dist/esm/utils/rowTimestamp.d.ts.map +1 -0
  143. package/dist/esm/utils/rowTimestamp.js +14 -0
  144. package/dist/esm/utils/rowTimestamp.js.map +1 -0
  145. package/dist/esm/utils/spotBalance.d.ts +5 -5
  146. package/dist/esm/utils/spotBalance.d.ts.map +1 -1
  147. package/dist/esm/utils/spotBalance.js +9 -8
  148. package/dist/esm/utils/spotBalance.js.map +1 -1
  149. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  150. package/dist/esm/utils/spotCollateral.js +3 -2
  151. package/dist/esm/utils/spotCollateral.js.map +1 -1
  152. package/dist/esm/utils/transferMargin.d.ts +2 -1
  153. package/dist/esm/utils/transferMargin.d.ts.map +1 -1
  154. package/dist/esm/utils/transferMargin.js +19 -18
  155. package/dist/esm/utils/transferMargin.js.map +1 -1
  156. package/dist/esm/utils/venueTotals.d.ts +7 -6
  157. package/dist/esm/utils/venueTotals.d.ts.map +1 -1
  158. package/dist/esm/utils/venueTotals.js +21 -8
  159. package/dist/esm/utils/venueTotals.js.map +1 -1
  160. package/dist/esm/utils/withdrawableBalances.d.ts +6 -4
  161. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  162. package/dist/esm/utils/withdrawableBalances.js +17 -15
  163. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  164. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +3 -0
  165. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  166. package/dist/esm/websocket/HyperliquidWsProvider.js +155 -115
  167. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  168. package/dist/types/accountSummary.d.ts +2 -1
  169. package/dist/types/accountSummary.d.ts.map +1 -1
  170. package/dist/types/services/getAccount.d.ts.map +1 -1
  171. package/dist/types/services/getFills.d.ts.map +1 -1
  172. package/dist/types/services/getMarketSettings.d.ts.map +1 -1
  173. package/dist/types/services/getOrder.d.ts.map +1 -1
  174. package/dist/types/services/getOrders.d.ts.map +1 -1
  175. package/dist/types/services/getPortfolioHistory.d.ts.map +1 -1
  176. package/dist/types/services/getPositions.d.ts.map +1 -1
  177. package/dist/types/services/getWithdrawableBalances.d.ts +1 -1
  178. package/dist/types/types/ws.d.ts +3 -2
  179. package/dist/types/types/ws.d.ts.map +1 -1
  180. package/dist/types/utils/index.d.ts +1 -1
  181. package/dist/types/utils/index.d.ts.map +1 -1
  182. package/dist/types/utils/liquidation.d.ts +23 -24
  183. package/dist/types/utils/liquidation.d.ts.map +1 -1
  184. package/dist/types/utils/mapActivity.d.ts +2 -1
  185. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  186. package/dist/types/utils/mapFill.d.ts +3 -2
  187. package/dist/types/utils/mapFill.d.ts.map +1 -1
  188. package/dist/types/utils/mapMarket.d.ts +2 -2
  189. package/dist/types/utils/mapMarket.d.ts.map +1 -1
  190. package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
  191. package/dist/types/utils/mapOrder.d.ts +12 -2
  192. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  193. package/dist/types/utils/mapPosition.d.ts +4 -2
  194. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  195. package/dist/types/utils/rowTimestamp.d.ts +6 -0
  196. package/dist/types/utils/rowTimestamp.d.ts.map +1 -0
  197. package/dist/types/utils/spotBalance.d.ts +5 -5
  198. package/dist/types/utils/spotBalance.d.ts.map +1 -1
  199. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  200. package/dist/types/utils/transferMargin.d.ts +2 -1
  201. package/dist/types/utils/transferMargin.d.ts.map +1 -1
  202. package/dist/types/utils/venueTotals.d.ts +7 -6
  203. package/dist/types/utils/venueTotals.d.ts.map +1 -1
  204. package/dist/types/utils/withdrawableBalances.d.ts +6 -4
  205. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  206. package/dist/types/websocket/HyperliquidWsProvider.d.ts +3 -0
  207. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  208. package/package.json +3 -3
  209. package/src/HyperliquidProvider.ts +1 -1
  210. package/src/accountSummary.ts +35 -23
  211. package/src/services/getAccount.ts +50 -33
  212. package/src/services/getFills.ts +2 -1
  213. package/src/services/getMarketSettings.ts +6 -2
  214. package/src/services/getOrder.ts +18 -7
  215. package/src/services/getOrders.ts +14 -46
  216. package/src/services/getPortfolioHistory.ts +34 -7
  217. package/src/services/getPositions.ts +4 -3
  218. package/src/services/getWithdrawableBalances.ts +1 -1
  219. package/src/types/ws.ts +3 -2
  220. package/src/utils/index.ts +2 -0
  221. package/src/utils/liquidation.ts +46 -34
  222. package/src/utils/mapActivity.ts +48 -20
  223. package/src/utils/mapFill.ts +25 -9
  224. package/src/utils/mapMarket.ts +16 -12
  225. package/src/utils/mapMarketContext.ts +6 -8
  226. package/src/utils/mapOrder.ts +107 -20
  227. package/src/utils/mapPosition.ts +72 -14
  228. package/src/utils/rowTimestamp.ts +15 -0
  229. package/src/utils/spotBalance.ts +19 -16
  230. package/src/utils/spotCollateral.ts +15 -2
  231. package/src/utils/transferMargin.ts +40 -18
  232. package/src/utils/venueTotals.ts +51 -17
  233. package/src/utils/withdrawableBalances.ts +33 -15
  234. package/src/websocket/HyperliquidWsProvider.ts +224 -133
  235. package/dist/cjs/utils/decimal.d.ts +0 -3
  236. package/dist/cjs/utils/decimal.d.ts.map +0 -1
  237. package/dist/cjs/utils/decimal.js +0 -19
  238. package/dist/cjs/utils/decimal.js.map +0 -1
  239. package/dist/esm/utils/decimal.d.ts +0 -10
  240. package/dist/esm/utils/decimal.d.ts.map +0 -1
  241. package/dist/esm/utils/decimal.js +0 -19
  242. package/dist/esm/utils/decimal.js.map +0 -1
  243. package/dist/types/utils/decimal.d.ts +0 -10
  244. package/dist/types/utils/decimal.d.ts.map +0 -1
  245. package/src/utils/decimal.ts +0 -21
@@ -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,35 @@ 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(PROVIDER_KEY, row, 'timestamp', timestamp, {
57
+ expected: 'timestamp',
58
+ })
59
+ return []
60
+ }
61
+ return [[timestamp, value]]
62
+ })
63
+
43
64
  /**
44
65
  * Join the window's two series on their timestamps. `pnlHistory` is cumulative
45
66
  * from the window's start, so a point with no PnL sample carries the last
46
67
  * sampled value — `'0'` before the first one.
47
68
  */
48
69
  const joinWindow = (
49
- window: HlPortfolioWindow,
70
+ accountValueHistory: [number, DecimalString][],
71
+ pnlHistory: [number, DecimalString][],
50
72
  period: HlPortfolioPeriod
51
73
  ): PortfolioHistoryPoint[] => {
52
- const pnlByTimestamp = new Map(window.pnlHistory)
74
+ const pnlByTimestamp = new Map(pnlHistory)
53
75
  let matched = 0
54
76
  let cumulative = '0'
55
- const points = window.accountValueHistory.map(([timestamp, accountValue]) => {
77
+ const points = accountValueHistory.map(([timestamp, accountValue]) => {
56
78
  const sample = pnlByTimestamp.get(timestamp)
57
79
  if (sample !== undefined) {
58
80
  matched += 1
@@ -60,7 +82,7 @@ const joinWindow = (
60
82
  }
61
83
  return { timestamp, accountValue, pnl: cumulative }
62
84
  })
63
- if (points.length > 0 && window.pnlHistory.length > 0 && matched === 0) {
85
+ if (points.length > 0 && pnlHistory.length > 0 && matched === 0) {
64
86
  throw new PerpsError(
65
87
  PerpsErrorCode.ThirdPartyError,
66
88
  `Hyperliquid portfolio window '${period}' samples PnL on no ` +
@@ -111,10 +133,15 @@ export async function getPortfolioHistory(
111
133
  )
112
134
  }
113
135
 
136
+ const pnlHistory = validSamples(window.pnlHistory, 'portfolio PnL sample')
114
137
  return {
115
138
  range: params.range,
116
- points: joinWindow(window, period),
139
+ points: joinWindow(
140
+ validSamples(window.accountValueHistory, 'portfolio value sample'),
141
+ pnlHistory,
142
+ period
143
+ ),
117
144
  volume: window.vlm,
118
- totalPnl: window.pnlHistory.at(-1)?.[1],
145
+ totalPnl: pnlHistory.at(-1)?.[1],
119
146
  }
120
147
  }
@@ -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
  /**
@@ -27,6 +27,8 @@ export { hlInfoOptions, infoRequest } from './infoClient.js'
27
27
  export {
28
28
  calculateLiquidationPrice,
29
29
  calculateMaintenanceMarginRate,
30
+ safeCalculateLiquidationPrice,
31
+ safeCalculateMaintenanceMarginRate,
30
32
  } from './liquidation.js'
31
33
  export {
32
34
  mapFundingActivity,
@@ -12,63 +12,75 @@
12
12
  * - margin_available = isolated_margin - maintenance_margin_required
13
13
  */
14
14
 
15
- import { estimateLiquidationPrice } from '@lifi/perps-sdk'
15
+ import {
16
+ createSafeFunction,
17
+ divideDecimalString,
18
+ estimateLiquidationPrice,
19
+ numberToDecimalString,
20
+ PerpsError,
21
+ } from '@lifi/perps-sdk'
22
+ import { type DecimalString, PerpsErrorCode } from '@lifi/perps-types'
16
23
 
17
24
  /**
18
- * Calculate the maintenance margin fraction for a Hyperliquid asset.
19
- *
20
- * Maintenance margin is half of the initial margin at max leverage.
25
+ * Maintenance margin fraction for a Hyperliquid asset: half of the initial
26
+ * margin at max leverage, `1 / (2 × maxLeverage)`.
21
27
  *
22
28
  * @param maxLeverage - Maximum leverage for the asset (e.g., 50 for BTC)
23
- * @returns Maintenance margin fraction (e.g., 0.01 for 50x max leverage),
24
- * or undefined if maxLeverage is zero
29
+ * @returns Maintenance margin fraction (`'0.01'` for 50x max leverage)
30
+ * @throws {PerpsError} `ValidationError` when `maxLeverage` is not a finite
31
+ * number above zero.
25
32
  * @public
26
33
  */
27
34
  export function calculateMaintenanceMarginRate(
28
35
  maxLeverage: number
29
- ): number | undefined {
30
- if (maxLeverage === 0) {
31
- return undefined
36
+ ): DecimalString {
37
+ if (!Number.isFinite(maxLeverage) || maxLeverage <= 0) {
38
+ throw new PerpsError(
39
+ PerpsErrorCode.ValidationError,
40
+ `\`maxLeverage\` must be a finite number above zero, got ${maxLeverage}.`
41
+ )
32
42
  }
33
- return 1 / (2 * maxLeverage)
43
+ return divideDecimalString('1', numberToDecimalString(2 * maxLeverage))
34
44
  }
35
45
 
46
+ /** @public */
47
+ export const safeCalculateMaintenanceMarginRate = createSafeFunction(
48
+ 'calculateMaintenanceMarginRate',
49
+ calculateMaintenanceMarginRate
50
+ )
51
+
36
52
  /**
37
- * Calculate liquidation price using the exact Hyperliquid formula.
38
- *
39
- * For isolated margin, new position prediction. For existing positions,
40
- * prefer Position.liquidationPrice from the API.
53
+ * Liquidation price of a new isolated position with the exact Hyperliquid
54
+ * formula. For an existing position, prefer `Position.liquidationPrice` from
55
+ * the API.
41
56
  *
42
57
  * Formula derivation (isolated margin, single position):
43
- * margin_per_unit = entryPrice / leverage
44
- * maintenance_per_unit = entryPrice * mmr
45
- * margin_available = margin_per_unit - maintenance_per_unit
46
- * liq_price = entryPrice - side * margin_available / (1 - mmr * side)
58
+ * margin_available = entryPrice × (1 / leverage - mmr)
59
+ * liq_price = entryPrice - side × margin_available / (1 - mmr × side)
47
60
  *
48
- * Where mmr = 1 / (2 * maxLeverage).
49
- *
50
- * @param entryPrice - Position entry price
51
- * @param leverage - User-selected leverage (e.g., 10)
52
- * @param isLong - True if long position, false if short
53
- * @param maxLeverage - Asset's maximum leverage (e.g., 50 for BTC). Determines
54
- * the maintenance margin rate: mmr = 1 / (2 * maxLeverage)
55
- * @returns Estimated liquidation price, or undefined if inputs are invalid
61
+ * @param leverage - User-selected leverage (e.g., `'10'`)
62
+ * @param maxLeverage - Asset's maximum leverage (e.g., 50 for BTC). Sets
63
+ * mmr = 1 / (2 × maxLeverage).
64
+ * @throws {PerpsError} `ValidationError` when `maxLeverage` is not a finite
65
+ * number above zero, or on an input that `estimateLiquidationPrice` rejects.
56
66
  * @public
57
67
  */
58
68
  export function calculateLiquidationPrice(
59
- entryPrice: number,
60
- leverage: number,
69
+ entryPrice: string,
70
+ leverage: string,
61
71
  isLong: boolean,
62
72
  maxLeverage: number
63
- ): number | undefined {
64
- const mmr = calculateMaintenanceMarginRate(maxLeverage)
65
- if (mmr === undefined) {
66
- return undefined
67
- }
73
+ ): string {
68
74
  return estimateLiquidationPrice({
69
75
  entryPrice,
70
76
  leverage,
71
77
  isLong,
72
- maintenanceMarginRate: mmr,
78
+ maintenanceMarginRate: calculateMaintenanceMarginRate(maxLeverage),
73
79
  })
74
80
  }
81
+
82
+ /** @public */
83
+ export const safeCalculateLiquidationPrice = createSafeFunction(
84
+ 'calculateLiquidationPrice',
85
+ calculateLiquidationPrice
86
+ )
@@ -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,14 @@ 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, {
106
+ expected: 'timestamp',
107
+ })
108
+ return null
103
109
  }
110
+ const base = { id: entry.hash, provider: providerKey, timestamp }
104
111
 
105
112
  if (
106
113
  isSpotTransferDelta(delta) ||
@@ -315,7 +322,13 @@ export const mapFundingActivity = (
315
322
  if (market === undefined) {
316
323
  return null
317
324
  }
318
- const timestamp = new Date(entry.time).toISOString()
325
+ const timestamp = rowTimestampToIsoStringOrUndefined(entry.time)
326
+ if (timestamp === undefined) {
327
+ warnSkippedVenueRow(providerKey, 'funding', 'time', entry.time, {
328
+ expected: 'timestamp',
329
+ })
330
+ return null
331
+ }
319
332
  return {
320
333
  id: `funding:${entry.delta.coin}:${timestamp}`,
321
334
  provider: providerKey,
@@ -334,7 +347,8 @@ export const mapFundingActivity = (
334
347
  * parties, so only fills whose `liquidatedUser` is the queried address count.
335
348
  * A fill whose hash a ledger liquidation row already carries is the same
336
349
  * 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.
350
+ * fill closes a long. Groups whose market or time does not resolve are
351
+ * dropped; an invalid fill size or price leaves the derived field absent.
338
352
  * @public
339
353
  */
340
354
  export const mapLiquidationFills = (
@@ -363,26 +377,40 @@ export const mapLiquidationFills = (
363
377
  if (market === undefined) {
364
378
  return []
365
379
  }
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)
380
+ const size = group.reduce<string | undefined>(
381
+ (sum, fill) =>
382
+ sum === undefined ? undefined : safeAddDecimalString(sum, fill.sz),
383
+ '0'
384
+ )
385
+ const notional = group.reduce<string | undefined>((sum, fill) => {
386
+ const fillNotional = safeMultiplyDecimalString(fill.px, fill.sz)
387
+ return sum === undefined || fillNotional === undefined
388
+ ? undefined
389
+ : safeAddDecimalString(sum, fillNotional)
390
+ }, '0')
391
+ const time = Math.max(...group.map((fill) => fill.time))
392
+ const timestamp = rowTimestampToIsoStringOrUndefined(time)
393
+ if (timestamp === undefined) {
394
+ warnSkippedVenueRow(providerKey, 'liquidation', 'time', time, {
395
+ expected: 'timestamp',
396
+ })
397
+ return []
373
398
  }
399
+ const closedSize =
400
+ size === undefined || first.side === 'A'
401
+ ? size
402
+ : safeSubtractDecimalString('0', size)
374
403
  return [
375
404
  {
376
405
  id: `liquidation:${oid}`,
377
406
  provider: providerKey,
378
- timestamp: new Date(time).toISOString(),
407
+ timestamp,
379
408
  type: ActivityType.LIQUIDATION,
380
- liquidatedNotionalPosition: notional.toFixed(),
409
+ ...(notional === undefined
410
+ ? {}
411
+ : { liquidatedNotionalPosition: notional }),
381
412
  liquidatedPositions: [
382
- {
383
- market,
384
- size: (first.side === 'A' ? size : size.neg()).toFixed(),
385
- },
413
+ { market, ...(closedSize === undefined ? {} : { size: closedSize }) },
386
414
  ],
387
415
  } satisfies LiquidationActivity,
388
416
  ]
@@ -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,35 @@ 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, {
35
+ marketId: market.id,
36
+ expected: 'timestamp',
37
+ })
38
+ return undefined
39
+ }
25
40
  // HL charges the builder portion in the same token as the total fee.
26
41
  const feeAsset = fill.feeToken ?? market.quoteAsset.displaySymbol
42
+ const side = fill.side === 'B' ? OrderSide.BUY : OrderSide.SELL
27
43
 
28
44
  return {
29
45
  ...(market.categoryId === SPOT_MARKET_ID
@@ -33,16 +49,16 @@ export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => {
33
49
  orderId: String(fill.oid),
34
50
  clientOrderId: fill.cloid,
35
51
  market,
36
- side: fill.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
52
+ side,
37
53
  // HL fills don't carry the originating order type. A maker fill (crossed:
38
54
  // false) can only come from a resting order, so it's necessarily a limit;
39
55
  // a taker fill (crossed: true) may be a market OR an aggressive limit order,
40
56
  // which the payload can't distinguish, so the type is left undefined.
41
57
  type: fill.crossed ? undefined : OrderType.LIMIT,
42
- size: fill.sz,
58
+ size,
43
59
  price: fill.px,
44
60
  liquidity: fill.crossed ? LiquidityRole.TAKER : LiquidityRole.MAKER,
45
- filledSize: fill.sz,
61
+ filledSize: size,
46
62
  fee: {
47
63
  amount: fill.fee,
48
64
  asset: feeAsset,
@@ -55,10 +71,10 @@ export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => {
55
71
  explorerLink: explorerTxUrl(ExplorerChainId.HYPERLIQUID, fill.hash),
56
72
  classification:
57
73
  market.categoryId === SPOT_MARKET_ID
58
- ? fill.side === 'B'
74
+ ? side === OrderSide.BUY
59
75
  ? FillClassification.SPOT_BUY
60
76
  : FillClassification.SPOT_SELL
61
- : classifyFillFromPosition(fill.startPosition, fill.side, fill.sz),
62
- createdAt: new Date(fill.time).toISOString(),
77
+ : classifyFillFromPosition(fill.startPosition, side, size),
78
+ createdAt,
63
79
  }
64
80
  }
@@ -1,16 +1,16 @@
1
+ import { safeMultiplyDecimalString } from '@lifi/perps-sdk'
1
2
  import type { PerpsMarket } from '@lifi/perps-types'
2
3
  import { PositionMarginAdjustment } from '@lifi/perps-types'
3
- import Big from 'big.js'
4
4
  import { PROVIDER_KEY } from '../constants.js'
5
5
  import type { HlMaxMarketOrderNtls, HlUniverseItem } from '../types/index.js'
6
- import { calculateMaintenanceMarginRate } from './liquidation.js'
6
+ import { safeCalculateMaintenanceMarginRate } from './liquidation.js'
7
7
  import { coinAsset } from './marketDisplay.js'
8
8
  import { getMaxPriceDecimals } from './orderFormatting.js'
9
9
 
10
10
  /** Hyperliquid quotes every perp market in USDC. */
11
11
  const QUOTE_SYMBOL = 'USDC'
12
12
  /** Hyperliquid documents the limit-order notional cap as 10x the market-order cap. */
13
- const LIMIT_ORDER_VALUE_MULTIPLIER = 10
13
+ const LIMIT_ORDER_VALUE_MULTIPLIER = '10'
14
14
 
15
15
  type MarketOrderLimits = Pick<
16
16
  PerpsMarket,
@@ -28,19 +28,21 @@ const mapMarketOrderLimits = (
28
28
  if (selected === undefined) {
29
29
  return {}
30
30
  }
31
- const maxMarketOrderUsd = new Big(selected[1])
32
- return {
33
- maxMarketOrderUsd: maxMarketOrderUsd.toFixed(),
34
- maxLimitOrderUsd: maxMarketOrderUsd
35
- .times(LIMIT_ORDER_VALUE_MULTIPLIER)
36
- .toFixed(),
31
+ const [, maxMarketOrderUsd] = selected
32
+ const maxLimitOrderUsd = safeMultiplyDecimalString(
33
+ maxMarketOrderUsd,
34
+ LIMIT_ORDER_VALUE_MULTIPLIER
35
+ )
36
+ if (maxLimitOrderUsd === undefined) {
37
+ return {}
37
38
  }
39
+ return { maxMarketOrderUsd, maxLimitOrderUsd }
38
40
  }
39
41
 
40
42
  /**
41
43
  * Map Hyperliquid universe metadata and optional `maxMarketOrderNtls` tiers to
42
- * a {@link PerpsMarket}. Missing tiers leave both order caps unset. Live stats
43
- * stay on {@link MarketContext}; callers supply the provider category id
44
+ * a {@link PerpsMarket}. Missing tiers or an invalid cap leave both order caps
45
+ * unset. Live stats stay on {@link MarketContext}; callers supply the provider category id
44
46
  * because the coin string does not contain it.
45
47
  * @public
46
48
  */
@@ -70,5 +72,7 @@ export const mapMarket = (
70
72
  (universe.marginMode === undefined && universe.onlyIsolated !== true)
71
73
  ? PositionMarginAdjustment.ADD_AND_REMOVE
72
74
  : PositionMarginAdjustment.ADD_ONLY,
73
- maintenanceMarginRate: calculateMaintenanceMarginRate(universe.maxLeverage),
75
+ maintenanceMarginRate: safeCalculateMaintenanceMarginRate(
76
+ universe.maxLeverage
77
+ ),
74
78
  })
@@ -1,5 +1,8 @@
1
+ import {
2
+ safeIsDecimalStringGreaterThan,
3
+ safeMultiplyDecimalString,
4
+ } from '@lifi/perps-sdk'
1
5
  import type { MarketContext } from '@lifi/perps-types'
2
- import Big from 'big.js'
3
6
  import type { HlWsFastAssetCtx, HlWsPerpAssetCtx } from '../types/index.js'
4
7
 
5
8
  const NEXT_FUNDING_INTERVAL_MS = 60 * 60 * 1000 // 1 hour
@@ -12,15 +15,10 @@ const toOpenInterestNotional = (
12
15
  openInterest: string,
13
16
  markPrice: string
14
17
  ): 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 {
18
+ if (safeIsDecimalStringGreaterThan(markPrice, '0') !== true) {
22
19
  return undefined
23
20
  }
21
+ return safeMultiplyDecimalString(openInterest, markPrice)
24
22
  }
25
23
 
26
24
  /**