@lifi/perps-sdk-provider-lighter 30.0.1 → 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 (129) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +53 -45
  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/ws.d.ts +5 -0
  13. package/dist/cjs/types/ws.d.ts.map +1 -1
  14. package/dist/cjs/utils/apiClient.d.ts +3 -2
  15. package/dist/cjs/utils/apiClient.d.ts.map +1 -1
  16. package/dist/cjs/utils/apiClient.js +5 -3
  17. package/dist/cjs/utils/apiClient.js.map +1 -1
  18. package/dist/cjs/utils/fetchDetailedAccount.d.ts +1 -1
  19. package/dist/cjs/utils/fetchDetailedAccount.d.ts.map +1 -1
  20. package/dist/cjs/utils/fetchDetailedAccount.js +2 -1
  21. package/dist/cjs/utils/fetchDetailedAccount.js.map +1 -1
  22. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  23. package/dist/cjs/utils/mapFill.js +1 -3
  24. package/dist/cjs/utils/mapFill.js.map +1 -1
  25. package/dist/cjs/utils/mapPortfolioHistory.d.ts.map +1 -1
  26. package/dist/cjs/utils/mapPortfolioHistory.js +8 -4
  27. package/dist/cjs/utils/mapPortfolioHistory.js.map +1 -1
  28. package/dist/cjs/utils/mapPosition.d.ts +1 -0
  29. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  30. package/dist/cjs/utils/mapPosition.js +13 -1
  31. package/dist/cjs/utils/mapPosition.js.map +1 -1
  32. package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -1
  33. package/dist/cjs/utils/ohlcvInterval.js.map +1 -1
  34. package/dist/cjs/utils/spotPrice.d.ts +5 -1
  35. package/dist/cjs/utils/spotPrice.d.ts.map +1 -1
  36. package/dist/cjs/utils/spotPrice.js +14 -1
  37. package/dist/cjs/utils/spotPrice.js.map +1 -1
  38. package/dist/cjs/utils/withdrawableBalances.d.ts +3 -3
  39. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  40. package/dist/cjs/utils/withdrawableBalances.js +11 -3
  41. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  42. package/dist/cjs/websocket/LighterWsProvider.d.ts +6 -0
  43. package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
  44. package/dist/cjs/websocket/LighterWsProvider.js +120 -9
  45. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
  46. package/dist/esm/LighterProvider.d.ts.map +1 -1
  47. package/dist/esm/LighterProvider.js +62 -52
  48. package/dist/esm/LighterProvider.js.map +1 -1
  49. package/dist/esm/accountSummary.d.ts +17 -3
  50. package/dist/esm/accountSummary.d.ts.map +1 -1
  51. package/dist/esm/accountSummary.js +18 -9
  52. package/dist/esm/accountSummary.js.map +1 -1
  53. package/dist/esm/availableToTrade.d.ts +2 -0
  54. package/dist/esm/availableToTrade.d.ts.map +1 -1
  55. package/dist/esm/availableToTrade.js +4 -6
  56. package/dist/esm/availableToTrade.js.map +1 -1
  57. package/dist/esm/types/ws.d.ts +13 -0
  58. package/dist/esm/types/ws.d.ts.map +1 -1
  59. package/dist/esm/types/ws.js +0 -11
  60. package/dist/esm/types/ws.js.map +1 -1
  61. package/dist/esm/utils/apiClient.d.ts +9 -2
  62. package/dist/esm/utils/apiClient.d.ts.map +1 -1
  63. package/dist/esm/utils/apiClient.js +9 -2
  64. package/dist/esm/utils/apiClient.js.map +1 -1
  65. package/dist/esm/utils/fetchDetailedAccount.d.ts +1 -1
  66. package/dist/esm/utils/fetchDetailedAccount.d.ts.map +1 -1
  67. package/dist/esm/utils/fetchDetailedAccount.js +2 -1
  68. package/dist/esm/utils/fetchDetailedAccount.js.map +1 -1
  69. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  70. package/dist/esm/utils/mapFill.js +6 -10
  71. package/dist/esm/utils/mapFill.js.map +1 -1
  72. package/dist/esm/utils/mapPortfolioHistory.d.ts.map +1 -1
  73. package/dist/esm/utils/mapPortfolioHistory.js +8 -4
  74. package/dist/esm/utils/mapPortfolioHistory.js.map +1 -1
  75. package/dist/esm/utils/mapPosition.d.ts +7 -0
  76. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  77. package/dist/esm/utils/mapPosition.js +17 -0
  78. package/dist/esm/utils/mapPosition.js.map +1 -1
  79. package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -1
  80. package/dist/esm/utils/ohlcvInterval.js +5 -3
  81. package/dist/esm/utils/ohlcvInterval.js.map +1 -1
  82. package/dist/esm/utils/spotPrice.d.ts +10 -1
  83. package/dist/esm/utils/spotPrice.d.ts.map +1 -1
  84. package/dist/esm/utils/spotPrice.js +14 -0
  85. package/dist/esm/utils/spotPrice.js.map +1 -1
  86. package/dist/esm/utils/withdrawableBalances.d.ts +12 -5
  87. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  88. package/dist/esm/utils/withdrawableBalances.js +17 -5
  89. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  90. package/dist/esm/websocket/LighterWsProvider.d.ts +24 -0
  91. package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
  92. package/dist/esm/websocket/LighterWsProvider.js +153 -15
  93. package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
  94. package/dist/types/LighterProvider.d.ts.map +1 -1
  95. package/dist/types/accountSummary.d.ts +17 -3
  96. package/dist/types/accountSummary.d.ts.map +1 -1
  97. package/dist/types/availableToTrade.d.ts +2 -0
  98. package/dist/types/availableToTrade.d.ts.map +1 -1
  99. package/dist/types/types/ws.d.ts +13 -0
  100. package/dist/types/types/ws.d.ts.map +1 -1
  101. package/dist/types/utils/apiClient.d.ts +9 -2
  102. package/dist/types/utils/apiClient.d.ts.map +1 -1
  103. package/dist/types/utils/fetchDetailedAccount.d.ts +1 -1
  104. package/dist/types/utils/fetchDetailedAccount.d.ts.map +1 -1
  105. package/dist/types/utils/mapFill.d.ts.map +1 -1
  106. package/dist/types/utils/mapPortfolioHistory.d.ts.map +1 -1
  107. package/dist/types/utils/mapPosition.d.ts +7 -0
  108. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  109. package/dist/types/utils/ohlcvInterval.d.ts.map +1 -1
  110. package/dist/types/utils/spotPrice.d.ts +10 -1
  111. package/dist/types/utils/spotPrice.d.ts.map +1 -1
  112. package/dist/types/utils/withdrawableBalances.d.ts +12 -5
  113. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  114. package/dist/types/websocket/LighterWsProvider.d.ts +24 -0
  115. package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
  116. package/package.json +3 -3
  117. package/src/LighterProvider.ts +108 -63
  118. package/src/accountSummary.ts +37 -14
  119. package/src/availableToTrade.ts +6 -10
  120. package/src/types/ws.ts +23 -0
  121. package/src/utils/apiClient.ts +16 -7
  122. package/src/utils/fetchDetailedAccount.ts +2 -2
  123. package/src/utils/mapFill.ts +6 -13
  124. package/src/utils/mapPortfolioHistory.ts +9 -4
  125. package/src/utils/mapPosition.ts +17 -0
  126. package/src/utils/ohlcvInterval.ts +5 -3
  127. package/src/utils/spotPrice.ts +21 -1
  128. package/src/utils/withdrawableBalances.ts +26 -7
  129. package/src/websocket/LighterWsProvider.ts +222 -14
