@lifi/perps-sdk-provider-hyperliquid 25.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 (233) 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/rowTimestamp.d.ts +2 -0
  53. package/dist/cjs/utils/rowTimestamp.d.ts.map +1 -0
  54. package/dist/cjs/utils/rowTimestamp.js +14 -0
  55. package/dist/cjs/utils/rowTimestamp.js.map +1 -0
  56. package/dist/cjs/utils/spotBalance.d.ts +3 -3
  57. package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
  58. package/dist/cjs/utils/spotBalance.js +6 -5
  59. package/dist/cjs/utils/spotBalance.js.map +1 -1
  60. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  61. package/dist/cjs/utils/spotCollateral.js +3 -2
  62. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  63. package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
  64. package/dist/cjs/utils/transferMargin.js +16 -19
  65. package/dist/cjs/utils/transferMargin.js.map +1 -1
  66. package/dist/cjs/utils/venueTotals.d.ts +5 -5
  67. package/dist/cjs/utils/venueTotals.d.ts.map +1 -1
  68. package/dist/cjs/utils/venueTotals.js +10 -11
  69. package/dist/cjs/utils/venueTotals.js.map +1 -1
  70. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  71. package/dist/cjs/utils/withdrawableBalances.js +10 -10
  72. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  73. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  74. package/dist/cjs/websocket/HyperliquidWsProvider.js +109 -89
  75. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  76. package/dist/esm/accountSummary.d.ts +2 -1
  77. package/dist/esm/accountSummary.d.ts.map +1 -1
  78. package/dist/esm/accountSummary.js +11 -13
  79. package/dist/esm/accountSummary.js.map +1 -1
  80. package/dist/esm/services/getAccount.d.ts.map +1 -1
  81. package/dist/esm/services/getAccount.js +24 -21
  82. package/dist/esm/services/getAccount.js.map +1 -1
  83. package/dist/esm/services/getFills.d.ts.map +1 -1
  84. package/dist/esm/services/getFills.js +2 -1
  85. package/dist/esm/services/getFills.js.map +1 -1
  86. package/dist/esm/services/getMarketSettings.d.ts.map +1 -1
  87. package/dist/esm/services/getMarketSettings.js +2 -2
  88. package/dist/esm/services/getMarketSettings.js.map +1 -1
  89. package/dist/esm/services/getOrder.d.ts.map +1 -1
  90. package/dist/esm/services/getOrder.js +14 -4
  91. package/dist/esm/services/getOrder.js.map +1 -1
  92. package/dist/esm/services/getOrders.d.ts.map +1 -1
  93. package/dist/esm/services/getOrders.js +5 -2
  94. package/dist/esm/services/getOrders.js.map +1 -1
  95. package/dist/esm/services/getPortfolioHistory.d.ts.map +1 -1
  96. package/dist/esm/services/getPortfolioHistory.js +17 -7
  97. package/dist/esm/services/getPortfolioHistory.js.map +1 -1
  98. package/dist/esm/services/getPositions.d.ts.map +1 -1
  99. package/dist/esm/services/getPositions.js +4 -1
  100. package/dist/esm/services/getPositions.js.map +1 -1
  101. package/dist/esm/services/getWithdrawableBalances.d.ts +1 -1
  102. package/dist/esm/services/getWithdrawableBalances.js +1 -1
  103. package/dist/esm/types/ws.d.ts +3 -2
  104. package/dist/esm/types/ws.d.ts.map +1 -1
  105. package/dist/esm/utils/liquidation.d.ts +1 -1
  106. package/dist/esm/utils/liquidation.d.ts.map +1 -1
  107. package/dist/esm/utils/liquidation.js +4 -5
  108. package/dist/esm/utils/liquidation.js.map +1 -1
  109. package/dist/esm/utils/mapActivity.d.ts +2 -1
  110. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  111. package/dist/esm/utils/mapActivity.js +35 -22
  112. package/dist/esm/utils/mapActivity.js.map +1 -1
  113. package/dist/esm/utils/mapFill.d.ts +3 -2
  114. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  115. package/dist/esm/utils/mapFill.js +18 -9
  116. package/dist/esm/utils/mapFill.js.map +1 -1
  117. package/dist/esm/utils/mapMarket.d.ts +2 -2
  118. package/dist/esm/utils/mapMarket.d.ts.map +1 -1
  119. package/dist/esm/utils/mapMarket.js +10 -11
  120. package/dist/esm/utils/mapMarket.js.map +1 -1
  121. package/dist/esm/utils/mapMarketContext.d.ts.map +1 -1
  122. package/dist/esm/utils/mapMarketContext.js +3 -9
  123. package/dist/esm/utils/mapMarketContext.js.map +1 -1
  124. package/dist/esm/utils/mapOrder.d.ts +5 -2
  125. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  126. package/dist/esm/utils/mapOrder.js +65 -17
  127. package/dist/esm/utils/mapOrder.js.map +1 -1
  128. package/dist/esm/utils/mapPosition.d.ts +4 -2
  129. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  130. package/dist/esm/utils/mapPosition.js +43 -12
  131. package/dist/esm/utils/mapPosition.js.map +1 -1
  132. package/dist/esm/utils/rowTimestamp.d.ts +6 -0
  133. package/dist/esm/utils/rowTimestamp.d.ts.map +1 -0
  134. package/dist/esm/utils/rowTimestamp.js +14 -0
  135. package/dist/esm/utils/rowTimestamp.js.map +1 -0
  136. package/dist/esm/utils/spotBalance.d.ts +5 -5
  137. package/dist/esm/utils/spotBalance.d.ts.map +1 -1
  138. package/dist/esm/utils/spotBalance.js +9 -8
  139. package/dist/esm/utils/spotBalance.js.map +1 -1
  140. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  141. package/dist/esm/utils/spotCollateral.js +3 -2
  142. package/dist/esm/utils/spotCollateral.js.map +1 -1
  143. package/dist/esm/utils/transferMargin.d.ts +2 -1
  144. package/dist/esm/utils/transferMargin.d.ts.map +1 -1
  145. package/dist/esm/utils/transferMargin.js +19 -18
  146. package/dist/esm/utils/transferMargin.js.map +1 -1
  147. package/dist/esm/utils/venueTotals.d.ts +10 -5
  148. package/dist/esm/utils/venueTotals.d.ts.map +1 -1
  149. package/dist/esm/utils/venueTotals.js +13 -7
  150. package/dist/esm/utils/venueTotals.js.map +1 -1
  151. package/dist/esm/utils/withdrawableBalances.d.ts +6 -4
  152. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  153. package/dist/esm/utils/withdrawableBalances.js +17 -15
  154. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  155. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +2 -0
  156. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  157. package/dist/esm/websocket/HyperliquidWsProvider.js +119 -88
  158. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  159. package/dist/types/accountSummary.d.ts +2 -1
  160. package/dist/types/accountSummary.d.ts.map +1 -1
  161. package/dist/types/services/getAccount.d.ts.map +1 -1
  162. package/dist/types/services/getFills.d.ts.map +1 -1
  163. package/dist/types/services/getMarketSettings.d.ts.map +1 -1
  164. package/dist/types/services/getOrder.d.ts.map +1 -1
  165. package/dist/types/services/getOrders.d.ts.map +1 -1
  166. package/dist/types/services/getPortfolioHistory.d.ts.map +1 -1
  167. package/dist/types/services/getPositions.d.ts.map +1 -1
  168. package/dist/types/services/getWithdrawableBalances.d.ts +1 -1
  169. package/dist/types/types/ws.d.ts +3 -2
  170. package/dist/types/types/ws.d.ts.map +1 -1
  171. package/dist/types/utils/liquidation.d.ts +1 -1
  172. package/dist/types/utils/liquidation.d.ts.map +1 -1
  173. package/dist/types/utils/mapActivity.d.ts +2 -1
  174. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  175. package/dist/types/utils/mapFill.d.ts +3 -2
  176. package/dist/types/utils/mapFill.d.ts.map +1 -1
  177. package/dist/types/utils/mapMarket.d.ts +2 -2
  178. package/dist/types/utils/mapMarket.d.ts.map +1 -1
  179. package/dist/types/utils/mapMarketContext.d.ts.map +1 -1
  180. package/dist/types/utils/mapOrder.d.ts +5 -2
  181. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  182. package/dist/types/utils/mapPosition.d.ts +4 -2
  183. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  184. package/dist/types/utils/rowTimestamp.d.ts +6 -0
  185. package/dist/types/utils/rowTimestamp.d.ts.map +1 -0
  186. package/dist/types/utils/spotBalance.d.ts +5 -5
  187. package/dist/types/utils/spotBalance.d.ts.map +1 -1
  188. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  189. package/dist/types/utils/transferMargin.d.ts +2 -1
  190. package/dist/types/utils/transferMargin.d.ts.map +1 -1
  191. package/dist/types/utils/venueTotals.d.ts +10 -5
  192. package/dist/types/utils/venueTotals.d.ts.map +1 -1
  193. package/dist/types/utils/withdrawableBalances.d.ts +6 -4
  194. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  195. package/dist/types/websocket/HyperliquidWsProvider.d.ts +2 -0
  196. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  197. package/package.json +3 -3
  198. package/src/HyperliquidProvider.ts +1 -1
  199. package/src/accountSummary.ts +35 -23
  200. package/src/services/getAccount.ts +50 -33
  201. package/src/services/getFills.ts +2 -1
  202. package/src/services/getMarketSettings.ts +6 -2
  203. package/src/services/getOrder.ts +18 -7
  204. package/src/services/getOrders.ts +6 -3
  205. package/src/services/getPortfolioHistory.ts +38 -7
  206. package/src/services/getPositions.ts +4 -3
  207. package/src/services/getWithdrawableBalances.ts +1 -1
  208. package/src/types/ws.ts +3 -2
  209. package/src/utils/liquidation.ts +14 -8
  210. package/src/utils/mapActivity.ts +42 -20
  211. package/src/utils/mapFill.ts +22 -9
  212. package/src/utils/mapMarket.ts +12 -10
  213. package/src/utils/mapMarketContext.ts +6 -8
  214. package/src/utils/mapOrder.ts +81 -20
  215. package/src/utils/mapPosition.ts +67 -13
  216. package/src/utils/rowTimestamp.ts +15 -0
  217. package/src/utils/spotBalance.ts +19 -16
  218. package/src/utils/spotCollateral.ts +15 -2
  219. package/src/utils/transferMargin.ts +40 -18
  220. package/src/utils/venueTotals.ts +54 -13
  221. package/src/utils/withdrawableBalances.ts +33 -15
  222. package/src/websocket/HyperliquidWsProvider.ts +169 -102
  223. package/dist/cjs/utils/decimal.d.ts +0 -3
  224. package/dist/cjs/utils/decimal.d.ts.map +0 -1
  225. package/dist/cjs/utils/decimal.js +0 -19
  226. package/dist/cjs/utils/decimal.js.map +0 -1
  227. package/dist/esm/utils/decimal.d.ts +0 -10
  228. package/dist/esm/utils/decimal.d.ts.map +0 -1
  229. package/dist/esm/utils/decimal.js +0 -19
  230. package/dist/esm/utils/decimal.js.map +0 -1
  231. package/dist/types/utils/decimal.d.ts +0 -10
  232. package/dist/types/utils/decimal.d.ts.map +0 -1
  233. package/src/utils/decimal.ts +0 -21
