@lifi/perps-sdk-provider-hyperliquid 30.0.2 → 31.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 (126) hide show
  1. package/README.md +21 -0
  2. package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
  3. package/dist/cjs/HyperliquidProvider.js +1 -1
  4. package/dist/cjs/HyperliquidProvider.js.map +1 -1
  5. package/dist/cjs/services/getActivity.d.ts.map +1 -1
  6. package/dist/cjs/services/getActivity.js +2 -2
  7. package/dist/cjs/services/getActivity.js.map +1 -1
  8. package/dist/cjs/services/getFills.js +1 -1
  9. package/dist/cjs/services/getFills.js.map +1 -1
  10. package/dist/cjs/services/getOrder.d.ts.map +1 -1
  11. package/dist/cjs/services/getOrder.js +2 -3
  12. package/dist/cjs/services/getOrder.js.map +1 -1
  13. package/dist/cjs/services/getOrders.d.ts.map +1 -1
  14. package/dist/cjs/services/getOrders.js +3 -3
  15. package/dist/cjs/services/getOrders.js.map +1 -1
  16. package/dist/cjs/services/twapSliceFills.d.ts +5 -0
  17. package/dist/cjs/services/twapSliceFills.d.ts.map +1 -0
  18. package/dist/cjs/services/twapSliceFills.js +37 -0
  19. package/dist/cjs/services/twapSliceFills.js.map +1 -0
  20. package/dist/cjs/types/account.d.ts +1 -1
  21. package/dist/cjs/types/account.d.ts.map +1 -1
  22. package/dist/cjs/types/account.js.map +1 -1
  23. package/dist/cjs/types/fill.d.ts.map +1 -1
  24. package/dist/cjs/utils/isFillLiquidatingAccount.d.ts +3 -0
  25. package/dist/cjs/utils/isFillLiquidatingAccount.d.ts.map +1 -0
  26. package/dist/cjs/utils/isFillLiquidatingAccount.js +6 -0
  27. package/dist/cjs/utils/isFillLiquidatingAccount.js.map +1 -0
  28. package/dist/cjs/utils/liquidation.d.ts +3 -2
  29. package/dist/cjs/utils/liquidation.d.ts.map +1 -1
  30. package/dist/cjs/utils/liquidation.js +21 -4
  31. package/dist/cjs/utils/liquidation.js.map +1 -1
  32. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  33. package/dist/cjs/utils/mapActivity.js +2 -2
  34. package/dist/cjs/utils/mapActivity.js.map +1 -1
  35. package/dist/cjs/utils/mapFill.d.ts +1 -1
  36. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  37. package/dist/cjs/utils/mapFill.js +24 -2
  38. package/dist/cjs/utils/mapFill.js.map +1 -1
  39. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  40. package/dist/cjs/utils/mapOrder.js +7 -1
  41. package/dist/cjs/utils/mapOrder.js.map +1 -1
  42. package/dist/cjs/utils/mapPosition.js +1 -1
  43. package/dist/cjs/utils/mapPosition.js.map +1 -1
  44. package/dist/cjs/websocket/HyperliquidWsProvider.js +1 -1
  45. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  46. package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
  47. package/dist/esm/HyperliquidProvider.js +1 -1
  48. package/dist/esm/HyperliquidProvider.js.map +1 -1
  49. package/dist/esm/services/getActivity.d.ts.map +1 -1
  50. package/dist/esm/services/getActivity.js +2 -2
  51. package/dist/esm/services/getActivity.js.map +1 -1
  52. package/dist/esm/services/getFills.js +1 -1
  53. package/dist/esm/services/getFills.js.map +1 -1
  54. package/dist/esm/services/getOrder.d.ts.map +1 -1
  55. package/dist/esm/services/getOrder.js +2 -3
  56. package/dist/esm/services/getOrder.js.map +1 -1
  57. package/dist/esm/services/getOrders.d.ts.map +1 -1
  58. package/dist/esm/services/getOrders.js +3 -3
  59. package/dist/esm/services/getOrders.js.map +1 -1
  60. package/dist/esm/services/twapSliceFills.d.ts +11 -0
  61. package/dist/esm/services/twapSliceFills.d.ts.map +1 -0
  62. package/dist/esm/services/twapSliceFills.js +41 -0
  63. package/dist/esm/services/twapSliceFills.js.map +1 -0
  64. package/dist/esm/types/account.d.ts +5 -2
  65. package/dist/esm/types/account.d.ts.map +1 -1
  66. package/dist/esm/types/account.js.map +1 -1
  67. package/dist/esm/types/fill.d.ts +4 -2
  68. package/dist/esm/types/fill.d.ts.map +1 -1
  69. package/dist/esm/utils/isFillLiquidatingAccount.d.ts +8 -0
  70. package/dist/esm/utils/isFillLiquidatingAccount.d.ts.map +1 -0
  71. package/dist/esm/utils/isFillLiquidatingAccount.js +7 -0
  72. package/dist/esm/utils/isFillLiquidatingAccount.js.map +1 -0
  73. package/dist/esm/utils/liquidation.d.ts +13 -12
  74. package/dist/esm/utils/liquidation.d.ts.map +1 -1
  75. package/dist/esm/utils/liquidation.js +33 -16
  76. package/dist/esm/utils/liquidation.js.map +1 -1
  77. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  78. package/dist/esm/utils/mapActivity.js +2 -2
  79. package/dist/esm/utils/mapActivity.js.map +1 -1
  80. package/dist/esm/utils/mapFill.d.ts +4 -5
  81. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  82. package/dist/esm/utils/mapFill.js +29 -11
  83. package/dist/esm/utils/mapFill.js.map +1 -1
  84. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  85. package/dist/esm/utils/mapOrder.js +7 -1
  86. package/dist/esm/utils/mapOrder.js.map +1 -1
  87. package/dist/esm/utils/mapPosition.d.ts +1 -1
  88. package/dist/esm/utils/mapPosition.js +2 -2
  89. package/dist/esm/utils/mapPosition.js.map +1 -1
  90. package/dist/esm/websocket/HyperliquidWsProvider.js +1 -1
  91. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  92. package/dist/types/HyperliquidProvider.d.ts.map +1 -1
  93. package/dist/types/services/getActivity.d.ts.map +1 -1
  94. package/dist/types/services/getOrder.d.ts.map +1 -1
  95. package/dist/types/services/getOrders.d.ts.map +1 -1
  96. package/dist/types/services/twapSliceFills.d.ts +11 -0
  97. package/dist/types/services/twapSliceFills.d.ts.map +1 -0
  98. package/dist/types/types/account.d.ts +5 -2
  99. package/dist/types/types/account.d.ts.map +1 -1
  100. package/dist/types/types/fill.d.ts +4 -2
  101. package/dist/types/types/fill.d.ts.map +1 -1
  102. package/dist/types/utils/isFillLiquidatingAccount.d.ts +8 -0
  103. package/dist/types/utils/isFillLiquidatingAccount.d.ts.map +1 -0
  104. package/dist/types/utils/liquidation.d.ts +13 -12
  105. package/dist/types/utils/liquidation.d.ts.map +1 -1
  106. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  107. package/dist/types/utils/mapFill.d.ts +4 -5
  108. package/dist/types/utils/mapFill.d.ts.map +1 -1
  109. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  110. package/dist/types/utils/mapPosition.d.ts +1 -1
  111. package/package.json +3 -3
  112. package/src/HyperliquidProvider.ts +1 -6
  113. package/src/services/getActivity.ts +3 -3
  114. package/src/services/getFills.ts +1 -1
  115. package/src/services/getOrder.ts +7 -9
  116. package/src/services/getOrders.ts +3 -11
  117. package/src/services/twapSliceFills.ts +65 -0
  118. package/src/types/account.ts +5 -2
  119. package/src/types/fill.ts +4 -2
  120. package/src/utils/isFillLiquidatingAccount.ts +12 -0
  121. package/src/utils/liquidation.ts +44 -17
  122. package/src/utils/mapActivity.ts +2 -2
  123. package/src/utils/mapFill.ts +41 -13
  124. package/src/utils/mapOrder.ts +7 -1
  125. package/src/utils/mapPosition.ts +2 -2
  126. package/src/websocket/HyperliquidWsProvider.ts +1 -1
