@lifi/perps-sdk-provider-hyperliquid 14.0.0 → 16.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 (189) hide show
  1. package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
  2. package/dist/cjs/HyperliquidProvider.js +4 -0
  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/getWithdrawableBalances.d.ts +5 -0
  27. package/dist/cjs/services/getWithdrawableBalances.d.ts.map +1 -0
  28. package/dist/cjs/services/getWithdrawableBalances.js +27 -0
  29. package/dist/cjs/services/getWithdrawableBalances.js.map +1 -0
  30. package/dist/cjs/types/account.d.ts +11 -8
  31. package/dist/cjs/types/account.d.ts.map +1 -1
  32. package/dist/cjs/types/account.js.map +1 -1
  33. package/dist/cjs/types/ws.d.ts +3 -4
  34. package/dist/cjs/types/ws.d.ts.map +1 -1
  35. package/dist/cjs/utils/abstractionMode.d.ts +1 -1
  36. package/dist/cjs/utils/abstractionMode.d.ts.map +1 -1
  37. package/dist/cjs/utils/abstractionMode.js.map +1 -1
  38. package/dist/cjs/utils/index.d.ts +2 -0
  39. package/dist/cjs/utils/index.d.ts.map +1 -1
  40. package/dist/cjs/utils/index.js +6 -1
  41. package/dist/cjs/utils/index.js.map +1 -1
  42. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  43. package/dist/cjs/utils/mapActivity.js +4 -12
  44. package/dist/cjs/utils/mapActivity.js.map +1 -1
  45. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  46. package/dist/cjs/utils/mapPosition.js +1 -4
  47. package/dist/cjs/utils/mapPosition.js.map +1 -1
  48. package/dist/cjs/utils/spotCollateral.d.ts +1 -1
  49. package/dist/cjs/utils/spotCollateral.d.ts.map +1 -1
  50. package/dist/cjs/utils/spotCollateral.js +1 -12
  51. package/dist/cjs/utils/spotCollateral.js.map +1 -1
  52. package/dist/cjs/utils/venueTotals.d.ts +11 -0
  53. package/dist/cjs/utils/venueTotals.d.ts.map +1 -0
  54. package/dist/cjs/utils/venueTotals.js +24 -0
  55. package/dist/cjs/utils/venueTotals.js.map +1 -0
  56. package/dist/cjs/utils/withdrawableBalances.d.ts +4 -0
  57. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -0
  58. package/dist/cjs/utils/withdrawableBalances.js +35 -0
  59. package/dist/cjs/utils/withdrawableBalances.js.map +1 -0
  60. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +3 -1
  61. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  62. package/dist/cjs/websocket/HyperliquidWsProvider.js +102 -36
  63. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  64. package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
  65. package/dist/esm/HyperliquidProvider.js +4 -0
  66. package/dist/esm/HyperliquidProvider.js.map +1 -1
  67. package/dist/esm/accountSummary.d.ts +4 -3
  68. package/dist/esm/accountSummary.d.ts.map +1 -1
  69. package/dist/esm/accountSummary.js +36 -18
  70. package/dist/esm/accountSummary.js.map +1 -1
  71. package/dist/esm/index.d.ts +2 -0
  72. package/dist/esm/index.d.ts.map +1 -1
  73. package/dist/esm/index.js +1 -0
  74. package/dist/esm/index.js.map +1 -1
  75. package/dist/esm/services/activeAssetData.d.ts +13 -0
  76. package/dist/esm/services/activeAssetData.d.ts.map +1 -0
  77. package/dist/esm/services/activeAssetData.js +10 -0
  78. package/dist/esm/services/activeAssetData.js.map +1 -0
  79. package/dist/esm/services/getAccount.d.ts.map +1 -1
  80. package/dist/esm/services/getAccount.js +19 -29
  81. package/dist/esm/services/getAccount.js.map +1 -1
  82. package/dist/esm/services/getAvailableToTrade.d.ts +26 -0
  83. package/dist/esm/services/getAvailableToTrade.d.ts.map +1 -0
  84. package/dist/esm/services/getAvailableToTrade.js +42 -0
  85. package/dist/esm/services/getAvailableToTrade.js.map +1 -0
  86. package/dist/esm/services/getMarketSettings.d.ts +1 -1
  87. package/dist/esm/services/getMarketSettings.d.ts.map +1 -1
  88. package/dist/esm/services/getMarketSettings.js +3 -7
  89. package/dist/esm/services/getMarketSettings.js.map +1 -1
  90. package/dist/esm/services/getWithdrawableBalances.d.ts +22 -0
  91. package/dist/esm/services/getWithdrawableBalances.d.ts.map +1 -0
  92. package/dist/esm/services/getWithdrawableBalances.js +35 -0
  93. package/dist/esm/services/getWithdrawableBalances.js.map +1 -0
  94. package/dist/esm/types/account.d.ts +21 -8
  95. package/dist/esm/types/account.d.ts.map +1 -1
  96. package/dist/esm/types/account.js.map +1 -1
  97. package/dist/esm/types/ws.d.ts +10 -4
  98. package/dist/esm/types/ws.d.ts.map +1 -1
  99. package/dist/esm/utils/abstractionMode.d.ts +1 -1
  100. package/dist/esm/utils/abstractionMode.d.ts.map +1 -1
  101. package/dist/esm/utils/abstractionMode.js.map +1 -1
  102. package/dist/esm/utils/index.d.ts +2 -0
  103. package/dist/esm/utils/index.d.ts.map +1 -1
  104. package/dist/esm/utils/index.js +2 -0
  105. package/dist/esm/utils/index.js.map +1 -1
  106. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  107. package/dist/esm/utils/mapActivity.js +5 -13
  108. package/dist/esm/utils/mapActivity.js.map +1 -1
  109. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  110. package/dist/esm/utils/mapPosition.js +1 -4
  111. package/dist/esm/utils/mapPosition.js.map +1 -1
  112. package/dist/esm/utils/spotCollateral.d.ts +4 -4
  113. package/dist/esm/utils/spotCollateral.d.ts.map +1 -1
  114. package/dist/esm/utils/spotCollateral.js +4 -23
  115. package/dist/esm/utils/spotCollateral.js.map +1 -1
  116. package/dist/esm/utils/venueTotals.d.ts +26 -0
  117. package/dist/esm/utils/venueTotals.d.ts.map +1 -0
  118. package/dist/esm/utils/venueTotals.js +26 -0
  119. package/dist/esm/utils/venueTotals.js.map +1 -0
  120. package/dist/esm/utils/withdrawableBalances.d.ts +14 -0
  121. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -0
  122. package/dist/esm/utils/withdrawableBalances.js +41 -0
  123. package/dist/esm/utils/withdrawableBalances.js.map +1 -0
  124. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +4 -1
  125. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  126. package/dist/esm/websocket/HyperliquidWsProvider.js +101 -35
  127. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  128. package/dist/types/HyperliquidProvider.d.ts.map +1 -1
  129. package/dist/types/accountSummary.d.ts +4 -3
  130. package/dist/types/accountSummary.d.ts.map +1 -1
  131. package/dist/types/index.d.ts +2 -0
  132. package/dist/types/index.d.ts.map +1 -1
  133. package/dist/types/services/activeAssetData.d.ts +13 -0
  134. package/dist/types/services/activeAssetData.d.ts.map +1 -0
  135. package/dist/types/services/getAccount.d.ts.map +1 -1
  136. package/dist/types/services/getAvailableToTrade.d.ts +26 -0
  137. package/dist/types/services/getAvailableToTrade.d.ts.map +1 -0
  138. package/dist/types/services/getMarketSettings.d.ts +1 -1
  139. package/dist/types/services/getMarketSettings.d.ts.map +1 -1
  140. package/dist/types/services/getWithdrawableBalances.d.ts +22 -0
  141. package/dist/types/services/getWithdrawableBalances.d.ts.map +1 -0
  142. package/dist/types/types/account.d.ts +21 -8
  143. package/dist/types/types/account.d.ts.map +1 -1
  144. package/dist/types/types/ws.d.ts +10 -4
  145. package/dist/types/types/ws.d.ts.map +1 -1
  146. package/dist/types/utils/abstractionMode.d.ts +1 -1
  147. package/dist/types/utils/abstractionMode.d.ts.map +1 -1
  148. package/dist/types/utils/index.d.ts +2 -0
  149. package/dist/types/utils/index.d.ts.map +1 -1
  150. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  151. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  152. package/dist/types/utils/spotCollateral.d.ts +4 -4
  153. package/dist/types/utils/spotCollateral.d.ts.map +1 -1
  154. package/dist/types/utils/venueTotals.d.ts +26 -0
  155. package/dist/types/utils/venueTotals.d.ts.map +1 -0
  156. package/dist/types/utils/withdrawableBalances.d.ts +14 -0
  157. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -0
  158. package/dist/types/websocket/HyperliquidWsProvider.d.ts +4 -1
  159. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  160. package/package.json +3 -3
  161. package/src/HyperliquidProvider.ts +26 -0
  162. package/src/accountSummary.ts +64 -19
  163. package/src/index.ts +2 -0
  164. package/src/services/activeAssetData.ts +24 -0
  165. package/src/services/getAccount.ts +37 -52
  166. package/src/services/getAvailableToTrade.ts +72 -0
  167. package/src/services/getMarketSettings.ts +7 -11
  168. package/src/services/getWithdrawableBalances.ts +84 -0
  169. package/src/types/account.ts +22 -8
  170. package/src/types/ws.ts +18 -5
  171. package/src/utils/abstractionMode.ts +1 -1
  172. package/src/utils/index.ts +6 -0
  173. package/src/utils/mapActivity.ts +10 -13
  174. package/src/utils/mapPosition.ts +1 -5
  175. package/src/utils/spotCollateral.ts +4 -25
  176. package/src/utils/venueTotals.ts +48 -0
  177. package/src/utils/withdrawableBalances.ts +57 -0
  178. package/src/websocket/HyperliquidWsProvider.ts +149 -42
  179. package/dist/cjs/websocket/decodeChain.d.ts +0 -11
  180. package/dist/cjs/websocket/decodeChain.d.ts.map +0 -1
  181. package/dist/cjs/websocket/decodeChain.js +0 -37
  182. package/dist/cjs/websocket/decodeChain.js.map +0 -1
  183. package/dist/esm/websocket/decodeChain.d.ts +0 -30
  184. package/dist/esm/websocket/decodeChain.d.ts.map +0 -1
  185. package/dist/esm/websocket/decodeChain.js +0 -44
  186. package/dist/esm/websocket/decodeChain.js.map +0 -1
  187. package/dist/types/websocket/decodeChain.d.ts +0 -30
  188. package/dist/types/websocket/decodeChain.d.ts.map +0 -1
  189. package/src/websocket/decodeChain.ts +0 -54