@@ -1,6 +1,6 @@
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
6
  import { calculateMaintenanceMarginRate } from './liquidation.js'
@@ -10,7 +10,7 @@ import { getMaxPriceDecimals } from './orderFormatting.js'
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
  */
@@ -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
  /**
@@ -1,4 +1,15 @@
1
- import { PerpsError, triggerConditionFor } from '@lifi/perps-sdk'
1
+ import {
2
+ isDecimalStringGreaterThan,
3
+ PerpsError,
4
+ safeDivideDecimalString,
5
+ safeIsDecimalStringGreaterThan,
6
+ safeIsDecimalStringZero,
7
+ safeSubtractDecimalString,
8
+ subtractDecimalString,
9
+ triggerConditionFor,
10
+ unknownToDecimalString,
11
+ warnSkippedVenueRow,
12
+ } from '@lifi/perps-sdk'
2
13
  import type {
3
14
  MarketDisplay,
4
15
  Order,
@@ -12,13 +23,13 @@ import {
12
23
  PerpsErrorCode,
13
24
  TimeInForce,
14
25
  } from '@lifi/perps-types'
15
- import Big from 'big.js'
16
26
  import { PROVIDER_KEY } from '../constants.js'
17
27
  import type {
18
28
  HlFrontendOpenOrder,
19
29
  HlOrderDetail,
20
30
  HlTwapHistoryEntry,
21
31
  } from '../types/index.js'
32
+ import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
22
33
 
23
34
  /** Order payload shared by frontend, historical, and single-order reads. */
