@lifi/perps-sdk-provider-lighter 41.0.2 → 42.0.1

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 (66) hide show
  1. package/README.md +34 -0
  2. package/dist/cjs/depositFlow.d.ts.map +1 -1
  3. package/dist/cjs/depositFlow.js +9 -10
  4. package/dist/cjs/depositFlow.js.map +1 -1
  5. package/dist/cjs/types/ws.d.ts +2 -0
  6. package/dist/cjs/types/ws.d.ts.map +1 -1
  7. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  8. package/dist/cjs/utils/mapFill.js +18 -2
  9. package/dist/cjs/utils/mapFill.js.map +1 -1
  10. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  11. package/dist/cjs/utils/mapOrder.js +1 -0
  12. package/dist/cjs/utils/mapOrder.js.map +1 -1
  13. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  14. package/dist/cjs/utils/mapPosition.js +5 -1
  15. package/dist/cjs/utils/mapPosition.js.map +1 -1
  16. package/dist/cjs/utils/tradePrice.d.ts +3 -0
  17. package/dist/cjs/utils/tradePrice.d.ts.map +1 -0
  18. package/dist/cjs/utils/tradePrice.js +9 -0
  19. package/dist/cjs/utils/tradePrice.js.map +1 -0
  20. package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
  21. package/dist/cjs/websocket/LighterWsProvider.js +2 -1
  22. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
  23. package/dist/esm/depositFlow.d.ts +3 -3
  24. package/dist/esm/depositFlow.d.ts.map +1 -1
  25. package/dist/esm/depositFlow.js +12 -13
  26. package/dist/esm/depositFlow.js.map +1 -1
  27. package/dist/esm/types/ws.d.ts +2 -0
  28. package/dist/esm/types/ws.d.ts.map +1 -1
  29. package/dist/esm/utils/mapFill.d.ts +2 -1
  30. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  31. package/dist/esm/utils/mapFill.js +23 -4
  32. package/dist/esm/utils/mapFill.js.map +1 -1
  33. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  34. package/dist/esm/utils/mapOrder.js +1 -0
  35. package/dist/esm/utils/mapOrder.js.map +1 -1
  36. package/dist/esm/utils/mapPosition.d.ts +3 -1
  37. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  38. package/dist/esm/utils/mapPosition.js +8 -2
  39. package/dist/esm/utils/mapPosition.js.map +1 -1
  40. package/dist/esm/utils/tradePrice.d.ts +11 -0
  41. package/dist/esm/utils/tradePrice.d.ts.map +1 -0
  42. package/dist/esm/utils/tradePrice.js +13 -0
  43. package/dist/esm/utils/tradePrice.js.map +1 -0
  44. package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
  45. package/dist/esm/websocket/LighterWsProvider.js +2 -1
  46. package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
  47. package/dist/types/depositFlow.d.ts +3 -3
  48. package/dist/types/depositFlow.d.ts.map +1 -1
  49. package/dist/types/types/ws.d.ts +2 -0
  50. package/dist/types/types/ws.d.ts.map +1 -1
  51. package/dist/types/utils/mapFill.d.ts +2 -1
  52. package/dist/types/utils/mapFill.d.ts.map +1 -1
  53. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  54. package/dist/types/utils/mapPosition.d.ts +3 -1
  55. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  56. package/dist/types/utils/tradePrice.d.ts +11 -0
  57. package/dist/types/utils/tradePrice.d.ts.map +1 -0
  58. package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
  59. package/package.json +3 -3
  60. package/src/depositFlow.ts +20 -18
  61. package/src/types/ws.ts +2 -0
  62. package/src/utils/mapFill.ts +28 -5
  63. package/src/utils/mapOrder.ts +1 -0
  64. package/src/utils/mapPosition.ts +8 -2
  65. package/src/utils/tradePrice.ts +20 -0
  66. package/src/websocket/LighterWsProvider.ts +2 -1
@@ -11,31 +11,33 @@ import { ActionType, type LighterProviderKey } from '@lifi/perps-types'
11
11
  import { LIGHTER_PROVIDER_KEY, LIGHTER_RH_PROVIDER_KEY } from './constants.js'