@@ -12,7 +12,6 @@ import type {
12
12
  HlFrontendOpenOrders,
13
13
  HlHistoricalOrders,
14
14
  HlTwapHistoryEntry,
15
- HlTwapSliceFill,
16
15
  } from '../types/index.js'
17
16
  import { withExplorerLinks } from '../utils/explorer.js'
18
17
  import { mapOrder, perpsDexNames } from '../utils/index.js'
@@ -23,6 +22,7 @@ import {
23
22
  resolveTwapExecution,
24
23
  } from '../utils/twapHistory.js'
25
24
  import { requireAccountExists } from './getAccountExists.js'
25
+ import { fetchTwapSliceFills } from './twapSliceFills.js'
26
26
 
27
27
  /** Parameters for a lifecycle-filtered Hyperliquid order read. */
28
28
  export type GetOrdersParams = ProviderGetOrdersParams
@@ -78,16 +78,8 @@ export const getOrders = async (
78
78
  ])
79
79
  const latestTwaps = resolveLatestTwapEntries(twaps)
80
80
  const sliceFills =
81
- (active || statuses.includes(OrderStatus.FILLED)) &&
82
- latestTwaps.some(
83
- (entry) =>
84
- entry.twapId !== undefined && entry.status.status === 'activated'
85
- )
86
- ? await infoRequest<HlTwapSliceFill[]>(
87
- apiUrl,
88
- { type: 'userTwapSliceFills', user: params.address },
89
- infoOpts
90
- )
81
+ active || statuses.includes(OrderStatus.FILLED)
82
+ ? await fetchTwapSliceFills(apiUrl, params.address, latestTwaps, infoOpts)
91
83
  : []