@@ -59,6 +59,7 @@ export declare class LighterWsProvider extends WsProviderBase<SubState> {
59
59
  * is a full open-position snapshot per the `PositionsEvent` contract.
60
60
  */
61
61
  private readonly positionsByAddress;
62
+ private readonly accountSummaryInputs;
62
63
  private readonly registry;
63
64
  private readonly accountIndexCache;
64
65
  private readonly accountIndexPromises;
@@ -77,6 +78,12 @@ export declare class LighterWsProvider extends WsProviderBase<SubState> {
77
78
  */
78
79
  subscribeQuote(params: ProviderGetQuoteParams, onQuote: QuoteListener): Promise<() => void>;
79
80
  protected openChannel(sub: Subscription): Promise<() => void>;
81
+ /**
82
+ * Hold a `marketsContext` subscription for the spot marks that price the
83
+ * account's spot balances. The ref-counted base shares the wire channels
84
+ * with any consumer `marketsContext` subscription.
85
+ */
86
+ private subscribeSpotMarks;
80
87
  protected onClose(): void;
81
88
  protected sendSubscribe({ channel, needsAuth, address, }: SubState): Promise<void>;
82
89
  /**
@@ -87,6 +94,8 @@ export declare class LighterWsProvider extends WsProviderBase<SubState> {
87
94
  * code, as it does for the Ondo and Lighter REST auth failures.
88
95
  */
89
96
  private requireAuthToken;
97
+ /** Whether a token resolves for the address; a resolver that throws counts as no token. */
98
+ private canResolveAuthToken;
90
99
  protected toKey(sub: Subscription): string;
91
100
  private resolveChannel;
92
101
  private resolveAccountIndex;
@@ -95,6 +104,21 @@ export declare class LighterWsProvider extends WsProviderBase<SubState> {
95
104
  private dispatch;
96
105
  private handleAccountOrders;
97
106
  private handleUserStats;
107
+ /**
108
+ * Replace the spot balances on a `subscribed/` frame and upsert them on an
109
+ * `update/` frame. An update before the first snapshot is dropped, so the
110
+ * map never holds a partial set. Balances parse here, so a non-decimal
111
+ * balance rejects its frame and never reaches the emit path.
112
+ */
113
+ private handleAccountAll;
114
+ /**
115
+ * Emit the account summary once both the `user_stats` figures and the spot
116
+ * balances are in. `portfolioValue` values the spot balances as REST
117
+ * `getAccount` does. `onlyOnChange` skips an emit whose `portfolioValue`
118
+ * equals the last one, so a mark tick on an unrelated market emits nothing.
119
+ */
120
+ private emitAccountSummary;
121
+ private settlementAssetIndex;
98
122
  private handleAccountTrades;
99
123
  private handleAccountPositions;
100
124
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"LighterWsProvider.d.ts","sourceRoot":"","sources":["../../../src/websocket/LighterWsProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAKlB,cAAc,EACd,KAAK,iBAAiB,EAGvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AA8EnC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;AAEhC,UAAU,QAAQ;IAChB,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAA;CACnB;AAkBD;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,wBAAwB,CAAA;CAC5C;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,cAAc,CAAC,QAAQ,CAAC;IAC7D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkB;IACtC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAE5B;IACb,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IAEnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,cAAc,CAAoC;IAE1D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA2C;IAE9E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAErD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA4B;IAC9D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqC;gBAGxE,KAAK,GAAE,MAA+B,EACtC,WAAW,GAAE,MAA6B,EAC1C,OAAO,GAAE,wBAA6B,EACtC,MAAM,CAAC,EAAE,cAAc;IAsBzB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;OAKG;IACG,cAAc,CAClB,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,IAAI,CAAC;cAkBN,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;cA6ChD,OAAO,IAAI,IAAI;cAMlB,aAAa,CAAC,EAC5B,OAAO,EACP,SAAS,EACT,OAAO,GACR,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3B;;;;;;OAMG;YACW,gBAAgB;IAsB9B,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM;YAyB5B,cAAc;YAyFd,mBAAmB;YAqBnB,iBAAiB;IAK/B,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAqB1C,OAAO,CAAC,QAAQ;IAkFhB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,eAAe;IAuDvB,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,sBAAsB;IAiC9B;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,eAAe;IA+BvB,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,mBAAmB;CAc5B;AAkID;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,GAC5B,UAAU,wBAAwB,KACjC,iBAKA,CAAA"}
1
+ {"version":3,"file":"LighterWsProvider.d.ts","sourceRoot":"","sources":["../../../src/websocket/LighterWsProvider.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/perps-sdk-provider-lighter",
3
- "version": "30.0.1",
3
+ "version": "31.0.0",
4
4
  "description": "Lighter provider plugin for @lifi/perps-sdk",
5
5
  "homepage": "https://github.com/lifinance/perps-sdk",
6
6
  "bugs": {
@@ -44,10 +44,10 @@
44
44
  "dependencies": {
45
45
  "big.js": "^7.0.1",
46
46
  "viem": "^2.55.18",
47
- "@lifi/perps-types": "^17.0.0"
47
+ "@lifi/perps-types": "^18.0.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@lifi/perps-sdk": "^19.0.0"
50
+ "@lifi/perps-sdk": "^20.0.0"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public"
@@ -9,6 +9,8 @@ import {
9
9
  getProviders,
10
10
  isActiveOrderStatus,
11
11
  localStorageAdapter,
12
+ maxOf,
13
+ minOf,
12
14
  PerpsError,
13
15
  type PerpsProviderPlugin,
14
16
  type PerpsSDKClient,
@@ -74,7 +76,7 @@ import {
74
76
  projectLighterConfigSettings,
75
77
  resolveAccountTier,
76
78
  } from './accountConfig.js'
77
- import { getAccountSummary } from './accountSummary.js'
79
+ import { getAccountSummary, lighterConfig } from './accountSummary.js'
78
80
  import { lighterAvailableToTrade } from './availableToTrade.js'
79
81
  import {
80
82
  DEFAULT_TRADES_LIMIT,
@@ -159,7 +161,7 @@ import {
159
161
  fetchRegisteredApiKey,
160
162
  normalizeLighterPublicKey,
161
163
  } from './utils/registeredApiKey.js'
162
- import { spotPriceByAssetId } from './utils/spotPrice.js'
164
+ import { spotPriceByAssetId, spotValuation } from './utils/spotPrice.js'
163
165
  import { isPlaceholderTxHash } from './utils/txHash.js'
164
166
  import { wireList } from './utils/wireList.js'
165
167
 
@@ -168,13 +170,6 @@ const ZERO_FEE_TIER = { maker: '0', taker: '0' }
168
170
  const tickToFeeString = (tick: number): string =>
169
171
  String(tick / LIGHTER_FEE_TICK_SCALE)
170
172
 
171
- const transferableWithin = (venueFigure: Big, units: Big): string => {
172
- if (venueFigure.lt(0)) {
173
- return '0'
174
- }
175
- return (venueFigure.gt(units) ? units : venueFigure).toFixed()
176
- }
177
-
178
173
  const projectFeeTier = (
179
174
  limits: LtAccountLimits
180
175
  ): { maker: string; taker: string } => ({
@@ -789,24 +784,48 @@ export const createLighterProvider = (
789
784
  }
790
785
  }
791
786
 
792
- const resolveAccountExists = async (
793
- address: Address,
794
- opts?: SDKRequestOptions
795
- ): Promise<boolean> => {
787
+ /** The account, or `undefined` when Lighter has no account for the address. */
788
+ const fetchAccountIfExists = async (
789
+ client: LighterApiClient,
790
+ address: Address
791
+ ): Promise<LtAccount | undefined> => {
796
792
  try {
797
- await fetchDetailedAccount(apiClient(opts), address)
798
- return true
793
+ return await fetchDetailedAccount(client, address)
799
794
  } catch (err) {
800
795
  if (
801
796
  err instanceof PerpsError &&
802
797
  err.code === PerpsErrorCode.AccountNotFound
803
798
  ) {
804
- return false
799
+ return undefined
805
800
  }
806
801
  throw err
807
802
  }
808
803
  }
809
804
 
805
+ /** The venue default for a market: its backend `defaultLeverage`, with cross margin. */
806
+ const resolveDefaultMarketSettings = async (
807
+ marketId: string
808
+ ): Promise<MarketSettings> => {
809
+ const registry = getMarketRegistry(requireClient(), providerKey)
810
+ await registry.sync()
811
+ const market = registry.require(marketId)
812
+ const leverage =
813
+ 'maxLeverage' in market ? market.defaultLeverage : undefined
814
+ if (leverage === undefined || !(leverage > 0)) {
815
+ throw new PerpsError(
816
+ PerpsErrorCode.SDKError,
817
+ `The backend market '${marketId}' carries no positive Lighter default leverage`
818
+ )
819
+ }
820
+ return { marginMode: MarginMode.CROSS, leverage }
821
+ }
822
+
823
+ const resolveAccountExists = async (
824
+ address: Address,
825
+ opts?: SDKRequestOptions
826
+ ): Promise<boolean> =>
827
+ (await fetchAccountIfExists(apiClient(opts), address)) !== undefined
828
+
810
829
  let setupPromise: Promise<SetupAction[]> | undefined
811
830
 
812
831
  /** The venue's own `Provider.setup` descriptor for an action, if declared. */
@@ -963,9 +982,6 @@ export const createLighterProvider = (
963
982
  const instanceMeta = providers.find((p) => p.key === providerKey)
964
983
  const categories = instanceMeta?.categories ?? []
965
984
  const perpsCategory = categories.find((c) => c.quoteAsset !== null)
966
- const spotCategoryId =
967
- categories.find((c) => c.quoteAsset === null)?.id ??
968
- LIGHTER_SPOT_CATEGORY_ID
969
985
 
970
986
  const availableBalance = toRequiredBig(
971
987
  account.available_balance,
@@ -982,7 +998,7 @@ export const createLighterProvider = (
982
998
  )
983
999
  const spotPrices = spotPriceByAssetId(
984
1000
  registry.markets,
985
- spotCategoryId,
1001
+ LIGHTER_SPOT_CATEGORY_ID,
986
1002
  prices,
987
1003
  new Set(
988
1004
  [...heldAssets, ...marginAssets]
@@ -996,16 +1012,17 @@ export const createLighterProvider = (
996
1012
  asset: Asset,
997
1013
  units: string
998
1014
  ): Balance => {
999
- const price =
1000
- a.asset_id === collateral.assetIndex
1001
- ? new Big(1)
1002
- : spotPrices.get(String(a.asset_id))
1015
+ const { valueUsd, price } = spotValuation(
1016
+ a.asset_id,
1017
+ units,
1018
+ collateral.assetIndex,
1019
+ spotPrices
1020
+ )
1003
1021
  return {
1004
1022
  categoryId,
1005
1023
  asset,
1006
1024
  units,
1007
- valueUsd:
1008
- price === undefined ? '0' : new Big(units).times(price).toFixed(),
1025
+ valueUsd,
1009
1026
  ...(price === undefined ? {} : { price: price.toFixed() }),
1010
1027
  }
1011
1028
  }
@@ -1033,13 +1050,31 @@ export const createLighterProvider = (
1033
1050
  a.margin_balance
1034
1051
  ),
1035
1052
  transferable: isSettlement
1036
- ? transferableWithin(availableBalance, new Big(a.margin_balance))
1053
+ ? minOf(
1054
+ maxOf(availableBalance, new Big(0)),
1055
+ new Big(a.margin_balance)
1056
+ ).toFixed()
1037
1057
  : '0',
1038
1058
  }
1039
1059
  })
1040
- const balances: Balance[] = heldAssets.map((a) =>
1041
- toBalance(a, spotCategoryId, registryAsset(a), a.balance)
1042
- )
1060
+ const balances: Balance[] = heldAssets.map((a) => {
1061
+ const balance = new Big(a.balance)
1062
+ return {
1063
+ ...toBalance(
1064
+ a,
1065
+ LIGHTER_SPOT_CATEGORY_ID,
1066
+ registryAsset(a),
1067
+ a.balance
1068
+ ),
1069
+ transferable: minOf(
1070
+ maxOf(
1071
+ balance.minus(toRequiredBig(a.locked_balance, 'locked_balance')),
1072
+ new Big(0)
1073
+ ),
1074
+ balance
1075
+ ).toFixed(),
1076
+ }
1077
+ })
1043
1078
 
1044
1079
  const assetCollateral = account.assets.flatMap((a) =>
1045
1080
  a.margin_mode === undefined
@@ -1059,6 +1094,8 @@ export const createLighterProvider = (
1059
1094
  apiKeyRegistered,
1060
1095
  accountType: account.account_type,
1061
1096
  availableBalance: account.available_balance,
1097
+ crossAssetValue: account.cross_asset_value,
1098
+ crossInitialMarginRequirement: account.cross_initial_margin_requirement,
1062
1099
  totalAssetValue: account.total_asset_value,
1063
1100
  userTierName: limitsResult?.user_tier_name,
1064
1101
  accountTradingMode:
@@ -1112,11 +1149,13 @@ export const createLighterProvider = (
1112
1149
  params: ProviderGetWithdrawableBalancesParams,
1113
1150
  opts?: SDKRequestOptions
1114
1151
  ): Promise<ProviderWithdrawableBalance[]> {
1115
- const account = await fetchDetailedAccount(
1152
+ const account = await fetchAccountIfExists(
1116
1153
  apiClient(opts),
1117
1154
  params.address
1118
1155
  )
1119
- return lighterWithdrawableBalances(account.assets)
1156
+ return account === undefined
1157
+ ? []
1158
+ : lighterWithdrawableBalances(account, collateral.assetIndex)
1120
1159
  },
1121
1160
 
1122
1161
  async getPositions(
@@ -1126,9 +1165,16 @@ export const createLighterProvider = (
1126
1165
  const client = apiClient(opts)
1127
1166
  const registry = getMarketRegistry(requireClient(), providerKey)
1128
1167
  const [account] = await Promise.all([
1129
- fetchDetailedAccount(client, params.address),
1168
+ fetchAccountIfExists(client, params.address),
1130
1169
  registry.sync(),
1131
1170
  ])
1171
+ if (account === undefined) {
1172
+ return {
1173
+ provider: providerKey,
1174
+ positions: [],
1175
+ pagination: { limit: params.limit ?? 0, hasMore: false },
1176
+ }
1177
+ }
1132
1178
 
1133
1179
  let positions: Position[] = mapOpenPositions(account.positions, (id) =>
1134
1180
  toPerpsMarketDisplay(registry.require(String(id)))
@@ -1165,39 +1211,38 @@ export const createLighterProvider = (
1165
1211
  },
1166
1212
 
1167
1213
  /**
1168
- * Lighter reports a market's margin mode and leverage only on the
1169
- * account's position row, so a market the account never touched (or a
1170
- * missing account) resolves `undefined` rather than a venue default.
1214
+ * Lighter reports a market's margin mode and leverage on the account's
1215
+ * position row. Without a row carrying a positive IMF (or without an
1216
+ * account), the venue applies the market's default leverage with cross margin.
1171
1217
  */
1172
1218
  async getMarketSettings(
1173
1219
  params: ProviderGetMarketSettingsParams,
1174
1220
  opts?: SDKRequestOptions
1175
- ): Promise<MarketSettings | undefined> {
1176
- // Spot markets carry no margin mode or leverage.
1177
- if (params.market.categoryId === LIGHTER_SPOT_CATEGORY_ID) {
1178
- return undefined
1179
- }
1180
- let account: LtAccount
1181
- try {
1182
- account = await fetchDetailedAccount(apiClient(opts), params.address)
1183
- } catch (err) {
1184
- if (
1185
- err instanceof PerpsError &&
1186
- err.code === PerpsErrorCode.AccountNotFound
1187
- ) {
1188
- return undefined
1189
- }
1190
- throw err
1221
+ ): Promise<MarketSettings> {
1222
+ const { marketId, categoryId } = params.market
1223
+ if (categoryId === LIGHTER_SPOT_CATEGORY_ID) {
1224
+ throw new PerpsError(
1225
+ PerpsErrorCode.ValidationError,
1226
+ `Lighter market '${marketId}' is a spot market and carries no leverage setting`
1227
+ )
1191
1228
  }
1192
- const row = account.positions.find(
1193
- (p) => String(p.market_id) === params.market.marketId
1229
+ const client = apiClient(opts)
1230
+ const account = await fetchAccountIfExists(client, params.address)
1231
+ const row = account?.positions.find(
1232
+ (p) => String(p.market_id) === marketId
1194
1233
  )
1195
- if (!row) {
1196
- return undefined
1197
- }
1198
- const leverage = leverageFromImf(row.initial_margin_fraction)
1199
- if (leverage === undefined) {
1200
- return undefined
1234
+ // An unparsable IMF throws; a non-positive one means no stored setting.
1235
+ const leverage =
1236
+ row === undefined
1237
+ ? undefined
1238
+ : leverageFromImf(
1239
+ toRequiredBig(
1240
+ row.initial_margin_fraction,
1241
+ 'initial_margin_fraction'
1242
+ ).toFixed()
1243
+ )
1244
+ if (row === undefined || leverage === undefined) {
1245
+ return resolveDefaultMarketSettings(marketId)
1201
1246
  }
1202
1247
  return {
1203
1248
  marginMode:
@@ -1493,12 +1538,12 @@ export const createLighterProvider = (
1493
1538
  }
1494
1539
 
1495
1540
  const client = apiClient(opts)
1496
- const account = await fetchDetailedAccount(client, params.address)
1541
+ const account = await plugin.getAccount({ address: params.address }, opts)
1497
1542
  const window = PNL_WINDOWS[params.range]
1498
1543
  const endTimestampSeconds = Math.floor(Date.now() / 1_000)
1499
1544
  const queryParams: Record<string, string | number | boolean> = {
1500
1545
  by: 'index',
1501
- value: String(account.index),
1546
+ value: String(lighterConfig(account).accountIndex),
1502
1547
  resolution: window.resolution,
1503
1548
  start_timestamp:
1504
1549
  params.range === 'all'
@@ -1537,7 +1582,7 @@ export const createLighterProvider = (
1537
1582
  return mapPortfolioHistory(
1538
1583
  params.range,
1539
1584
  wireList(response.pnl),
1540
- toRequiredBig(account.total_asset_value, 'total_asset_value')
1585
+ new Big(getAccountSummary(account, account.positions).portfolioValue)
1541
1586
  )
1542
1587
  },
1543
1588
 
@@ -7,10 +7,18 @@ import type {
7
7
  } from '@lifi/perps-types'
8
8
  import { PerpsErrorCode } from '@lifi/perps-types'
9
9
  import Big from 'big.js'
10
+ import { atLeastZero } from './availableToTrade.js'
10
11
  import { LIGHTER_PROVIDER_KEY, LIGHTER_RH_PROVIDER_KEY } from './constants.js'
11
12
  import { toRequiredBig } from './utils/decimal.js'
12
13
 
13
- const lighterConfig = (account: AccountResponse): LighterAccountConfig => {
14
+ /**
15
+ * The Lighter arm of `account.config`.
16
+ *
17
+ * @throws {PerpsError} `SDKError` when the account is not a Lighter one.
18
+ */
19
+ export const lighterConfig = (
20
+ account: AccountResponse
21
+ ): LighterAccountConfig => {
14
22
  const { config } = account
15
23
  if (
16
24
  config.provider !== LIGHTER_PROVIDER_KEY &&
@@ -24,12 +32,27 @@ const lighterConfig = (account: AccountResponse): LighterAccountConfig => {
24
32
  return config
25
33
  }
26
34
 
35
+ /**
36
+ * Lighter portfolio value: the perps-route equity (`total_asset_value`) plus
37
+ * the USD value of every spot-route holding, settlement asset included.
38
+ */
39
+ export const lighterPortfolioValue = (
40
+ perpsEquity: Big,
41
+ spotValuesUsd: readonly string[]
42
+ ): Big =>
43
+ spotValuesUsd.reduce(
44
+ (sum, valueUsd) => sum.plus(toRequiredBig(valueUsd, 'valueUsd')),
45
+ perpsEquity
46
+ )
47
+
27
48
  /**
28
49
  * Roll a Lighter account up into an {@link AccountSummary}. `totalAssetValue`
29
50
  * is perps-route equity and excludes the spot route, so `portfolioValue` adds
30
51
  * every spot `balances` row, settlement included; the `collateralBalances` rows
31
- * are the perps-route holding already inside `totalAssetValue`. The positions
32
- * supply only the margin and PnL breakdown.
52
+ * are the perps-route holding already inside `totalAssetValue`.
53
+ * `availableMargin` is the cross free collateral, floored at 0: an isolated
54
+ * position's equity stays with that position until the user removes it. The
55
+ * positions supply only the margin and PnL breakdown.
33
56
  *
34
57
  * @throws {PerpsError} `SDKError` when the account is not a Lighter one.
35
58
  * @public
@@ -47,18 +70,18 @@ export function getAccountSummary(
47
70
  unrealizedPnl = unrealizedPnl.plus(position.unrealizedPnl)
48
71
  }
49
72
 
50
- let portfolioValue = toRequiredBig(config.totalAssetValue, 'totalAssetValue')
51
- for (const balance of account.balances) {
52
- portfolioValue = portfolioValue.plus(
53
- toRequiredBig(balance.valueUsd, 'valueUsd')
54
- )
55
- }
56
-
57
73
  return {
58
- portfolioValue: portfolioValue.toFixed(),
59
- availableMargin: toRequiredBig(
60
- config.availableBalance,
61
- 'availableBalance'
74
+ portfolioValue: lighterPortfolioValue(
75
+ toRequiredBig(config.totalAssetValue, 'totalAssetValue'),
76
+ account.balances.map((balance) => balance.valueUsd)
77
+ ).toFixed(),
78
+ availableMargin: atLeastZero(
79
+ toRequiredBig(config.crossAssetValue, 'crossAssetValue').minus(
80
+ toRequiredBig(
81
+ config.crossInitialMarginRequirement,
82
+ 'crossInitialMarginRequirement'
83
+ )
84
+ )
62
85
  ).toFixed(),
63
86
  marginUsed: marginUsed.toFixed(),
64
87
  unrealizedPnl: unrealizedPnl.toFixed(),
@@ -4,23 +4,19 @@ import { MarginMode, PositionSide } from '@lifi/perps-types'
4
4
  import Big from 'big.js'
5
5
  import { toRequiredBig } from './utils/decimal.js'
6
6
 
7
- const atLeastZero = (value: Big): Big => (value.lt(0) ? new Big(0) : value)
7
+ export const atLeastZero = (value: Big): Big =>
8
+ value.lt(0) ? new Big(0) : value
8
9
 
9
- // Lighter's `available_balance` already nets cross unrealized PnL and counts
10
- // an isolated position's equity above its IMR, so only the rest is released.
10
+ // `availableMargin` is the cross free collateral and holds nothing of an
11
+ // isolated position, so closing one releases its whole equity.
11
12
  const releasedOnClose = (position: Position): Big => {
12
13
  const marginUsed = toRequiredBig(position.marginUsed, 'marginUsed')
13
14
  if (position.marginMode !== MarginMode.ISOLATED) {
14
15
  return marginUsed
15
16
  }
16
- const equity = marginUsed.plus(
17
- toRequiredBig(position.unrealizedPnl, 'unrealizedPnl')
17
+ return atLeastZero(
18
+ marginUsed.plus(toRequiredBig(position.unrealizedPnl, 'unrealizedPnl'))
18
19
  )
19
- const imr = toRequiredBig(
20
- position.initialMarginRequirement,
21
- 'initialMarginRequirement'
22
- )
23
- return equity.lt(imr) ? equity : imr
24
20
  }
25
21
 
26
22
  /**
package/src/types/ws.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { LtAccountAsset } from './account.js'
2
+
1
3
  // The Lighter stream server sends JSON messages of shape:
2
4
  // { type: 'subscribed/<channel>' | 'update/<channel>' | 'ping',
3
5
  // channel: '<channel>/<id>',
@@ -9,6 +11,7 @@
9
11
  // - spot_market_stats/all → context across all spot markets
10
12
  // - spot_market_stats/{id} → context for one spot market
11
13
  // - order_book/{market_id} → bids/asks per market (stateful with deltas)
14
+ // - account_all/{account} → per-asset spot-route balances
12
15
 
13
16
  /**
14
17
  * Common envelope fields on every Lighter WebSocket frame.
@@ -211,6 +214,26 @@ export type LtWsUserStatsMessage = LtWsMessage & {
211
214
  }
212
215
  }
213
216
 
217
+ /**
218
+ * `account_all/{account_index}` or `account_all_assets/{account_index}` frame,
219
+ * read for its `assets` map only. The `subscribed/` frame carries every asset
220
+ * keyed by asset index; an `update/` frame carries the changed assets, or
221
+ * `null` when no balance changed. `balance` is the spot-route balance in asset
222
+ * units.
223
+ * @public
224
+ */
225
+ export type LtWsAccountAllMessage = LtWsMessage & {
226
+ type:
227
+ | 'subscribed/account_all'
228
+ | 'update/account_all'
229
+ | 'subscribed/account_all_assets'
230
+ | 'update/account_all_assets'
231
+ assets?: Record<
232
+ string,
233
+ Pick<LtAccountAsset, 'symbol' | 'asset_id' | 'balance' | 'locked_balance'>
234
+ > | null
235
+ }
236
+
214
237
  /**
215
238
  * Authenticated account-position frame. Position and funding maps are keyed
216
239
  * by Lighter market index; the provider normalizes the payload before emitting
@@ -15,6 +15,15 @@ import { lighterErrorCodeFromBody } from './lighterErrorCode.js'
15
15
  /** @internal */
16
16
  export type ApiParams = Record<string, string | number | boolean>
17
17
 
18
+ /**
19
+ * Short excerpt of a parsed Lighter body for an error message. `undefined`
20
+ * stands for a body that did not parse as JSON.
21
+ *
22
+ * @internal
23
+ */
24
+ export const lighterBodyExcerpt = (data: unknown): string =>
25
+ data === undefined ? 'non-JSON body' : JSON.stringify(data).slice(0, 200)
26
+
18
27
  /**
19
28
  * Lighter signals errors on two channels: a non-2xx HTTP status, or an HTTP 200
20
29
  * carrying an error `code` in the JSON body. Returns the body error `code` when
@@ -269,7 +278,7 @@ export class LighterApiClient {
269
278
  async getWithStatus<T>(
270
279
  path: string,
271
280
  params?: ApiParams
272
- ): Promise<{ status: number; data: T }> {
281
+ ): Promise<{ status: number; data: T | undefined }> {
273
282
  return this.sendGet<T>(path, params)
274
283
  }
275
284
 
@@ -277,7 +286,7 @@ export class LighterApiClient {
277
286
  path: string,
278
287
  params?: ApiParams,
279
288
  headers?: Record<string, string>
280
- ): Promise<{ status: number; data: T }> {
289
+ ): Promise<{ status: number; data: T | undefined }> {
281
290
  this.assertRequestAllowed()
282
291
  const url = this.buildUrl(path, params)
283
292
  const response = await fetchWithRetry(
@@ -290,7 +299,7 @@ export class LighterApiClient {
290
299
  }
291
300
  )
292
301
  this.assertNotRateLimited(response)
293
- const data = (await response.json().catch(() => undefined)) as T
302
+ const data = (await response.json().catch(() => undefined)) as T | undefined
294
303
  return { status: response.status, data }
295
304
  }
296
305
 
@@ -305,7 +314,7 @@ export class LighterApiClient {
305
314
  path: string,
306
315
  authToken: string,
307
316
  params: ApiParams
308
- ): Promise<{ status: number; data: T }> {
317
+ ): Promise<{ status: number; data: T | undefined }> {
309
318
  const body = new URLSearchParams()
310
319
  for (const [k, v] of Object.entries(params)) {
311
320
  body.set(k, String(v))
@@ -320,7 +329,7 @@ export class LighterApiClient {
320
329
  signal: this.signal,
321
330
  })
322
331
  this.assertNotRateLimited(response)
323
- const data = (await response.json().catch(() => undefined)) as T
332
+ const data = (await response.json().catch(() => undefined)) as T | undefined
324
333
  return { status: response.status, data }
325
334
  }
326
335
 
@@ -344,13 +353,13 @@ export class LighterApiClient {
344
353
  if (status < 200 || status >= 300) {
345
354
  throw new PerpsError(
346
355
  code,
347
- `Lighter API request failed: ${status} — ${JSON.stringify(data).slice(0, 200)}`
356
+ `Lighter API request failed: ${status} — ${lighterBodyExcerpt(data)}`
348
357
  )
349
358
  }
350
359
  if (errorCode !== undefined) {
351
360
  throw new PerpsError(
352
361
  code,
353
- `Lighter API error for ${path}: code ${errorCode} — ${JSON.stringify(data).slice(0, 200)}`
362
+ `Lighter API error for ${path}: code ${errorCode} — ${lighterBodyExcerpt(data)}`
354
363
  )
355
364
  }
356
365
  }
@@ -3,7 +3,7 @@ import { PerpsErrorCode } from '@lifi/perps-types'
3
3
  import type { Address } from 'viem'
4
4
  import { LIGHTER_CODE_ACCOUNT_NOT_FOUND } from '../constants.js'
5
5
  import type { LtAccount, LtDetailedAccount } from '../types/index.js'
6
- import type { LighterApiClient } from './apiClient.js'
6
+ import { type LighterApiClient, lighterBodyExcerpt } from './apiClient.js'
7
7
  import { wireList } from './wireList.js'
8
8
 
9
9
  /**
@@ -33,7 +33,7 @@ export const fetchDetailedAccount = async (
33
33
  if (status < 200 || status >= 300) {
34
34
  throw new PerpsError(
35
35
  PerpsErrorCode.ThirdPartyError,
36
- `Lighter account request failed: ${status} — ${JSON.stringify(data).slice(0, 200)}`
36
+ `Lighter account request failed: ${status} — ${lighterBodyExcerpt(data)}`
37
37
  )
38
38
  }
39
39
 
@@ -6,12 +6,9 @@ import {
6
6
  import type { Fill, MarketDisplay } from '@lifi/perps-types'
7
7
  import { LiquidityRole, OrderSide, OrderType } from '@lifi/perps-types'
8
8
  import Big from 'big.js'
9
- import {
10
- LIGHTER_FEE_TICK_SCALE,
11
- LIGHTER_IMF_PERCENT_SCALE,
12
- } from '../constants.js'
9
+ import { LIGHTER_FEE_TICK_SCALE } from '../constants.js'
13
10
  import type { LtTrade } from '../types/index.js'
14
- import { leverageFromImf } from './mapPosition.js'
11
+ import { leverageFromScaledImf } from './mapPosition.js'
15
12
  import { isPlaceholderTxHash } from './txHash.js'
16
13
 
17
14
  /**
@@ -36,16 +33,12 @@ const tickToFeeAmount = (
36
33
  .toFixed()
37
34
 
38
35
  /**
39
- * Display leverage the viewer had set on the market when the trade executed.
40
- * Lighter reports it as the pre-trade initial margin fraction, an integer
41
- * percent on `LIGHTER_IMF_PERCENT_SCALE`: `500` is 5.00%, so 20x. Returns
42
- * `undefined` when the row omits the fraction. Lighter declares the fraction a
43
- * `StrictInt`, so the scale division is exact and `toFixed()` loses nothing.
36
+ * Display leverage the viewer had set on the market when the trade executed,
37
+ * from the pre-trade initial margin fraction. Returns `undefined` when the
38
+ * row omits the fraction.
44
39
  */
45
40
  const leverageFromTradeImf = (imf: number | undefined): number | undefined =>
46
- imf === undefined
47
- ? undefined
48
- : leverageFromImf(new Big(imf).div(LIGHTER_IMF_PERCENT_SCALE).toFixed())
41
+ imf === undefined ? undefined : leverageFromScaledImf(imf)
49
42
 
50
43
  /**
51
44
  * Realized PnL on a position-reducing fill, derived from the pre-trade entry