12
12
 
13
13
  interface LighterDepositAssets {
14
- /** Collateral token on the venue chain, credited to an account that exists. */
15
- venueCollateral: DeclaredDepositAsset
16
- /** Collateral the account-opening deposit is made in, on the chain that deposit runs on. */
17
- firstDepositCollateral: DeclaredDepositAsset
18
- firstDepositGas: DeclaredDepositAsset
14
+ /**
15
+ * Collateral token on the venue chain that a LI.FI route credits to an
16
+ * account that exists. Absent when only the venue deposit credits an account.
17
+ */
18
+ venueCollateral?: DeclaredDepositAsset
19
+ /** Collateral the venue deposit is made in, on the chain that deposit runs on. */
20
+ venueDepositCollateral: DeclaredDepositAsset
21
+ venueDepositGas: DeclaredDepositAsset
19
22
  }
20
23
 
21
24
  const DEPOSIT_ASSETS: Record<LighterProviderKey, LighterDepositAssets> = {
22
25
  [LIGHTER_PROVIDER_KEY]: {
23
26
  venueCollateral: LIGHTER_USDC,
24
- firstDepositCollateral: ETHEREUM_USDC,
25
- firstDepositGas: ETHEREUM_NATIVE_GAS,
27
+ venueDepositCollateral: ETHEREUM_USDC,
28
+ venueDepositGas: ETHEREUM_NATIVE_GAS,
26
29
  },
27
30
  [LIGHTER_RH_PROVIDER_KEY]: {
28
- venueCollateral: ROBINHOOD_USDG,
29
- firstDepositCollateral: ROBINHOOD_USDG,
30
- firstDepositGas: ROBINHOOD_NATIVE_GAS,
31
+ venueDepositCollateral: ROBINHOOD_USDG,
32
+ venueDepositGas: ROBINHOOD_NATIVE_GAS,
31
33
  },
32
34
  }
33
35
 
34
36
  /**
35
37
  * Resolve one Lighter instance's deposit flow from whether the account already
36
- * exists: an existing account takes a swap into the instance's venue collateral,
37
- * while an address with no account runs the account-opening deposit on the chain
38
- * that instance's collateral is deposited from.
38
+ * exists: an existing account takes a swap into the instance's venue collateral
39
+ * when a LI.FI route credits it there. Every other case runs the venue deposit
40
+ * on the chain that instance's collateral is deposited from.
39
41
  *
40
42
  * @public
41
43
  */
