@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,102 +1,217 @@
1
- import { getMarketRegistry, type SDKRequestOptions } from '@lifi/perps-sdk'
2
- import type { OpenOrder, OrdersResponse, TriggerOrder } from '@lifi/perps-types'
3
- import type { Address } from 'viem'
1
+ import {
2
+ ACTIVE_ORDER_STATUSES,
3
+ createWarnOnce,
4
+ ExplorerChainId,
5
+ explorerTxUrl,
6
+ getMarketRegistry,
7
+ isActiveOrderStatus,
8
+ type MarketRegistry,
9
+ PerpsError,
10
+ type ProviderGetOrdersParams,
11
+ type SDKRequestOptions,
12
+ } from '@lifi/perps-sdk'
13
+ import type { MarketDisplay, Order, OrdersResponse } from '@lifi/perps-types'
4
14
  import { PROVIDER_KEY } from '../constants.js'
5
15
  import type { HyperliquidContext } from '../context.js'
6
16
  import type {
7
- HlFrontendOpenOrder,
17
+ HlExplorerTx,
8
18
  HlFrontendOpenOrders,
19
+ HlHistoricalOrders,
20
+ HlTwapHistoryEntry,
9
21
  } from '../types/index.js'
10
22
  import {
11
- isTriggerOrder,
12
- mapOpenOrder,
13
- mapTriggerOrder,
23
+ assetIsOutcome,
24
+ fetchUserTransactions,
25
+ mapOrder,
26
+ matchOrderActionHash,
14
27
  perpsDexNames,
15
28
  } from '../utils/index.js'
16
- import { hlInfoOptions, infoRequest } from '../utils/infoClient.js'
29
+ import {
30
+ hlInfoOptions,
31
+ type InfoRequestOptions,
32
+ infoRequest,
33
+ } from '../utils/infoClient.js'
34
+
35
+ /** Parameters for a lifecycle-filtered Hyperliquid order read. */
36
+ export type GetOrdersParams = ProviderGetOrdersParams
37
+
38
+ const warn = createWarnOnce()
39
+
40
+ const warnOnce = (key: string, detail?: string): void => {
41
+ warn(
42
+ key,
43
+ `[${PROVIDER_KEY}] ${detail === undefined ? key : `${key}: ${detail}`}`
44
+ )
45
+ }
17
46
 
18
47
  /**
19
- * Parameters for {@link getOrders}.
20
- *
21
- * @public
48
+ * Map one venue row, or drop it. An outcome market identity, a coin the backend
49
+ * market list does not hold, and a row the mapper rejects each drop only their
50
+ * own row instead of rejecting the whole page; each distinct mapper message
51
+ * warns once.
22
52
  */