@@ -1,4 +1,5 @@
1
1
  import {
2
+ DecodeChain,
2
3
  getMarketRegistry,
3
4
  isActiveMarket,
4
5
  isActiveOrderStatus,
@@ -10,7 +11,7 @@ import {
10
11
  ReconnectingWebSocket,
11
12
  resolveSubscribeQuote,
12
13
  type SubscriptionListener,
13
- summarizeAccount,
14
+ toAssetDisplay,
14
15
  toPerpsMarketDisplay,
15
16
  WsProviderBase,
16
17
  type WsProviderFactory,
@@ -19,7 +20,6 @@ import {
19
20
  wsLog,
20
21
  } from '@lifi/perps-sdk'
21
22
  import type {
22
- AccountResponse,
23
23
  Balance,
24
24
  MarketContext,
25
25
  Order,
@@ -37,9 +37,11 @@ import {
37
37
  SPOT_MARKET_ID,
38
38
  } from '../constants.js'
39
39
  import type {
40
+ HlActiveAssetData,
40
41
  HlAssetPosition,
41
42
  HlOrderDetail,
42
43
  HlOrderStatusResponse,
44
+ HlSpotClearinghouseState,
43
45
  HlUserFill,
44
46
  HlWsActiveAssetCtxData,
45
47
  HlWsActiveSpotAssetCtxData,
@@ -75,12 +77,13 @@ import {
75
77
  mapOrder,
76
78
  mapPosition,
77
79
  partitionSpotBalances,
80
+ perpsTotals,
78
81
  priceStepToAggregation,
79
82
  spotAssetFromToken,
80
83
  spotBalance,
81
84
  spotPriceById,
85
+ sumUnrealizedPnl,
82
86
  } from '../utils/index.js'
83
- import { DecodeChain } from './decodeChain.js'
84
87
 
85
88
  /** HL's compact `l2` snapshot carries 20 levels per side. */
86
89
  const HL_L2_BOOK_MAX_LEVELS_PER_SIDE = 20
@@ -88,6 +91,15 @@ const HL_L2_BOOK_MAX_LEVELS_PER_SIDE = 20
88
91
  const normalizeHlAddress = (address: string): string =>
89
92
  isAddress(address, { strict: false }) ? address.toLowerCase() : address
90
93
 
94
+ /** Venue buying power for the asset the main perps dex settles in. */
95
+ const availableAfterMaintenance = (
96
+ spotState: { tokenToAvailableAfterMaintenance?: readonly [number, string][] },
97
+ quoteAssetId: string | undefined
98
+ ): string | undefined =>
99
+ spotState.tokenToAvailableAfterMaintenance?.find(
100
+ ([token]) => String(token) === quoteAssetId
101
+ )?.[1]
102
+
91
103
  /**
92
104
  * `WsProviderFactory` constructor for Hyperliquid — pass to
93
105
  * `new PerpsWsClient(client, { wsProviders: { hyperliquid: hyperliquidWsProvider() } })`.
@@ -147,11 +159,16 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
147
159
  {
148
160
  releaseSpot: Promise<() => void>
149
161
  spot?: { collateralBalances: Balance[]; balances: Balance[] }
162
+ availableMargin?: string
163
+ availableMarginPending?: boolean
150
164
  }
151
165
  >()
152
166
  // Kept outside the pipeline entry: the first clearinghouse frame lands
153
167
  // while the mode read is still pending, before the pipeline exists.
154
- private readonly latestPositionsByUser = new Map<string, Position[]>()
168
+ private readonly latestPerpsByUser = new Map<
169
+ string,
170
+ { positions: Position[]; marginUsed: string }
171
+ >()
155
172
  private readonly heldSummaryByUser = new Map<
156
173
  string,
157
174
  {
@@ -273,7 +290,8 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
273
290
  (sub.channel === 'marketContext' ||
274
291
  sub.channel === 'orderbook' ||
275
292
  sub.channel === 'candle' ||
276
- sub.channel === 'trades')
293
+ sub.channel === 'trades' ||
294
+ sub.channel === 'availableToTrade')
277
295
  ) {
278
296
  this.registry.requireActive(sub.marketId)
279
297
  }
@@ -331,7 +349,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
331
349
  // subscription so a resubscribe reflects an account-mode change.
332
350
  this.abstractionByUser.delete(user)
333
351
  this.heldSummaryByUser.delete(user)
334
- this.latestPositionsByUser.delete(user)
352
+ this.latestPerpsByUser.delete(user)
335
353
  this.syncUnifiedPipeline(user, user, null)
336
354
  this.unregisterSub(wireKey)
337
355
  this.rws.send(
@@ -506,6 +524,8 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
506
524
  return `accountSummary:${sub.address.toLowerCase()}`
507
525
  case 'spotBalances':
508
526
  return `spotState:${sub.address.toLowerCase()}`
527
+ case 'availableToTrade':
528
+ return `activeAssetData:${sub.address.toLowerCase()}:${sub.marketId}`
509
529
  }
510
530
  }
511
531
 
@@ -581,6 +601,12 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
581
601
  }
582
602
  case 'spotBalances':
583
603
  return { type: 'spotState', user: normalizeHlAddress(sub.address) }
604
+ case 'availableToTrade':
605
+ return {
606
+ type: 'activeAssetData',
607
+ user: normalizeHlAddress(sub.address),
608
+ coin: sub.marketId,
609
+ }
584
610
  }
585
611
  }
586
612
 
@@ -656,6 +682,9 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
656
682
  case 'spotState':
657
683
  this.handleSpotState(msg.data as HlWsSpotStateData)
658
684
  break
685
+ case 'activeAssetData':
686
+ this.handleActiveAssetData(msg.data as HlActiveAssetData)
687
+ break
659
688
  }
660
689
  } catch (error) {
661
690
  wsLog.handlerFailure(this.providerKey, error)
@@ -1130,30 +1159,26 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1130
1159
  data: positions,
1131
1160
  })
1132
1161
 
1133
- this.latestPositionsByUser.set(data.user.toLowerCase(), positions)
1134
- this.emitUnifiedSummary(data.user.toLowerCase())
1135
-
1136
- let accountValue = 0
1137
- let marginUsed = 0
1138
- for (const [, state] of data.clearinghouseStates) {
1139
- accountValue +=
1140
- Number.parseFloat(state.marginSummary?.accountValue ?? '') || 0
1141
- marginUsed +=
1142
- Number.parseFloat(state.marginSummary?.totalMarginUsed ?? '') || 0
1143
- }
1144
- const unrealizedPnl = positions.reduce(
1145
- (sum, p) => sum + (Number.parseFloat(p.unrealizedPnl) || 0),
1146
- 0
1162
+ const { accountValue, marginUsed } = perpsTotals(
1163
+ data.clearinghouseStates.map(([, state]) => state)
1147
1164
  )
1165
+
1166
+ const key = data.user.toLowerCase()
1167
+ this.latestPerpsByUser.set(key, {
1168
+ positions,
1169
+ marginUsed: marginUsed.toFixed(),
1170
+ })
1171
+ this.emitUnifiedSummary(key)
1172
+
1148
1173
  // Equity semantics, matching the REST summary: `accountValue` already
1149
1174
  // carries locked margin and unrealized PnL. Spot balances stream apart,
1150
1175
  // so this portfolio value covers perps equity only — which is why the
1151
1176
  // frame is gated on the abstraction mode below.
1152
1177
  this.emitSummaryIfModeAllows(data.user, {
1153
- portfolioValue: accountValue.toString(),
1154
- availableMargin: (accountValue - marginUsed).toString(),
1155
- marginUsed: marginUsed.toString(),
1156
- unrealizedPnl: unrealizedPnl.toString(),
1178
+ portfolioValue: accountValue.toFixed(),
1179
+ availableMargin: accountValue.minus(marginUsed).toFixed(),
1180
+ marginUsed: marginUsed.toFixed(),
1181
+ unrealizedPnl: sumUnrealizedPnl(positions).toFixed(),
1157
1182
  })
1158
1183
  }
1159
1184
 
@@ -1219,24 +1244,32 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1219
1244
 
1220
1245
  private emitUnifiedSummary(key: string) {
1221
1246
  const pipeline = this.unifiedSummaryByUser.get(key)
1222
- const positions = this.latestPositionsByUser.get(key)
1247
+ const perps = this.latestPerpsByUser.get(key)
1223
1248
  // Both envelopes must have arrived: a spot-only summary would report
1224
1249
  // zero margin used against real positions.
1225
- if (pipeline?.spot === undefined || positions === undefined) {
1250
+ if (
1251
+ pipeline?.spot === undefined ||
1252
+ pipeline.availableMargin === undefined ||
1253
+ perps === undefined
1254
+ ) {
1226
1255
  return
1227
1256
  }
1228
- const summary = summarizeAccount(
1229
- // summarizeAccount reads only the two balance lists.
1230
- {
1231
- collateralBalances: pipeline.spot.collateralBalances,
1232
- balances: pipeline.spot.balances,
1233
- } as AccountResponse,
1234
- positions,
1235
- 'gross'
1257
+ const portfolioValue = [
1258
+ ...pipeline.spot.collateralBalances,
1259
+ ...pipeline.spot.balances,
1260
+ ].reduce(
1261
+ (sum, balance) =>
1262
+ sum.plus(toWireBig(balance.valueUsd, 'spotBalance.valueUsd')),
1263
+ new Big(0)
1236
1264
  )
1237
1265
  this.emit(`accountSummary:${key}`, {
1238
1266
  channel: 'accountSummary',
1239
- data: summary,
1267
+ data: {
1268
+ portfolioValue: portfolioValue.toFixed(),
1269
+ availableMargin: pipeline.availableMargin,
1270
+ marginUsed: perps.marginUsed,
1271
+ unrealizedPnl: sumUnrealizedPnl(perps.positions).toFixed(),
1272
+ },
1240
1273
  })
1241
1274
  }
1242
1275
 
@@ -1334,6 +1367,27 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1334
1367
  )
1335
1368
  }
1336
1369
 
1370
+ private handleActiveAssetData(data: HlActiveAssetData) {
1371
+ const market = this.registry?.get(data.coin)
1372
+ if (market === undefined) {
1373
+ throw new PerpsError(
1374
+ PerpsErrorCode.ValidationError,
1375
+ `[${this.providerKey}] activeAssetData frame names unknown market '${data.coin}'.`
1376
+ )
1377
+ }
1378
+ const [buy, sell] = data.availableToTrade
1379
+ this.emit(`activeAssetData:${data.user.toLowerCase()}:${data.coin}`, {
1380
+ channel: 'availableToTrade',
1381
+ data: {
1382
+ providerId: market.providerId,
1383
+ marketId: market.id,
1384
+ asset: toAssetDisplay(market.quoteAsset),
1385
+ buy,
1386
+ sell,
1387
+ },
1388
+ })
1389
+ }
1390
+
1337
1391
  private handleSpotState(data: HlWsSpotStateData) {
1338
1392
  const user = data.user.toLowerCase()
1339
1393
  const markets = this.registry?.activeMarkets ?? []
@@ -1360,20 +1414,66 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1360
1414
  const pipeline = this.unifiedSummaryByUser.get(user)
1361
1415
  if (pipeline !== undefined) {
1362
1416
  const quoteAssetIds = new Set(markets.map((m) => m.quoteAsset.id))
1363
- const entry = this.abstractionByUser.get(user)
1364
- const portfolioMargin =
1365
- entry !== undefined &&
1366
- entry !== 'pending' &&
1367
- entry.mode === HlAbstractionMode.PORTFOLIO_MARGIN
1368
1417
  // Same partition as getAccount so REST and WS agree on collateral.
1369
1418
  pipeline.spot = partitionSpotBalances(
1370
1419
  rows.map(({ balance }) => balance),
1371
- quoteAssetIds,
1372
- portfolioMargin
1420
+ quoteAssetIds
1373
1421
  )
1422
+ const settlementAssetId = markets.find(
1423
+ (m) => m.categoryId === this.providerKey
1424
+ )?.quoteAsset.id
1425
+ const available = availableAfterMaintenance(
1426
+ data.spotState,
1427
+ settlementAssetId
1428
+ )
1429
+ if (available === undefined) {
1430
+ this.fetchAvailableAfterMaintenance(user, data.user, settlementAssetId)
1431
+ } else {
1432
+ pipeline.availableMargin = available
1433
+ }
1374
1434
  this.emitUnifiedSummary(user)
1375
1435
  }
1376
1436
  }
1437
+
1438
+ /** Read the venue buying power over REST when the spot frame omits it. */
1439
+ private fetchAvailableAfterMaintenance(
1440
+ key: string,
1441
+ user: string,
1442
+ quoteAssetId: string | undefined
1443
+ ) {
1444
+ const client = this.client
1445
+ const pipeline = this.unifiedSummaryByUser.get(key)
1446
+ if (
1447
+ client === undefined ||
1448
+ pipeline === undefined ||
1449
+ pipeline.availableMarginPending
1450
+ ) {
1451
+ return
1452
+ }
1453
+ pipeline.availableMarginPending = true
1454
+ infoRequest<HlSpotClearinghouseState>(
1455
+ DEFAULT_HYPERLIQUID_API_URL,
1456
+ { type: 'spotClearinghouseState', user },
1457
+ hlInfoOptions(client)
1458
+ ).then(
1459
+ (state) => {
1460
+ const current = this.unifiedSummaryByUser.get(key)
1461
+ if (current === undefined) {
1462
+ return
1463
+ }
1464
+ current.availableMarginPending = false
1465
+ current.availableMargin = availableAfterMaintenance(state, quoteAssetId)
1466
+ this.emitUnifiedSummary(key)
1467
+ },
1468
+ (error) => {
1469
+ const current = this.unifiedSummaryByUser.get(key)
1470
+ if (current !== undefined) {
1471
+ current.availableMarginPending = false
1472
+ }
1473
+ wsLog.handlerFailure(this.providerKey, error)
1474
+ }
1475
+ )
1476
+ }
1377
1477
  }
1378
1478
 
1379
1479
  const isObject = (v: unknown): v is Record<string, unknown> =>
@@ -1599,6 +1699,13 @@ function isValidHlFrame(channel: string, data: unknown): boolean {
1599
1699
  isObject(data.spotState) &&
1600
1700
  Array.isArray(data.spotState.balances)
1601
1701
  )
1702
+ case 'activeAssetData':
1703
+ return (
1704
+ typeof data.user === 'string' &&
1705
+ typeof data.coin === 'string' &&
1706
+ Array.isArray(data.availableToTrade) &&
1707
+ data.availableToTrade.length === 2
1708
+ )
1602
1709
  default:
1603
1710
  return true
1604
1711
  }
@@ -1,11 +0,0 @@
1
- export type DecodeChainMode = 'every' | 'latest';
2
- export declare class DecodeChain {
3
- private readonly mode;
4
- private readonly onError;
5
- private tail;
6
- private queued;
7
- constructor(mode: DecodeChainMode, onError: (error: unknown) => void);
8
- push(decode: () => Promise<void>): void;
9
- reset(): void;
10
- }
11
- //# sourceMappingURL=decodeChain.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decodeChain.d.ts","sourceRoot":"","sources":["../../../src/websocket/decodeChain.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAA;AAQhD,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,MAAM,CAAmC;gBAG9B,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI;IAGpD,IAAI,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAwBvC,KAAK,IAAI,IAAI;CAId"}
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DecodeChain = void 0;
4
- class DecodeChain {
5
- mode;
6
- onError;
7
- tail = Promise.resolve();
8
- queued;
9
- constructor(mode, onError) {
10
- this.mode = mode;
11
- this.onError = onError;
12
- }
13
- push(decode) {
14
- if (this.mode === 'every') {
15
- this.tail = this.tail.then(decode).catch(this.onError);
16
- return;
17
- }
18
- const drainScheduled = this.queued !== undefined;
19
- this.queued = decode;
20
- if (drainScheduled) {
21
- return;
22
- }
23
- this.tail = this.tail
24
- .then(() => {
25
- const next = this.queued;
26
- this.queued = undefined;
27
- return next?.();
28
- })
29
- .catch(this.onError);
30
- }
31
- reset() {
32
- this.tail = Promise.resolve();
33
- this.queued = undefined;
34
- }
35
- }
36
- exports.DecodeChain = DecodeChain;
37
- //# sourceMappingURL=decodeChain.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decodeChain.js","sourceRoot":"","sources":["../../../src/websocket/decodeChain.ts"],"names":[],"mappings":";;;AAgBA,MAAa,WAAW;IAKH;IACA;IALX,IAAI,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAA;IACvC,MAAM,CAAmC;IAEjD,YACmB,IAAqB,EACrB,OAAiC;QADjC,SAAI,GAAJ,IAAI,CAAiB;QACrB,YAAO,GAAP,OAAO,CAA0B;IACjD,CAAC;IAEJ,IAAI,CAAC,MAA2B;QAC9B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACtD,OAAM;QACR,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,CAAA;QAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,cAAc,EAAE,CAAC;YACnB,OAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI;aAClB,IAAI,CAAC,GAAG,EAAE;YACT,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAA;YACxB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;YACvB,OAAO,IAAI,EAAE,EAAE,CAAA;QACjB,CAAC,CAAC;aACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxB,CAAC;IAOD,KAAK;QACH,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA;QAC7B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;IACzB,CAAC;CACF;AArCD,kCAqCC"}
@@ -1,30 +0,0 @@
1
- /**
2
- * Frame semantics of a {@link DecodeChain}:
3
- * - `'every'`: every frame decodes and applies in arrival order — required for
4
- * delta-carrying channels, where dropping a frame corrupts downstream state.
5
- * - `'latest'`: frames queued behind an in-flight decode coalesce to the
6
- * newest — for snapshot-like channels, where a newer frame supersedes
7
- * anything still waiting.
8
- */
9
- export type DecodeChainMode = 'every' | 'latest';
10
- /**
11
- * Serializes async WS frame decodes so results apply in arrival order.
12
- * In `'latest'` mode a backlog (e.g. queued behind a main-thread stall)
13
- * collapses to its newest frame instead of replaying every stale decode.
14
- * Decode failures are reported to `onError` and never break the chain.
15
- */
16
- export declare class DecodeChain {
17
- private readonly mode;
18
- private readonly onError;
19
- private tail;
20
- private queued;
21
- constructor(mode: DecodeChainMode, onError: (error: unknown) => void);
22
- push(decode: () => Promise<void>): void;
23
- /**
24
- * Detach on connection teardown: drops the queued latest-mode decode and
25
- * starts subsequent pushes on a fresh chain. A decode already in flight
26
- * still settles.
27
- */
28
- reset(): void;
29
- }
30
- //# sourceMappingURL=decodeChain.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decodeChain.d.ts","sourceRoot":"","sources":["../../../src/websocket/decodeChain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEhD;;;;;GAKG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,MAAM,CAAmC;gBAG9B,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI;IAGpD,IAAI,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAmBvC;;;;OAIG;IACH,KAAK,IAAI,IAAI;CAId"}
@@ -1,44 +0,0 @@
1
- /**
2
- * Serializes async WS frame decodes so results apply in arrival order.
3
- * In `'latest'` mode a backlog (e.g. queued behind a main-thread stall)
4
- * collapses to its newest frame instead of replaying every stale decode.
5
- * Decode failures are reported to `onError` and never break the chain.
6
- */
7
- export class DecodeChain {
8
- mode;
9
- onError;
10
- tail = Promise.resolve();
11
- queued;
12
- constructor(mode, onError) {
13
- this.mode = mode;
14
- this.onError = onError;
15
- }
16
- push(decode) {
17
- if (this.mode === 'every') {
18
- this.tail = this.tail.then(decode).catch(this.onError);
19
- return;
20
- }
21
- const drainScheduled = this.queued !== undefined;
22
- this.queued = decode;
23
- if (drainScheduled) {
24
- return;
25
- }
26
- this.tail = this.tail
27
- .then(() => {
28
- const next = this.queued;
29
- this.queued = undefined;
30
- return next?.();
31
- })
32
- .catch(this.onError);
33
- }
34
- /**
35
- * Detach on connection teardown: drops the queued latest-mode decode and
36
- * starts subsequent pushes on a fresh chain. A decode already in flight
37
- * still settles.
38
- */
39
- reset() {
40
- this.tail = Promise.resolve();
41
- this.queued = undefined;
42
- }
43
- }
44
- //# sourceMappingURL=decodeChain.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decodeChain.js","sourceRoot":"","sources":["../../../src/websocket/decodeChain.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAKH;IACA;IALX,IAAI,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAA;IACvC,MAAM,CAAmC;IAEjD,YACmB,IAAqB,EACrB,OAAiC;QADjC,SAAI,GAAJ,IAAI,CAAiB;QACrB,YAAO,GAAP,OAAO,CAA0B;IACjD,CAAC;IAEJ,IAAI,CAAC,MAA2B;QAC9B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YACtD,OAAM;QACR,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,CAAA;QAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,cAAc,EAAE,CAAC;YACnB,OAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI;aAClB,IAAI,CAAC,GAAG,EAAE;YACT,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAA;YACxB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;YACvB,OAAO,IAAI,EAAE,EAAE,CAAA;QACjB,CAAC,CAAC;aACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACxB,CAAC;IAED;;;;OAIG;IACH,KAAK;QACH,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAA;QAC7B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;IACzB,CAAC;CACF"}
@@ -1,30 +0,0 @@
1
- /**
2
- * Frame semantics of a {@link DecodeChain}:
3
- * - `'every'`: every frame decodes and applies in arrival order — required for
4
- * delta-carrying channels, where dropping a frame corrupts downstream state.
5
- * - `'latest'`: frames queued behind an in-flight decode coalesce to the
6
- * newest — for snapshot-like channels, where a newer frame supersedes
7
- * anything still waiting.
8
- */
9
- export type DecodeChainMode = 'every' | 'latest';
10
- /**
11
- * Serializes async WS frame decodes so results apply in arrival order.
12
- * In `'latest'` mode a backlog (e.g. queued behind a main-thread stall)
13
- * collapses to its newest frame instead of replaying every stale decode.
14
- * Decode failures are reported to `onError` and never break the chain.
15
- */
16
- export declare class DecodeChain {
17
- private readonly mode;
18
- private readonly onError;
19
- private tail;
20
- private queued;
21
- constructor(mode: DecodeChainMode, onError: (error: unknown) => void);
22
- push(decode: () => Promise<void>): void;
23
- /**
24
- * Detach on connection teardown: drops the queued latest-mode decode and
25
- * starts subsequent pushes on a fresh chain. A decode already in flight
26
- * still settles.
27
- */
28
- reset(): void;
29
- }
30
- //# sourceMappingURL=decodeChain.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decodeChain.d.ts","sourceRoot":"","sources":["../../../src/websocket/decodeChain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEhD;;;;;GAKG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,IAAI,CAAmC;IAC/C,OAAO,CAAC,MAAM,CAAmC;gBAG9B,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI;IAGpD,IAAI,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAmBvC;;;;OAIG;IACH,KAAK,IAAI,IAAI;CAId"}
@@ -1,54 +0,0 @@
1
- /**
2
- * Frame semantics of a {@link DecodeChain}:
3
- * - `'every'`: every frame decodes and applies in arrival order — required for
4
- * delta-carrying channels, where dropping a frame corrupts downstream state.
5
- * - `'latest'`: frames queued behind an in-flight decode coalesce to the
6
- * newest — for snapshot-like channels, where a newer frame supersedes
7
- * anything still waiting.
8
- */
9
- export type DecodeChainMode = 'every' | 'latest'
10
-
11
- /**
12
- * Serializes async WS frame decodes so results apply in arrival order.
13
- * In `'latest'` mode a backlog (e.g. queued behind a main-thread stall)
14
- * collapses to its newest frame instead of replaying every stale decode.
15
- * Decode failures are reported to `onError` and never break the chain.
16
- */
17
- export class DecodeChain {
18
- private tail: Promise<void> = Promise.resolve()
19
- private queued: (() => Promise<void>) | undefined
20
-
21
- constructor(
22
- private readonly mode: DecodeChainMode,
23
- private readonly onError: (error: unknown) => void
24
- ) {}
25
-
26
- push(decode: () => Promise<void>): void {
27
- if (this.mode === 'every') {
28
- this.tail = this.tail.then(decode).catch(this.onError)
29
- return
30
- }
31
- const drainScheduled = this.queued !== undefined
32
- this.queued = decode
33
- if (drainScheduled) {
34
- return
35
- }
36
- this.tail = this.tail
37
- .then(() => {
38
- const next = this.queued
39
- this.queued = undefined
40
- return next?.()
41
- })
42
- .catch(this.onError)
43
- }
44
-
45
- /**
46
- * Detach on connection teardown: drops the queued latest-mode decode and
47
- * starts subsequent pushes on a fresh chain. A decode already in flight
48
- * still settles.
49
- */
50
- reset(): void {
51
- this.tail = Promise.resolve()
52
- this.queued = undefined
53
- }
54
- }