@lifi/perps-sdk-provider-lighter 30.0.0 → 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 (138) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +62 -54
  3. package/dist/cjs/LighterProvider.js.map +1 -1
  4. package/dist/cjs/accountSummary.d.ts +4 -1
  5. package/dist/cjs/accountSummary.d.ts.map +1 -1
  6. package/dist/cjs/accountSummary.js +8 -7
  7. package/dist/cjs/accountSummary.js.map +1 -1
  8. package/dist/cjs/availableToTrade.d.ts +2 -0
  9. package/dist/cjs/availableToTrade.d.ts.map +1 -1
  10. package/dist/cjs/availableToTrade.js +6 -7
  11. package/dist/cjs/availableToTrade.js.map +1 -1
  12. package/dist/cjs/types/pnl.d.ts.map +1 -1
  13. package/dist/cjs/types/ws.d.ts +5 -0
  14. package/dist/cjs/types/ws.d.ts.map +1 -1
  15. package/dist/cjs/utils/apiClient.d.ts +3 -2
  16. package/dist/cjs/utils/apiClient.d.ts.map +1 -1
  17. package/dist/cjs/utils/apiClient.js +5 -3
  18. package/dist/cjs/utils/apiClient.js.map +1 -1
  19. package/dist/cjs/utils/fetchDetailedAccount.d.ts +1 -1
  20. package/dist/cjs/utils/fetchDetailedAccount.d.ts.map +1 -1
  21. package/dist/cjs/utils/fetchDetailedAccount.js +2 -1
  22. package/dist/cjs/utils/fetchDetailedAccount.js.map +1 -1
  23. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  24. package/dist/cjs/utils/mapFill.js +1 -3
  25. package/dist/cjs/utils/mapFill.js.map +1 -1
  26. package/dist/cjs/utils/mapPortfolioHistory.d.ts +1 -1
  27. package/dist/cjs/utils/mapPortfolioHistory.d.ts.map +1 -1
  28. package/dist/cjs/utils/mapPortfolioHistory.js +36 -21
  29. package/dist/cjs/utils/mapPortfolioHistory.js.map +1 -1
  30. package/dist/cjs/utils/mapPosition.d.ts +1 -0
  31. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  32. package/dist/cjs/utils/mapPosition.js +13 -1
  33. package/dist/cjs/utils/mapPosition.js.map +1 -1
  34. package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -1
  35. package/dist/cjs/utils/ohlcvInterval.js.map +1 -1
  36. package/dist/cjs/utils/spotPrice.d.ts +5 -1
  37. package/dist/cjs/utils/spotPrice.d.ts.map +1 -1
  38. package/dist/cjs/utils/spotPrice.js +14 -1
  39. package/dist/cjs/utils/spotPrice.js.map +1 -1
  40. package/dist/cjs/utils/withdrawableBalances.d.ts +3 -3
  41. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  42. package/dist/cjs/utils/withdrawableBalances.js +11 -3
  43. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  44. package/dist/cjs/websocket/LighterWsProvider.d.ts +6 -0
  45. package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
  46. package/dist/cjs/websocket/LighterWsProvider.js +120 -9
  47. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
  48. package/dist/esm/LighterProvider.d.ts.map +1 -1
  49. package/dist/esm/LighterProvider.js +71 -61
  50. package/dist/esm/LighterProvider.js.map +1 -1
  51. package/dist/esm/accountSummary.d.ts +17 -3
  52. package/dist/esm/accountSummary.d.ts.map +1 -1
  53. package/dist/esm/accountSummary.js +18 -9
  54. package/dist/esm/accountSummary.js.map +1 -1
  55. package/dist/esm/availableToTrade.d.ts +2 -0
  56. package/dist/esm/availableToTrade.d.ts.map +1 -1
  57. package/dist/esm/availableToTrade.js +4 -6
  58. package/dist/esm/availableToTrade.js.map +1 -1
  59. package/dist/esm/types/pnl.d.ts +2 -3
  60. package/dist/esm/types/pnl.d.ts.map +1 -1
  61. package/dist/esm/types/ws.d.ts +13 -0
  62. package/dist/esm/types/ws.d.ts.map +1 -1
  63. package/dist/esm/types/ws.js +0 -11
  64. package/dist/esm/types/ws.js.map +1 -1
  65. package/dist/esm/utils/apiClient.d.ts +9 -2
  66. package/dist/esm/utils/apiClient.d.ts.map +1 -1
  67. package/dist/esm/utils/apiClient.js +9 -2
  68. package/dist/esm/utils/apiClient.js.map +1 -1
  69. package/dist/esm/utils/fetchDetailedAccount.d.ts +1 -1
  70. package/dist/esm/utils/fetchDetailedAccount.d.ts.map +1 -1
  71. package/dist/esm/utils/fetchDetailedAccount.js +2 -1
  72. package/dist/esm/utils/fetchDetailedAccount.js.map +1 -1
  73. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  74. package/dist/esm/utils/mapFill.js +6 -10
  75. package/dist/esm/utils/mapFill.js.map +1 -1
  76. package/dist/esm/utils/mapPortfolioHistory.d.ts +3 -6
  77. package/dist/esm/utils/mapPortfolioHistory.d.ts.map +1 -1
  78. package/dist/esm/utils/mapPortfolioHistory.js +38 -26
  79. package/dist/esm/utils/mapPortfolioHistory.js.map +1 -1
  80. package/dist/esm/utils/mapPosition.d.ts +7 -0
  81. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  82. package/dist/esm/utils/mapPosition.js +17 -0
  83. package/dist/esm/utils/mapPosition.js.map +1 -1
  84. package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -1
  85. package/dist/esm/utils/ohlcvInterval.js +5 -3
  86. package/dist/esm/utils/ohlcvInterval.js.map +1 -1
  87. package/dist/esm/utils/spotPrice.d.ts +10 -1
  88. package/dist/esm/utils/spotPrice.d.ts.map +1 -1
  89. package/dist/esm/utils/spotPrice.js +14 -0
  90. package/dist/esm/utils/spotPrice.js.map +1 -1
  91. package/dist/esm/utils/withdrawableBalances.d.ts +12 -5
  92. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  93. package/dist/esm/utils/withdrawableBalances.js +17 -5
  94. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  95. package/dist/esm/websocket/LighterWsProvider.d.ts +24 -0
  96. package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
  97. package/dist/esm/websocket/LighterWsProvider.js +153 -15
  98. package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
  99. package/dist/types/LighterProvider.d.ts.map +1 -1
  100. package/dist/types/accountSummary.d.ts +17 -3
  101. package/dist/types/accountSummary.d.ts.map +1 -1
  102. package/dist/types/availableToTrade.d.ts +2 -0
  103. package/dist/types/availableToTrade.d.ts.map +1 -1
  104. package/dist/types/types/pnl.d.ts +2 -3
  105. package/dist/types/types/pnl.d.ts.map +1 -1
  106. package/dist/types/types/ws.d.ts +13 -0
  107. package/dist/types/types/ws.d.ts.map +1 -1
  108. package/dist/types/utils/apiClient.d.ts +9 -2
  109. package/dist/types/utils/apiClient.d.ts.map +1 -1
  110. package/dist/types/utils/fetchDetailedAccount.d.ts +1 -1
  111. package/dist/types/utils/fetchDetailedAccount.d.ts.map +1 -1
  112. package/dist/types/utils/mapFill.d.ts.map +1 -1
  113. package/dist/types/utils/mapPortfolioHistory.d.ts +3 -6
  114. package/dist/types/utils/mapPortfolioHistory.d.ts.map +1 -1
  115. package/dist/types/utils/mapPosition.d.ts +7 -0
  116. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  117. package/dist/types/utils/ohlcvInterval.d.ts.map +1 -1
  118. package/dist/types/utils/spotPrice.d.ts +10 -1
  119. package/dist/types/utils/spotPrice.d.ts.map +1 -1
  120. package/dist/types/utils/withdrawableBalances.d.ts +12 -5
  121. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  122. package/dist/types/websocket/LighterWsProvider.d.ts +24 -0
  123. package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
  124. package/package.json +3 -3
  125. package/src/LighterProvider.ts +119 -74
  126. package/src/accountSummary.ts +37 -14
  127. package/src/availableToTrade.ts +6 -10
  128. package/src/types/pnl.ts +2 -3
  129. package/src/types/ws.ts +23 -0
  130. package/src/utils/apiClient.ts +16 -7
  131. package/src/utils/fetchDetailedAccount.ts +2 -2
  132. package/src/utils/mapFill.ts +6 -13
  133. package/src/utils/mapPortfolioHistory.ts +42 -27
  134. package/src/utils/mapPosition.ts +17 -0
  135. package/src/utils/ohlcvInterval.ts +5 -3
  136. package/src/utils/spotPrice.ts +21 -1
  137. package/src/utils/withdrawableBalances.ts +26 -7
  138. package/src/websocket/LighterWsProvider.ts +222 -14
