@lifi/perps-sdk-provider-hyperliquid 15.0.0 → 17.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 (219) hide show
  1. package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
  2. package/dist/cjs/HyperliquidProvider.js +5 -1
  3. package/dist/cjs/HyperliquidProvider.js.map +1 -1
  4. package/dist/cjs/accountSummary.d.ts.map +1 -1
  5. package/dist/cjs/accountSummary.js +34 -7
  6. package/dist/cjs/accountSummary.js.map +1 -1
  7. package/dist/cjs/index.d.ts +2 -0
  8. package/dist/cjs/index.d.ts.map +1 -1
  9. package/dist/cjs/index.js +3 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/services/activeAssetData.d.ts +6 -0
  12. package/dist/cjs/services/activeAssetData.d.ts.map +1 -0
  13. package/dist/cjs/services/activeAssetData.js +7 -0
  14. package/dist/cjs/services/activeAssetData.js.map +1 -0
  15. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  16. package/dist/cjs/services/getAccount.js +23 -35
  17. package/dist/cjs/services/getAccount.js.map +1 -1
  18. package/dist/cjs/services/getAvailableToTrade.d.ts +10 -0
  19. package/dist/cjs/services/getAvailableToTrade.d.ts.map +1 -0
  20. package/dist/cjs/services/getAvailableToTrade.js +32 -0
  21. package/dist/cjs/services/getAvailableToTrade.js.map +1 -0
  22. package/dist/cjs/services/getMarketSettings.d.ts +1 -1
  23. package/dist/cjs/services/getMarketSettings.d.ts.map +1 -1
  24. package/dist/cjs/services/getMarketSettings.js +3 -7
  25. package/dist/cjs/services/getMarketSettings.js.map +1 -1
  26. package/dist/cjs/services/getOrder.d.ts.map +1 -1
  27. package/dist/cjs/services/getOrder.js +7 -3
  28. package/dist/cjs/services/getOrder.js.map +1 -1
  29. package/dist/cjs/services/getOrders.d.ts.map +1 -1
  30. package/dist/cjs/services/getOrders.js +4 -23
  31. package/dist/cjs/services/getOrders.js.map +1 -1
  32. package/dist/cjs/services/getWithdrawableBalances.d.ts +5 -0
  33. package/dist/cjs/services/getWithdrawableBalances.d.ts.map +1 -0
  34. package/dist/cjs/services/getWithdrawableBalances.js +29 -0
  35. package/dist/cjs/services/getWithdrawableBalances.js.map +1 -0
  36. package/dist/cjs/types/account.d.ts +11 -8
  37. package/dist/cjs/types/account.d.ts.map +1 -1
  38. package/dist/cjs/types/account.js.map +1 -1
  39. package/dist/cjs/types/ws.d.ts +3 -4
  40. package/dist/cjs/types/ws.d.ts.map +1 -1
  41. package/dist/cjs/utils/abstractionMode.d.ts +1 -1
  42. package/dist/cjs/utils/abstractionMode.d.ts.map +1 -1
  43. package/dist/cjs/utils/abstractionMode.js.map +1 -1
  44. package/dist/cjs/utils/explorer.d.ts +2 -1
  45. package/dist/cjs/utils/explorer.d.ts.map +1 -1
  46. package/dist/cjs/utils/explorer.js +24 -1
  47. package/dist/cjs/utils/explorer.js.map +1 -1
  48. package/dist/cjs/utils/index.d.ts +3 -1
  49. package/dist/cjs/utils/index.d.ts.map +1 -1
  50. package/dist/cjs/utils/index.js +7 -2
  51. package/dist/cjs/utils/index.js.map +1 -1
  52. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  53. package/dist/cjs/utils/mapPosition.js +1 -4
  54. package/dist/cjs/utils/mapPosition.js.map +1 -1
  55. package/dist/cjs/utils/spotCollateral.d.ts +1 -1
  56. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  57. package/dist/cjs/utils/spotCollateral.js +1 -12
  58. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  59. package/dist/cjs/utils/transferMargin.d.ts +2 -2
  60. package/dist/cjs/utils/transferMargin.d.ts.map +1 -1
  61. package/dist/cjs/utils/transferMargin.js +20 -12
  62. package/dist/cjs/utils/transferMargin.js.map +1 -1
  63. package/dist/cjs/utils/venueTotals.d.ts +11 -0
  64. package/dist/cjs/utils/venueTotals.d.ts.map +1 -0
  65. package/dist/cjs/utils/venueTotals.js +24 -0
  66. package/dist/cjs/utils/venueTotals.js.map +1 -0
  67. package/dist/cjs/utils/withdrawableBalances.d.ts +4 -0
  68. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -0
  69. package/dist/cjs/utils/withdrawableBalances.js +42 -0
  70. package/dist/cjs/utils/withdrawableBalances.js.map +1 -0
  71. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +3 -1
  72. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  73. package/dist/cjs/websocket/HyperliquidWsProvider.js +102 -36
  74. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  75. package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
  76. package/dist/esm/HyperliquidProvider.js +6 -2
  77. package/dist/esm/HyperliquidProvider.js.map +1 -1
  78. package/dist/esm/accountSummary.d.ts +4 -3
  79. package/dist/esm/accountSummary.d.ts.map +1 -1
  80. package/dist/esm/accountSummary.js +36 -18
  81. package/dist/esm/accountSummary.js.map +1 -1
  82. package/dist/esm/index.d.ts +2 -0
  83. package/dist/esm/index.d.ts.map +1 -1
  84. package/dist/esm/index.js +1 -0
  85. package/dist/esm/index.js.map +1 -1
  86. package/dist/esm/services/activeAssetData.d.ts +13 -0
  87. package/dist/esm/services/activeAssetData.d.ts.map +1 -0
  88. package/dist/esm/services/activeAssetData.js +10 -0
  89. package/dist/esm/services/activeAssetData.js.map +1 -0
  90. package/dist/esm/services/getAccount.d.ts.map +1 -1
  91. package/dist/esm/services/getAccount.js +19 -29
  92. package/dist/esm/services/getAccount.js.map +1 -1
  93. package/dist/esm/services/getAvailableToTrade.d.ts +26 -0
  94. package/dist/esm/services/getAvailableToTrade.d.ts.map +1 -0
  95. package/dist/esm/services/getAvailableToTrade.js +42 -0
  96. package/dist/esm/services/getAvailableToTrade.js.map +1 -0
  97. package/dist/esm/services/getMarketSettings.d.ts +1 -1
  98. package/dist/esm/services/getMarketSettings.d.ts.map +1 -1
  99. package/dist/esm/services/getMarketSettings.js +3 -7
  100. package/dist/esm/services/getMarketSettings.js.map +1 -1
  101. package/dist/esm/services/getOrder.d.ts.map +1 -1
  102. package/dist/esm/services/getOrder.js +7 -3
  103. package/dist/esm/services/getOrder.js.map +1 -1
  104. package/dist/esm/services/getOrders.d.ts.map +1 -1
  105. package/dist/esm/services/getOrders.js +6 -34
  106. package/dist/esm/services/getOrders.js.map +1 -1
  107. package/dist/esm/services/getWithdrawableBalances.d.ts +24 -0
  108. package/dist/esm/services/getWithdrawableBalances.d.ts.map +1 -0
  109. package/dist/esm/services/getWithdrawableBalances.js +39 -0
  110. package/dist/esm/services/getWithdrawableBalances.js.map +1 -0
  111. package/dist/esm/types/account.d.ts +21 -8
  112. package/dist/esm/types/account.d.ts.map +1 -1
  113. package/dist/esm/types/account.js.map +1 -1
  114. package/dist/esm/types/ws.d.ts +10 -4
  115. package/dist/esm/types/ws.d.ts.map +1 -1
  116. package/dist/esm/utils/abstractionMode.d.ts +1 -1
  117. package/dist/esm/utils/abstractionMode.d.ts.map +1 -1
  118. package/dist/esm/utils/abstractionMode.js.map +1 -1
  119. package/dist/esm/utils/explorer.d.ts +9 -1
  120. package/dist/esm/utils/explorer.d.ts.map +1 -1
  121. package/dist/esm/utils/explorer.js +34 -1
  122. package/dist/esm/utils/explorer.js.map +1 -1
  123. package/dist/esm/utils/index.d.ts +3 -1
  124. package/dist/esm/utils/index.d.ts.map +1 -1
  125. package/dist/esm/utils/index.js +3 -1
  126. package/dist/esm/utils/index.js.map +1 -1
  127. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  128. package/dist/esm/utils/mapPosition.js +1 -4
  129. package/dist/esm/utils/mapPosition.js.map +1 -1
  130. package/dist/esm/utils/spotCollateral.d.ts +4 -4
  131. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  132. package/dist/esm/utils/spotCollateral.js +4 -23
  133. package/dist/esm/utils/spotCollateral.js.map +1 -1
  134. package/dist/esm/utils/transferMargin.d.ts +12 -8
  135. package/dist/esm/utils/transferMargin.d.ts.map +1 -1
  136. package/dist/esm/utils/transferMargin.js +30 -18
  137. package/dist/esm/utils/transferMargin.js.map +1 -1
  138. package/dist/esm/utils/venueTotals.d.ts +26 -0
  139. package/dist/esm/utils/venueTotals.d.ts.map +1 -0
  140. package/dist/esm/utils/venueTotals.js +26 -0
  141. package/dist/esm/utils/venueTotals.js.map +1 -0
  142. package/dist/esm/utils/withdrawableBalances.d.ts +16 -0
  143. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -0
  144. package/dist/esm/utils/withdrawableBalances.js +50 -0
  145. package/dist/esm/utils/withdrawableBalances.js.map +1 -0
  146. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +4 -1
  147. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  148. package/dist/esm/websocket/HyperliquidWsProvider.js +101 -35
  149. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  150. package/dist/types/HyperliquidProvider.d.ts.map +1 -1
  151. package/dist/types/accountSummary.d.ts +4 -3
  152. package/dist/types/accountSummary.d.ts.map +1 -1
  153. package/dist/types/index.d.ts +2 -0
  154. package/dist/types/index.d.ts.map +1 -1
  155. package/dist/types/services/activeAssetData.d.ts +13 -0
  156. package/dist/types/services/activeAssetData.d.ts.map +1 -0
  157. package/dist/types/services/getAccount.d.ts.map +1 -1
  158. package/dist/types/services/getAvailableToTrade.d.ts +26 -0
  159. package/dist/types/services/getAvailableToTrade.d.ts.map +1 -0
  160. package/dist/types/services/getMarketSettings.d.ts +1 -1
  161. package/dist/types/services/getMarketSettings.d.ts.map +1 -1
  162. package/dist/types/services/getOrder.d.ts.map +1 -1
  163. package/dist/types/services/getOrders.d.ts.map +1 -1
  164. package/dist/types/services/getWithdrawableBalances.d.ts +24 -0
  165. package/dist/types/services/getWithdrawableBalances.d.ts.map +1 -0
  166. package/dist/types/types/account.d.ts +21 -8
  167. package/dist/types/types/account.d.ts.map +1 -1
  168. package/dist/types/types/ws.d.ts +10 -4
  169. package/dist/types/types/ws.d.ts.map +1 -1
  170. package/dist/types/utils/abstractionMode.d.ts +1 -1
  171. package/dist/types/utils/abstractionMode.d.ts.map +1 -1
  172. package/dist/types/utils/explorer.d.ts +9 -1
  173. package/dist/types/utils/explorer.d.ts.map +1 -1
  174. package/dist/types/utils/index.d.ts +3 -1
  175. package/dist/types/utils/index.d.ts.map +1 -1
  176. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  177. package/dist/types/utils/spotCollateral.d.ts +4 -4
  178. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  179. package/dist/types/utils/transferMargin.d.ts +12 -8
  180. package/dist/types/utils/transferMargin.d.ts.map +1 -1
  181. package/dist/types/utils/venueTotals.d.ts +26 -0
  182. package/dist/types/utils/venueTotals.d.ts.map +1 -0
  183. package/dist/types/utils/withdrawableBalances.d.ts +16 -0
  184. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -0
  185. package/dist/types/websocket/HyperliquidWsProvider.d.ts +4 -1
  186. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  187. package/package.json +3 -3
  188. package/src/HyperliquidProvider.ts +28 -2
  189. package/src/accountSummary.ts +64 -19
  190. package/src/index.ts +2 -0
  191. package/src/services/activeAssetData.ts +24 -0
  192. package/src/services/getAccount.ts +37 -52
  193. package/src/services/getAvailableToTrade.ts +72 -0
  194. package/src/services/getMarketSettings.ts +7 -11
  195. package/src/services/getOrder.ts +10 -3
  196. package/src/services/getOrders.ts +5 -56
  197. package/src/services/getWithdrawableBalances.ts +94 -0
  198. package/src/types/account.ts +22 -8
  199. package/src/types/ws.ts +18 -5
  200. package/src/utils/abstractionMode.ts +1 -1
  201. package/src/utils/explorer.ts +51 -2
  202. package/src/utils/index.ts +7 -1
  203. package/src/utils/mapPosition.ts +1 -5
  204. package/src/utils/spotCollateral.ts +4 -25
  205. package/src/utils/transferMargin.ts +31 -20
  206. package/src/utils/venueTotals.ts +48 -0
  207. package/src/utils/withdrawableBalances.ts +69 -0
  208. package/src/websocket/HyperliquidWsProvider.ts +149 -42
  209. package/dist/cjs/websocket/decodeChain.d.ts +0 -11
  210. package/dist/cjs/websocket/decodeChain.d.ts.map +0 -1
  211. package/dist/cjs/websocket/decodeChain.js +0 -37
  212. package/dist/cjs/websocket/decodeChain.js.map +0 -1
  213. package/dist/esm/websocket/decodeChain.d.ts +0 -30
  214. package/dist/esm/websocket/decodeChain.d.ts.map +0 -1
  215. package/dist/esm/websocket/decodeChain.js +0 -44
  216. package/dist/esm/websocket/decodeChain.js.map +0 -1
  217. package/dist/types/websocket/decodeChain.d.ts +0 -30
  218. package/dist/types/websocket/decodeChain.d.ts.map +0 -1
  219. package/src/websocket/decodeChain.ts +0 -54