23
- export interface GetOrdersParams {
24
- address: Address
25
- /** Optional filter using the normalized opaque `Market.id`. */
26
- marketId?: string
27
- /** Page-size hint; Hyperliquid returns all open orders in one response. */
28
- limit?: number
53
+ const mapRow = (
54
+ coin: string,
55
+ registry: MarketRegistry,
56
+ map: (market: MarketDisplay) => Order
57
+ ): Order | undefined => {
58
+ if (assetIsOutcome(coin)) {
59
+ return undefined
60
+ }
61
+ const market = registry.get(coin)
62
+ if (market === undefined) {
63
+ return undefined
64
+ }
65
+ try {
66
+ return map(market)
67
+ } catch (error) {
68
+ if (!(error instanceof PerpsError)) {
69
+ throw error
70
+ }
71
+ warnOnce(`dropped order row: ${error.message}`)
72
+ return undefined
73
+ }
29
74
  }
30
75
 
31
76
  /**
32
- * Fetch open + trigger orders across every supported perps sub-dex for
33
- * `address`, normalised into `OrdersResponse`. Trigger orders that appear as
34
- * children of a parent limit order (Hyperliquid's `normalTpsl` flow) are
35
- * extracted and surfaced alongside top-level trigger orders. The backend's
36
- * enriched asset list supplies the sub-dex fan-out and display fields; only
37
- * `frontendOpenOrders` is read direct.
38
- * @throws {PerpsError} On Hyperliquid REST error, network, or parsing failures.
39
- * @public
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.
40
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
+ /** Read regular, trigger, and TWAP orders from the requested lifecycle feeds. */
41
113
  export const getOrders = async (
42
114
  { client, apiUrl }: HyperliquidContext,
43
115
  params: GetOrdersParams,
44
116
  options?: SDKRequestOptions
45
117
  ): Promise<OrdersResponse> => {
118
+ const statuses = params.statuses ?? [...ACTIVE_ORDER_STATUSES]
119
+ if (statuses.length === 0) {
120
+ return {
121
+ provider: PROVIDER_KEY,
122
+ orders: [],
123
+ pagination: { limit: params.limit ?? 0, hasMore: false },
124
+ }
125
+ }
46
126
  const registry = getMarketRegistry(client, PROVIDER_KEY)
47
127
  const markets = await registry.sync()
48
128
  const infoOpts = hlInfoOptions(client, options)
49
-
50
- const ordersResults = await Promise.all(
51
- perpsDexNames(markets).map((name) =>
52
- infoRequest<HlFrontendOpenOrders>(
53
- apiUrl,
54
- {
55
- type: 'frontendOpenOrders',
56
- user: params.address,
57
- ...(name ? { dex: name } : {}),
58
- },
59
- infoOpts
60
- )
129
+ const active = statuses.some(isActiveOrderStatus)
130
+ const terminal = statuses.some((status) => !isActiveOrderStatus(status))
131
+ const [open, historical, twaps] = await Promise.all([
132
+ active
133
+ ? Promise.all(
134
+ perpsDexNames(markets).map((name) =>
135
+ infoRequest<HlFrontendOpenOrders>(
136
+ apiUrl,
137
+ {
138
+ type: 'frontendOpenOrders',
139
+ user: params.address,
140
+ ...(name ? { dex: name } : {}),
141
+ },
142
+ infoOpts
143
+ )
144
+ )
145
+ )
146
+ : [],
147
+ terminal
148
+ ? infoRequest<HlHistoricalOrders>(
149
+ apiUrl,
150
+ { type: 'historicalOrders', user: params.address },
151
+ infoOpts
152
+ )
153
+ : [],
154
+ infoRequest<HlTwapHistoryEntry[]>(
155
+ apiUrl,
156
+ { type: 'twapHistory', user: params.address },
157
+ infoOpts
158
+ ),
159
+ ])
160
+ const rows = new Map<string, Order>()
161
+ const keep = (key: string, order: Order | undefined): void => {
162
+ if (order !== undefined) {
163
+ rows.set(key, order)
164
+ }
165
+ }
166
+ for (const detail of historical) {
167
+ const key = String(detail.order.oid)
168
+ // The feed carries one row per lifecycle transition, newest first, and a
169
+ // terminal row shares its timestamp with the `open` row beneath it, so
170
+ // only the first row of an id states that order's current status.
171
+ if (rows.has(key)) {
172
+ continue
173
+ }
174
+ keep(
175
+ key,
176
+ mapRow(detail.order.coin, registry, (market) => mapOrder(detail, market))
61
177
  )
178
+ }
179
+ const raw = open.flat()
180
+ const childIds = new Set(
181
+ raw.flatMap((order) => (order.children ?? []).map((child) => child.oid))
62
182
  )
63
-
64
- const raw: HlFrontendOpenOrder[] = ordersResults.flat()
65
-
66
- const childOids = new Set<number>()
67
- for (const o of raw) {
68
- for (const child of o.children ?? []) {
69
- childOids.add(child.oid)
183
+ for (const order of raw) {
184
+ if (!childIds.has(order.oid)) {
185
+ keep(
186
+ String(order.oid),
187
+ mapRow(order.coin, registry, (market) => mapOrder(order, market))
188
+ )
189
+ }
190
+ for (const child of order.children ?? []) {
191
+ keep(
192
+ String(child.oid),
193
+ mapRow(child.coin, registry, (market) =>
194
+ mapOrder(child, market, String(order.oid))
195
+ )
196
+ )
70
197
  }
71
198
  }
72
-
73
- const nonChild = raw.filter((o) => !childOids.has(o.oid))
74
-
75
- let openOrders: OpenOrder[] = nonChild
76
- .filter((o) => !isTriggerOrder(o))
77
- .map((o) => mapOpenOrder(o, registry.require(o.coin)))
78
-
79
- let triggerOrders: TriggerOrder[] = [
80
- ...nonChild
81
- .filter((o) => isTriggerOrder(o))
82
- .map((o) => mapTriggerOrder(o, registry.require(o.coin))),
83
- ...raw
84
- .filter((o) => childOids.has(o.oid))
85
- .map((o) => mapTriggerOrder(o, registry.require(o.coin))),
86
- ]
87
-
88
- if (params.marketId !== undefined) {
89
- openOrders = openOrders.filter((o) => o.market.id === params.marketId)
90
- triggerOrders = triggerOrders.filter((o) => o.market.id === params.marketId)
199
+ // TWAP ids and regular order ids occupy separate venue namespaces.
200
+ for (const twap of twaps) {
201
+ keep(
202
+ `twap:${twap.twapId}`,
203
+ mapRow(twap.state.coin, registry, (market) => mapOrder(twap, market))
204
+ )
91
205
  }
92
-
206
+ const matching = [...rows.values()].filter(
207
+ (order) =>
208
+ statuses.includes(order.status) &&
209
+ (params.marketId === undefined || order.market.id === params.marketId)
210
+ )
211
+ const orders = await withExplorerLinks(matching, params.address, infoOpts)
93
212
  return {
94
213
  provider: PROVIDER_KEY,
95
- openOrders,
96
- triggerOrders,
97
- pagination: {
98
- limit: params.limit ?? openOrders.length + triggerOrders.length,
99
- hasMore: false,
100
- },
214
+ orders,
215
+ pagination: { limit: params.limit ?? orders.length, hasMore: false },
101
216
  }
102
217
  }
@@ -9,6 +9,7 @@ import { PROVIDER_KEY } from '../constants.js'
9
9
  import type { HyperliquidContext } from '../context.js'
10
10
  import type { HlClearinghouseState } from '../types/index.js'
11
11
  import {
12
+ assetIsOutcome,
12
13
  isOpenAssetPosition,
13
14
  mapPosition,
14
15
  perpsDexNames,
@@ -61,7 +62,9 @@ export const getPositions = async (
61
62
 
62
63
  let positions = stateResults.flatMap((state) =>
63
64
  state.assetPositions
64
- .filter(isOpenAssetPosition)
65
+ .filter(
66
+ (ap) => !assetIsOutcome(ap.position.coin) && isOpenAssetPosition(ap)
67
+ )
65
68
  .map((ap) =>
66
69
  mapPosition(
67
70
  ap,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * One HyperCore transaction as the explorer `userDetails` RPC reports it.
3
+ *
4
+ * @public
5
+ */
6
+ export interface HlExplorerTx {
7
+ time: number
8
+ user: string
9
+ /**
10
+ * The action body HyperCore received. Untyped because the shape varies by
11
+ * action type; read it through `getClientOrderIds`, not by field name.
12
+ */
13
+ action: unknown
14
+ block: number
15
+ hash: string
16
+ /** `null` on a tx the engine applied, a message on one it landed but refused. */
17
+ error: string | null
18
+ }
19
+
20
+ /**
21
+ * Whether one `userDetails` entry carries every field this package reads. The
22
+ * explorer RPC is undocumented, so entries are checked one at a time and a
23
+ * drifted entry drops on its own instead of failing the whole window.
24
+ *
25
+ * @public
26
+ */
27
+ export const isHlExplorerTx = (value: unknown): value is HlExplorerTx =>
28
+ typeof value === 'object' &&
29
+ value !== null &&
30
+ 'time' in value &&
31
+ typeof value.time === 'number' &&
32
+ 'user' in value &&
33
+ typeof value.user === 'string' &&
34
+ 'block' in value &&
35
+ typeof value.block === 'number' &&
36
+ 'hash' in value &&
37
+ typeof value.hash === 'string' &&
38
+ 'error' in value &&
39
+ (value.error === null || typeof value.error === 'string')
package/src/types/fill.ts CHANGED
@@ -34,6 +34,18 @@ export type HlUserFill = {
34
34
  crossed: boolean
35
35
  time: number
36
36
  startPosition: string
37
+ /**
38
+ * Present on both sides of a liquidation fill. `liquidatedUser` names the
39
+ * account that was liquidated; the other party is the liquidator.
40
+ */
41
+ liquidation?: HlFillLiquidation
42
+ }
43
+
44
+ /** Liquidation detail Hyperliquid attaches to a fill. @public */
45
+ export type HlFillLiquidation = {
46
+ liquidatedUser: string
47
+ markPx: string
48
+ method: string
37
49
  }
38
50
 
39
51
  /** All fills returned by the unbounded `userFills` query. @public */
@@ -5,6 +5,7 @@
5
5
  export * from './account.js'
6
6
  export * from './action.js'
7
7
  export * from './asset.js'
8
+ export * from './explorer.js'
8
9
  export * from './fill.js'
9
10
  export * from './ledger.js'
10
11
  export * from './order.js'
@@ -50,6 +50,41 @@ export type HlSendAssetDelta = {
50
50
  feeToken: string
51
51
  }
52
52
 
53
+ /** USDC transfers between Hyperliquid accounts, including subaccounts. @public */
54
+ export type HlCollateralTransferDelta = {
55
+ type: 'internalTransfer' | 'subAccountTransfer'
56
+ usdc: string
57
+ user: Address
58
+ destination: Address
59
+ fee?: string
60
+ }
61
+
62
+ /** Narrow the USDC account-transfer ledger variants. @public */
63
+ export const isCollateralTransferDelta = (
64
+ delta: HlLedgerDelta
65
+ ): delta is HlCollateralTransferDelta =>
66
+ delta.type === 'internalTransfer' || delta.type === 'subAccountTransfer'
67
+
68
+ /** USDC movements into or out of a vault, including withdrawal accounting. @public */
69
+ export type HlVaultTransferDelta =
70
+ | { type: 'vaultDeposit'; vault: Address; usdc: string }
71
+ | {
72
+ type: 'vaultWithdraw'
73
+ vault: Address
74
+ user: Address
75
+ requestedUsd: string
76
+ commission: string
77
+ closingCost: string
78
+ basis: string
79
+ netWithdrawnUsd: string
80
+ }
81
+
82
+ /** Narrow the vault fund-movement ledger variants. @public */
83
+ export const isVaultTransferDelta = (
84
+ delta: HlLedgerDelta
85
+ ): delta is HlVaultTransferDelta =>
86
+ delta.type === 'vaultDeposit' || delta.type === 'vaultWithdraw'
87
+
53
88
  /**
54
89
  * Hyperliquid `deposit` ledger delta. Perp-collateral deposits are always
55
90
  * USDC-denominated, which is why the amount arrives in a field named `usdc`.
@@ -103,6 +138,8 @@ export type HlLiquidationDelta = {
103
138
  export type HlLedgerDelta =
104
139
  | HlSpotTransferDelta
105
140
  | HlSendAssetDelta
141
+ | HlCollateralTransferDelta
142
+ | HlVaultTransferDelta
106
143
  | HlDepositDelta
107
144
  | HlWithdrawDelta
108
145
  | HlLiquidationDelta
@@ -17,6 +17,7 @@ export type HlFrontendOpenOrder = {
17
17
  reduceOnly: boolean
18
18
  timestamp: number
19
19
  isTrigger: boolean
20
+ tpsl?: 'tp' | 'sl'
20
21
  isPositionTpsl: boolean
21
22
  triggerCondition: string
22
23
  triggerPx: string
@@ -49,6 +50,10 @@ export type HlOrderDetail = {
49
50
  cloid: string | null
50
51
  triggerCondition: string
51
52
  triggerPx: string | null
53
+ isTrigger?: boolean
54
+ tpsl?: 'tp' | 'sl'
55
+ children?: HlFrontendOpenOrder[]
56
+ isPositionTpsl?: boolean
52
57
  }
53
58
  status: string
54
59
  statusTimestamp: number
@@ -68,3 +73,23 @@ export type HlOrderStatusFound = {
68
73
  export type HlOrderStatusResponse =
69
74
  | HlOrderStatusFound
70
75
  | { status: 'unknownOid' }
76
+
77
+ /** Rows returned by the `historicalOrders` info query. */
78
+ export type HlHistoricalOrders = HlOrderDetail[]
79
+
80
+ /** TWAP execution state returned by `twapHistory`; time uses epoch seconds. */
81
+ export interface HlTwapHistoryEntry {
82
+ state: {
83
+ coin: string
84
+ executedNtl: string
85
+ executedSz: string
86
+ minutes: number
87
+ side: string
88
+ sz: string
89
+ timestamp: number
90
+ reduceOnly: boolean
91
+ }
92
+ status: { status: string; description?: string }
93
+ time: number
94
+ twapId?: number
95
+ }
package/src/types/ws.ts CHANGED
@@ -6,6 +6,22 @@ import type { HlUserFill } from './fill.js'
6
6
 
7
7
  type HlWsNumberString = string | number
8
8
 
9
+ /** Sparse order update returned by the `orderUpdates` WebSocket subscription. */
10
+ export interface HlWsOrder {
11
+ order: {
12
+ coin: string
13
+ side: string
14
+ limitPx: string
15
+ sz: string
16
+ oid: number
17
+ timestamp: number
18
+ origSz: string
19
+ cloid?: string
20
+ }
21
+ status: string
22
+ statusTimestamp: number
23
+ }
24
+
9
25
  /**
10
26
  * Perp asset context as carried on the `allDexsAssetCtxs` WS feed. Extends the
11
27
  * REST `/info` `HlAssetCtx` shape with the wire-only `coin` tag, the order-book
@@ -5,6 +5,22 @@
5
5
  */
6
6
  export const assetIsSpot = (assetId: string): boolean => assetId.startsWith('@')
7
7
 
8
+ /** Lowest numeric asset ID Hyperliquid assigns to an outcome market. */
9
+ const OUTCOME_ASSET_ID_BASE = 100_000_000
10
+
11
+ /**
12
+ * Return whether a Hyperliquid asset identity belongs to an HIP-4 outcome
13
+ * market: spot coin `#<encoding>`, token name `+<encoding>`, or numeric asset
14
+ * ID `100000000 + encoding`. LI.FI supports no outcome market, and the backend
15
+ * market list holds none, so the provider skips every such identity.
16
+ * @param assetId - Raw Hyperliquid coin, token name, or numeric asset ID.
17
+ * @public
18
+ */
19
+ export const assetIsOutcome = (assetId: string | number): boolean =>
20
+ typeof assetId === 'number'
21
+ ? assetId >= OUTCOME_ASSET_ID_BASE
22
+ : assetId.startsWith('#') || assetId.startsWith('+')
23
+
8
24
  /**
9
25
  * Calculate Hyperliquid's numeric asset ID from a perp DEX index and the
10
26
  * asset's zero-based index within that DEX. The main DEX uses the index
@@ -0,0 +1,93 @@
1
+ import type { OrderBase } from '@lifi/perps-types'
2
+ import { isHex } from 'viem'
3
+ import { HYPERLIQUID_EXPLORER_RPC_URL } from '../constants.js'
4
+ import { type HlExplorerTx, isHlExplorerTx } from '../types/explorer.js'
5
+ import { hlPostJson, type InfoRequestOptions } from './infoClient.js'
6
+
7
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
8
+ typeof value === 'object' && value !== null
9
+
10
+ /**
11
+ * Read the address's recent HyperCore transactions from the explorer RPC.
12
+ *
13
+ * The surface is undocumented, so it is read defensively: an address the
14
+ * explorer has never indexed answers `200` with no `txs` array, and each entry
15
+ * is checked on its own so one drifted entry cannot drop the rest.
16
+ *
17
+ * @throws {PerpsError} On a non-2xx status or a transport failure.
18
+ * @public
19
+ */
20
+ export const fetchUserTransactions = async (
21
+ address: string,
22
+ options?: InfoRequestOptions
23
+ ): Promise<HlExplorerTx[]> => {
24
+ const body = await hlPostJson<{ txs?: unknown } | null>(
25
+ HYPERLIQUID_EXPLORER_RPC_URL,
26
+ 'explorer userDetails',
27
+ { type: 'userDetails', user: address },
28
+ options
29
+ )
30
+ const txs = body?.txs
31
+ return Array.isArray(txs) ? txs.filter(isHlExplorerTx) : []
32
+ }
33
+
34
+ /**
35
+ * Every client order id an action names: `orders[].c` on a placement, `order.c`
36
+ * on a single modify, and `modifies[].order.c` on a batch modify. A batched
37
+ * placement names one id per leg, so every id is returned, not just the first.
38
+ *
39
+ * @public
40
+ */
41
+ export const getClientOrderIds = (
42
+ action: Record<string, unknown>
43
+ ): string[] => {
44
+ const wires: unknown[] = [
45
+ ...(Array.isArray(action.orders) ? action.orders : []),
46
+ ...(Array.isArray(action.modifies)
47
+ ? action.modifies.map((modify) =>
48
+ isRecord(modify) ? modify.order : undefined
49
+ )
50
+ : []),
51
+ action.order,
52
+ ]
53
+ const ids: string[] = []
54
+ for (const wire of wires) {
55
+ if (!isRecord(wire)) {
56
+ continue
57
+ }
58
+ const cloid = wire.c
59
+ if (
60
+ typeof cloid === 'string' &&
61
+ cloid !== '0x' &&
62
+ isHex(cloid, { strict: true })
63
+ ) {
64
+ ids.push(cloid)
65
+ }
66
+ }
67
+ return ids
68
+ }
69
+
70
+ /**
71
+ * The hash of the transaction whose action names the order's client order id.
72
+ *
73
+ * @returns The transaction hash, or `undefined` when the order carries no
74
+ * client order id or no transaction in the window names it.
75
+ * @public
76
+ */
77
+ export const matchOrderActionHash = (
78
+ txs: HlExplorerTx[],
79
+ order: Pick<OrderBase, 'clientOrderId'>
80
+ ): string | undefined => {
81
+ const { clientOrderId } = order
82
+ if (clientOrderId === undefined) {
83
+ return undefined
84
+ }
85
+ // A client order id is hex, so the venue may echo a case the placer did not send.
86
+ const wanted = clientOrderId.toLowerCase()
87
+ const match = txs.find(
88
+ (tx) =>
89
+ isRecord(tx.action) &&
90
+ getClientOrderIds(tx.action).some((id) => id.toLowerCase() === wanted)
91
+ )
92
+ return match?.hash
93
+ }
@@ -2,7 +2,12 @@
2
2
  // for the package's utility surface. Internal modules import per-file paths
3
3
  // (e.g. `./assetId.js`, `./mapFill.js`) directly.
4
4
 
5
- export { assetIsSpot, calculateAssetId, getProviderIndex } from './assetId.js'
5
+ export {
6
+ assetIsOutcome,
7
+ assetIsSpot,
8
+ calculateAssetId,
9
+ getProviderIndex,
10
+ } from './assetId.js'
6
11
  export {
7
12
  applyLogoOverride,
8
13
  spotLogoURI,
@@ -12,26 +17,30 @@ export {
12
17
  decodeCompressedJson,
13
18
  decodeFastAssetCtxs,
14
19
  } from './decodeFastAssetCtxs.js'
20
+ export {
21
+ fetchUserTransactions,
22
+ getClientOrderIds,
23
+ matchOrderActionHash,
24
+ } from './explorer.js'
15
25
  export type { InfoRequestOptions } from './infoClient.js'
16
26
  export { hlInfoOptions, infoRequest } from './infoClient.js'
17
27
  export {
18
28
  calculateLiquidationPrice,
19
29
  calculateMaintenanceMarginRate,
20
30
  } from './liquidation.js'
21
- export { mapFundingActivity, mapLedgerEntry } from './mapActivity.js'
31
+ export {
32
+ mapFundingActivity,
33
+ mapLedgerEntry,
34
+ mapLiquidationFills,
35
+ } from './mapActivity.js'
22
36
  export { classifyFillFromPosition, mapFill } from './mapFill.js'
23
37
  export { mapMarket } from './mapMarket.js'
24
38
  export { mapMarketContext } from './mapMarketContext.js'
25
39
  export {
26
40
  type HlOrderLike,
27
- isTriggerOrder,
28
- isTriggerType,
29
- mapOpenOrder,
30
41
  mapOrder,
31
42
  mapOrderStatus,
32
43
  mapOrderType,
33
- mapStatusReason,
34
- mapTriggerOrder,
35
44
  } from './mapOrder.js'
36
45
  export { isOpenAssetPosition, mapPosition } from './mapPosition.js'
37
46
  export { coinAsset, perpsDexNames } from './marketDisplay.js'