@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,4 +1,16 @@
1
- import { PerpsError, triggerConditionFor } from '@lifi/perps-sdk'
1
+ import {
2
+ createWarnOnce,
3
+ isDecimalString,
4
+ isDecimalStringGreaterThan,
5
+ type MarketRegistry,
6
+ PerpsError,
7
+ safeDivideDecimalString,
8
+ safeIsDecimalStringGreaterThan,
9
+ safeIsDecimalStringZero,
10
+ safeSubtractDecimalString,
11
+ triggerConditionFor,
12
+ warnSkippedVenueRow,
13
+ } from '@lifi/perps-sdk'
2
14
  import type {
3
15
  MarketDisplay,
4
16
  Order,
@@ -12,13 +24,14 @@ import {
12
24
  PerpsErrorCode,
13
25
  TimeInForce,
14
26
  } from '@lifi/perps-types'
15
- import Big from 'big.js'
16
27
  import { PROVIDER_KEY } from '../constants.js'
17
28
  import type {
18
29
  HlFrontendOpenOrder,
19
30
  HlOrderDetail,
20
31
  HlTwapHistoryEntry,
21
32
  } from '../types/index.js'
33
+ import { assetIsOutcome } from './assetId.js'
34
+ import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
22
35
 
23
36
  /** Order payload shared by frontend, historical, and single-order reads. */
24
37
  export type HlOrderLike = HlFrontendOpenOrder | HlOrderDetail['order']
@@ -87,28 +100,85 @@ export const mapOrderStatus = (status: string): OrderStatus => {
87
100
  }
88
101
  }
89
102
 
103
+ const skipOrder = (
104
+ marketId: string,
105
+ field: string,
106
+ value: unknown
107
+ ): undefined => {
108
+ warnSkippedVenueRow(PROVIDER_KEY, 'order', field, value, { marketId })
109
+ return undefined
110
+ }
111
+
112
+ const warnDroppedRowOnce = createWarnOnce()
113
+
114
+ /**
115
+ * Map one venue row, or drop it. An outcome market identity, a coin the backend
116
+ * market list does not hold, and a row the mapper rejects each drop only their
117
+ * own row; each distinct mapper message warns once.
118
+ */
119
+ export const mapOrderRow = (
120
+ coin: string,
121
+ registry: MarketRegistry | undefined,
122
+ map: (market: MarketDisplay) => Order | undefined
123
+ ): Order | undefined => {
124
+ if (assetIsOutcome(coin)) {
125
+ return undefined
126
+ }
127
+ const market = registry?.get(coin)
128
+ if (market === undefined) {
129
+ return undefined
130
+ }
131
+ try {
132
+ return map(market)
133
+ } catch (error) {
134
+ if (!(error instanceof PerpsError)) {
135
+ throw error
136
+ }
137
+ const message = `[${PROVIDER_KEY}] dropped order row: ${error.message}`
138
+ warnDroppedRowOnce(message, message)
139
+ return undefined
140
+ }
141
+ }
142
+
90
143
  function venueError(message: string): PerpsError {
91
144
  const error = new PerpsError(PerpsErrorCode.ThirdPartyError, message)
92
145
  error.tool = PROVIDER_KEY
93
146
  return error
94
147
  }
95
148
 
