@lifi/perps-sdk-provider-hyperliquid 8.0.0 → 9.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 (76) hide show
  1. package/dist/cjs/services/getActivity.d.ts.map +1 -1
  2. package/dist/cjs/services/getActivity.js +8 -1
  3. package/dist/cjs/services/getActivity.js.map +1 -1
  4. package/dist/cjs/types/fill.d.ts +1 -0
  5. package/dist/cjs/types/fill.d.ts.map +1 -1
  6. package/dist/cjs/types/ledger.d.ts +24 -1
  7. package/dist/cjs/types/ledger.d.ts.map +1 -1
  8. package/dist/cjs/types/ledger.js +8 -1
  9. package/dist/cjs/types/ledger.js.map +1 -1
  10. package/dist/cjs/utils/index.d.ts +1 -1
  11. package/dist/cjs/utils/index.d.ts.map +1 -1
  12. package/dist/cjs/utils/index.js.map +1 -1
  13. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  14. package/dist/cjs/utils/mapActivity.js +70 -49
  15. package/dist/cjs/utils/mapActivity.js.map +1 -1
  16. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  17. package/dist/cjs/utils/mapFill.js +4 -1
  18. package/dist/cjs/utils/mapFill.js.map +1 -1
  19. package/dist/cjs/utils/mapOrder.d.ts +4 -3
  20. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  21. package/dist/cjs/utils/mapOrder.js +4 -3
  22. package/dist/cjs/utils/mapOrder.js.map +1 -1
  23. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +1 -1
  24. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  25. package/dist/cjs/websocket/HyperliquidWsProvider.js +2 -26
  26. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  27. package/dist/esm/services/getActivity.d.ts.map +1 -1
  28. package/dist/esm/services/getActivity.js +10 -1
  29. package/dist/esm/services/getActivity.js.map +1 -1
  30. package/dist/esm/types/fill.d.ts +5 -0
  31. package/dist/esm/types/fill.d.ts.map +1 -1
  32. package/dist/esm/types/ledger.d.ts +65 -1
  33. package/dist/esm/types/ledger.d.ts.map +1 -1
  34. package/dist/esm/types/ledger.js +22 -0
  35. package/dist/esm/types/ledger.js.map +1 -1
  36. package/dist/esm/utils/index.d.ts +1 -1
  37. package/dist/esm/utils/index.d.ts.map +1 -1
  38. package/dist/esm/utils/index.js.map +1 -1
  39. package/dist/esm/utils/mapActivity.d.ts +2 -2
  40. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  41. package/dist/esm/utils/mapActivity.js +92 -55
  42. package/dist/esm/utils/mapActivity.js.map +1 -1
  43. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  44. package/dist/esm/utils/mapFill.js +4 -1
  45. package/dist/esm/utils/mapFill.js.map +1 -1
  46. package/dist/esm/utils/mapOrder.d.ts +10 -3
  47. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  48. package/dist/esm/utils/mapOrder.js +4 -3
  49. package/dist/esm/utils/mapOrder.js.map +1 -1
  50. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +1 -1
  51. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  52. package/dist/esm/websocket/HyperliquidWsProvider.js +3 -27
  53. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  54. package/dist/types/services/getActivity.d.ts.map +1 -1
  55. package/dist/types/types/fill.d.ts +5 -0
  56. package/dist/types/types/fill.d.ts.map +1 -1
  57. package/dist/types/types/ledger.d.ts +65 -1
  58. package/dist/types/types/ledger.d.ts.map +1 -1
  59. package/dist/types/utils/index.d.ts +1 -1
  60. package/dist/types/utils/index.d.ts.map +1 -1
  61. package/dist/types/utils/mapActivity.d.ts +2 -2
  62. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  63. package/dist/types/utils/mapFill.d.ts.map +1 -1
  64. package/dist/types/utils/mapOrder.d.ts +10 -3
  65. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  66. package/dist/types/websocket/HyperliquidWsProvider.d.ts +1 -1
  67. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  68. package/package.json +3 -3
  69. package/src/services/getActivity.ts +13 -1
  70. package/src/types/fill.ts +5 -0
  71. package/src/types/ledger.ts +84 -0
  72. package/src/utils/index.ts +1 -0
  73. package/src/utils/mapActivity.ts +105 -62
  74. package/src/utils/mapFill.ts +4 -1
  75. package/src/utils/mapOrder.ts +15 -8
  76. package/src/websocket/HyperliquidWsProvider.ts +14 -39
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  ActivityItem,
3
3
  DepositActivity,