@@ -17,6 +17,7 @@ import {
17
17
  wsLog,
18
18
  } from '@lifi/perps-sdk'
19
19
  import {
20
+ type AccountSummary,
20
21
  type Fill,
21
22
  type MarketContext,
22
23
  PerpsErrorCode,
@@ -24,11 +25,14 @@ import {
24
25
  type Subscription,
25
26
  } from '@lifi/perps-types'
26
27
  import type { Address } from 'viem'
28
+ import { lighterPortfolioValue } from '../accountSummary.js'
27
29
  import {
28
30
  DEFAULT_LIGHTER_REST_URL,
29
31
  DEFAULT_LIGHTER_WS_URL,
30
32
  LIGHTER_BASE_FEE_TIER,
33
+ LIGHTER_COLLATERAL_ASSETS,
31
34
  LIGHTER_PROVIDER_KEY,
35
+ LIGHTER_RH_PROVIDER_KEY,
32
36
  LIGHTER_SPOT_CATEGORY_ID,
33
37
  } from '../constants.js'
34
38
  import type { LighterPerpsProvider } from '../LighterProvider.js'
@@ -36,6 +40,7 @@ import type {
36
40
  LtAccountPosition,
37
41
  LtOrder,
38
42
  LtTrade,
43
+ LtWsAccountAllMessage,
39
44
  LtWsAccountAllOrdersMessage,
40
45
  LtWsAccountAllPositionsMessage,
41
46
  LtWsAccountAllTradesMessage,
@@ -58,14 +63,18 @@ import {
58
63
  mapPosition,
59
64
  toRequiredBig,
60
65
  } from '../utils/index.js'
66
+ import { spotPriceByAssetId, spotValuation } from '../utils/spotPrice.js'
61
67
 
62
68
  // Public channels: `marketsContext` (market_stats/all + spot_market_stats/all),
63
69
  // `marketContext` (market_stats/N or spot_market_stats/N), `orderbook`
64
- // (order_book/N), `trades` (trade/N).
70
+ // (order_book/N), `trades` (trade/N), `accountSummary` (user_stats/N +
71
+ // account_all/N, priced by the `marketsContext` spot marks).
65
72
  // Authenticated channels (resolve an auth token per subscribe send):
66
73
  // - orderUpdates → account_all_orders/{account_index}
67
74
  // - fills → account_all_trades/{account_index}
68
75
  // - positions → account_all_positions/{account_index}
76
+ // - accountSummary spot balances → account_all_assets/{account_index}, when
77
+ // a token resolves at subscribe time; else the public account_all/N
69
78
  //
70
79
  // Auth pattern (per Lighter WS spec): the subscribe payload carries the
71
80
  // token directly — `{ type: "subscribe", channel: "...", auth: "<token>" }`.
@@ -95,6 +104,7 @@ const LIGHTER_AUTH_CHANNEL = {
95
104
  function channelNeedsMarkets(channel: Subscription['channel']): boolean {
96
105
  return (
97
106
  channel === 'marketContext' ||
107
+ channel === 'accountSummary' ||
98
108
  channel === 'orderUpdates' ||
99
109
  channel === 'fills' ||
100
110
  channel === 'positions'
@@ -138,6 +148,17 @@ interface BookLevel {
138
148
  priceNum: number
139
149
  }
140
150
 
151
+ /**
152
+ * Per-address inputs of the `accountSummary` emit: the latest `user_stats`
153
+ * figures (`perps`, whose `equity` is perps-route only) and the spot-route
154
+ * balance per asset index from `account_all` or `account_all_assets`.
155
+ */
156
+ interface AccountSummaryInputs {
157
+ perps?: { equity: Big } & Omit<AccountSummary, 'portfolioValue'>
158
+ spotBalances?: Map<number, Big>
159
+ lastPortfolioValue?: string
160
+ }
161
+
141
162
  interface OrderbookState {
142
163
  bids: Map<string, BookLevel>
143
164
  asks: Map<string, BookLevel>
@@ -185,6 +206,11 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
185
206
  */
186
207
  private readonly positionsByAddress = new Map<string, Map<number, Position>>()
187
208
 
209
+ private readonly accountSummaryInputs = new Map<
210
+ string,
211
+ AccountSummaryInputs
212
+ >()
213
+
188
214
  private readonly registry: MarketRegistry | undefined
189
215
 
190
216
  private readonly accountIndexCache = new Map<string, number>()
@@ -279,15 +305,37 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
279
305
  }
280
306
  }
281
307
 
282
- // Registry keyed by the wire channel (unique per sub), so replay-failure
283
- // logs name the channel the venue knows. A logical sub may fan out to
284
- // several wire channels (e.g. `marketsContext`), each registered independently.
285
- for (const { channel, needsAuth, address } of wireChannels) {
286
- await this.registerSub(channel, { channel, needsAuth, address })
308
+ // Acquired before the wire subs register, so a rejected spot-mark open
309
+ // leaves no `user_stats`/`account_all` entry behind for the next replay.
310
+ const releaseSpotMarks =
311
+ sub.channel === 'accountSummary'
312
+ ? await this.subscribeSpotMarks(sub)
313
+ : undefined
314
+ if (sub.channel === 'accountSummary') {
315
+ this.accountSummaryInputs.set(sub.address.toLowerCase(), {})
316
+ }
317
+
318
+ try {
319
+ // Registry keyed by the wire channel (unique per sub), so replay-failure
320
+ // logs name the channel the venue knows. A logical sub may fan out to
321
+ // several wire channels (e.g. `marketsContext`), each registered independently.
322
+ for (const { channel, needsAuth, address } of wireChannels) {
323
+ await this.registerSub(channel, { channel, needsAuth, address })
324
+ }
325
+ await this.rws.ready()
326
+ } catch (error) {
327
+ releaseSpotMarks?.()
328
+ for (const { channel } of wireChannels) {
329
+ this.unregisterSub(channel)
330
+ }
331
+ if (sub.channel === 'accountSummary') {
332
+ this.accountSummaryInputs.delete(sub.address.toLowerCase())
333
+ }
334
+ throw error
287
335
  }
288
- await this.rws.ready()
289
336
 
290
337
  return () => {
338
+ releaseSpotMarks?.()
291
339
  for (const { channel } of wireChannels) {
292
340
  this.unregisterSub(channel)
293
341
  this.rws.send(JSON.stringify({ type: 'unsubscribe', channel }))
@@ -301,13 +349,31 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
301
349
  if (sub.channel === 'positions') {
302
350
  this.positionsByAddress.delete(sub.address.toLowerCase())
303
351
  }
352
+ if (sub.channel === 'accountSummary') {
353
+ this.accountSummaryInputs.delete(sub.address.toLowerCase())
354
+ }
304
355
  }
305
356
  }
306
357
 
358
+ /**
359
+ * Hold a `marketsContext` subscription for the spot marks that price the
360
+ * account's spot balances. The ref-counted base shares the wire channels
361
+ * with any consumer `marketsContext` subscription.
362
+ */
363
+ private subscribeSpotMarks(
364
+ sub: Extract<Subscription, { channel: 'accountSummary' }>
365
+ ): Promise<() => void> {
366
+ const address = sub.address.toLowerCase()
367
+ return this.subscribe({ channel: 'marketsContext', dex: sub.dex }, () =>
368
+ this.emitAccountSummary(address, true)
369
+ )
370
+ }
371
+
307
372
  protected override onClose(): void {
308
373
  this.orderbooks.clear()
309
374
  this.marketsContext = {}
310
375
  this.positionsByAddress.clear()
376
+ this.accountSummaryInputs.clear()
311
377
  }
312
378
 
313
379
  protected async sendSubscribe({
@@ -354,6 +420,19 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
354
420
  return token
355
421
  }
356
422
 
423
+ /** Whether a token resolves for the address; a resolver that throws counts as no token. */
424
+ private async canResolveAuthToken(address: Address): Promise<boolean> {
425
+ const resolve = this.authTokenResolver()
426
+ if (!resolve) {
427
+ return false
428
+ }
429
+ try {
430
+ return Boolean(await resolve(address))
431
+ } catch {
432
+ return false
433
+ }
434
+ }
435
+
357
436
  protected toKey(sub: Subscription): string {
358
437
  switch (sub.channel) {
359
438
  case 'marketsContext':
@@ -433,14 +512,26 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
433
512
  return [{ channel: `trade/${id}`, needsAuth: false }]
434
513
  }
435
514
  if (sub.channel === 'accountSummary') {
515
+ // Rejects the subscribe for a provider key with no settlement asset,
516
+ // instead of every later frame.
517
+ this.settlementAssetIndex()
436
518
  const accountIndex = await this.resolveAccountIndex(sub.address)
437
- // `user_stats` is publicly readable; no token needed.
519
+ // `account_all_assets` carries only the spot balances but rejects a
520
+ // subscribe without auth; the public `account_all` also carries the
521
+ // account's trades, positions and funding.
522
+ const hasToken = await this.canResolveAuthToken(sub.address)
523
+ const assetsChannel = hasToken ? 'account_all_assets' : 'account_all'
438
524
  return [
439
525
  {
440
526
  channel: `user_stats/${accountIndex}`,
441
527
  needsAuth: false,
442
528
  address: sub.address,
443
529
  },
530
+ {
531
+ channel: `${assetsChannel}/${accountIndex}`,
532
+ needsAuth: hasToken,
533
+ address: sub.address,
534
+ },
444
535
  ]
445
536
  }
446
537
  if (
@@ -585,6 +676,30 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
585
676
  return
586
677
  }
587
678
 
679
+ if (
680
+ msg.type === 'subscribed/account_all' ||
681
+ msg.type === 'update/account_all'
682
+ ) {
683
+ this.handleAccountAll(
684
+ msg as LtWsAccountAllMessage,
685
+ 'account_all',
686
+ msg.type === 'subscribed/account_all'
687
+ )
688
+ return
689
+ }
690
+
691
+ if (
692
+ msg.type === 'subscribed/account_all_assets' ||
693
+ msg.type === 'update/account_all_assets'
694
+ ) {
695
+ this.handleAccountAll(
696
+ msg as LtWsAccountAllMessage,
697
+ 'account_all_assets',
698
+ msg.type === 'subscribed/account_all_assets'
699
+ )
700
+ return
701
+ }
702
+
588
703
  if (
589
704
  msg.type === 'subscribed/account_all_positions' ||
590
705
  msg.type === 'update/account_all_positions'
@@ -657,17 +772,110 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
657
772
  marginUsed = isolatedMargin.plus(crossMargin)
658
773
  }
659
774
 
775
+ const inputs = this.accountSummaryInputs.get(address)
776
+ if (inputs === undefined) {
777
+ return
778
+ }
779
+ inputs.perps = {
780
+ equity: portfolio,
781
+ availableMargin: available.toFixed(),
782
+ marginUsed: marginUsed.toFixed(),
783
+ unrealizedPnl: portfolio.minus(collateral).toFixed(),
784
+ }
785
+ this.emitAccountSummary(address)
786
+ }
787
+
788
+ /**
789
+ * Replace the spot balances on a `subscribed/` frame and upsert them on an
790
+ * `update/` frame. An update before the first snapshot is dropped, so the
791
+ * map never holds a partial set. Balances parse here, so a non-decimal
792
+ * balance rejects its frame and never reaches the emit path.
793
+ */
794
+ private handleAccountAll(
795
+ msg: LtWsAccountAllMessage,
796
+ prefix: 'account_all' | 'account_all_assets',
797
+ isSnapshot: boolean
798
+ ): void {
799
+ const address = this.addressFromChannel(msg.channel, prefix)
800
+ if (!address) {
801
+ return
802
+ }
803
+ const inputs = this.accountSummaryInputs.get(address)
804
+ if (inputs === undefined || (!isSnapshot && !msg.assets)) {
805
+ return
806
+ }
807
+ const parsed = Object.values(msg.assets ?? {}).map(
808
+ (asset) =>
809
+ [asset.asset_id, toRequiredBig(asset.balance, 'balance')] as const
810
+ )
811
+ const balances = isSnapshot ? new Map<number, Big>() : inputs.spotBalances
812
+ if (balances === undefined) {
813
+ return
814
+ }
815
+ for (const [assetId, balance] of parsed) {
816
+ balances.set(assetId, balance)
817
+ }
818
+ inputs.spotBalances = balances
819
+ this.emitAccountSummary(address)
820
+ }
821
+
822
+ /**
823
+ * Emit the account summary once both the `user_stats` figures and the spot
824
+ * balances are in. `portfolioValue` values the spot balances as REST
825
+ * `getAccount` does. `onlyOnChange` skips an emit whose `portfolioValue`
826
+ * equals the last one, so a mark tick on an unrelated market emits nothing.
827
+ */
828
+ private emitAccountSummary(address: string, onlyOnChange = false): void {
829
+ const inputs = this.accountSummaryInputs.get(address)
830
+ if (inputs?.perps === undefined || inputs.spotBalances === undefined) {
831
+ return
832
+ }
833
+ const settlementAssetIndex = this.settlementAssetIndex()
834
+ const held = [...inputs.spotBalances].filter(([, balance]) => balance.gt(0))
835
+ const spotPrices = spotPriceByAssetId(
836
+ this.registry?.markets ?? [],
837
+ LIGHTER_SPOT_CATEGORY_ID,
838
+ Object.values(this.marketsContext),
839
+ new Set(
840
+ held
841
+ .filter(([assetId]) => assetId !== settlementAssetIndex)
842
+ .map(([assetId]) => String(assetId))
843
+ )
844
+ )
845
+ const portfolioValue = lighterPortfolioValue(
846
+ inputs.perps.equity,
847
+ held.map(
848
+ ([assetId, balance]) =>
849
+ spotValuation(
850
+ assetId,
851
+ balance.toFixed(),
852
+ settlementAssetIndex,
853
+ spotPrices
854
+ ).valueUsd
855
+ )
856
+ ).toFixed()
857
+ if (onlyOnChange && portfolioValue === inputs.lastPortfolioValue) {
858
+ return
859
+ }
860
+ inputs.lastPortfolioValue = portfolioValue
861
+ const { equity: _equity, ...perps } = inputs.perps
660
862
  this.emit(`accountSummary:${address}`, {
661
863
  channel: 'accountSummary',
662
- data: {
663
- portfolioValue: portfolio.toFixed(),
664
- availableMargin: available.toFixed(),
665
- marginUsed: marginUsed.toFixed(),
666
- unrealizedPnl: portfolio.minus(collateral).toFixed(),
667
- },
864
+ data: { portfolioValue, ...perps },
668
865
  })
669
866
  }
670
867
 
868
+ private settlementAssetIndex(): number {
869
+ const key = this.providerKey
870
+ if (key === LIGHTER_PROVIDER_KEY || key === LIGHTER_RH_PROVIDER_KEY) {
871
+ return LIGHTER_COLLATERAL_ASSETS[key].assetIndex
872
+ }
873
+ throw new PerpsError(
874
+ PerpsErrorCode.SDKError,
875
+ `Lighter WS: no settlement asset for provider '${key}'.`
876
+ )
877
+ }
878
+
671
879
  private handleAccountTrades(msg: LtWsAccountAllTradesMessage): void {
672
880
  const address = this.addressFromChannel(msg.channel, 'account_all_trades')
673
881
  if (!address) {