96
- /** Normalize regular, trigger, and TWAP rows through one lifecycle model. */
149
+ /**
150
+ * Normalize regular, trigger, and TWAP rows through one lifecycle model. A row
151
+ * whose filled or remaining size or timestamp cannot be derived gives `undefined`.
152
+ */
97
153
  export const mapOrder = (
98
154
  raw: HlOrderLike | HlOrderDetail | HlTwapHistoryEntry,
99
155
  market: MarketDisplay,
100
156
  parentOrderId?: string
101
- ): Order => {
157
+ ): Order | undefined => {
102
158
  if ('state' in raw) {
103
159
  if (raw.twapId === undefined) {
104
160
  throw venueError('Hyperliquid returned a TWAP without a twapId.')
105
161
  }
106
162
  const { state } = raw
107
- const filled = new Big(state.executedSz)
163
+ const remainingSize = safeSubtractDecimalString(state.sz, state.executedSz)
164
+ if (remainingSize === undefined) {
165
+ return skipOrder(market.id, 'executedSz', state.executedSz)
166
+ }
167
+ const createdAt = rowTimestampToIsoStringOrUndefined(state.timestamp)
168
+ if (createdAt === undefined) {
169
+ return skipOrder(market.id, 'timestamp', state.timestamp)
170
+ }
171
+ const updatedAt = rowTimestampToIsoStringOrUndefined(raw.time * 1000)
172
+ if (updatedAt === undefined) {
173
+ return skipOrder(market.id, 'time', raw.time)
174
+ }
108
175
  let status: OrderStatus
109
176
  switch (raw.status.status) {
110
177
  case 'activated':
111
- status = filled.gt(0) ? OrderStatus.PARTIALLY_FILLED : OrderStatus.OPEN
178
+ status =
179
+ safeIsDecimalStringGreaterThan(state.executedSz, '0') === true
180
+ ? OrderStatus.PARTIALLY_FILLED
181
+ : OrderStatus.OPEN
112
182
  break
113
183
  case 'waitingForTrigger':
114
184
  status = OrderStatus.OPEN
@@ -128,35 +198,54 @@ export const mapOrder = (
128
198
  `Unknown Hyperliquid TWAP status: ${raw.status.status}`
129
199
  )
130
200
  }
201
+ const averagePrice =
202
+ safeIsDecimalStringZero(state.executedSz) === false
203
+ ? safeDivideDecimalString(state.executedNtl, state.executedSz)
204
+ : undefined
131
205
  return {
132
206
  orderId: String(raw.twapId),
133
207
  market,
134
208
  type: OrderType.TWAP,
135
209
  side: state.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
136
210
  originalSize: state.sz,
137
- remainingSize: new Big(state.sz).minus(filled).toFixed(),
211
+ remainingSize,
138
212
  filledSize: state.executedSz,
139
- ...(filled.eq(0)
140
- ? {}
141
- : { averagePrice: new Big(state.executedNtl).div(filled).toFixed() }),
213
+ ...(averagePrice === undefined ? {} : { averagePrice }),
142
214
  reduceOnly: state.reduceOnly,
143
215
  status,
144
216
  ...(status === OrderStatus.CANCELLED || status === OrderStatus.REJECTED
145
217
  ? { statusReason: raw.status.description ?? raw.status.status }
146
218
  : {}),
147
- createdAt: new Date(state.timestamp).toISOString(),
148
- updatedAt: new Date(raw.time * 1000).toISOString(),
149
- startedAt: new Date(state.timestamp).toISOString(),
219
+ createdAt,
220
+ updatedAt,
221
+ startedAt: createdAt,
150
222
  durationSeconds: state.minutes * 60,
151
223
  }
152
224
  }
153
225
  const o = 'order' in raw ? raw.order : raw
154
226
  const venueStatus = 'order' in raw ? raw.status : 'open'
155
- const filled = new Big(o.origSz).minus(o.sz)
227
+ const filledSize = safeSubtractDecimalString(o.origSz, o.sz)
228
+ if (filledSize === undefined) {
229
+ return isDecimalString(o.origSz)
230
+ ? skipOrder(market.id, 'sz', o.sz)
231
+ : skipOrder(market.id, 'origSz', o.origSz)
232
+ }
233
+ const createdAt = rowTimestampToIsoStringOrUndefined(o.timestamp)
234
+ if (createdAt === undefined) {
235
+ return skipOrder(market.id, 'timestamp', o.timestamp)
236
+ }
237
+ const statusTimestamp = 'order' in raw ? raw.statusTimestamp : o.timestamp
238
+ const updatedAt = rowTimestampToIsoStringOrUndefined(statusTimestamp)
239
+ if (updatedAt === undefined) {
240
+ return skipOrder(market.id, 'statusTimestamp', statusTimestamp)
241
+ }
156
242
  let status = mapOrderStatus(venueStatus)
157
243
  if (parentOrderId !== undefined && !('order' in raw)) {
158
244
  status = OrderStatus.PENDING
159
- } else if (status === OrderStatus.OPEN && filled.gt(0)) {
245
+ } else if (
246
+ status === OrderStatus.OPEN &&
247
+ isDecimalStringGreaterThan(filledSize, '0')
248
+ ) {
160
249
  status = OrderStatus.PARTIALLY_FILLED
161
250
  }
162
251
  const base: OrderBase = {
@@ -170,13 +259,11 @@ export const mapOrder = (
170
259
  : {}),
171
260
  originalSize: o.origSz,
172
261
  remainingSize: o.sz,
173
- filledSize: filled.toFixed(),
262
+ filledSize,
174
263
  reduceOnly: o.reduceOnly,
175
264
  ...(parentOrderId === undefined ? {} : { parentOrderId }),
176
- createdAt: new Date(o.timestamp).toISOString(),
177
- updatedAt: new Date(
178
- 'order' in raw ? raw.statusTimestamp : o.timestamp
179
- ).toISOString(),
265
+ createdAt,
266
+ updatedAt,
180
267
  }
181
268
  let type = mapOrderType(o.orderType)
182
269
  if (o.isTrigger && (type === OrderType.MARKET || type === OrderType.LIMIT)) {
@@ -1,8 +1,32 @@
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 = (
22
+ marketId: string,
23
+ field: string,
24
+ value: unknown
25
+ ): undefined => {
26
+ warnSkippedVenueRow(PROVIDER_KEY, 'position', field, value, { marketId })
27
+ return undefined
28
+ }
29
+
6
30
  /**
7
31
  * True when the assetPosition has non-zero size. Hyperliquid keeps zero-size
8
32
  * rows in clearinghouse states after a close; both the REST and WS positions
@@ -10,39 +34,73 @@ import type { HlAssetPosition } from '../types/index.js'
10
34
  * @public
11
35
  */
12
36
  export const isOpenAssetPosition = (ap: HlAssetPosition): boolean =>
13
- Number.parseFloat(ap.position.szi) !== 0
37
+ safeIsDecimalStringZero(ap.position.szi) !== true
14
38
 
15
39
  /**
16
40
  * Map a non-zero Hyperliquid position payload to the SDK's normalized
17
41
  * position. Signed wire size determines side; decimal strings remain strings
18
42
  * in the normalized response. `cumFunding.sinceOpen` is negated because
19
43
  * Hyperliquid signs funding paid as positive and `accruedFunding` signs it as
20
- * negative.
44
+ * negative. A row with an invalid size gives `undefined`; an invalid position
45
+ * value, a non-positive leverage or an invalid funding leaves the derived field
46
+ * absent.
21
47
  * @public
22
48
  */
23
49
  export const mapPosition = (
24
50
  ap: HlAssetPosition,
25
51
  market: PerpsMarketDisplay
26
- ): Position => {
52
+ ): Position | undefined => {
27
53
  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)
54
+ if (!isDecimalString(pos.szi)) {
55
+ return skipPosition(market.id, 'szi', pos.szi)
56
+ }
57
+ const size = absDecimalString(pos.szi)
58
+ const positionValue = safeAbsDecimalString(pos.positionValue)
59
+ const markPrice =
60
+ positionValue === undefined
61
+ ? undefined
62
+ : isDecimalStringZero(size)
63
+ ? '0'
64
+ : safeDivideDecimalString(positionValue, size)
65
+ const accruedFunding = safeSubtractDecimalString(
66
+ '0',
67
+ pos.cumFunding.sinceOpen
68
+ )
69
+ const venueLeverage = safeNumberToDecimalString(pos.leverage.value)
70
+ const leverage =
71
+ venueLeverage !== undefined &&
72
+ isDecimalStringGreaterThan(venueLeverage, '0')
73
+ ? venueLeverage
74
+ : undefined
75
+ if (venueLeverage !== undefined && leverage === undefined) {
76
+ warnNonPositiveLeverageOnce(
77
+ venueLeverage,
78
+ `[${PROVIDER_KEY}] position \`leverage.value\` is not positive: '${venueLeverage}'`
79
+ )
80
+ }
81
+ const initialMarginRequirement =
82
+ positionValue === undefined || leverage === undefined
83
+ ? undefined
84
+ : safeDivideDecimalString(positionValue, leverage)
31
85
  const marginMode =
32
86
  pos.leverage.type === 'cross' ? MarginMode.CROSS : MarginMode.ISOLATED
33
87
 
34
88
  return {
35
89
  market,
36
- side: szi.gte(0) ? PositionSide.LONG : PositionSide.SHORT,
37
- size: szi.abs().toFixed(),
90
+ side: isDecimalStringGreaterThan('0', pos.szi)
91
+ ? PositionSide.SHORT
92
+ : PositionSide.LONG,
93
+ size,
38
94
  entryPrice: pos.entryPx ?? '0',
39
- markPrice: szi.eq(0) ? '0' : positionValue.div(szi.abs()).toFixed(),
40
- liquidationPrice: pos.liquidationPx ?? '0',
95
+ ...(markPrice === undefined ? {} : { markPrice }),
96
+ liquidationPrice: pos.liquidationPx || '0',
41
97
  unrealizedPnl: pos.unrealizedPnl,
42
- accruedFunding: new Big(pos.cumFunding.sinceOpen).neg().toFixed(),
43
- leverage: ap.position.leverage.value,
98
+ ...(accruedFunding === undefined ? {} : { accruedFunding }),
99
+ ...(leverage === undefined ? {} : { leverage }),
44
100
  marginUsed: pos.marginUsed,
45
- initialMarginRequirement: positionValue.div(leverage).toFixed(),
101
+ ...(initialMarginRequirement === undefined
102
+ ? {}
103
+ : { initialMarginRequirement }),
46
104
  marginMode,
47
105
  }
48
106
  }
@@ -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}' does not match the decimal pattern.`
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,16 @@
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
+ addDecimalStrings,
4
+ createWarnOnce,
5
+ isDecimalString,
6
+ unknownToDecimalString,
7
+ } from '@lifi/perps-sdk'
8
+ import type {
9
+ DecimalString,
10
+ HyperliquidMarginSummary,
11
+ Position,
12
+ } from '@lifi/perps-types'
13
+ import { PROVIDER_KEY } from '../constants.js'
4
14
 