4
+ Fee,
4
5
  FundingActivity,
5
6
  LiquidationActivity,
6
7
  MarketDisplay,
@@ -9,15 +10,44 @@ import type {
9
10
  } from '@lifi/perps-types'
10
11
  import { ActivityType } from '@lifi/perps-types'
11
12
  import type { HlFundingUpdate, HlLedgerUpdate } from '../types/index.js'
12
- import { isSendAssetDelta, isSpotTransferDelta } from '../types/index.js'
13
+ import {
14
+ isDepositDelta,
15
+ isLiquidationDelta,
16
+ isSendAssetDelta,
17
+ isSpotTransferDelta,
18
+ isWithdrawDelta,
19
+ } from '../types/index.js'
20
+
21
+ /**
22
+ * Hyperliquid settles every perp deposit and withdrawal in USDC, and charges
23
+ * the `spotTransfer` fee in USDC too — that delta carries no `feeToken`.
24
+ */
25
+ const HL_COLLATERAL_SYMBOL = 'USDC'
26
+
27
+ /**
28
+ * Hyperliquid's native token. A `nativeTokenFee` names no token on the wire,
29
+ * so the symbol comes from the venue rather than from the delta.
30
+ */
31
+ const HL_NATIVE_TOKEN_SYMBOL = 'HYPE'
32
+
33
+ /**
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.
38
+ */
39
+ const ledgerTokenSymbol = (token: string): string => {
40
+ const separator = token.indexOf(':')
41
+ return separator === -1 ? token : token.slice(0, separator)
42
+ }
13
43
 
14
44
  /**
15
45
  * Map a Hyperliquid non-funding ledger entry to an ActivityItem.
16
46
  *
17
47
  * Direction for `spotTransfer` and `sendAsset` is derived from
18
48
  * `queriedAddress` matching the delta's `user` (OUT) or `destination` (IN).
19
- * Returns null for unsupported delta types and for same-user `sendAsset`
20
- * dex moves (where `user === destination === queriedAddress`).
49
+ * Returns null for unsupported delta types and for same-account moves, where
50
+ * `user === destination === queriedAddress`.
21
51
  * @public
22
52
  */