24
35
  export type HlOrderLike = HlFrontendOpenOrder | HlOrderDetail['order']
@@ -87,28 +98,58 @@ export const mapOrderStatus = (status: string): OrderStatus => {
87
98
  }
88
99
  }
89
100
 
101
+ const skipOrder = (field: string, value: unknown): undefined => {
102
+ warnSkippedVenueRow(PROVIDER_KEY, 'order', field, value)
103
+ return undefined
104
+ }
105
+
106
+ const venueDecimal = (value: unknown, field: string): string | undefined => {
107
+ try {
108
+ return unknownToDecimalString(value, field, PROVIDER_KEY)
109
+ } catch {
110
+ return undefined
111
+ }
112
+ }
113
+
90
114
  function venueError(message: string): PerpsError {
91
115
  const error = new PerpsError(PerpsErrorCode.ThirdPartyError, message)
92
116
  error.tool = PROVIDER_KEY
93
117
  return error
94
118
  }
95
119
 
96
- /** Normalize regular, trigger, and TWAP rows through one lifecycle model. */
120
+ /**
121
+ * Normalize regular, trigger, and TWAP rows through one lifecycle model. A row
122
+ * whose filled or remaining size or timestamp cannot be derived gives `undefined`.
123
+ */
97
124
  export const mapOrder = (
98
125
  raw: HlOrderLike | HlOrderDetail | HlTwapHistoryEntry,
99
126
  market: MarketDisplay,
100
127
  parentOrderId?: string
101
- ): Order => {
128
+ ): Order | undefined => {
102
129
  if ('state' in raw) {
103
130
  if (raw.twapId === undefined) {
104
131
  throw venueError('Hyperliquid returned a TWAP without a twapId.')
105
132
  }
106
133
  const { state } = raw
107
- const filled = new Big(state.executedSz)
134
+ const remainingSize = safeSubtractDecimalString(state.sz, state.executedSz)
135
+ if (remainingSize === undefined) {
136
+ return skipOrder('executedSz', state.executedSz)
137
+ }
138
+ const createdAt = rowTimestampToIsoStringOrUndefined(state.timestamp)
139
+ if (createdAt === undefined) {
140
+ return skipOrder('timestamp', state.timestamp)
141
+ }
142
+ const updatedAt = rowTimestampToIsoStringOrUndefined(raw.time * 1000)
143
+ if (updatedAt === undefined) {
144
+ return skipOrder('time', raw.time)
145
+ }
108
146
  let status: OrderStatus
109
147
  switch (raw.status.status) {
110
148
  case 'activated':
111
- status = filled.gt(0) ? OrderStatus.PARTIALLY_FILLED : OrderStatus.OPEN
149
+ status =
150
+ safeIsDecimalStringGreaterThan(state.executedSz, '0') === true
151
+ ? OrderStatus.PARTIALLY_FILLED
152
+ : OrderStatus.OPEN
112
153
  break
113
154
  case 'waitingForTrigger':
114
155
  status = OrderStatus.OPEN
@@ -128,35 +169,57 @@ export const mapOrder = (
128
169
  `Unknown Hyperliquid TWAP status: ${raw.status.status}`
129
170
  )
130
171
  }
172
+ const averagePrice =
173
+ safeIsDecimalStringZero(state.executedSz) === false
174
+ ? safeDivideDecimalString(state.executedNtl, state.executedSz)
175
+ : undefined
131
176
  return {
132
177
  orderId: String(raw.twapId),
133
178
  market,
134
179
  type: OrderType.TWAP,
135
180
  side: state.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
136
181
  originalSize: state.sz,
137
- remainingSize: new Big(state.sz).minus(filled).toFixed(),
182
+ remainingSize,
138
183
  filledSize: state.executedSz,
139
- ...(filled.eq(0)
140
- ? {}
141
- : { averagePrice: new Big(state.executedNtl).div(filled).toFixed() }),
184
+ ...(averagePrice === undefined ? {} : { averagePrice }),
142
185
  reduceOnly: state.reduceOnly,
143
186
  status,
144
187
  ...(status === OrderStatus.CANCELLED || status === OrderStatus.REJECTED
145
188
  ? { statusReason: raw.status.description ?? raw.status.status }
146
189
  : {}),
147
- createdAt: new Date(state.timestamp).toISOString(),
148
- updatedAt: new Date(raw.time * 1000).toISOString(),
149
- startedAt: new Date(state.timestamp).toISOString(),
190
+ createdAt,
191
+ updatedAt,
192
+ startedAt: createdAt,
150
193
  durationSeconds: state.minutes * 60,
151
194
  }
152
195
  }
153
196
  const o = 'order' in raw ? raw.order : raw
154
197
  const venueStatus = 'order' in raw ? raw.status : 'open'
155
- const filled = new Big(o.origSz).minus(o.sz)
198
+ const originalSize = venueDecimal(o.origSz, 'origSz')
199
+ if (originalSize === undefined) {
200
+ return skipOrder('origSz', o.origSz)
201
+ }
202
+ const remainingSize = venueDecimal(o.sz, 'sz')
203
+ if (remainingSize === undefined) {
204
+ return skipOrder('sz', o.sz)
205
+ }
206
+ const filledSize = subtractDecimalString(originalSize, remainingSize)
207
+ const createdAt = rowTimestampToIsoStringOrUndefined(o.timestamp)
208
+ if (createdAt === undefined) {
209
+ return skipOrder('timestamp', o.timestamp)
210
+ }
211
+ const statusTimestamp = 'order' in raw ? raw.statusTimestamp : o.timestamp
212
+ const updatedAt = rowTimestampToIsoStringOrUndefined(statusTimestamp)
213
+ if (updatedAt === undefined) {
214
+ return skipOrder('statusTimestamp', statusTimestamp)
215
+ }
156
216
  let status = mapOrderStatus(venueStatus)
157
217
  if (parentOrderId !== undefined && !('order' in raw)) {
158
218
  status = OrderStatus.PENDING
159
- } else if (status === OrderStatus.OPEN && filled.gt(0)) {
219
+ } else if (
220
+ status === OrderStatus.OPEN &&
221
+ isDecimalStringGreaterThan(filledSize, '0')
222
+ ) {
160
223
  status = OrderStatus.PARTIALLY_FILLED
161
224
  }
162
225
  const base: OrderBase = {
@@ -170,13 +233,11 @@ export const mapOrder = (
170
233
  : {}),
171
234
  originalSize: o.origSz,
172
235
  remainingSize: o.sz,
173
- filledSize: filled.toFixed(),
236
+ filledSize,
174
237
  reduceOnly: o.reduceOnly,
175
238
  ...(parentOrderId === undefined ? {} : { parentOrderId }),
176
- createdAt: new Date(o.timestamp).toISOString(),
177
- updatedAt: new Date(
178
- 'order' in raw ? raw.statusTimestamp : o.timestamp
179
- ).toISOString(),
239
+ createdAt,
240
+ updatedAt,
180
241
  }
181
242
  let type = mapOrderType(o.orderType)
182
243
  if (o.isTrigger && (type === OrderType.MARKET || type === OrderType.LIMIT)) {
@@ -1,8 +1,28 @@
1
+ import {
2
+ absDecimalString,
3
+ createWarnOnce,
4
+ isDecimalString,
5
+ isDecimalStringGreaterThan,
6
+ isDecimalStringZero,
7
+ safeAbsDecimalString,
8
+ safeDivideDecimalString,
9
+ safeIsDecimalStringZero,
10
+ safeNumberToDecimalString,
11
+ safeSubtractDecimalString,
12
+ warnSkippedVenueRow,
13
+ } from '@lifi/perps-sdk'
1
14
  import type { PerpsMarketDisplay, Position } from '@lifi/perps-types'
2
15
  import { MarginMode, PositionSide } from '@lifi/perps-types'
3
- import Big from 'big.js'
16
+ import { PROVIDER_KEY } from '../constants.js'
4
17
  import type { HlAssetPosition } from '../types/index.js'
5
18
 
19
+ const warnNonPositiveLeverageOnce = createWarnOnce()
20
+
21
+ const skipPosition = (field: string, value: unknown): undefined => {
22
+ warnSkippedVenueRow(PROVIDER_KEY, 'position', field, value)
23
+ return undefined
24
+ }
25
+
6
26
  /**
7
27
  * True when the assetPosition has non-zero size. Hyperliquid keeps zero-size
8
28
  * rows in clearinghouse states after a close; both the REST and WS positions
@@ -10,39 +30,73 @@ import type { HlAssetPosition } from '../types/index.js'
10
30
  * @public
11
31
  */
12
32
  export const isOpenAssetPosition = (ap: HlAssetPosition): boolean =>
13
- Number.parseFloat(ap.position.szi) !== 0
33
+ safeIsDecimalStringZero(ap.position.szi) !== true
14
34
 
15
35
  /**
16
36
  * Map a non-zero Hyperliquid position payload to the SDK's normalized
17
37
  * position. Signed wire size determines side; decimal strings remain strings
18
38
  * in the normalized response. `cumFunding.sinceOpen` is negated because
19
39
  * Hyperliquid signs funding paid as positive and `accruedFunding` signs it as
20
- * negative.
40
+ * negative. A row with an invalid size gives `undefined`; an invalid position
41
+ * value, a non-positive leverage or an invalid funding leaves the derived field
42
+ * absent.
21
43
  * @public
22
44
  */
23
45
  export const mapPosition = (
24
46
  ap: HlAssetPosition,
25
47
  market: PerpsMarketDisplay
26
- ): Position => {
48
+ ): Position | undefined => {
27
49
  const pos = ap.position
28
- const szi = new Big(pos.szi)
29
- const positionValue = new Big(pos.positionValue).abs()
30
- const leverage = new Big(pos.leverage.value)
50
+ if (!isDecimalString(pos.szi)) {
51
+ return skipPosition('szi', pos.szi)
52
+ }
53
+ const size = absDecimalString(pos.szi)
54
+ const positionValue = safeAbsDecimalString(pos.positionValue)
55
+ const markPrice =
56
+ positionValue === undefined
57
+ ? undefined
58
+ : isDecimalStringZero(size)
59
+ ? '0'
60
+ : safeDivideDecimalString(positionValue, size)
61
+ const accruedFunding = safeSubtractDecimalString(
62
+ '0',
63
+ pos.cumFunding.sinceOpen
64
+ )
65
+ const venueLeverage = safeNumberToDecimalString(pos.leverage.value)
66
+ const leverage =
67
+ venueLeverage !== undefined &&
68
+ isDecimalStringGreaterThan(venueLeverage, '0')
69
+ ? venueLeverage
70
+ : undefined
71
+ if (venueLeverage !== undefined && leverage === undefined) {
72
+ warnNonPositiveLeverageOnce(
73
+ venueLeverage,
74
+ `[${PROVIDER_KEY}] position \`leverage.value\` is not positive: '${venueLeverage}'`
75
+ )
76
+ }
77
+ const initialMarginRequirement =
78
+ positionValue === undefined || leverage === undefined
79
+ ? undefined
80
+ : safeDivideDecimalString(positionValue, leverage)
31
81
  const marginMode =
32
82
  pos.leverage.type === 'cross' ? MarginMode.CROSS : MarginMode.ISOLATED
33
83
 
34
84
  return {
35
85
  market,
36
- side: szi.gte(0) ? PositionSide.LONG : PositionSide.SHORT,
37
- size: szi.abs().toFixed(),
86
+ side: isDecimalStringGreaterThan('0', pos.szi)
87
+ ? PositionSide.SHORT
88
+ : PositionSide.LONG,
89
+ size,
38
90
  entryPrice: pos.entryPx ?? '0',
39
- markPrice: szi.eq(0) ? '0' : positionValue.div(szi.abs()).toFixed(),
91
+ ...(markPrice === undefined ? {} : { markPrice }),
40
92
  liquidationPrice: pos.liquidationPx ?? '0',
41
93
  unrealizedPnl: pos.unrealizedPnl,
42
- accruedFunding: new Big(pos.cumFunding.sinceOpen).neg().toFixed(),
43
- leverage: ap.position.leverage.value,
94
+ ...(accruedFunding === undefined ? {} : { accruedFunding }),
95
+ ...(leverage === undefined ? {} : { leverage }),
44
96
  marginUsed: pos.marginUsed,
45
- initialMarginRequirement: positionValue.div(leverage).toFixed(),
97
+ ...(initialMarginRequirement === undefined
98
+ ? {}
99
+ : { initialMarginRequirement }),
46
100
  marginMode,
47
101
  }
48
102
  }
@@ -0,0 +1,15 @@
1
+ import { timestampToIsoString } from '@lifi/perps-sdk'
2
+
3
+ /**
4
+ * Read a venue row time as an ISO-8601 string, or `undefined` when the time is
5
+ * not valid. The caller logs the row skip, so this logs nothing.
6
+ */
7
+ export const rowTimestampToIsoStringOrUndefined = (
8
+ value: string | number
9
+ ): string | undefined => {
10
+ try {
11
+ return timestampToIsoString(value)
12
+ } catch {
13
+ return undefined
14
+ }
15
+ }
@@ -1,5 +1,9 @@
1
- import { numberToDecimalString, parseDecimal } from '@lifi/perps-sdk'
2
- import type { Asset, Balance, Market } from '@lifi/perps-types'
1
+ import {
2
+ isDecimalString,
3
+ isDecimalStringGreaterThan,
4
+ multiplyDecimalString,
5
+ } from '@lifi/perps-sdk'
6
+ import type { Asset, Balance, DecimalString, Market } from '@lifi/perps-types'
3
7
  import { SPOT_MARKET_ID } from '../constants.js'
4
8
  import type { HlSpotBalance } from '../types/index.js'
5
9
  import { spotLogoURI } from './assetLogo.js'
@@ -7,23 +11,24 @@ import { coinAsset } from './marketDisplay.js'
7
11
 
8
12
  /**
9
13
  * Build USD prices keyed by spot token asset ID. Base tokens use the supplied
10
- * market mark price; quote tokens default to exactly `$1`; missing base prices
11
- * default to `0`.
14
+ * market mark price; quote tokens default to exactly `$1`; missing or invalid
15
+ * base prices default to `0`.
12
16
  * @public
13
17
  */
14
18
  export const spotPriceById = (
15
19
  markets: readonly Market[],
16
- priceByMarketId: ReadonlyMap<string, number>
17
- ): Map<string, number> => {
18
- const map = new Map<string, number>()
20
+ priceByMarketId: ReadonlyMap<string, DecimalString>
21
+ ): Map<string, DecimalString> => {
22
+ const map = new Map<string, DecimalString>()
19
23
  for (const m of markets) {
20
24
  if (m.categoryId === SPOT_MARKET_ID) {
21
- map.set(m.baseAsset.id, priceByMarketId.get(m.id) ?? 0)
25
+ const price = priceByMarketId.get(m.id)
26
+ map.set(m.baseAsset.id, isDecimalString(price) ? price : '0')
22
27
  }
23
28
  }
24
29
  for (const m of markets) {
25
30
  if (!map.has(m.quoteAsset.id)) {
26
- map.set(m.quoteAsset.id, 1)
31
+ map.set(m.quoteAsset.id, '1')
27
32
  }
28
33
  }
29
34
  return map
@@ -44,18 +49,16 @@ export const spotAssetFromToken = (b: HlSpotBalance): Asset => ({
44
49
  /** Assemble a typed spot {@link Balance}; `total` is native token units and its unit price and USD value use `priceById`. @public */
45
50
  export const spotBalance = (
46
51
  asset: Asset,
47
- total: string,
48
- priceById: Map<string, number>
52
+ total: DecimalString,
53
+ priceById: ReadonlyMap<string, DecimalString>
49
54
  ): Balance => {
50
- const price = priceById.get(asset.id) ?? 0
55
+ const price = priceById.get(asset.id) ?? '0'
51
56
  return {
52
57
  categoryId: SPOT_MARKET_ID,
53
58
  asset,
54
59
  units: total,
55
- valueUsd: numberToDecimalString(
56
- (parseDecimal(total) ?? Number.NaN) * price
57
- ),
60
+ valueUsd: multiplyDecimalString(total, price),
58
61
  // A zero entry means the map holds no mark for the asset, not a free asset.
59
- ...(price > 0 ? { price: numberToDecimalString(price) } : {}),
62
+ ...(isDecimalStringGreaterThan(price, '0') ? { price } : {}),
60
63
  }
61
64
  }
@@ -1,5 +1,9 @@
1
+ import {
2
+ isDecimalStringGreaterThan,
3
+ unknownToDecimalString,
4
+ } from '@lifi/perps-sdk'
1
5
  import type { Balance } from '@lifi/perps-types'
2
- import { toWireBig } from './decimal.js'
6
+ import { PROVIDER_KEY } from '../constants.js'
3
7
 
4
8
  /**
5
9
  * Result of {@link partitionSpotBalances}: balances that count toward
@@ -24,7 +28,16 @@ export const partitionSpotBalances = (
24
28
  const collateralBalances: Balance[] = []
25
29
  const balances: Balance[] = []
26
30
  for (const balance of spotBalances) {
27
- if (!toWireBig(balance.units, 'spotBalance.units').gt(0)) {
31
+ if (
32
+ !isDecimalStringGreaterThan(
33
+ unknownToDecimalString(
34
+ balance.units,
35
+ 'spotBalance.units',
36
+ PROVIDER_KEY
37
+ ),
38
+ '0'
39
+ )
40
+ ) {
28
41
  continue
29
42
  }
30
43
  if (quoteAssetIds.has(balance.asset.id)) {
@@ -1,32 +1,49 @@
1
1
  import {
2
+ isDecimalString,
3
+ isDecimalStringGreaterThan,
4
+ multiplyDecimalString,
2
5
  PerpsError,
3
6
  positionSupportsMarginAdjustment,
4
7
  positionSupportsMarginRemoval,
5
- validateDecimalString,
8
+ roundDecimalString,
9
+ subtractDecimalString,
6
10
  } from '@lifi/perps-sdk'
7
11
  import {
8
12
  type DecimalString,
9
13
  PerpsErrorCode,
10
14
  type Position,
11
15
  } from '@lifi/perps-types'
12
- import Big from 'big.js'
13
16
 
14
17
  const NOTIONAL_FLOOR_RATIO = '0.1'
15
18
  const AMOUNT_DECIMALS = 6
16
19
 
17
- function positionAmount(value: DecimalString, field: string): Big {
18
- return new Big(validateDecimalString(value, `Position.${field}`))
20
+ function requirePositionAmount(
21
+ value: DecimalString,
22
+ field: string
23
+ ): DecimalString {
24
+ if (!isDecimalString(value)) {
25
+ throw new PerpsError(
26
+ PerpsErrorCode.ValidationError,
27
+ `Invalid \`Position.${field}\`: '${value}' is not a decimal string.`
28
+ )
29
+ }
30
+ return value
19
31
  }
20
32
 
21
- function positivePositionAmount(value: DecimalString, field: string): Big {
22
- const amount = positionAmount(value, field)
23
- if (amount.lte(0)) {
33
+ function requirePositivePositionAmount(
34
+ value: DecimalString | undefined,
35
+ field: string
36
+ ): DecimalString {
37
+ if (
38
+ value === undefined ||
39
+ !isDecimalStringGreaterThan(requirePositionAmount(value, field), '0')
40
+ ) {
24
41
  throw new PerpsError(
25
42
  PerpsErrorCode.ValidationError,
26
43
  `Position.${field} must be greater than zero.`
27
44
  )
28
45
  }
29
- return amount
46
+ return value
30
47
  }
31
48
 
32
49
  /**
@@ -40,7 +57,8 @@ function positivePositionAmount(value: DecimalString, field: string): Big {
40
57
  * margin adjustment; `'0'` for add-only strict-isolated markets and when
41
58
  * nothing is removable.
42
59
  * @throws {PerpsError} `ValidationError` when a required `Position` decimal
43
- * is malformed, or when a size, price or requirement is not positive.
60
+ * is absent or malformed, or when a size, price or requirement is not
61
+ * positive.
44
62
  * @see https://hyperliquid.gitbook.io/hyperliquid-docs/trading/margining
45
63
  * @public
46
64
  */
@@ -53,21 +71,25 @@ export function positionRemovableMargin(
53
71
  if (!positionSupportsMarginRemoval(position)) {
54
72
  return '0'
55
73
  }
56
- const marginUsed = positionAmount(position.marginUsed, 'marginUsed')
57
- const initialMargin = positivePositionAmount(
74
+ const marginUsed = requirePositionAmount(position.marginUsed, 'marginUsed')
75
+ const initialMargin = requirePositivePositionAmount(
58
76
  position.initialMarginRequirement,
59
77
  'initialMarginRequirement'
60
78
  )
61
- const notionalFloor = positivePositionAmount(position.size, 'size')
62
- .times(positivePositionAmount(position.markPrice, 'markPrice'))
63
- .times(NOTIONAL_FLOOR_RATIO)
64
- const minimumMargin = initialMargin.gt(notionalFloor)
79
+ const notionalFloor = multiplyDecimalString(
80
+ multiplyDecimalString(
81
+ requirePositivePositionAmount(position.size, 'size'),
82
+ requirePositivePositionAmount(position.markPrice, 'markPrice')
83
+ ),
84
+ NOTIONAL_FLOOR_RATIO
85
+ )
86
+ const minimumMargin = isDecimalStringGreaterThan(initialMargin, notionalFloor)
65
87
  ? initialMargin
66
88
  : notionalFloor
67
89
 
68
- const removable = marginUsed.minus(minimumMargin)
69
- if (removable.lte(0)) {
90
+ const removable = subtractDecimalString(marginUsed, minimumMargin)
91
+ if (!isDecimalStringGreaterThan(removable, '0')) {
70
92
  return '0'
71
93
  }
72
- return removable.round(AMOUNT_DECIMALS, Big.roundDown).toFixed()
94
+ return roundDecimalString(removable, AMOUNT_DECIMALS, 'truncate')
73
95
  }
@@ -1,6 +1,14 @@
1
- import type { HyperliquidMarginSummary, Position } from '@lifi/perps-types'
2
- import Big from 'big.js'
3
- import { toWireBig } from './decimal.js'
1
+ import {
2
+ addDecimalString,
3
+ safeAddDecimalString,
4
+ unknownToDecimalString,
5
+ } from '@lifi/perps-sdk'
6
+ import type {
7
+ DecimalString,
8
+ HyperliquidMarginSummary,
9
+ Position,
10
+ } from '@lifi/perps-types'
11
+ import { PROVIDER_KEY } from '../constants.js'
4
12
 
5
13
  /**
6
14
  * One perps sub-dex entry {@link perpsTotals} reads. The REST response always
@@ -15,22 +23,33 @@ export interface DexMarginSummary {
15
23
  * Sum the venue `marginSummary` totals over every perps sub-dex of an
16
24
  * account. `accountValue` is total equity, so it already carries locked
17
25
  * margin and unrealized PnL. An entry without a `marginSummary` adds nothing.
26
+ * @throws {PerpsError} `SDKError` when a venue figure is not a decimal.
18
27
  * @public
19
28
  */
20
29
  export const perpsTotals = (
21
30
  dexStates: readonly DexMarginSummary[]
22
- ): { accountValue: Big; marginUsed: Big } => {
23
- let accountValue = new Big(0)
24
- let marginUsed = new Big(0)
31
+ ): { accountValue: DecimalString; marginUsed: DecimalString } => {
32
+ let accountValue = '0'
33
+ let marginUsed = '0'
25
34
  for (const { marginSummary } of dexStates) {
26
35
  if (marginSummary === undefined) {
27
36
  continue
28
37
  }
29
- accountValue = accountValue.plus(
30
- toWireBig(marginSummary.accountValue, 'marginSummary.accountValue')
38
+ accountValue = addDecimalString(
39
+ accountValue,
40
+ unknownToDecimalString(
41
+ marginSummary.accountValue,
42
+ 'marginSummary.accountValue',
43
+ PROVIDER_KEY
44
+ )
31
45
  )
32
- marginUsed = marginUsed.plus(
33
- toWireBig(marginSummary.totalMarginUsed, 'marginSummary.totalMarginUsed')
46
+ marginUsed = addDecimalString(
47
+ marginUsed,
48
+ unknownToDecimalString(
49
+ marginSummary.totalMarginUsed,
50
+ 'marginSummary.totalMarginUsed',
51
+ PROVIDER_KEY
52
+ )
34
53
  )
35
54
  }
36
55
  return { accountValue, marginUsed }
@@ -40,9 +59,31 @@ export const perpsTotals = (
40
59
  * Sum the unrealized PnL of every position, in quote-asset units.
41
60
  * @public
42
61
  */
43
- export const sumUnrealizedPnl = (positions: readonly Position[]): Big =>
62
+ export const sumUnrealizedPnl = (
63
+ positions: readonly Position[]
64
+ ): DecimalString =>
44
65
  positions.reduce(
45
66
  (sum, position) =>
46
- sum.plus(toWireBig(position.unrealizedPnl, 'position.unrealizedPnl')),
47
- new Big(0)
67
+ addDecimalString(
68
+ sum,
69
+ unknownToDecimalString(
70
+ position.unrealizedPnl,
71
+ 'position.unrealizedPnl',
72
+ PROVIDER_KEY
73
+ )
74
+ ),
75
+ '0'
76
+ )
77
+
78
+ /**
79
+ * Sum every term, or `undefined` with a warning when any term is not a
80
+ * decimal string. A bad term never gives a partial sum.
81
+ */
82
+ export const safeSumDecimalStrings = (
83
+ terms: readonly string[]
84
+ ): DecimalString | undefined =>
85
+ terms.reduce<DecimalString | undefined>(
86
+ (sum, term) =>
87
+ sum === undefined ? undefined : safeAddDecimalString(sum, term),
88
+ '0'
48
89
  )