@@ -45,15 +47,15 @@ export function lighterDepositFlow(
45
47
  ): DepositFlow {
46
48
  const assets = DEPOSIT_ASSETS[providerKey]
47
49
 
48
- if (accountExists) {
50
+ if (accountExists && assets.venueCollateral) {
49
51
  return { kind: 'lifiSwap', destination: assets.venueCollateral }
50
52
  }
51
53
 
52
54
  return {
53
- kind: 'firstDepositPipeline',
54
- chainId: assets.firstDepositCollateral.chainId,
55
- gasAsset: assets.firstDepositGas,
56
- collateral: assets.firstDepositCollateral,
55
+ kind: 'venueDepositPipeline',
56
+ chainId: assets.venueDepositCollateral.chainId,
57
+ gasAsset: assets.venueDepositGas,
58
+ collateral: assets.venueDepositCollateral,
57
59
  bridgeAction: ActionType.DEPOSIT,
58
60
  }
59
61
  }
package/src/types/ws.ts CHANGED
@@ -142,8 +142,10 @@ export type LtWsTrade = {
142
142
  trade_id: number
143
143
  trade_id_str?: string
144
144
  market_id?: number
145
+ type: string
145
146
  size: string
146
147
  price: string
148
+ usd_amount: string
147
149
  is_maker_ask: boolean
148
150
  timestamp: number
149
151
  }
@@ -13,12 +13,13 @@ import {
13
13
  safeMultiplyDecimalString,
14
14
  warnSkippedVenueRow,
15
15
  } from '@lifi/perps-sdk'
16
- import type { Fill, MarketDisplay } from '@lifi/perps-types'
17
- import { LiquidityRole, OrderSide, OrderType } from '@lifi/perps-types'
16
+ import type { Fill, FillLiquidation, MarketDisplay } from '@lifi/perps-types'
17
+ import { LiquidationMethod, LiquidityRole, OrderSide } from '@lifi/perps-types'
18
18
  import { LIGHTER_FEE_TICK_SCALE, LIGHTER_PROVIDER_KEY } from '../constants.js'
19
19
  import type { LtTrade } from '../types/index.js'
20
20
  import { leverageFromScaledImf } from './mapPosition.js'
21
21
  import { rowTimestampToIsoStringOrUndefined } from './rowTimestamp.js'
22
+ import { resolveTradePrice } from './tradePrice.js'
22
23
  import { isPlaceholderTxHash } from './txHash.js'
23
24
 
24
25
  /**
@@ -111,8 +112,28 @@ const deriveRealizedPnl = (
111
112
  return isDecimalStringZero(pnl) ? null : pnl
112
113
  }
113
114
 
115
+ // The liquidation engine trades as the taker for the liquidated account; the
116
+ // maker of a forced trade is its counterparty.
117
+ const mapFillLiquidation = (
118
+ type: string,
119
+ isMaker: boolean
120
+ ): FillLiquidation | undefined => {
121
+ if (isMaker) {
122
+ return undefined
123
+ }
124
+ switch (type) {
125
+ case 'liquidation':
126
+ return { method: LiquidationMethod.MARKET }
127
+ case 'deleverage':
128
+ return { method: LiquidationMethod.DELEVERAGE }
129
+ default:
130
+ return undefined
131
+ }
132
+ }
133
+
114
134
  /**
115
- * Map a raw Lighter trade to the generic Fill type.
135
+ * Map a raw Lighter trade to the generic Fill type. Leaves `type` absent: a
136
+ * trade row does not name the originating order type.
116
137
  * @param accountIndex - The viewer's Lighter account index (selects buy/sell side and maker/taker role).
117
138
  * @param market - Backend-resolved market identity for `trade.market_id`.
118
139
  * @public
@@ -122,7 +143,8 @@ export const mapFill = (
122
143
  accountIndex: number,
123
144
  market: MarketDisplay
124
145
  ): Fill | undefined => {
125
- const { size, price } = trade
146
+ const { size } = trade
147
+ const price = resolveTradePrice(trade)
126
148
  const createdAt = rowTimestampToIsoStringOrUndefined(trade.timestamp)
127
149
  if (createdAt === undefined) {
128
150
  warnSkippedVenueRow(
@@ -159,13 +181,13 @@ export const mapFill = (
159
181
  feeTick === undefined
160
182
  ? undefined
161
183
  : tickToFeeAmount(trade.usd_amount, feeTick, integratorFeeTick)
184
+ const liquidation = mapFillLiquidation(trade.type, isMaker)
162
185
 
163
186
  return {
164
187
  id: trade.trade_id_str,
165
188
  orderId: isBuyer ? trade.bid_id_str : trade.ask_id_str,
166
189
  market,
167
190
  side: isBuyer ? OrderSide.BUY : OrderSide.SELL,
168
- type: OrderType.LIMIT,
169
191
  size,
170
192
  price,
171
193
  liquidity: isMaker ? LiquidityRole.MAKER : LiquidityRole.TAKER,
@@ -192,5 +214,6 @@ export const mapFill = (
192
214
  explorerLink: isPlaceholderTxHash(trade.tx_hash)
193
215
  ? undefined
194
216
  : explorerTxUrl(ExplorerChainId.LIGHTER, trade.tx_hash),
217
+ ...(liquidation === undefined ? {} : { liquidation }),
195
218
  }
196
219
  }
@@ -221,6 +221,7 @@ export const mapOrder = (
221
221
  price: order.price,
222
222
  timeInForce: mapTimeInForce(order.time_in_force),
223
223
  ...(expiresAt === undefined ? {} : { expiresAt }),
224
+ ...(order.type === 'liquidation' ? { isLiquidation: true } : {}),
224
225
  }
225
226
  }
226
227
  }
@@ -79,7 +79,9 @@ const warnUnreadableField = (field: string, value: unknown): void => {
79
79
  * an invalid size gives `undefined`. An invalid position value or a
80
80
  * non-positive initial margin fraction keeps the row, leaves `leverage` and
81
81
  * `initialMarginRequirement` absent, sets a cross `marginUsed` to `'0'` and
82
- * warns once. Prices, PnL, funding and isolated margin pass through raw.
82
+ * warns once. Prices, PnL, funding and isolated margin pass through raw. A
83
+ * zero or empty `liquidation_price`, which Lighter sends for a position that
84
+ * no price liquidates, leaves `liquidationPrice` absent.
83
85
  * @param market - Backend-resolved market identity for `pos.market_id`.
84
86
  * @public
85
87
  */
@@ -107,6 +109,10 @@ export const mapPosition = (
107
109
  positionValue === undefined || leverage === undefined
108
110
  ? undefined
109
111
  : divideDecimalString(multiplyDecimalString(positionValue, imf), '100')
112
+ const liquidationPrice = pos.liquidation_price
113
+ const hasLiquidationPrice = isDecimalString(liquidationPrice)
114
+ ? !isDecimalStringZero(liquidationPrice)
115
+ : liquidationPrice !== ''
110
116
 
111
117
  return {
112
118
  market,
@@ -121,7 +127,7 @@ export const mapPosition = (
121
127
  ? '0'
122
128
  : divideDecimalString(positionValue, size),
123
129
  }),
124
- liquidationPrice: pos.liquidation_price,
130
+ ...(hasLiquidationPrice ? { liquidationPrice } : {}),
125
131
  unrealizedPnl: pos.unrealized_pnl,
126
132
  accruedFunding: pos.total_funding_paid_out ?? '0',
127
133
  ...(leverage === undefined ? {} : { leverage }),
@@ -0,0 +1,20 @@
1
+ import {
2
+ safeDivideDecimalString,
3
+ safeIsDecimalStringZero,
4
+ } from '@lifi/perps-sdk'
5
+ import type { LtTrade } from '../types/index.js'
6
+
7
+ /**
8
+ * Execution price of a Lighter trade row. Lighter reports a zero `price` on a
9
+ * `deleverage` row, but `usd_amount` still carries the execution notional, so
10
+ * that row resolves to `usd_amount / size`. Every other row, and a row whose
11
+ * division fails, keeps the venue `price`.
12
+ *
13
+ * @internal
14
+ */
15
+ export const resolveTradePrice = (
16
+ trade: Pick<LtTrade, 'type' | 'price' | 'size' | 'usd_amount'>
17
+ ): string =>
18
+ trade.type === 'deleverage' && safeIsDecimalStringZero(trade.price) === true
19
+ ? (safeDivideDecimalString(trade.usd_amount, trade.size) ?? trade.price)
20
+ : trade.price
@@ -73,6 +73,7 @@ import {
73
73
  mapPosition,
74
74
  } from '../utils/index.js'
75
75
  import { spotPriceByAssetId, spotValuation } from '../utils/spotPrice.js'
76
+ import { resolveTradePrice } from '../utils/tradePrice.js'
76
77
  import { askOrder, bidOrder, OrderBookSide } from './orderBookSide.js'
77
78
 
78
79
  // Public channels: `marketsContext` (market_stats/all + spot_market_stats/all),
@@ -1097,7 +1098,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
1097
1098
  data: trades.map((t) => ({
1098
1099
  provider: this.providerKey,
1099
1100
  marketId: assetId,
1100
- price: t.price,
1101
+ price: resolveTradePrice(t),
1101
1102
  size: t.size,
1102
1103
  timestamp: t.timestamp,
1103
1104
  side: t.is_maker_ask ? 'buy' : 'sell',