23
53
  export const mapLedgerEntry = (
@@ -40,6 +70,13 @@ export const mapLedgerEntry = (
40
70
  const queried = queriedAddress.toLowerCase()
41
71
  const sender = delta.user.toLowerCase()
42
72
  const recipient = delta.destination.toLowerCase()
73
+
74
+ // A transfer reports a movement between two accounts. A row whose sender
75
+ // and recipient are the queried account moves nothing between accounts.
76
+ if (sender === recipient && sender === queried) {
77
+ return null
78
+ }
79
+
43
80
  const direction: 'IN' | 'OUT' = queried === sender ? 'OUT' : 'IN'
44
81
  const counterpartyAddress = direction === 'OUT' ? recipient : sender
45
82
  const meta: Record<string, unknown> = {
@@ -48,22 +85,27 @@ export const mapLedgerEntry = (
48
85
  if (delta.usdcValue !== undefined) {
49
86
  meta.usdcValue = delta.usdcValue
50
87
  }
88
+ if (delta.nonce !== undefined) {
89
+ meta.nonce = delta.nonce
90
+ }
91
+ const fees: Fee[] = []
51
92
  if (delta.fee !== undefined) {
52
- meta.fee = delta.fee
93
+ fees.push({ amount: delta.fee, asset: HL_COLLATERAL_SYMBOL })
53
94
  }
54
95
  if (delta.nativeTokenFee !== undefined) {
55
- meta.nativeTokenFee = delta.nativeTokenFee
56
- }
57
- if (delta.nonce !== undefined) {
58
- meta.nonce = delta.nonce
96
+ fees.push({
97
+ amount: delta.nativeTokenFee,
98
+ asset: HL_NATIVE_TOKEN_SYMBOL,
99
+ })
59
100
  }
60
101
  return {
61
102
  ...base,
62
103
  type: ActivityType.TRANSFER,
63
104
  direction,
64
105
  counterpartyAddress,
65
- asset: delta.token,
106
+ asset: ledgerTokenSymbol(delta.token),
66
107
  amount: delta.amount,
108
+ ...(fees.length === 0 ? {} : { fees }),
67
109
  meta,
68
110
  explorerLink: entry.hash
69
111
  ? `https://app.hyperliquid.xyz/explorer/tx/${entry.hash}`
@@ -91,9 +133,6 @@ export const mapLedgerEntry = (
91
133
  sourceDex: delta.sourceDex,
92
134
  destinationDex: delta.destinationDex,
93
135
  usdcValue: delta.usdcValue,
94
- fee: delta.fee,
95
- nativeTokenFee: delta.nativeTokenFee,
96
- feeToken: delta.feeToken,
97
136
  nonce: delta.nonce,
98
137
  }
99
138
  return {
@@ -101,8 +140,12 @@ export const mapLedgerEntry = (
101
140
  type: ActivityType.TRANSFER,
102
141
  direction,
103
142
  counterpartyAddress,
104
- asset: delta.token,
143
+ asset: ledgerTokenSymbol(delta.token),
105
144
  amount: delta.amount,
145
+ fees: [
146
+ { amount: delta.fee, asset: ledgerTokenSymbol(delta.feeToken) },
147
+ { amount: delta.nativeTokenFee, asset: HL_NATIVE_TOKEN_SYMBOL },
148
+ ],
106
149
  meta,
107
150
  explorerLink: entry.hash
108
151
  ? `https://app.hyperliquid.xyz/explorer/tx/${entry.hash}`
@@ -110,59 +153,59 @@ export const mapLedgerEntry = (
110
153
  } satisfies TransferActivity
111
154
  }
112
155
 
113
- switch (delta.type) {
114
- case 'deposit':
115
- return {
116
- ...base,
117
- type: ActivityType.DEPOSIT,
118
- amount: delta.usdc ?? '0',
119
- explorerLink: entry.hash
120
- ? `https://scan.li.fi/tx/${entry.hash}`
121
- : undefined,
122
- } satisfies DepositActivity
123
-
124
- case 'withdraw':
125
- return {
126
- ...base,
127
- type: ActivityType.WITHDRAWAL,
128
- amount: delta.usdc ?? '0',
129
- fee: (delta as { fee?: string }).fee ?? '0',
130
- explorerLink: entry.hash
131
- ? `https://scan.li.fi/tx/${entry.hash}`
132
- : undefined,
133
- } satisfies WithdrawalActivity
156
+ if (isDepositDelta(delta)) {
157
+ return {
158
+ ...base,
159
+ type: ActivityType.DEPOSIT,
160
+ asset: HL_COLLATERAL_SYMBOL,
161
+ amount: delta.usdc,
162
+ explorerLink: entry.hash
163
+ ? `https://scan.li.fi/tx/${entry.hash}`
164
+ : undefined,
165
+ } satisfies DepositActivity
166
+ }
134
167
 
135
- case 'liquidation': {
136
- const d = delta as unknown as {
137
- type: string
138
- liquidatedNtlPos: string
139
- accountValue: string
140
- leverageType: string
141
- liquidatedPositions?: { coin: string; szi: string }[]
142
- }
143
- const liquidatedPositions = (d.liquidatedPositions ?? []).map((p) => ({
144
- market: resolveMarket(p.coin),
145
- size: p.szi,
146
- }))
147
- // Liquidation rows must point to at least one market. Drop entries with
148
- // missing/empty positions so downstream consumers can rely on that
149
- // invariant and avoid rendering a market-less liquidation card.
150
- if (liquidatedPositions.length === 0) {
151
- return null
152
- }
153
- return {
154
- ...base,
155
- type: ActivityType.LIQUIDATION,
156
- liquidatedNotionalPosition: d.liquidatedNtlPos,
157
- accountValue: d.accountValue,
158
- leverageType: d.leverageType,
159
- liquidatedPositions,
160
- } satisfies LiquidationActivity
161
- }
168
+ if (isWithdrawDelta(delta)) {
169
+ return {
170
+ ...base,
171
+ type: ActivityType.WITHDRAWAL,
172
+ asset: HL_COLLATERAL_SYMBOL,
173
+ amount: delta.usdc,
174
+ ...(delta.fee === undefined
175
+ ? {}
176
+ : { fee: { amount: delta.fee, asset: HL_COLLATERAL_SYMBOL } }),
177
+ explorerLink: entry.hash
178
+ ? `https://scan.li.fi/tx/${entry.hash}`
179
+ : undefined,
180
+ } satisfies WithdrawalActivity
181
+ }
162
182
 
163
- default:
183
+ if (isLiquidationDelta(delta)) {
184
+ const liquidatedPositions = (delta.liquidatedPositions ?? []).map((p) => ({
185
+ market: resolveMarket(p.coin),
186
+ size: p.szi,
187
+ }))
188
+ // Liquidation rows must point to at least one market. Drop entries with
189
+ // missing/empty positions so downstream consumers can rely on that
190
+ // invariant and avoid rendering a market-less liquidation card.
191
+ if (liquidatedPositions.length === 0) {
164
192
  return null
193
+ }
194
+ return {
195
+ ...base,
196
+ type: ActivityType.LIQUIDATION,
197
+ ...(delta.liquidatedNtlPos === undefined
198
+ ? {}
199
+ : { liquidatedNotionalPosition: delta.liquidatedNtlPos }),
200
+ ...(delta.accountValue === undefined
201
+ ? {}
202
+ : { accountValue: delta.accountValue }),
203
+ leverageType: delta.leverageType,
204
+ liquidatedPositions,
205
+ } satisfies LiquidationActivity
165
206
  }
207
+
208
+ return null
166
209
  }
167
210
 
168
211
  /**
@@ -40,7 +40,10 @@ export const mapFill = (fill: HlUserFill, market: MarketDisplay): Fill => ({
40
40
  status: FillStatus.FILLED,
41
41
  liquidity: fill.crossed ? LiquidityRole.TAKER : LiquidityRole.MAKER,
42
42
  filledSize: fill.sz,
43
- fee: fill.fee,
43
+ fee: {
44
+ amount: fill.fee,
45
+ asset: fill.feeToken ?? market.quoteAsset.displaySymbol,
46
+ },
44
47
  startPosition: fill.startPosition,
45
48
  explorerLink: explorerTxUrl(ExplorerChainId.HYPERLIQUID, fill.hash),
46
49
  classification:
@@ -12,6 +12,14 @@ import {
12
12
  } from '@lifi/perps-types'
13
13
  import type { HlFrontendOpenOrder, HlOrderDetail } from '../types/index.js'
14
14
 
15
+ /**
16
+ * An order as seen by the REST `frontendOpenOrders` payload, or the smaller
17
+ * shape nested in the `orderUpdates` WebSocket frame. `mapOpenOrder` and
18
+ * `mapTriggerOrder` accept either so both code paths share one mapper.
19
+ * @public
20
+ */
21
+ export type HlOrderLike = HlFrontendOpenOrder | HlOrderDetail['order']
22
+
15
23
  /**
16
24
  * Map a Hyperliquid orderType string to the OrderType enum.
17
25
  *
@@ -61,9 +69,7 @@ export const isTriggerType = (type: OrderType): boolean =>
61
69
  * 5. `orderType` — final fall-back via `isTriggerType`.
62
70
  * @public
63
71
  */
64
- export const isTriggerOrder = (
65
- o: HlFrontendOpenOrder | HlOrderDetail['order']
66
- ): boolean => {
72
+ export const isTriggerOrder = (o: HlOrderLike): boolean => {
67
73
  if ('isTrigger' in o && o.isTrigger === true) {
68
74
  return true
69
75
  }
@@ -89,20 +95,21 @@ export const isTriggerOrder = (
89
95
  * @public
90
96
  */
91
97
  export const mapOpenOrder = (
92
- o: HlFrontendOpenOrder,
98
+ o: HlOrderLike,
93
99
  market: MarketDisplay
94
100
  ): OpenOrder => ({
95
101
  orderId: String(o.oid),
96
102
  market,
97
103
  side: o.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
98
104
  type: mapOrderType(o.orderType),
99
- size: o.sz,
105
+ originalSize: o.origSz,
106
+ remainingSize: o.sz,
100
107
  price: o.limitPx,
101
108
  filledSize: o.origSz
102
109
  ? (parseFloat(o.origSz) - parseFloat(o.sz)).toString()
103
110
  : '0',
104
111
  reduceOnly: o.reduceOnly ?? false,
105
- label: o.isTrigger ? o.triggerCondition : undefined,
112
+ label: 'isTrigger' in o && o.isTrigger ? o.triggerCondition : undefined,
106
113
  createdAt: new Date(o.timestamp).toISOString(),
107
114
  })
108
115
 
@@ -111,7 +118,7 @@ export const mapOpenOrder = (
111
118
  * @public
112
119
  */
113
120
  export const mapTriggerOrder = (
114
- o: HlFrontendOpenOrder,
121
+ o: HlOrderLike,
115
122
  market: MarketDisplay
116
123
  ): TriggerOrder => {
117
124
  const type = mapOrderType(o.orderType)
@@ -122,7 +129,7 @@ export const mapTriggerOrder = (
122
129
  market,
123
130
  type,
124
131
  size: o.sz,
125
- triggerPrice: o.triggerPx,
132
+ triggerPrice: o.triggerPx ?? '0',
126
133
  ...(isLimit ? { limitPrice: o.limitPx } : {}),
127
134
  label: o.triggerCondition,
128
135
  createdAt: new Date(o.timestamp).toISOString(),
@@ -16,18 +16,16 @@ import {
16
16
  type WsStatusListener,
17
17
  wsLog,
18
18
  } from '@lifi/perps-sdk'
19
- import {
20
- type AccountResponse,
21
- type Balance,
22
- type MarketContext,
23
- type OpenOrder,
24
- type OrderbookLevel,
25
- type OrderbookResponse,
26
- OrderSide,
27
- OrderType,
28
- type Position,
29
- type Subscription,
30
- type TriggerOrder,
19
+ import type {
20
+ AccountResponse,
21
+ Balance,
22
+ MarketContext,
23
+ OpenOrder,
24
+ OrderbookLevel,
25
+ OrderbookResponse,
26
+ Position,
27
+ Subscription,
28
+ TriggerOrder,
31
29
  } from '@lifi/perps-types'
32
30
  import Big from 'big.js'
33
31
  import { isAddress } from 'viem'
@@ -69,9 +67,10 @@ import {
69
67
  isTriggerOrder,
70
68
  mapFill,
71
69
  mapMarketContext,
70
+ mapOpenOrder,
72
71
  mapOrderStatus,
73
- mapOrderType,
74
72
  mapPosition,
73
+ mapTriggerOrder,
75
74
  partitionSpotBalances,
76
75
  priceStepToAggregation,
77
76
  spotAssetFromToken,
@@ -993,39 +992,15 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
993
992
  terminated.push(orderId)
994
993
  continue
995
994
  }
996
- const type = mapOrderType(o.orderType)
997
995
  // Unknown market id (absent from the synced snapshot); skip just this item.
998
996
  const market = this.registry?.get(o.coin)
999
997
  if (!market) {
1000
998
  continue
1001
999
  }
1002
- const createdAt = new Date(o.timestamp).toISOString()
1003
1000
  if (isTriggerOrder(o)) {
1004
- const isLimit =
1005
- type === OrderType.TAKE_PROFIT_LIMIT || type === OrderType.STOP_LIMIT
1006
- triggerOrders.push({
1007
- orderId,
1008
- market,
1009
- type,
1010
- size: o.sz,
1011
- triggerPrice: o.triggerPx ?? '0',
1012
- ...(isLimit ? { limitPrice: o.limitPx } : {}),
1013
- label: o.triggerCondition,
1014
- createdAt,
1015
- })
1001
+ triggerOrders.push(mapTriggerOrder(o, market))
1016
1002
  } else {
1017
- const filled = parseFloat(o.origSz) - parseFloat(o.sz)
1018
- openOrders.push({
1019
- orderId,
1020
- market,
1021
- side: o.side === 'B' ? OrderSide.BUY : OrderSide.SELL,
1022
- type,
1023
- size: o.sz,
1024
- price: o.limitPx,
1025
- filledSize: filled.toString(),
1026
- reduceOnly: o.reduceOnly ?? false,
1027
- createdAt,
1028
- })
1003
+ openOrders.push(mapOpenOrder(o, market))
1029
1004
  }
1030
1005
  }
1031
1006
  this.emit(key, {