@lifi/perps-sdk-provider-hyperliquid 13.0.0 → 14.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/README.md +115 -0
  2. package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
  3. package/dist/cjs/HyperliquidProvider.js +1 -2
  4. package/dist/cjs/HyperliquidProvider.js.map +1 -1
  5. package/dist/cjs/constants.d.ts +1 -0
  6. package/dist/cjs/constants.d.ts.map +1 -1
  7. package/dist/cjs/constants.js +2 -1
  8. package/dist/cjs/constants.js.map +1 -1
  9. package/dist/cjs/index.d.ts +1 -3
  10. package/dist/cjs/index.d.ts.map +1 -1
  11. package/dist/cjs/index.js +2 -3
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  14. package/dist/cjs/services/getAccount.js +16 -9
  15. package/dist/cjs/services/getAccount.js.map +1 -1
  16. package/dist/cjs/services/getActivity.d.ts.map +1 -1
  17. package/dist/cjs/services/getActivity.js +40 -7
  18. package/dist/cjs/services/getActivity.js.map +1 -1
  19. package/dist/cjs/services/getFills.d.ts.map +1 -1
  20. package/dist/cjs/services/getFills.js +3 -0
  21. package/dist/cjs/services/getFills.js.map +1 -1
  22. package/dist/cjs/services/getOrder.d.ts.map +1 -1
  23. package/dist/cjs/services/getOrder.js +14 -3
  24. package/dist/cjs/services/getOrder.js.map +1 -1
  25. package/dist/cjs/services/getOrders.d.ts +2 -7
  26. package/dist/cjs/services/getOrders.d.ts.map +1 -1
  27. package/dist/cjs/services/getOrders.js +94 -31
  28. package/dist/cjs/services/getOrders.js.map +1 -1
  29. package/dist/cjs/services/getPositions.d.ts.map +1 -1
  30. package/dist/cjs/services/getPositions.js +1 -1
  31. package/dist/cjs/services/getPositions.js.map +1 -1
  32. package/dist/cjs/types/explorer.d.ts +10 -0
  33. package/dist/cjs/types/explorer.d.ts.map +1 -0
  34. package/dist/cjs/types/explorer.js +17 -0
  35. package/dist/cjs/types/explorer.js.map +1 -0
  36. package/dist/cjs/types/fill.d.ts +6 -0
  37. package/dist/cjs/types/fill.d.ts.map +1 -1
  38. package/dist/cjs/types/index.d.ts +1 -0
  39. package/dist/cjs/types/index.d.ts.map +1 -1
  40. package/dist/cjs/types/index.js +1 -0
  41. package/dist/cjs/types/index.js.map +1 -1
  42. package/dist/cjs/types/ledger.d.ts +24 -1
  43. package/dist/cjs/types/ledger.d.ts.map +1 -1
  44. package/dist/cjs/types/ledger.js +5 -1
  45. package/dist/cjs/types/ledger.js.map +1 -1
  46. package/dist/cjs/types/order.d.ts +24 -0
  47. package/dist/cjs/types/order.d.ts.map +1 -1
  48. package/dist/cjs/types/ws.d.ts +14 -0
  49. package/dist/cjs/types/ws.d.ts.map +1 -1
  50. package/dist/cjs/utils/assetId.d.ts +1 -0
  51. package/dist/cjs/utils/assetId.d.ts.map +1 -1
  52. package/dist/cjs/utils/assetId.js +6 -1
  53. package/dist/cjs/utils/assetId.js.map +1 -1
  54. package/dist/cjs/utils/explorer.d.ts +7 -0
  55. package/dist/cjs/utils/explorer.d.ts.map +1 -0
  56. package/dist/cjs/utils/explorer.js +49 -0
  57. package/dist/cjs/utils/explorer.js.map +1 -0
  58. package/dist/cjs/utils/index.d.ts +4 -3
  59. package/dist/cjs/utils/index.d.ts.map +1 -1
  60. package/dist/cjs/utils/index.js +7 -6
  61. package/dist/cjs/utils/index.js.map +1 -1
  62. package/dist/cjs/utils/infoClient.d.ts +1 -0
  63. package/dist/cjs/utils/infoClient.d.ts.map +1 -1
  64. package/dist/cjs/utils/infoClient.js +16 -10
  65. package/dist/cjs/utils/infoClient.js.map +1 -1
  66. package/dist/cjs/utils/mapActivity.d.ts +5 -3
  67. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  68. package/dist/cjs/utils/mapActivity.js +116 -48
  69. package/dist/cjs/utils/mapActivity.js.map +1 -1
  70. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  71. package/dist/cjs/utils/mapFill.js +0 -1
  72. package/dist/cjs/utils/mapFill.js.map +1 -1
  73. package/dist/cjs/utils/mapOrder.d.ts +4 -9
  74. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  75. package/dist/cjs/utils/mapOrder.js +149 -135
  76. package/dist/cjs/utils/mapOrder.js.map +1 -1
  77. package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
  78. package/dist/cjs/utils/spotBalance.js +10 -6
  79. package/dist/cjs/utils/spotBalance.js.map +1 -1
  80. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +3 -0
  81. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  82. package/dist/cjs/websocket/HyperliquidWsProvider.js +100 -29
  83. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  84. package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
  85. package/dist/esm/HyperliquidProvider.js +1 -2
  86. package/dist/esm/HyperliquidProvider.js.map +1 -1
  87. package/dist/esm/constants.d.ts +8 -0
  88. package/dist/esm/constants.d.ts.map +1 -1
  89. package/dist/esm/constants.js +8 -0
  90. package/dist/esm/constants.js.map +1 -1
  91. package/dist/esm/index.d.ts +1 -3
  92. package/dist/esm/index.d.ts.map +1 -1
  93. package/dist/esm/index.js +1 -2
  94. package/dist/esm/index.js.map +1 -1
  95. package/dist/esm/services/getAccount.d.ts.map +1 -1
  96. package/dist/esm/services/getAccount.js +14 -10
  97. package/dist/esm/services/getAccount.js.map +1 -1
  98. package/dist/esm/services/getActivity.d.ts +2 -2
  99. package/dist/esm/services/getActivity.d.ts.map +1 -1
  100. package/dist/esm/services/getActivity.js +44 -9
  101. package/dist/esm/services/getActivity.js.map +1 -1
  102. package/dist/esm/services/getFills.d.ts.map +1 -1
  103. package/dist/esm/services/getFills.js +4 -1
  104. package/dist/esm/services/getFills.js.map +1 -1
  105. package/dist/esm/services/getOrder.d.ts.map +1 -1
  106. package/dist/esm/services/getOrder.js +15 -4
  107. package/dist/esm/services/getOrder.js.map +1 -1
  108. package/dist/esm/services/getOrders.d.ts +4 -24
  109. package/dist/esm/services/getOrders.d.ts.map +1 -1
  110. package/dist/esm/services/getOrders.js +115 -42
  111. package/dist/esm/services/getOrders.js.map +1 -1
  112. package/dist/esm/services/getPositions.d.ts.map +1 -1
  113. package/dist/esm/services/getPositions.js +2 -2
  114. package/dist/esm/services/getPositions.js.map +1 -1
  115. package/dist/esm/types/explorer.d.ts +27 -0
  116. package/dist/esm/types/explorer.d.ts.map +1 -0
  117. package/dist/esm/types/explorer.js +20 -0
  118. package/dist/esm/types/explorer.js.map +1 -0
  119. package/dist/esm/types/fill.d.ts +11 -0
  120. package/dist/esm/types/fill.d.ts.map +1 -1
  121. package/dist/esm/types/index.d.ts +1 -0
  122. package/dist/esm/types/index.d.ts.map +1 -1
  123. package/dist/esm/types/index.js +1 -0
  124. package/dist/esm/types/index.js.map +1 -1
  125. package/dist/esm/types/ledger.d.ts +28 -1
  126. package/dist/esm/types/ledger.d.ts.map +1 -1
  127. package/dist/esm/types/ledger.js +4 -0
  128. package/dist/esm/types/ledger.js.map +1 -1
  129. package/dist/esm/types/order.d.ts +26 -0
  130. package/dist/esm/types/order.d.ts.map +1 -1
  131. package/dist/esm/types/ws.d.ts +15 -0
  132. package/dist/esm/types/ws.d.ts.map +1 -1
  133. package/dist/esm/utils/assetId.d.ts +9 -0
  134. package/dist/esm/utils/assetId.d.ts.map +1 -1
  135. package/dist/esm/utils/assetId.js +13 -0
  136. package/dist/esm/utils/assetId.js.map +1 -1
  137. package/dist/esm/utils/explorer.d.ts +31 -0
  138. package/dist/esm/utils/explorer.d.ts.map +1 -0
  139. package/dist/esm/utils/explorer.js +68 -0
  140. package/dist/esm/utils/explorer.js.map +1 -0
  141. package/dist/esm/utils/index.d.ts +4 -3
  142. package/dist/esm/utils/index.d.ts.map +1 -1
  143. package/dist/esm/utils/index.js +4 -3
  144. package/dist/esm/utils/index.js.map +1 -1
  145. package/dist/esm/utils/infoClient.d.ts +14 -3
  146. package/dist/esm/utils/infoClient.d.ts.map +1 -1
  147. package/dist/esm/utils/infoClient.js +29 -14
  148. package/dist/esm/utils/infoClient.js.map +1 -1
  149. package/dist/esm/utils/mapActivity.d.ts +16 -13
  150. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  151. package/dist/esm/utils/mapActivity.js +128 -72
  152. package/dist/esm/utils/mapActivity.js.map +1 -1
  153. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  154. package/dist/esm/utils/mapFill.js +1 -2
  155. package/dist/esm/utils/mapFill.js.map +1 -1
  156. package/dist/esm/utils/mapOrder.d.ts +8 -73
  157. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  158. package/dist/esm/utils/mapOrder.js +148 -187
  159. package/dist/esm/utils/mapOrder.js.map +1 -1
  160. package/dist/esm/utils/spotBalance.d.ts +1 -1
  161. package/dist/esm/utils/spotBalance.d.ts.map +1 -1
  162. package/dist/esm/utils/spotBalance.js +12 -7
  163. package/dist/esm/utils/spotBalance.js.map +1 -1
  164. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +5 -1
  165. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  166. package/dist/esm/websocket/HyperliquidWsProvider.js +105 -34
  167. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  168. package/dist/types/HyperliquidProvider.d.ts.map +1 -1
  169. package/dist/types/constants.d.ts +8 -0
  170. package/dist/types/constants.d.ts.map +1 -1
  171. package/dist/types/index.d.ts +1 -3
  172. package/dist/types/index.d.ts.map +1 -1
  173. package/dist/types/services/getAccount.d.ts.map +1 -1
  174. package/dist/types/services/getActivity.d.ts +2 -2
  175. package/dist/types/services/getActivity.d.ts.map +1 -1
  176. package/dist/types/services/getFills.d.ts.map +1 -1
  177. package/dist/types/services/getOrder.d.ts.map +1 -1
  178. package/dist/types/services/getOrders.d.ts +4 -24
  179. package/dist/types/services/getOrders.d.ts.map +1 -1
  180. package/dist/types/services/getPositions.d.ts.map +1 -1
  181. package/dist/types/types/explorer.d.ts +27 -0
  182. package/dist/types/types/explorer.d.ts.map +1 -0
  183. package/dist/types/types/fill.d.ts +11 -0
  184. package/dist/types/types/fill.d.ts.map +1 -1
  185. package/dist/types/types/index.d.ts +1 -0
  186. package/dist/types/types/index.d.ts.map +1 -1
  187. package/dist/types/types/ledger.d.ts +28 -1
  188. package/dist/types/types/ledger.d.ts.map +1 -1
  189. package/dist/types/types/order.d.ts +26 -0
  190. package/dist/types/types/order.d.ts.map +1 -1
  191. package/dist/types/types/ws.d.ts +15 -0
  192. package/dist/types/types/ws.d.ts.map +1 -1
  193. package/dist/types/utils/assetId.d.ts +9 -0
  194. package/dist/types/utils/assetId.d.ts.map +1 -1
  195. package/dist/types/utils/explorer.d.ts +31 -0
  196. package/dist/types/utils/explorer.d.ts.map +1 -0
  197. package/dist/types/utils/index.d.ts +4 -3
  198. package/dist/types/utils/index.d.ts.map +1 -1
  199. package/dist/types/utils/infoClient.d.ts +14 -3
  200. package/dist/types/utils/infoClient.d.ts.map +1 -1
  201. package/dist/types/utils/mapActivity.d.ts +16 -13
  202. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  203. package/dist/types/utils/mapFill.d.ts.map +1 -1
  204. package/dist/types/utils/mapOrder.d.ts +8 -73
  205. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  206. package/dist/types/utils/spotBalance.d.ts +1 -1
  207. package/dist/types/utils/spotBalance.d.ts.map +1 -1
  208. package/dist/types/websocket/HyperliquidWsProvider.d.ts +5 -1
  209. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  210. package/package.json +3 -3
  211. package/src/HyperliquidProvider.ts +1 -13
  212. package/src/constants.ts +10 -0
  213. package/src/index.ts +1 -2
  214. package/src/services/getAccount.ts +22 -14
  215. package/src/services/getActivity.ts +81 -6
  216. package/src/services/getFills.ts +4 -1
  217. package/src/services/getOrder.ts +29 -5
  218. package/src/services/getOrders.ts +185 -70
  219. package/src/services/getPositions.ts +4 -1
  220. package/src/types/explorer.ts +39 -0
  221. package/src/types/fill.ts +12 -0
  222. package/src/types/index.ts +1 -0
  223. package/src/types/ledger.ts +37 -0
  224. package/src/types/order.ts +25 -0
  225. package/src/types/ws.ts +16 -0
  226. package/src/utils/assetId.ts +16 -0
  227. package/src/utils/explorer.ts +93 -0
  228. package/src/utils/index.ts +16 -7
  229. package/src/utils/infoClient.ts +46 -24
  230. package/src/utils/mapActivity.ts +163 -75
  231. package/src/utils/mapFill.ts +0 -2
  232. package/src/utils/mapOrder.ts +163 -212
  233. package/src/utils/spotBalance.ts +12 -7
  234. package/src/websocket/HyperliquidWsProvider.ts +127 -40
  235. package/dist/cjs/services/getRunningTwaps.d.ts +0 -10
  236. package/dist/cjs/services/getRunningTwaps.d.ts.map +0 -1
  237. package/dist/cjs/services/getRunningTwaps.js +0 -46
  238. package/dist/cjs/services/getRunningTwaps.js.map +0 -1
  239. package/dist/esm/services/getRunningTwaps.d.ts +0 -12
  240. package/dist/esm/services/getRunningTwaps.d.ts.map +0 -1
  241. package/dist/esm/services/getRunningTwaps.js +0 -40
  242. package/dist/esm/services/getRunningTwaps.js.map +0 -1
  243. package/dist/types/services/getRunningTwaps.d.ts +0 -12
  244. package/dist/types/services/getRunningTwaps.d.ts.map +0 -1
  245. package/src/services/getRunningTwaps.ts +0 -90
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  errorCodeFromStatus,
3
3
  fetchWithRetry,