5
15
  /**
6
16
  * One perps sub-dex entry {@link perpsTotals} reads. The REST response always
@@ -15,34 +25,58 @@ export interface DexMarginSummary {
15
25
  * Sum the venue `marginSummary` totals over every perps sub-dex of an
16
26
  * account. `accountValue` is total equity, so it already carries locked
17
27
  * margin and unrealized PnL. An entry without a `marginSummary` adds nothing.
28
+ * @throws {PerpsError} `SDKError` when a venue figure is not a decimal.
18
29
  * @public
19
30
  */
20
31
  export const perpsTotals = (
21
32
  dexStates: readonly DexMarginSummary[]
22
- ): { accountValue: Big; marginUsed: Big } => {
23
- let accountValue = new Big(0)
24
- let marginUsed = new Big(0)
33
+ ): { accountValue: DecimalString; marginUsed: DecimalString } => {
34
+ let accountValue = '0'
35
+ let marginUsed = '0'
25
36
  for (const { marginSummary } of dexStates) {
26
37
  if (marginSummary === undefined) {
27
38
  continue
28
39
  }
29
- accountValue = accountValue.plus(
30
- toWireBig(marginSummary.accountValue, 'marginSummary.accountValue')
40
+ accountValue = addDecimalString(
41
+ accountValue,
42
+ unknownToDecimalString(
43
+ marginSummary.accountValue,
44
+ 'marginSummary.accountValue',
45
+ PROVIDER_KEY
46
+ )
31
47
  )
32
- marginUsed = marginUsed.plus(
33
- toWireBig(marginSummary.totalMarginUsed, 'marginSummary.totalMarginUsed')
48
+ marginUsed = addDecimalString(
49
+ marginUsed,
50
+ unknownToDecimalString(
51
+ marginSummary.totalMarginUsed,
52
+ 'marginSummary.totalMarginUsed',
53
+ PROVIDER_KEY
54
+ )
34
55
  )
35
56
  }
36
57
  return { accountValue, marginUsed }
37
58
  }
38
59
 
60
+ const warnSkippedTermOnce = createWarnOnce()
61
+
39
62
  /**
40
- * Sum the unrealized PnL of every position, in quote-asset units.
63
+ * Sum the unrealized PnL of every position, in quote-asset units. A term that
64
+ * does not match the decimal pattern adds nothing and warns once.
41
65
  * @public
42
66
  */
43
- export const sumUnrealizedPnl = (positions: readonly Position[]): Big =>
44
- positions.reduce(
45
- (sum, position) =>
46
- sum.plus(toWireBig(position.unrealizedPnl, 'position.unrealizedPnl')),
47
- new Big(0)
48
- )
67
+ export const sumUnrealizedPnl = (
68
+ positions: readonly Position[]
69
+ ): DecimalString => {
70
+ const terms: DecimalString[] = []
71
+ for (const { unrealizedPnl } of positions) {
72
+ if (isDecimalString(unrealizedPnl)) {
73
+ terms.push(unrealizedPnl)
74
+ continue
75
+ }
76
+ warnSkippedTermOnce(
77
+ `${PROVIDER_KEY}|position.unrealizedPnl`,
78
+ `[${PROVIDER_KEY}] skipping a \`position.unrealizedPnl\` term that does not match the decimal pattern: '${String(unrealizedPnl).slice(0, 64)}'`
79
+ )
80
+ }
81
+ return addDecimalStrings(terms)
82
+ }