@@ -11,17 +11,18 @@ import type {
11
11
  Asset,
12
12
  Balance,
13
13
  HyperliquidAccountConfig,
14
+ HyperliquidDexAccountState,
14
15
  Position,
15
16
  } from '@lifi/perps-types'
16
- import Big from 'big.js'
17
+ import type Big from 'big.js'
17
18
  import { PROVIDER_KEY } from '../constants.js'
18
19
  import type { HyperliquidContext } from '../context.js'
19
- import {
20
+ import type {
20
21
  HlAbstractionMode,
21
- type HlClearinghouseState,
22
- type HlExtraAgents,
23
- type HlSpotClearinghouseState,
24
- type HlUserFees,
22
+ HlClearinghouseState,
23
+ HlExtraAgents,
24
+ HlSpotClearinghouseState,
25
+ HlUserFees,
25
26
  } from '../types/index.js'
26
27
  import { isUnifiedAbstraction } from '../utils/abstractionMode.js'
27
28
  import { toWireBig } from '../utils/decimal.js'
@@ -29,9 +30,11 @@ import {
29
30
  assetIsOutcome,
30
31
  partitionSpotBalances,
31
32
  perpsDexNames,
33
+ perpsTotals,
32
34
  spotAssetFromToken,
33
35
  spotBalance,
34
36
  spotPriceById,
37
+ sumUnrealizedPnl,
35
38
  } from '../utils/index.js'
36
39
  import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
37
40
  import { isOpenAssetPosition, mapPosition } from '../utils/mapPosition.js'
@@ -49,45 +52,14 @@ export type GetAccountParams = ProviderGetAccountParams
49
52
  const getAccountValue = (state: HlClearinghouseState): Big =>
50
53
  toWireBig(state.marginSummary.accountValue, 'marginSummary.accountValue')
51
54
 
52
- const getTotalMarginUsed = (state: HlClearinghouseState): Big =>
53
- toWireBig(
54
- state.marginSummary.totalMarginUsed,
55
- 'marginSummary.totalMarginUsed'
56
- )
57
-
58
- const getMarginUsed = (
59
- abstraction: HlAbstractionMode | null,
60
- positions: Position[],
61
- stateByDex: Map<string, HlClearinghouseState>
62
- ): string => {
63
- if (
64
- abstraction === HlAbstractionMode.UNIFIED_ACCOUNT ||
65
- abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
66
- ) {
67
- // Per HL docs, individual per-dex states are not meaningful for these
68
- // modes; derive total margin from the already-mapped positions.
69
- const total = positions.reduce(
70
- (sum, position) =>
71
- sum.plus(toWireBig(position.marginUsed, 'positions.marginUsed')),
72
- new Big(0)
73
- )
74
- return total.toFixed()
75
- }
76
-
77
- if (abstraction === HlAbstractionMode.DEX_ABSTRACTION) {
78
- let total = new Big(0)
79
- for (const [, state] of stateByDex) {
80
- total = total.plus(getTotalMarginUsed(state))
81
- }
82
- return total.toFixed()
83
- }
84
-
85
- const mainState = stateByDex.get('')
86
- if (!mainState) {
87
- return '0'
88
- }
89
- return getTotalMarginUsed(mainState).toFixed()
90
- }
55
+ /** Venue buying power for the quote asset the perps dex settles in. */
56
+ const getAvailableAfterMaintenance = (
57
+ spotState: HlSpotClearinghouseState,
58
+ quoteAssetId: string | undefined
59
+ ): string | undefined =>
60
+ spotState.tokenToAvailableAfterMaintenance.find(
61
+ ([token]) => String(token) === quoteAssetId
62
+ )?.[1]
91
63
 
92
64
  interface BalancePartition {
93
65
  balances: Balance[]
@@ -106,8 +78,7 @@ const buildBalances = (
106
78
  spotState.balances
107
79
  .filter((b) => !assetIsOutcome(b.coin))
108
80
  .map((b) => spotBalance(spotAssetFromToken(b), b.total, priceById)),
109
- quoteAssetIds,
110
- abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
81
+ quoteAssetIds
111
82
  )
112
83
 
113
84
  // Unified/portfolio modes hold everything in spot — per-dex equity would
@@ -238,15 +209,29 @@ export const getAccount = async (
238
209
  stateByDex.set(dexNames[i], state)
239
210
  })
240
211
 
241
- const totalUnrealizedPnl = positions.reduce(
242
- (sum, p) => sum + Number.parseFloat(p.unrealizedPnl),
243
- 0
212
+ const dexStates: HyperliquidDexAccountState[] = stateResults.map(
213
+ (state, i) => ({
214
+ dex: dexNames[i],
215
+ marginSummary: state.marginSummary,
216
+ crossMarginSummary: state.crossMarginSummary,
217
+ crossMaintenanceMarginUsed: state.crossMaintenanceMarginUsed,
218
+ withdrawable: state.withdrawable,
219
+ })
244
220
  )
245
221
 
246
222
  const config: HyperliquidAccountConfig = {
247
223
  provider: PROVIDER_KEY,
248
224
  abstractionMode: abstractionResult,
249
225
  agents: agentsResult,
226
+ dexStates,
227
+ ...(isUnifiedAbstraction(abstractionResult)
228
+ ? {
229
+ availableAfterMaintenance: getAvailableAfterMaintenance(
230
+ spotState,
231
+ quoteAssetByCategory.get(PROVIDER_KEY)?.id
232
+ ),
233
+ }
234
+ : {}),
250
235
  }
251
236
 
252
237
  const { balances, collateralBalances } = buildBalances(
@@ -264,8 +249,8 @@ export const getAccount = async (
264
249
  balances,
265
250
  collateralBalances,
266
251
  positions,
267
- marginUsed: getMarginUsed(abstractionResult, positions, stateByDex),
268
- unrealizedPnl: totalUnrealizedPnl.toString(),
252
+ marginUsed: perpsTotals(dexStates).marginUsed.toFixed(),
253
+ unrealizedPnl: sumUnrealizedPnl(positions).toFixed(),
269
254
  feeTier: {
270
255
  maker: feesResult.userAddRate ?? '0',
271
256
  taker: feesResult.userCrossRate ?? '0',
@@ -0,0 +1,72 @@
1
+ import {
2
+ getMarketRegistry,
3
+ PerpsError,
4
+ type SDKRequestOptions,
5
+ toAssetDisplay,
6
+ } from '@lifi/perps-sdk'
7
+ import type { AvailableToTrade } from '@lifi/perps-types'
8
+ import { PerpsErrorCode } from '@lifi/perps-types'
9
+ import type { Address } from 'viem'
10
+ import { PROVIDER_KEY, SPOT_MARKET_ID } from '../constants.js'
11
+ import type { HyperliquidContext } from '../context.js'
12
+ import { fetchActiveAssetData } from './activeAssetData.js'
13
+
14
+ /**
15
+ * Parameters for {@link getAvailableToTrade}.
16
+ *
17
+ * @public
18
+ */
19
+ export interface GetAvailableToTradeParams {
20
+ address: Address
21
+ marketId: string
22
+ }
23
+
24
+ /**
25
+ * The amounts the account can still buy and sell on one perps market, read
26
+ * from `activeAssetData`. The amounts are denominated in the market's margin
27
+ * asset: USDC on the main dex, and the dex margin asset on a builder-deployed
28
+ * dex. Resolves `undefined` for a spot market, which carries no such state.
29
+ *
30
+ * @throws {PerpsError} On an unknown market, an `availableToTrade` payload
31
+ * that is not a buy/sell pair, Hyperliquid REST error, network, or parsing
32
+ * failures.
33
+ * @public
34
+ */
35
+ export const getAvailableToTrade = async (
36
+ context: HyperliquidContext,
37
+ params: GetAvailableToTradeParams,
38
+ options?: SDKRequestOptions
39
+ ): Promise<AvailableToTrade | undefined> => {
40
+ const registry = getMarketRegistry(context.client, PROVIDER_KEY)
41
+ await registry.sync()
42
+ const market = registry.require(params.marketId)
43
+ if (market.categoryId === SPOT_MARKET_ID) {
44
+ return undefined
45
+ }
46
+ const data = await fetchActiveAssetData(
47
+ context,
48
+ params.address,
49
+ params.marketId,
50
+ options
51
+ )
52
+ // The wire type states the pair; nothing verifies the response, so read it
53
+ // as a plain list to keep the length check in reach of the type checker.
54
+ const amounts: readonly string[] = data.availableToTrade
55
+ if (amounts.length !== 2) {
56
+ const err = new PerpsError(
57
+ PerpsErrorCode.ValidationError,
58
+ `Hyperliquid returned ${amounts.length} availableToTrade amounts for market '${params.marketId}'. Expected a buy amount and a sell amount.`
59
+ )
60
+ err.tool = PROVIDER_KEY
61
+ throw err
62
+ }
63
+ // Hyperliquid orders the pair buy-side first, sell-side second.
64
+ const [buy, sell] = amounts
65
+ return {
66
+ providerId: market.providerId,
67
+ marketId: market.id,
68
+ asset: toAssetDisplay(market.quoteAsset),
69
+ buy,
70
+ sell,
71
+ }
72
+ }
@@ -7,8 +7,7 @@ import {
7
7
  import type { Address } from 'viem'
8
8
  import { SPOT_MARKET_ID } from '../constants.js'
9
9
  import type { HyperliquidContext } from '../context.js'
10
- import type { HlActiveAssetData } from '../types/index.js'
11
- import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
10
+ import { fetchActiveAssetData } from './activeAssetData.js'
12
11
 
13
12
  /**
14
13
  * Parameters for {@link getMarketSettings}.
@@ -27,7 +26,7 @@ export interface GetMarketSettingsParams {
27
26
  * @public
28
27
  */
29
28
  export const getMarketSettings = async (
30
- { client, apiUrl }: HyperliquidContext,
29
+ context: HyperliquidContext,
31
30
  params: GetMarketSettingsParams,
32
31
  options?: SDKRequestOptions
33
32
  ): Promise<MarketSettings | undefined> => {
@@ -35,14 +34,11 @@ export const getMarketSettings = async (
35
34
  if (params.market.categoryId === SPOT_MARKET_ID) {
36
35
  return undefined
37
36
  }
38
- const data = await infoRequest<HlActiveAssetData>(
39
- apiUrl,
40
- {
41
- type: 'activeAssetData',
42
- user: params.address,
43
- coin: params.market.marketId,
44
- },
45
- hlInfoOptions(client, options)
37
+ const data = await fetchActiveAssetData(
38
+ context,
39
+ params.address,
40
+ params.market.marketId,
41
+ options
46
42
  )
47
43
  const leverage = data.leverage
48
44
  if (!leverage) {
@@ -12,6 +12,7 @@ import type {
12
12
  HlOrderStatusResponse,
13
13
  HlTwapHistoryEntry,
14
14
  } from '../types/index.js'
15
+ import { withExplorerLinks } from '../utils/explorer.js'
15
16
  import { assetIsOutcome, mapOrder } from '../utils/index.js'
16
17
  import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
17
18
 
@@ -47,17 +48,18 @@ export const getOrder = async (
47
48
  throw err
48
49
  }
49
50
 
51
+ const infoOpts = hlInfoOptions(client, options)
50
52
  const status = await infoRequest<HlOrderStatusResponse>(
51
53
  apiUrl,
52
54
  { type: 'orderStatus', user: params.address, oid },
53
- hlInfoOptions(client, options)
55
+ infoOpts
54
56
  )
55
57
 
56
58
  if (status.status !== 'order') {
57
59
  const history = await infoRequest<HlTwapHistoryEntry[]>(
58
60
  apiUrl,
59
61
  { type: 'twapHistory', user: params.address },
60
- hlInfoOptions(client, options)
62
+ infoOpts
61
63
  )
62
64
  const twap = history.find(
63
65
  (entry) =>
@@ -81,5 +83,10 @@ export const getOrder = async (
81
83
 
82
84
  const registry = getMarketRegistry(client, PROVIDER_KEY)
83
85
  await registry.sync()
84
- return mapOrder(status.order, registry.require(status.order.order.coin))
86
+ const order = mapOrder(
87
+ status.order,
88
+ registry.require(status.order.order.coin)
89
+ )
90
+ const [linked] = await withExplorerLinks([order], params.address, infoOpts)
91
+ return linked
85
92
  }
@@ -1,8 +1,6 @@
1
1
  import {
2
2
  ACTIVE_ORDER_STATUSES,
3
3
  createWarnOnce,
4
- ExplorerChainId,
5
- explorerTxUrl,
6
4
  getMarketRegistry,
7
5
  isActiveOrderStatus,
8
6
  type MarketRegistry,
@@ -14,34 +12,21 @@ import type { MarketDisplay, Order, OrdersResponse } from '@lifi/perps-types'
14
12
  import { PROVIDER_KEY } from '../constants.js'
15
13
  import type { HyperliquidContext } from '../context.js'
16
14
  import type {
17
- HlExplorerTx,
18
15
  HlFrontendOpenOrders,
19
16
  HlHistoricalOrders,
20
17
  HlTwapHistoryEntry,
21
18
  } from '../types/index.js'
22
- import {
23
- assetIsOutcome,
24
- fetchUserTransactions,
25
- mapOrder,
26
- matchOrderActionHash,
27
- perpsDexNames,
28
- } from '../utils/index.js'
29
- import {
30
- hlInfoOptions,
31
- type InfoRequestOptions,
32
- infoRequest,
33
- } from '../utils/infoClient.js'
19
+ import { withExplorerLinks } from '../utils/explorer.js'
20
+ import { assetIsOutcome, mapOrder, perpsDexNames } from '../utils/index.js'
21
+ import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
34
22
 
35
23
  /** Parameters for a lifecycle-filtered Hyperliquid order read. */
36
24
  export type GetOrdersParams = ProviderGetOrdersParams
37
25
 
38
26
  const warn = createWarnOnce()
39
27
 
40
- const warnOnce = (key: string, detail?: string): void => {
41
- warn(
42
- key,
43
- `[${PROVIDER_KEY}] ${detail === undefined ? key : `${key}: ${detail}`}`
44
- )
28
+ const warnOnce = (key: string): void => {
29
+ warn(key, `[${PROVIDER_KEY}] ${key}`)
45
30
  }
46
31
 
47
32
  /**
@@ -73,42 +58,6 @@ const mapRow = (
73
58
  }
74
59
  }
75
60
 
76
- /**
77
- * Set `explorerLink` on each row whose placement transaction the explorer
78
- * window still holds. One explorer read serves the whole page, and it is
79
- * skipped when no row carries a client order id to match on. The link is
80
- * supplementary, so an explorer failure warns and leaves the rows unlinked
81
- * instead of failing the order read.
82
- */
83
- const withExplorerLinks = async (
84
- orders: Order[],
85
- address: string,
86
- options: InfoRequestOptions
87
- ): Promise<Order[]> => {
88
- if (!orders.some((order) => order.clientOrderId !== undefined)) {
89
- return orders
90
- }
91
- let txs: HlExplorerTx[]
92
- try {
93
- txs = await fetchUserTransactions(address, options)
94
- } catch (error) {
95
- if (!(error instanceof PerpsError)) {
96
- throw error
97
- }
98
- // The message stays out of the key: a transport failure names hosts and
99
- // ports, so keying on it would let the dedupe set grow without bound.
100
- warnOnce('explorer link lookup failed', error.message)
101
- return orders
102
- }
103
- return orders.map((order) => {
104
- const explorerLink = explorerTxUrl(
105
- ExplorerChainId.HYPERLIQUID,
106
- matchOrderActionHash(txs, order)
107
- )
108
- return explorerLink === undefined ? order : { ...order, explorerLink }
109
- })
110
- }
111
-
112
61
  /** Read regular, trigger, and TWAP orders from the requested lifecycle feeds. */
113
62
  export const getOrders = async (
114
63
  { client, apiUrl }: HyperliquidContext,
@@ -0,0 +1,94 @@
1
+ import {
2
+ getMarketRegistry,
3
+ getProviders,
4
+ PerpsError,
5
+ type ProviderGetWithdrawableBalancesParams,
6
+ type ProviderWithdrawableBalance,
7
+ type SDKRequestOptions,
8
+ } from '@lifi/perps-sdk'
9
+ import { PerpsErrorCode } from '@lifi/perps-types'
10
+ import { MAIN_MARKET_ID, PROVIDER_KEY } from '../constants.js'
11
+ import type { HyperliquidContext } from '../context.js'
12
+ import type {
13
+ HlAbstractionMode,
14
+ HlClearinghouseState,
15
+ HlSpotClearinghouseState,
16
+ } from '../types/index.js'
17
+ import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
18
+ import { hyperliquidWithdrawableBalances } from '../utils/withdrawableBalances.js'
19
+
20
+ /**
21
+ * Parameters for {@link getWithdrawableBalances}.
22
+ *
23
+ * @public
24
+ */
25
+ export type GetWithdrawableBalancesParams =
26
+ ProviderGetWithdrawableBalancesParams
27
+
28
+ /**
29
+ * The `(asset, route)` pairs `params.address` can withdraw from Hyperliquid,
30
+ * read from the venue's own `withdrawable` and spot `total`/`hold` figures.
31
+ * Hyperliquid charges its flat withdrawal fee in USDC, so the backend's
32
+ * `withdrawalFeeUsd` is the fee in quote-asset units on every USDC row.
33
+ *
34
+ * Only the main perps dex is read: a withdrawal to L1 draws on that account,
35
+ * and a sub-dex balance reaches it through a transfer first.
36
+ *
37
+ * @throws {PerpsError} On Hyperliquid REST or backend `/providers` error,
38
+ * network, or parsing failures, and `SDKError` when the market registry
39
+ * carries no main perps market to name the quote asset.
40
+ * @public
41
+ */
42
+ export const getWithdrawableBalances = async (
43
+ { client, apiUrl }: HyperliquidContext,
44
+ params: GetWithdrawableBalancesParams,
45
+ options?: SDKRequestOptions
46
+ ): Promise<ProviderWithdrawableBalance[]> => {
47
+ const registry = getMarketRegistry(client, PROVIDER_KEY)
48
+ const infoOpts = hlInfoOptions(client, options)
49
+
50
+ const [{ providers }, markets, abstraction, state, spotState] =
51
+ await Promise.all([
52
+ getProviders(client, options),
53
+ registry.sync(),
54
+ infoRequest<HlAbstractionMode | null>(
55
+ apiUrl,
56
+ { type: 'userAbstraction', user: params.address },
57
+ infoOpts
58
+ ),
59
+ infoRequest<HlClearinghouseState>(
60
+ apiUrl,
61
+ { type: 'clearinghouseState', user: params.address },
62
+ infoOpts
63
+ ),
64
+ infoRequest<HlSpotClearinghouseState>(
65
+ apiUrl,
66
+ { type: 'spotClearinghouseState', user: params.address },
67
+ infoOpts
68
+ ),
69
+ ])
70
+
71
+ const quoteAsset = markets.find(
72
+ (market) => market.categoryId === MAIN_MARKET_ID
73
+ )?.quoteAsset
74
+ if (quoteAsset === undefined) {
75
+ const error = new PerpsError(
76
+ PerpsErrorCode.SDKError,
77
+ `Hyperliquid market registry carries no '${MAIN_MARKET_ID}' market to name the withdrawal quote asset`
78
+ )
79
+ error.tool = PROVIDER_KEY
80
+ throw error
81
+ }
82
+
83
+ const feeUsd = providers.find(
84
+ (provider) => provider.key === PROVIDER_KEY
85
+ )?.withdrawalFeeUsd
86
+
87
+ return hyperliquidWithdrawableBalances(
88
+ abstraction,
89
+ state,
90
+ spotState,
91
+ quoteAsset.id,
92
+ feeUsd === undefined ? undefined : String(feeUsd)
93
+ )
94
+ }
@@ -51,6 +51,19 @@ export type HlAssetPosition = {
51
51
  position: HlPosition
52
52
  }
53
53
 
54
+ /**
55
+ * Margin totals block of a Hyperliquid clearinghouse state. Monetary fields
56
+ * are decimal strings in the account's quote asset.
57
+ * @public
58
+ */
59
+ export type HlMarginSummary = {
60
+ /** Total equity: locked margin and unrealized PnL included. */
61
+ accountValue: string
62
+ totalNtlPos: string
63
+ totalRawUsd: string
64
+ totalMarginUsed: string
65
+ }
66
+
54
67
  /**
55
68
  * Perpetual clearinghouse account state returned by Hyperliquid `/info`.
56
69
  * `assetPositions` includes zero-size rows that callers may discard. Monetary
@@ -60,14 +73,10 @@ export type HlAssetPosition = {
60
73
  */
61
74
  export type HlClearinghouseState = {
62
75
  assetPositions: HlAssetPosition[]
63
- marginSummary: {
64
- accountValue: string
65
- totalMarginUsed: string
66
- }
67
- crossMarginSummary: {
68
- accountValue: string
69
- totalMarginUsed: string
70
- }
76
+ marginSummary: HlMarginSummary
77
+ crossMarginSummary: HlMarginSummary
78
+ crossMaintenanceMarginUsed: string
79
+ withdrawable: string
71
80
  }
72
81
 
73
82
  /**
@@ -90,6 +99,11 @@ export type HlSpotBalance = {
90
99
  */
91
100
  export type HlSpotClearinghouseState = {
92
101
  balances: HlSpotBalance[]
102
+ /**
103
+ * Venue buying power per spot token, as `[token index, amount]` pairs.
104
+ * The venue reports the amount after maintenance margin.
105
+ */
106
+ tokenToAvailableAfterMaintenance: [number, string][]
93
107
  }
94
108
 
95
109
  /**
package/src/types/ws.ts CHANGED
@@ -184,6 +184,18 @@ export type HlWsUserFillsData = {
184
184
  fills: HlUserFill[]
185
185
  }
186
186
 
187
+ /**
188
+ * One sub-dex slice of an `allDexsClearinghouseState` frame. `marginSummary`
189
+ * is absent on a frame that reports positions only, so a reader must treat it
190
+ * as optional.
191
+ * @public
192
+ */
193
+ export type HlWsDexClearinghouseState = Pick<
194
+ HlClearinghouseState,
195
+ 'assetPositions'
196
+ > &
197
+ Partial<Pick<HlClearinghouseState, 'marginSummary'>>
198
+
187
199
  /**
188
200
  * Per-dex clearinghouse event for one user. Each tuple contains a wire DEX name
189
201
  * and the perp positions/equity fields supplied by that DEX.
@@ -191,14 +203,15 @@ export type HlWsUserFillsData = {
191
203
  */
192
204
  export type HlWsAllDexsClearinghouseStateData = {
193
205
  user: string
194
- clearinghouseStates: [
195
- string,
196
- Pick<HlClearinghouseState, 'assetPositions' | 'marginSummary'>,
197
- ][]
206
+ clearinghouseStates: [string, HlWsDexClearinghouseState][]
198
207
  }
199
208
 
200
209
  /** User spot clearinghouse event containing the current spot balances. @public */
201
210
  export type HlWsSpotStateData = {
202
211
  user: string
203
- spotState: { balances: HlSpotBalance[] }
212
+ spotState: {
213
+ balances: HlSpotBalance[]
214
+ /** Absent on some frames; the REST `spotClearinghouseState` read covers it. */
215
+ tokenToAvailableAfterMaintenance?: [number, string][]
216
+ }
204
217
  }
@@ -6,7 +6,7 @@ import { HlAbstractionMode } from '../types/account.js'
6
6
  * per-dex, apart from the spot balances.
7
7
  */
8
8
  export const isUnifiedAbstraction = (
9
- abstraction: HlAbstractionMode | null
9
+ abstraction: HlAbstractionMode | string | null
10
10
  ): boolean =>
11
11
  abstraction === HlAbstractionMode.UNIFIED_ACCOUNT ||
12
12
  abstraction === HlAbstractionMode.PORTFOLIO_MARGIN
@@ -1,6 +1,12 @@
1
- import type { OrderBase } from '@lifi/perps-types'
1
+ import {
2
+ createWarnOnce,
3
+ ExplorerChainId,
4
+ explorerTxUrl,
5
+ PerpsError,
6
+ } from '@lifi/perps-sdk'
7
+ import type { Order, OrderBase } from '@lifi/perps-types'
2
8
  import { isHex } from 'viem'
3
- import { HYPERLIQUID_EXPLORER_RPC_URL } from '../constants.js'
9
+ import { HYPERLIQUID_EXPLORER_RPC_URL, PROVIDER_KEY } from '../constants.js'
4
10
  import { type HlExplorerTx, isHlExplorerTx } from '../types/explorer.js'
5
11
  import { hlPostJson, type InfoRequestOptions } from './infoClient.js'
6
12
 
@@ -91,3 +97,46 @@ export const matchOrderActionHash = (
91
97
  )
92
98
  return match?.hash
93
99
  }
100
+
101
+ const warn = createWarnOnce()
102
+
103
+ /**
104
+ * Set `explorerLink` on each order whose placement transaction the explorer
105
+ * window still holds. One explorer read serves every order, and it is skipped
106
+ * when no order carries a client order id to match on. The link is
107
+ * supplementary, so an explorer failure warns and leaves the orders unlinked
108
+ * instead of failing the order read. A caller cancellation still rejects.
109
+ */
110
+ export const withExplorerLinks = async (
111
+ orders: Order[],
112
+ address: string,
113
+ options: InfoRequestOptions
114
+ ): Promise<Order[]> => {
115
+ if (!orders.some((order) => order.clientOrderId !== undefined)) {
116
+ return orders
117
+ }
118
+ let txs: HlExplorerTx[]
119
+ try {
120
+ txs = await fetchUserTransactions(address, options)
121
+ } catch (error) {
122
+ // `hlPostJson` wraps a signal timeout as a `ServerError`, so the signal,
123
+ // not the error type, tells a caller cancellation from an explorer failure.
124
+ if (!(error instanceof PerpsError) || options.signal?.aborted) {
125
+ throw error
126
+ }
127
+ // The message stays out of the key: a transport failure names hosts and
128
+ // ports, so keying on it would let the dedupe set grow without bound.
129
+ warn(
130
+ 'explorer link lookup failed',
131
+ `[${PROVIDER_KEY}] explorer link lookup failed: ${error.message}`
132
+ )
133
+ return orders
134
+ }
135
+ return orders.map((order) => {
136
+ const explorerLink = explorerTxUrl(
137
+ ExplorerChainId.HYPERLIQUID,
138
+ matchOrderActionHash(txs, order)
139
+ )
140
+ return explorerLink === undefined ? order : { ...order, explorerLink }
141
+ })
142
+ }
@@ -56,4 +56,10 @@ export {
56
56
  spotPriceById,
57
57
  } from './spotBalance.js'
58
58
  export { partitionSpotBalances, type SpotPartition } from './spotCollateral.js'
59
- export { positionMarginConstraints } from './transferMargin.js'
59
+ export { positionRemovableMargin } from './transferMargin.js'
60
+ export {
61
+ type DexMarginSummary,
62
+ perpsTotals,
63
+ sumUnrealizedPnl,
64
+ } from './venueTotals.js'
65
+ export { hyperliquidWithdrawableBalances } from './withdrawableBalances.js'
@@ -30,10 +30,6 @@ export const mapPosition = (
30
30
  const leverage = new Big(pos.leverage.value)
31
31
  const marginMode =
32
32
  pos.leverage.type === 'cross' ? MarginMode.CROSS : MarginMode.ISOLATED
33
- const marginUsed =
34
- marginMode === MarginMode.ISOLATED
35
- ? new Big(pos.marginUsed).minus(pos.unrealizedPnl).toFixed()
36
- : pos.marginUsed
37
33
 
38
34
  return {
39
35
  market,
@@ -45,7 +41,7 @@ export const mapPosition = (
45
41
  unrealizedPnl: pos.unrealizedPnl,
46
42
  accruedFunding: new Big(pos.cumFunding.sinceOpen).neg().toFixed(),
47
43
  leverage: ap.position.leverage.value,
48
- marginUsed,
44
+ marginUsed: pos.marginUsed,
49
45
  initialMarginRequirement: positionValue.div(leverage).toFixed(),
50
46
  marginMode,
51
47
  }