4
+ isAbortError,
4
5
  PerpsError,
5
6
  type PerpsSDKClient,
6
7
  type ResolvedRetryPolicy,
@@ -57,7 +58,7 @@ export const HYPERLIQUID_RETRY_DEFAULTS: ResolvedRetryPolicy = {
57
58
  }
58
59
 
59
60
  /**
60
- * Transport options for a direct Hyperliquid `/info` request. `policy`
61
+ * Transport options for a direct Hyperliquid REST request. `policy`
61
62
  * controls retries, `signal` cancels the request, and `fetchImpl` overrides
62
63
  * the runtime's global `fetch` implementation.
63
64
  * @public
@@ -87,7 +88,7 @@ export const hlInfoOptions = (
87
88
  })
88
89
 
89
90
  /**
90
- * POST to the Hyperliquid `/info` endpoint and return the parsed JSON body.
91
+ * POST a JSON body to a Hyperliquid REST surface and return the parsed body.
91
92
  *
92
93
  * Direct-to-venue: no proxy, no AJV validation, no cache. The caller's type
93
94
  * parameter is trusted; consumers should treat the response shape as
@@ -97,20 +98,24 @@ export const hlInfoOptions = (
97
98
  * Non-2xx responses raise a {@link PerpsError} tagged with the Hyperliquid
98
99
  * provider key, carrying the code the status resolves to: `RateLimitExceeded`
99
100
  * for a 429, `Unauthorized` for a 401, `AgentUnauthorized` for a 403, and
100
- * `ThirdPartyError` for every other status.
101
- * @public
101
+ * `ThirdPartyError` for every other status. A transport failure and a 2xx body
102
+ * that is not JSON both raise `ServerError`, so every failure but a caller
103
+ * abort reaches the caller as a `PerpsError`. An abort rejects untouched.
104
+ *
105
+ * @param label - Names the surface in the error message, e.g. `info request`.
106
+ * @internal
102
107
  */