92
84
  const rows = new Map<string, Order>()
93
85
  const keep = (key: string, order: Order | undefined): void => {
@@ -0,0 +1,65 @@
1
+ import type { Address } from 'viem'
2
+ import type { HlTwapHistoryEntry, HlTwapSliceFill } from '../types/index.js'
3
+ import { type InfoRequestOptions, infoRequest } from '../utils/infoClient.js'
4
+ import { historyPage } from './historyPagination.js'
5
+
6
+ /** Row cap of both `userTwapSliceFills` and `userTwapSliceFillsByTime`. */
7
+ const TWAP_SLICE_FILLS_CAP = 2000
8
+
9
+ /**
10
+ * Read the slice fills of each `activated` TWAP in `entries`, or none without
11
+ * a request when no entry is `activated`. `userTwapSliceFills` holds only the
12
+ * newest rows of the account, so a full window that does not reach back to a
13
+ * TWAP start gets its older rows from `userTwapSliceFillsByTime`.
14
+ */
15
+ export const fetchTwapSliceFills = async (
16
+ apiUrl: string,
17
+ user: Address,
18
+ entries: readonly HlTwapHistoryEntry[],
19
+ options?: InfoRequestOptions
20
+ ): Promise<HlTwapSliceFill[]> => {
21
+ const starts = entries.flatMap((entry) =>
22
+ entry.twapId !== undefined && entry.status.status === 'activated'
23
+ ? [entry.state.timestamp]
24
+ : []
25
+ )
26
+ if (starts.length === 0) {
27
+ return []
28
+ }
29
+ const window = await infoRequest<HlTwapSliceFill[]>(
30
+ apiUrl,
31
+ { type: 'userTwapSliceFills', user },
32
+ options
33
+ )
34
+ if (window.length < TWAP_SLICE_FILLS_CAP) {
35
+ return window
36
+ }
37
+ const oldest = Math.min(...window.map(({ fill }) => fill.time))
38
+ const startTime = Math.min(...starts)
39
+ if (startTime > oldest) {
40
+ return window
41
+ }
42
+ // The window can end inside its oldest millisecond, so read that millisecond again.
43
+ const older = await historyPage(
44
+ { startTime, endTime: oldest },
45
+ Number.MAX_SAFE_INTEGER,
46
+ async (rangeStart, rangeEnd) => {
47
+ const fills = await infoRequest<HlTwapSliceFill[]>(
48
+ apiUrl,
49
+ {
50
+ type: 'userTwapSliceFillsByTime',
51
+ user,
52
+ startTime: rangeStart,
53
+ endTime: rangeEnd,
54
+ },
55
+ options
56
+ )
57
+ return fills.length >= TWAP_SLICE_FILLS_CAP
58
+ ? { items: [], saturated: true }
59
+ : { items: fills, saturated: false }
60
+ },
61
+ ({ fill }) => ({ time: fill.time, key: fill.tid }),
62
+ options?.signal
63
+ )
64
+ return [...window.filter(({ fill }) => fill.time > oldest), ...older.items]
65
+ }
@@ -18,8 +18,11 @@ export type HlPosition = {
18
18
  entryPx: string
19
19
  /** Position notional value as a decimal string. */
20
20
  positionValue: string
21
- /** Estimated liquidation price; may be an empty string when unavailable. */
22
- liquidationPx: string
21
+ /**
22
+ * Estimated liquidation price. `null` when no price liquidates the
23
+ * position, which Hyperliquid reports for a cross position.
24
+ */
25
+ liquidationPx: string | null
23
26
  /** Unrealized PnL in quote-asset units. */
24
27
  unrealizedPnl: string
25
28
  /** Margin currently assigned to this position. */
package/src/types/fill.ts CHANGED
@@ -55,8 +55,10 @@ export type HlUserFills = HlUserFill[]
55
55
  export type HlUserFillsByTime = HlUserFill[]
56
56
 
57
57
  /**
58
- * One row of the `userTwapSliceFills` query: a fill that a TWAP slice made.
59
- * The venue returns at most 2,000 recent rows, newest first.
58
+ * One row of the `userTwapSliceFills` or `userTwapSliceFillsByTime` query: a
59
+ * fill that a TWAP slice made. Each query returns at most 2,000 rows:
60
+ * `userTwapSliceFills` the newest, newest first, and `userTwapSliceFillsByTime`
61
+ * the oldest of its inclusive range, oldest first.
60
62
  * @public
61
63
  */
62
64
  export type HlTwapSliceFill = {
@@ -0,0 +1,12 @@
1
+ import type { HlUserFill } from '../types/index.js'
2
+
3
+ /**
4
+ * Whether the fill liquidates `address`. Hyperliquid attaches `liquidation` to
5
+ * the fills of both parties, so only a fill whose `liquidatedUser` is the
6
+ * address counts. The address compare ignores case.
7
+ */
8
+ export const isFillLiquidatingAccount = (
9
+ fill: Pick<HlUserFill, 'liquidation'>,
10
+ address: string
11
+ ): boolean =>
12
+ fill.liquidation?.liquidatedUser.toLowerCase() === address.toLowerCase()
@@ -16,10 +16,17 @@ import {
16
16
  createSafeFunction,
17
17
  divideDecimalString,
18
18
  estimateLiquidationPrice,
19
+ type LiquidationEstimateParams,
19
20
  numberToDecimalString,
20
21
  PerpsError,
22
+ subtractDecimalString,
21
23
  } from '@lifi/perps-sdk'
22
- import { type DecimalString, PerpsErrorCode } from '@lifi/perps-types'
24
+ import {
25
+ type DecimalString,
26
+ MarginMode,
27
+ PerpsErrorCode,
28
+ PositionSide,
29
+ } from '@lifi/perps-types'
23
30
 
24
31
  /**
25
32
  * Maintenance margin fraction for a Hyperliquid asset: half of the initial
@@ -50,34 +57,54 @@ export const safeCalculateMaintenanceMarginRate = createSafeFunction(
50
57
  )
51
58
 
52
59
  /**
53
- * Liquidation price of a new isolated position with the exact Hyperliquid
54
- * formula. For an existing position, prefer `Position.liquidationPrice` from
55
- * the API.
56
- *
57
- * Formula derivation (isolated margin, single position):
58
- * margin_available = entryPrice × (1 / leverage - mmr)
59
- * liq_price = entryPrice - side × margin_available / (1 - mmr × side)
60
+ * Liquidation price of the isolated position that an order leaves, with the
61
+ * exact Hyperliquid formula: a new position, or the blend of the order into
62
+ * `params.position`. Hyperliquid isolated `marginUsed` includes the
63
+ * unrealized PnL, so the collateral of the position is `marginUsed` minus
64
+ * `unrealizedPnl`. For an existing position, prefer
65
+ * `Position.liquidationPrice` from the API.
60
66
  *
61
- * @param leverage - User-selected leverage (e.g., `'10'`)
62
67
  * @param maxLeverage - Asset's maximum leverage (e.g., 50 for BTC). Sets
63
68
  * mmr = 1 / (2 × maxLeverage).
64
- * @returns The estimate, or `undefined` when `leverage` is at or above
65
- * `2 × maxLeverage`, as {@link estimateLiquidationPrice} documents.
69
+ * @returns The estimate, or `undefined` when the order or the position is
70
+ * cross (the liquidation depends on the whole account), when the order
71
+ * reduces the position, or when `leverage` is at or above `2 × maxLeverage`,
72
+ * as {@link estimateLiquidationPrice} documents.
66
73
  * @throws {PerpsError} `ValidationError` when `maxLeverage` is not a finite
67
74
  * number above zero, or on an input that `estimateLiquidationPrice` rejects.
68
75
  * @public
69
76
  */
70
77
  export function calculateLiquidationPrice(
71
- entryPrice: string,
72
- leverage: string,
73
- isLong: boolean,
78
+ params: LiquidationEstimateParams,
74
79
  maxLeverage: number
75
80
  ): string | undefined {
81
+ const { position } = params
82
+ if (
83
+ params.marginMode === MarginMode.CROSS ||
84
+ position?.marginMode === MarginMode.CROSS ||
85
+ (position !== undefined &&
86
+ (position.side === PositionSide.LONG) !== params.isLong)
87
+ ) {
88
+ return undefined
89
+ }
76
90
  return estimateLiquidationPrice({
77
- entryPrice,
78
- leverage,
79
- isLong,
91
+ entryPrice: params.entryPrice,
92
+ leverage: params.leverage,
93
+ isLong: params.isLong,
80
94
  maintenanceMarginRate: calculateMaintenanceMarginRate(maxLeverage),
95
+ size: params.size,
96
+ ...(position === undefined
97
+ ? {}
98
+ : {
99
+ current: {
100
+ size: position.size,
101
+ entryPrice: position.entryPrice,
102
+ margin: subtractDecimalString(
103
+ position.marginUsed,
104
+ position.unrealizedPnl
105
+ ),
106
+ },
107
+ }),
81
108
  })
82
109
  }
83
110
 
@@ -38,6 +38,7 @@ import {
38
38
  isVaultTransferDelta,
39
39
  isWithdrawDelta,
40
40
  } from '../types/index.js'
41
+ import { isFillLiquidatingAccount } from './isFillLiquidatingAccount.js'
41
42
  import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
42
43
 
43
44
  /**
@@ -371,11 +372,10 @@ export const mapLiquidationFills = (
371
372
  resolveMarket: (coin: string) => MarketDisplay | undefined,
372
373
  ledgerHashes: ReadonlySet<string>
373
374
  ): LiquidationActivity[] => {
374
- const queried = queriedAddress.toLowerCase()
375
375
  const byOrder = new Map<number, HlUserFill[]>()
376
376
  for (const fill of fills) {
377
377
  if (
378
- fill.liquidation?.liquidatedUser.toLowerCase() !== queried ||
378
+ !isFillLiquidatingAccount(fill, queriedAddress) ||
379
379
  (fill.hash !== undefined && ledgerHashes.has(fill.hash))
380
380
  ) {
381
381
  continue
@@ -4,29 +4,60 @@ import {
4
4
  explorerTxUrl,
5
5
  warnSkippedVenueRow,
6
6
  } from '@lifi/perps-sdk'
7
- import type { Fill, MarketDisplay } from '@lifi/perps-types'
7
+ import type { Fill, FillLiquidation, MarketDisplay } from '@lifi/perps-types'
8
8
  import {
9
9
  FillClassification,
10
+ LiquidationMethod,
10
11
  LiquidityRole,
11
12
  OrderSide,
12
- OrderType,
13
13
  } from '@lifi/perps-types'
14
14
  import { PROVIDER_KEY, SPOT_MARKET_ID } from '../constants.js'
15
- import type { HlUserFill } from '../types/index.js'
15
+ import type { HlFillLiquidation, HlUserFill } from '../types/index.js'
16
16
  import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
17
17
 
18
18
  /** Re-export the shared fill-position classifier used by Hyperliquid mappings. @public */
19
19
  export { classifyFillFromPosition }
20
20
 
21
- /**
22
- * Spot-ness comes from the resolved market's category, not the coin string —
23
- * HL's canonical spot pair 0 is addressed as `PURR/USDC`, not `@0`. A fill with
24
- * an invalid time gives `undefined`.
21
+ const mapLiquidationMethod = (
22
+ method: string
23
+ ): LiquidationMethod | undefined => {
24
+ switch (method) {
25
+ case 'market':
26
+ return LiquidationMethod.MARKET
27
+ case 'backstop':
28
+ return LiquidationMethod.BACKSTOP
29
+ default:
30
+ return undefined
31
+ }
32
+ }
33
+
34
+ // Hyperliquid attaches `liquidation` to the fills of both parties.
35
+ const mapFillLiquidation = (
36
+ liquidation: HlFillLiquidation | undefined,
37
+ address: string
38
+ ): FillLiquidation | undefined => {
39
+ if (
40
+ liquidation === undefined ||
41
+ liquidation.liquidatedUser.toLowerCase() !== address.toLowerCase()
42
+ ) {
43
+ return undefined
44
+ }
45
+ const method = mapLiquidationMethod(liquidation.method)
46
+ return {
47
+ ...(method === undefined ? {} : { method }),
48
+ markPrice: liquidation.markPx,
49
+ }
50
+ }
51
+
52
+ /** Maps a fill using the market category; the order type stays absent.
53
+ * Invalid times return `undefined`. The venue does not report the order type.
54
+ * @param address - Fill owner; only the liquidated account carries `liquidation`.
25
55
  * @public
26
56
  */
27
57
  export const mapFill = (
28
58
  fill: HlUserFill,
29
- market: MarketDisplay
59
+ market: MarketDisplay,
60
+ address: string
30
61
  ): Fill | undefined => {
31
62
  const size = fill.sz
32
63
  const createdAt = rowTimestampToIsoStringOrUndefined(fill.time)
@@ -40,6 +71,7 @@ export const mapFill = (
40
71
  // HL charges the builder portion in the same token as the total fee.
41
72
  const feeAsset = fill.feeToken ?? market.quoteAsset.displaySymbol
42
73
  const side = fill.side === 'B' ? OrderSide.BUY : OrderSide.SELL
74
+ const liquidation = mapFillLiquidation(fill.liquidation, address)
43
75
 
44
76
  return {
45
77
  ...(market.categoryId === SPOT_MARKET_ID
@@ -50,11 +82,6 @@ export const mapFill = (
50
82
  clientOrderId: fill.cloid,
51
83
  market,
52
84
  side,
53
- // HL fills don't carry the originating order type. A maker fill (crossed:
54
- // false) can only come from a resting order, so it's necessarily a limit;
55
- // a taker fill (crossed: true) may be a market OR an aggressive limit order,
56
- // which the payload can't distinguish, so the type is left undefined.
57
- type: fill.crossed ? undefined : OrderType.LIMIT,
58
85
  size,
59
86
  price: fill.px,
60
87
  liquidity: fill.crossed ? LiquidityRole.TAKER : LiquidityRole.MAKER,
@@ -76,5 +103,6 @@ export const mapFill = (
76
103
  : FillClassification.SPOT_SELL
77
104
  : classifyFillFromPosition(fill.startPosition, side, size),
78
105
  createdAt,
106
+ ...(liquidation === undefined ? {} : { liquidation }),
79
107
  }
80
108
  }
@@ -388,5 +388,11 @@ export const mapOrder = (
388
388
  default:
389
389
  throw venueError(`Unknown Hyperliquid time in force: ${o.tif}`)
390
390
  }
391
- return { ...base, type, price: o.limitPx, timeInForce }
391
+ return {
392
+ ...base,
393
+ type,
394
+ price: o.limitPx,
395
+ timeInForce,
396
+ ...(o.tif === 'LiquidationMarket' ? { isLiquidation: true } : {}),
397
+ }
392
398
  }
@@ -43,7 +43,7 @@ export const isOpenAssetPosition = (ap: HlAssetPosition): boolean =>
43
43
  * Hyperliquid signs funding paid as positive and `accruedFunding` signs it as
44
44
  * negative. A row with an invalid size gives `undefined`; an invalid position
45
45
  * value, a non-positive leverage or an invalid funding leaves the derived field
46
- * absent.
46
+ * absent, and a `null` or empty `liquidationPx` leaves `liquidationPrice` absent.
47
47
  * @public
48
48
  */
49
49
  export const mapPosition = (
@@ -93,7 +93,7 @@ export const mapPosition = (
93
93
  size,
94
94
  entryPrice: pos.entryPx ?? '0',
95
95
  ...(markPrice === undefined ? {} : { markPrice }),
96
- liquidationPrice: pos.liquidationPx || '0',
96
+ ...(pos.liquidationPx ? { liquidationPrice: pos.liquidationPx } : {}),
97
97
  unrealizedPnl: pos.unrealizedPnl,
98
98
  ...(accruedFunding === undefined ? {} : { accruedFunding }),
99
99
  ...(leverage === undefined ? {} : { leverage }),
@@ -1243,7 +1243,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1243
1243
  return []
1244
1244
  }
1245
1245
  const market = this.registry?.get(f.coin)
1246
- const mapped = market && mapFill(f as HlUserFill, market)
1246
+ const mapped = market && mapFill(f as HlUserFill, market, data.user)
1247
1247
  return mapped ? [mapped] : []
1248
1248
  })
1249
1249
  this.emit(`userFills:${data.user.toLowerCase()}`, {