103
- export async function infoRequest<T>(
104
- apiUrl: string,
108
+ export async function hlPostJson<T>(
109
+ url: string,
110
+ label: string,
105
111
  body: Record<string, unknown>,
106
112
  options?: InfoRequestOptions
107
113
  ): Promise<T> {
108
114
  const policy = options?.policy ?? HYPERLIQUID_RETRY_DEFAULTS
109
115
 
110
- let response: Response
111
116
  try {
112
- response = await fetchWithRetry(
113
- `${apiUrl}/info`,
117
+ const response = await fetchWithRetry(
118
+ url,
114
119
  {
115
120
  method: 'POST',
116
121
  headers: { 'Content-Type': 'application/json' },
@@ -122,27 +127,44 @@ export async function infoRequest<T>(
122
127
  signal: options?.signal,
123
128
  }
124
129
  )
130
+
131
+ if (!response.ok) {
132
+ const err = new PerpsError(
133
+ errorCodeFromStatus(
134
+ response.status,
135
+ PerpsErrorCode.ThirdPartyError,
136
+ HYPERLIQUID_STATUS_ERROR_CODES
137
+ ),
138
+ `Hyperliquid ${label} failed: ${response.status}`
139
+ )
140
+ err.tool = PROVIDER_KEY
141
+ throw err
142
+ }
143
+
144
+ return (await response.json()) as T
125
145
  } catch (error) {
146
+ if (error instanceof PerpsError || isAbortError(error)) {
147
+ throw error
148
+ }
126
149
  const err = new PerpsError(
127
150
  PerpsErrorCode.ServerError,
128
- error instanceof Error ? error.message : 'Hyperliquid info request failed'
129
- )
130
- err.tool = PROVIDER_KEY
131
- throw err
132
- }
133
-
134
- if (!response.ok) {
135
- const err = new PerpsError(
136
- errorCodeFromStatus(
137
- response.status,
138
- PerpsErrorCode.ThirdPartyError,
139
- HYPERLIQUID_STATUS_ERROR_CODES
140
- ),
141
- `Hyperliquid info request failed: ${response.status}`
151
+ error instanceof Error ? error.message : `Hyperliquid ${label} failed`
142
152
  )
143
153
  err.tool = PROVIDER_KEY
144
154
  throw err
145
155
  }
156
+ }
146
157
 
147
- return (await response.json()) as T
158
+ /**
159
+ * POST to the Hyperliquid `/info` endpoint and return the parsed JSON body.
160
+ * See {@link hlPostJson} for the transport and error contract.
161
+ *
162
+ * @public
163
+ */
164
+ export function infoRequest<T>(
165
+ apiUrl: string,
166
+ body: Record<string, unknown>,
167
+ options?: InfoRequestOptions
168
+ ): Promise<T> {
169
+ return hlPostJson<T>(`${apiUrl}/info`, 'info request', body, options)
148
170
  }
@@ -1,5 +1,7 @@
1
+ import { type AssetRegistry, PerpsError } from '@lifi/perps-sdk'
1
2
  import type {
2
3
  ActivityItem,
4
+ Asset,
3
5
  DepositActivity,
4
6
  Fee,
5
7
  FundingActivity,
@@ -8,13 +10,20 @@ import type {
8
10
  TransferActivity,
9
11
  WithdrawalActivity,
10
12
  } from '@lifi/perps-types'
11
- import { ActivityType } from '@lifi/perps-types'
12
- import type { HlFundingUpdate, HlLedgerUpdate } from '../types/index.js'
13
+ import { ActivityType, PerpsErrorCode } from '@lifi/perps-types'
14
+ import Big from 'big.js'
15
+ import type {
16
+ HlFundingUpdate,
17
+ HlLedgerUpdate,
18
+ HlUserFill,
19
+ } from '../types/index.js'
13
20
  import {
21
+ isCollateralTransferDelta,
14
22
  isDepositDelta,
15
23
  isLiquidationDelta,
16
24
  isSendAssetDelta,
17
25
  isSpotTransferDelta,
26
+ isVaultTransferDelta,
18
27
  isWithdrawDelta,
19
28
  } from '../types/index.js'
20
29
 
@@ -30,34 +39,39 @@ const HL_COLLATERAL_SYMBOL = 'USDC'
30
39
  */
31
40
  const HL_NATIVE_TOKEN_SYMBOL = 'HYPE'
32
41
 
42
+ // Hyperliquid reserves spot token index 0 for USDC.
43
+ const HL_COLLATERAL_ASSET_ID = '0'
44
+
33
45
  /**
34
- * Display symbol for a ledger delta's wire token (`"USDC"`, `"PURR:0x..."`).
35
- * The colon separates the name from the token id, so the symbol is the part
36
- * BEFORE it the opposite half to a market coin's `dex:COIN`, which is why
37
- * `coinAsset` cannot be reused here.
46
+ * A ledger row moves a spot asset a perp contract cannot be sent, received
47
+ * or withdrawn so the delta's token symbol resolves inside the spot asset
48
+ * registry. The HIP-1 ticker auction keeps those symbols unique.
38
49
  */
39
- const ledgerTokenSymbol = (token: string): string => {
40
- const separator = token.indexOf(':')
41
- return separator === -1 ? token : token.slice(0, separator)
50
+ const resolveLedgerAsset = (symbol: string, registry: AssetRegistry): Asset => {
51
+ const asset = registry.assets.find(
52
+ (candidate) => candidate.displaySymbol === symbol
53
+ )
54
+ if (asset === undefined) {
55
+ const error = new PerpsError(
56
+ PerpsErrorCode.ValidationError,
57
+ `[hyperliquid] stale or mis-keyed asset registry: unknown spot symbol '${symbol}'`
58
+ )
59
+ error.tool = 'hyperliquid'
60
+ throw error
61
+ }
62
+ return asset
42
63
  }
43
64
 
44
65
  /**
45
- * Map a Hyperliquid non-funding ledger entry to an ActivityItem.
46
- *
47
- * Direction for `spotTransfer` and `sendAsset` is derived from
48
- * `queriedAddress` matching the delta's `user` (OUT) or `destination` (IN).
49
- * Returns null for unsupported delta types and for same-account moves, where
50
- * `user === destination === queriedAddress`.
51
- *
52
- * @param resolveMarket - Market identity for a venue coin, or `undefined` when
53
- * the backend market list does not hold it. A liquidated position the resolver
54
- * cannot identify is dropped.
66
+ * Map supported ledger entries; exclude same-account moves and unsupported types.
67
+ * Missing assets throw; unresolved liquidation markets omit only those positions.
55
68
  * @public
56
69
  */
57
70
  export const mapLedgerEntry = (
58
71
  entry: HlLedgerUpdate,
59
72
  providerKey: string,
60
73
  queriedAddress: string,
74
+ assetRegistry: AssetRegistry,
61
75
  resolveMarket: (coin: string) => MarketDisplay | undefined
62
76
  ): ActivityItem | null => {
63
77
  const { delta } = entry
@@ -67,48 +81,57 @@ export const mapLedgerEntry = (
67
81
  timestamp: new Date(entry.time).toISOString(),
68
82
  }
69
83
 
70
- // Handled before the switch: the catch-all arm of `HlLedgerDelta` is a
71
- // structural supertype of the concrete delta, so a `switch (delta.type)`
72
- // cannot narrow off the discriminant. The user-defined type guard does.
73
- if (isSpotTransferDelta(delta)) {
84
+ if (
85
+ isSpotTransferDelta(delta) ||
86
+ isSendAssetDelta(delta) ||
87
+ isCollateralTransferDelta(delta)
88
+ ) {
74
89
  const queried = queriedAddress.toLowerCase()
75
90
  const sender = delta.user.toLowerCase()
76
91
  const recipient = delta.destination.toLowerCase()
77
-
78
- // A transfer reports a movement between two accounts. A row whose sender
79
- // and recipient are the queried account moves nothing between accounts.
80
92
  if (sender === recipient && sender === queried) {
81
93
  return null
82
94
  }
83
-
84
95
  const direction: 'IN' | 'OUT' = queried === sender ? 'OUT' : 'IN'
85
- const counterpartyAddress = direction === 'OUT' ? recipient : sender
86
- const meta: Record<string, unknown> = {
87
- transferType: 'spotTransfer',
88
- }
89
- if (delta.usdcValue !== undefined) {
90
- meta.usdcValue = delta.usdcValue
91
- }
92
- if (delta.nonce !== undefined) {
93
- meta.nonce = delta.nonce
94
- }
95
96
  const fees: Fee[] = []
96
97
  if (delta.fee !== undefined) {
97
- fees.push({ amount: delta.fee, asset: HL_COLLATERAL_SYMBOL })
98
- }
99
- if (delta.nativeTokenFee !== undefined) {
100
98
  fees.push({
101
- amount: delta.nativeTokenFee,
102
- asset: HL_NATIVE_TOKEN_SYMBOL,
99
+ amount: delta.fee,
100
+ asset: isSendAssetDelta(delta)
101
+ ? delta.feeToken.split(':')[0]
102
+ : HL_COLLATERAL_SYMBOL,
103
103
  })
104
104
  }
105
+ if (
106
+ !isCollateralTransferDelta(delta) &&
107
+ delta.nativeTokenFee !== undefined
108
+ ) {
109
+ fees.push({ amount: delta.nativeTokenFee, asset: HL_NATIVE_TOKEN_SYMBOL })
110
+ }
111
+ const meta: Record<string, unknown> = {
112
+ transferType: isSendAssetDelta(delta) ? 'sendAsset' : delta.type,
113
+ }
114
+ if (!isCollateralTransferDelta(delta)) {
115
+ if (delta.usdcValue !== undefined) {
116
+ meta.usdcValue = delta.usdcValue
117
+ }
118
+ if (delta.nonce !== undefined) {
119
+ meta.nonce = delta.nonce
120
+ }
121
+ }
122
+ if (isSendAssetDelta(delta)) {
123
+ meta.sourceDex = delta.sourceDex
124
+ meta.destinationDex = delta.destinationDex
125
+ }
105
126
  return {
106
127
  ...base,
107
128
  type: ActivityType.TRANSFER,
108
129
  direction,
109
- counterpartyAddress,
110
- asset: ledgerTokenSymbol(delta.token),
111
- amount: delta.amount,
130
+ counterpartyAddress: direction === 'OUT' ? recipient : sender,
131
+ asset: isCollateralTransferDelta(delta)
132
+ ? assetRegistry.require(HL_COLLATERAL_ASSET_ID)
133
+ : resolveLedgerAsset(delta.token, assetRegistry),
134
+ amount: isCollateralTransferDelta(delta) ? delta.usdc : delta.amount,
112
135
  ...(fees.length === 0 ? {} : { fees }),
113
136
  meta,
114
137
  explorerLink: entry.hash
@@ -117,40 +140,44 @@ export const mapLedgerEntry = (
117
140
  } satisfies TransferActivity
118
141
  }
119
142
 
120
- // `sendAsset` (wire `type === 'send'`) covers cross-user transfers
121
- // (modelled as TRANSFER) and same-user dex moves (returned as null —
122
- // overloading TRANSFER for those would lie about direction). Pre-switch
123
- // narrowing for the same reason as spotTransfer.
124
- if (isSendAssetDelta(delta)) {
143
+ if (isVaultTransferDelta(delta)) {
125
144
  const queried = queriedAddress.toLowerCase()
126
- const sender = delta.user.toLowerCase()
127
- const recipient = delta.destination.toLowerCase()
128
-
129
- if (sender === recipient && sender === queried) {
130
- return null
145
+ const vault = delta.vault.toLowerCase()
146
+ if (delta.type === 'vaultDeposit' && queried === vault) {
147
+ throw new PerpsError(
148
+ PerpsErrorCode.ValidationError,
149
+ 'Hyperliquid vaultDeposit identifies no depositor for the vault account'
150
+ )
131
151
  }
132
-
133
- const direction: 'IN' | 'OUT' = queried === sender ? 'OUT' : 'IN'
134
- const counterpartyAddress = direction === 'OUT' ? recipient : sender
135
- const meta: Record<string, unknown> = {
136
- transferType: 'sendAsset',
137
- sourceDex: delta.sourceDex,
138
- destinationDex: delta.destinationDex,
139
- usdcValue: delta.usdcValue,
140
- nonce: delta.nonce,
152
+ if (
153
+ delta.type === 'vaultWithdraw' &&
154
+ delta.user.toLowerCase() === vault &&
155
+ queried === vault
156
+ ) {
157
+ return null
141
158
  }
142
159
  return {
143
160
  ...base,
144
161
  type: ActivityType.TRANSFER,
145
- direction,
146
- counterpartyAddress,
147
- asset: ledgerTokenSymbol(delta.token),
148
- amount: delta.amount,
149
- fees: [
150
- { amount: delta.fee, asset: ledgerTokenSymbol(delta.feeToken) },
151
- { amount: delta.nativeTokenFee, asset: HL_NATIVE_TOKEN_SYMBOL },
152
- ],
153
- meta,
162
+ direction:
163
+ delta.type === 'vaultDeposit' || queried === vault ? 'OUT' : 'IN',
164
+ counterpartyAddress:
165
+ delta.type === 'vaultWithdraw' && queried === vault
166
+ ? delta.user.toLowerCase()
167
+ : vault,
168
+ asset: assetRegistry.require(HL_COLLATERAL_ASSET_ID),
169
+ amount:
170
+ delta.type === 'vaultDeposit' ? delta.usdc : delta.netWithdrawnUsd,
171
+ meta:
172
+ delta.type === 'vaultDeposit'
173
+ ? { transferType: delta.type }
174
+ : {
175
+ transferType: delta.type,
176
+ requestedUsd: delta.requestedUsd,
177
+ commission: delta.commission,
178
+ closingCost: delta.closingCost,
179
+ basis: delta.basis,
180
+ },
154
181
  explorerLink: entry.hash
155
182
  ? `https://app.hyperliquid.xyz/explorer/tx/${entry.hash}`
156
183
  : undefined,
@@ -163,7 +190,7 @@ export const mapLedgerEntry = (
163
190
  return {
164
191
  ...base,
165
192
  type: ActivityType.DEPOSIT,
166
- asset: HL_COLLATERAL_SYMBOL,
193
+ asset: assetRegistry.require(HL_COLLATERAL_ASSET_ID),
167
194
  amount: delta.usdc,
168
195
  explorerLink: entry.hash
169
196
  ? `https://scan.li.fi/tx/${entry.hash}`
@@ -175,7 +202,7 @@ export const mapLedgerEntry = (
175
202
  return {
176
203
  ...base,
177
204
  type: ActivityType.WITHDRAWAL,
178
- asset: HL_COLLATERAL_SYMBOL,
205
+ asset: assetRegistry.require(HL_COLLATERAL_ASSET_ID),
179
206
  amount: delta.usdc,
180
207
  ...(delta.fee === undefined
181
208
  ? {}
@@ -247,3 +274,64 @@ export const mapFundingActivity = (
247
274
  fundingRate: entry.delta.fundingRate,
248
275
  }
249
276
  }
277
+
278
+ /**
279
+ * Build one liquidation activity per liquidation order from the fills of the
280
+ * liquidated account. Hyperliquid attaches `liquidation` to the fills of both
281
+ * parties, so only fills whose `liquidatedUser` is the queried address count.
282
+ * A fill whose hash a ledger liquidation row already carries is the same
283
+ * event and is skipped. The size sign follows the closed position: a sell
284
+ * fill closes a long. Groups whose market does not resolve are dropped.
285
+ * @public
286
+ */
287
+ export const mapLiquidationFills = (
288
+ fills: HlUserFill[],
289
+ providerKey: string,
290
+ queriedAddress: string,
291
+ resolveMarket: (coin: string) => MarketDisplay | undefined,
292
+ ledgerHashes: ReadonlySet<string>
293
+ ): LiquidationActivity[] => {
294
+ const queried = queriedAddress.toLowerCase()
295
+ const byOrder = new Map<number, HlUserFill[]>()
296
+ for (const fill of fills) {
297
+ if (
298
+ fill.liquidation?.liquidatedUser.toLowerCase() !== queried ||
299
+ (fill.hash !== undefined && ledgerHashes.has(fill.hash))
300
+ ) {
301
+ continue
302
+ }
303
+ const group = byOrder.get(fill.oid) ?? []
304
+ group.push(fill)
305
+ byOrder.set(fill.oid, group)
306
+ }
307
+ return [...byOrder.entries()].flatMap(([oid, group]) => {
308
+ const [first] = group
309
+ const market = resolveMarket(first.coin)
310
+ if (market === undefined) {
311
+ return []
312
+ }
313
+ let size = new Big(0)
314
+ let notional = new Big(0)
315
+ let time = first.time
316
+ for (const fill of group) {
317
+ size = size.plus(fill.sz)
318
+ notional = notional.plus(new Big(fill.px).times(fill.sz))
319
+ time = Math.max(time, fill.time)
320
+ }
321
+ return [
322
+ {
323
+ id: `liquidation:${oid}`,
324
+ provider: providerKey,
325
+ timestamp: new Date(time).toISOString(),
326
+ type: ActivityType.LIQUIDATION,
327
+ liquidatedNotionalPosition: notional.toFixed(),
328
+ liquidatedPositions: [
329
+ {
330
+ market,
331
+ size: (first.side === 'A' ? size : size.neg()).toFixed(),
332
+ },
333
+ ],
334
+ } satisfies LiquidationActivity,
335
+ ]
336
+ })
337
+ }
@@ -6,7 +6,6 @@ import {
6
6
  import type { Fill, MarketDisplay } from '@lifi/perps-types'
7
7
  import {
8
8
  FillClassification,
9
- FillStatus,
10
9
  LiquidityRole,
11
10
  OrderSide,
12
11
  OrderType,
@@ -42,7 +41,6 @@ export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => {
42
41
  type: fill.crossed ? undefined : OrderType.LIMIT,
43
42
  size: fill.sz,
44
43
  price: fill.px,
45
- status: FillStatus.FILLED,
46
44
  liquidity: fill.crossed ? LiquidityRole.TAKER : LiquidityRole.MAKER,
47
45
  filledSize: fill.sz,
48
46
  fee: {