@lifi/perps-sdk-provider-lighter 7.0.0 → 8.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 (214) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +60 -27
  3. package/dist/cjs/LighterProvider.js.map +1 -1
  4. package/dist/cjs/accountSummary.d.ts.map +1 -1
  5. package/dist/cjs/accountSummary.js +33 -2
  6. package/dist/cjs/accountSummary.js.map +1 -1
  7. package/dist/cjs/constants.d.ts.map +1 -1
  8. package/dist/cjs/constants.js.map +1 -1
  9. package/dist/cjs/signers/LighterKeyStore.d.ts.map +1 -1
  10. package/dist/cjs/signers/LighterKeyStore.js.map +1 -1
  11. package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  12. package/dist/cjs/signers/LighterReadOnlyTokenManager.js.map +1 -1
  13. package/dist/cjs/signers/LighterSigner.d.ts.map +1 -1
  14. package/dist/cjs/signers/LighterSigner.js.map +1 -1
  15. package/dist/cjs/signers/createAuthToken.d.ts.map +1 -1
  16. package/dist/cjs/signers/createAuthToken.js.map +1 -1
  17. package/dist/cjs/signers/wasmLoader.d.ts.map +1 -1
  18. package/dist/cjs/signers/wasmLoader.js.map +1 -1
  19. package/dist/cjs/types/account.d.ts +1 -0
  20. package/dist/cjs/types/account.d.ts.map +1 -1
  21. package/dist/cjs/types/action.d.ts.map +1 -1
  22. package/dist/cjs/types/action.js.map +1 -1
  23. package/dist/cjs/types/activity.d.ts.map +1 -1
  24. package/dist/cjs/types/asset.d.ts.map +1 -1
  25. package/dist/cjs/types/fill.d.ts.map +1 -1
  26. package/dist/cjs/types/order.d.ts.map +1 -1
  27. package/dist/cjs/types/submit.d.ts.map +1 -1
  28. package/dist/cjs/types/ws.d.ts +5 -0
  29. package/dist/cjs/types/ws.d.ts.map +1 -1
  30. package/dist/cjs/utils/decimal.d.ts +5 -0
  31. package/dist/cjs/utils/decimal.d.ts.map +1 -0
  32. package/dist/cjs/utils/decimal.js +38 -0
  33. package/dist/cjs/utils/decimal.js.map +1 -0
  34. package/dist/cjs/utils/index.d.ts +3 -1
  35. package/dist/cjs/utils/index.d.ts.map +1 -1
  36. package/dist/cjs/utils/index.js +7 -1
  37. package/dist/cjs/utils/index.js.map +1 -1
  38. package/dist/cjs/utils/lighterAsset.d.ts.map +1 -1
  39. package/dist/cjs/utils/lighterAsset.js.map +1 -1
  40. package/dist/cjs/utils/mapOpenPositions.d.ts +2 -2
  41. package/dist/cjs/utils/mapOpenPositions.d.ts.map +1 -1
  42. package/dist/cjs/utils/mapOpenPositions.js.map +1 -1
  43. package/dist/cjs/utils/mapPosition.d.ts +3 -2
  44. package/dist/cjs/utils/mapPosition.d.ts.map +1 -1
  45. package/dist/cjs/utils/mapPosition.js +30 -9
  46. package/dist/cjs/utils/mapPosition.js.map +1 -1
  47. package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -1
  48. package/dist/cjs/utils/ohlcvInterval.js.map +1 -1
  49. package/dist/cjs/utils/transferMargin.d.ts +3 -0
  50. package/dist/cjs/utils/transferMargin.d.ts.map +1 -0
  51. package/dist/cjs/utils/transferMargin.js +16 -0
  52. package/dist/cjs/utils/transferMargin.js.map +1 -0
  53. package/dist/cjs/utils/wireEncoding.d.ts.map +1 -1
  54. package/dist/cjs/utils/wireEncoding.js.map +1 -1
  55. package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
  56. package/dist/cjs/websocket/LighterWsProvider.js +20 -9
  57. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
  58. package/dist/esm/LighterProvider.d.ts.map +1 -1
  59. package/dist/esm/LighterProvider.js +49 -11
  60. package/dist/esm/LighterProvider.js.map +1 -1
  61. package/dist/esm/accountSummary.d.ts +5 -4
  62. package/dist/esm/accountSummary.d.ts.map +1 -1
  63. package/dist/esm/accountSummary.js +35 -6
  64. package/dist/esm/accountSummary.js.map +1 -1
  65. package/dist/esm/constants.d.ts +15 -2
  66. package/dist/esm/constants.d.ts.map +1 -1
  67. package/dist/esm/constants.js +15 -2
  68. package/dist/esm/constants.js.map +1 -1
  69. package/dist/esm/signers/LighterKeyStore.d.ts +31 -2
  70. package/dist/esm/signers/LighterKeyStore.d.ts.map +1 -1
  71. package/dist/esm/signers/LighterKeyStore.js +24 -1
  72. package/dist/esm/signers/LighterKeyStore.js.map +1 -1
  73. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts +23 -3
  74. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  75. package/dist/esm/signers/LighterReadOnlyTokenManager.js +4 -0
  76. package/dist/esm/signers/LighterReadOnlyTokenManager.js.map +1 -1
  77. package/dist/esm/signers/LighterSigner.d.ts +49 -7
  78. package/dist/esm/signers/LighterSigner.d.ts.map +1 -1
  79. package/dist/esm/signers/LighterSigner.js +11 -1
  80. package/dist/esm/signers/LighterSigner.js.map +1 -1
  81. package/dist/esm/signers/createAuthToken.d.ts +8 -2
  82. package/dist/esm/signers/createAuthToken.d.ts.map +1 -1
  83. package/dist/esm/signers/createAuthToken.js.map +1 -1
  84. package/dist/esm/signers/wasmLoader.d.ts +14 -2
  85. package/dist/esm/signers/wasmLoader.d.ts.map +1 -1
  86. package/dist/esm/signers/wasmLoader.js.map +1 -1
  87. package/dist/esm/types/account.d.ts +61 -8
  88. package/dist/esm/types/account.d.ts.map +1 -1
  89. package/dist/esm/types/action.d.ts +72 -18
  90. package/dist/esm/types/action.d.ts.map +1 -1
  91. package/dist/esm/types/action.js +72 -18
  92. package/dist/esm/types/action.js.map +1 -1
  93. package/dist/esm/types/activity.d.ts +64 -10
  94. package/dist/esm/types/activity.d.ts.map +1 -1
  95. package/dist/esm/types/asset.d.ts +72 -12
  96. package/dist/esm/types/asset.d.ts.map +1 -1
  97. package/dist/esm/types/fill.d.ts +14 -2
  98. package/dist/esm/types/fill.d.ts.map +1 -1
  99. package/dist/esm/types/order.d.ts +27 -4
  100. package/dist/esm/types/order.d.ts.map +1 -1
  101. package/dist/esm/types/submit.d.ts +26 -4
  102. package/dist/esm/types/submit.d.ts.map +1 -1
  103. package/dist/esm/types/ws.d.ts +65 -9
  104. package/dist/esm/types/ws.d.ts.map +1 -1
  105. package/dist/esm/utils/decimal.d.ts +15 -0
  106. package/dist/esm/utils/decimal.d.ts.map +1 -0
  107. package/dist/esm/utils/decimal.js +39 -0
  108. package/dist/esm/utils/decimal.js.map +1 -0
  109. package/dist/esm/utils/index.d.ts +3 -1
  110. package/dist/esm/utils/index.d.ts.map +1 -1
  111. package/dist/esm/utils/index.js +3 -1
  112. package/dist/esm/utils/index.js.map +1 -1
  113. package/dist/esm/utils/lighterAsset.d.ts +7 -1
  114. package/dist/esm/utils/lighterAsset.d.ts.map +1 -1
  115. package/dist/esm/utils/lighterAsset.js +7 -1
  116. package/dist/esm/utils/lighterAsset.js.map +1 -1
  117. package/dist/esm/utils/mapOpenPositions.d.ts +2 -2
  118. package/dist/esm/utils/mapOpenPositions.d.ts.map +1 -1
  119. package/dist/esm/utils/mapOpenPositions.js.map +1 -1
  120. package/dist/esm/utils/mapPosition.d.ts +10 -2
  121. package/dist/esm/utils/mapPosition.d.ts.map +1 -1
  122. package/dist/esm/utils/mapPosition.js +32 -11
  123. package/dist/esm/utils/mapPosition.js.map +1 -1
  124. package/dist/esm/utils/ohlcvInterval.d.ts +6 -1
  125. package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -1
  126. package/dist/esm/utils/ohlcvInterval.js +6 -1
  127. package/dist/esm/utils/ohlcvInterval.js.map +1 -1
  128. package/dist/esm/utils/transferMargin.d.ts +11 -0
  129. package/dist/esm/utils/transferMargin.d.ts.map +1 -0
  130. package/dist/esm/utils/transferMargin.js +21 -0
  131. package/dist/esm/utils/transferMargin.js.map +1 -0
  132. package/dist/esm/utils/wireEncoding.d.ts +58 -23
  133. package/dist/esm/utils/wireEncoding.d.ts.map +1 -1
  134. package/dist/esm/utils/wireEncoding.js +58 -23
  135. package/dist/esm/utils/wireEncoding.js.map +1 -1
  136. package/dist/esm/websocket/LighterWsProvider.d.ts +6 -0
  137. package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
  138. package/dist/esm/websocket/LighterWsProvider.js +30 -13
  139. package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
  140. package/dist/types/LighterProvider.d.ts.map +1 -1
  141. package/dist/types/accountSummary.d.ts +5 -4
  142. package/dist/types/accountSummary.d.ts.map +1 -1
  143. package/dist/types/constants.d.ts +15 -2
  144. package/dist/types/constants.d.ts.map +1 -1
  145. package/dist/types/signers/LighterKeyStore.d.ts +31 -2
  146. package/dist/types/signers/LighterKeyStore.d.ts.map +1 -1
  147. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts +23 -3
  148. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  149. package/dist/types/signers/LighterSigner.d.ts +49 -7
  150. package/dist/types/signers/LighterSigner.d.ts.map +1 -1
  151. package/dist/types/signers/createAuthToken.d.ts +8 -2
  152. package/dist/types/signers/createAuthToken.d.ts.map +1 -1
  153. package/dist/types/signers/wasmLoader.d.ts +14 -2
  154. package/dist/types/signers/wasmLoader.d.ts.map +1 -1
  155. package/dist/types/types/account.d.ts +61 -8
  156. package/dist/types/types/account.d.ts.map +1 -1
  157. package/dist/types/types/action.d.ts +72 -18
  158. package/dist/types/types/action.d.ts.map +1 -1
  159. package/dist/types/types/activity.d.ts +64 -10
  160. package/dist/types/types/activity.d.ts.map +1 -1
  161. package/dist/types/types/asset.d.ts +72 -12
  162. package/dist/types/types/asset.d.ts.map +1 -1
  163. package/dist/types/types/fill.d.ts +14 -2
  164. package/dist/types/types/fill.d.ts.map +1 -1
  165. package/dist/types/types/order.d.ts +27 -4
  166. package/dist/types/types/order.d.ts.map +1 -1
  167. package/dist/types/types/submit.d.ts +26 -4
  168. package/dist/types/types/submit.d.ts.map +1 -1
  169. package/dist/types/types/ws.d.ts +65 -9
  170. package/dist/types/types/ws.d.ts.map +1 -1
  171. package/dist/types/utils/decimal.d.ts +15 -0
  172. package/dist/types/utils/decimal.d.ts.map +1 -0
  173. package/dist/types/utils/index.d.ts +3 -1
  174. package/dist/types/utils/index.d.ts.map +1 -1
  175. package/dist/types/utils/lighterAsset.d.ts +7 -1
  176. package/dist/types/utils/lighterAsset.d.ts.map +1 -1
  177. package/dist/types/utils/mapOpenPositions.d.ts +2 -2
  178. package/dist/types/utils/mapOpenPositions.d.ts.map +1 -1
  179. package/dist/types/utils/mapPosition.d.ts +10 -2
  180. package/dist/types/utils/mapPosition.d.ts.map +1 -1
  181. package/dist/types/utils/ohlcvInterval.d.ts +6 -1
  182. package/dist/types/utils/ohlcvInterval.d.ts.map +1 -1
  183. package/dist/types/utils/transferMargin.d.ts +11 -0
  184. package/dist/types/utils/transferMargin.d.ts.map +1 -0
  185. package/dist/types/utils/wireEncoding.d.ts +58 -23
  186. package/dist/types/utils/wireEncoding.d.ts.map +1 -1
  187. package/dist/types/websocket/LighterWsProvider.d.ts +6 -0
  188. package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
  189. package/package.json +3 -3
  190. package/src/LighterProvider.ts +75 -9
  191. package/src/accountSummary.ts +38 -6
  192. package/src/constants.ts +15 -2
  193. package/src/signers/LighterKeyStore.ts +31 -2
  194. package/src/signers/LighterReadOnlyTokenManager.ts +23 -3
  195. package/src/signers/LighterSigner.ts +49 -7
  196. package/src/signers/createAuthToken.ts +8 -2
  197. package/src/signers/wasmLoader.ts +14 -2
  198. package/src/types/account.ts +61 -8
  199. package/src/types/action.ts +72 -18
  200. package/src/types/activity.ts +64 -10
  201. package/src/types/asset.ts +72 -12
  202. package/src/types/fill.ts +14 -2
  203. package/src/types/order.ts +27 -4
  204. package/src/types/submit.ts +26 -4
  205. package/src/types/ws.ts +65 -9
  206. package/src/utils/decimal.ts +52 -0
  207. package/src/utils/index.ts +3 -1
  208. package/src/utils/lighterAsset.ts +7 -1
  209. package/src/utils/mapOpenPositions.ts +2 -2
  210. package/src/utils/mapPosition.ts +40 -14
  211. package/src/utils/ohlcvInterval.ts +6 -1
  212. package/src/utils/transferMargin.ts +31 -0
  213. package/src/utils/wireEncoding.ts +58 -23
  214. package/src/websocket/LighterWsProvider.ts +47 -11
@@ -1,13 +1,24 @@
1
1
  // Lighter submit/execute-path wire shapes (sendTx, fastwithdraw, changeAccountTier).
2
2
 
3
- /** @public */
3
+ /**
4
+ * Request body for Lighter's `/api/v1/sendTx` endpoint. `tx_type` and
5
+ * `tx_info` are the signed WASM transaction envelope; `price_protection` is an
6
+ * optional venue-side execution guard.
7
+ *
8
+ * @public
9
+ */
4
10
  export interface LtSendTxRequest {
5
11
  tx_type: number
6
12
  tx_info: string
7
13
  price_protection?: boolean
8
14
  }
9
15
 
10
- /** @public */
16
+ /**
17
+ * Response from Lighter's `/api/v1/sendTx` endpoint. `tx_hash` identifies the
18
+ * submitted transaction; timing and quota fields are optional venue estimates.
19
+ *
20
+ * @public
21
+ */
11
22
  export interface LtSendTxResponse {
12
23
  code: number
13
24
  message: string
@@ -16,13 +27,24 @@ export interface LtSendTxResponse {
16
27
  volume_quota_remaining?: number
17
28
  }
18
29
 
19
- /** @public */
30
+ /**
31
+ * Batch request body for Lighter's transaction submission endpoint.
32
+ * `tx_types` is a JSON-encoded number array and `tx_infos` a JSON-encoded
33
+ * string array; corresponding entries are submitted in array order.
34
+ *
35
+ * @public
36
+ */
20
37
  export interface LtSendTxBatchRequest {
21
38
  tx_types: string // JSON-encoded number[]
22
39
  tx_infos: string // JSON-encoded string[]
23
40
  }
24
41
 
25
- /** @public */
42
+ /**
43
+ * Batch submission response from Lighter. Each `tx_hash` corresponds to the
44
+ * transaction at the same index in the submitted batch.
45
+ *
46
+ * @public
47
+ */
26
48
  export interface LtSendTxBatchResponse {
27
49
  code: number
28
50
  message: string
package/src/types/ws.ts CHANGED
@@ -10,16 +10,33 @@
10
10
  // - spot_market_stats/{id} → context for one spot market
11
11
  // - order_book/{market_id} → bids/asks per market (stateful with deltas)
12
12
 
13
- /** @public */
13
+ /**
14
+ * Common envelope fields on every Lighter WebSocket frame.
15
+ *
16
+ * `channel` identifies the subscribed stream when present; `type` is the
17
+ * subscription, update, or control-frame discriminator.
18
+ *
19
+ * @public
20
+ */
14
21
  export type LtWsMessage = {
15
22
  channel?: string
16
23
  type: string
17
24
  }
18
25
 
19
- /** @public */
26
+ /**
27
+ * Lighter WebSocket keepalive frame.
28
+ *
29
+ * @public
30
+ */
20
31
  export type LtWsPingMessage = LtWsMessage & { type: 'ping' }
21
32
 
22
- /** @public */
33
+ /**
34
+ * Perpetual-market statistics pushed by Lighter's `market_stats` channels.
35
+ * Price, interest, volume, and funding values are decimal strings; the funding
36
+ * timestamp is a Unix timestamp in milliseconds.
37
+ *
38
+ * @public
39
+ */
23
40
  export type LtWsMarketStats = {
24
41
  market_id: number
25
42
  index_price: string
@@ -75,20 +92,34 @@ export type LtWsSpotMarketStatsAllMessage = LtWsMessage & {
75
92
  spot_market_stats?: LtWsSpotMarketStats | Record<string, LtWsSpotMarketStats>
76
93
  }
77
94
 
78
- /** @public */
95
+ /**
96
+ * One bid or ask level in a Lighter order-book snapshot or delta. Price and
97
+ * size are decimal strings in the market's native precision.
98
+ *
99
+ * @public
100
+ */
79
101
  export type LtWsOrderBookLevel = {
80
102
  price: string
81
103
  size: string
82
104
  }
83
105
 
84
- /** @public */
106
+ /**
107
+ * Lighter order-book state containing ask and bid levels. `offset`, when
108
+ * present, identifies the sequence offset used for stateful delta updates.
109
+ *
110
+ * @public
111
+ */
85
112
  export type LtWsOrderBook = {
86
113
  asks: LtWsOrderBookLevel[]
87
114
  bids: LtWsOrderBookLevel[]
88
115
  offset?: number
89
116
  }
90
117
 
91
- /** @public */
118
+ /**
119
+ * Subscription or update frame carrying a Lighter order-book snapshot/delta.
120
+ *
121
+ * @public
122
+ */
92
123
  export type LtWsOrderBookMessage = LtWsMessage & {
93
124
  type: 'subscribed/order_book' | 'update/order_book'
94
125
  order_book: LtWsOrderBook
@@ -109,7 +140,11 @@ export type LtWsTrade = {
109
140
  timestamp: number
110
141
  }
111
142
 
112
- /** @public */
143
+ /**
144
+ * Subscription or update frame carrying public trades from a Lighter market.
145
+ *
146
+ * @public
147
+ */
113
148
  export type LtWsTradeMessage = LtWsMessage & {
114
149
  type: 'subscribed/trade' | 'update/trade'
115
150
  trades?: LtWsTrade[]
@@ -138,7 +173,13 @@ export type LtWsAccountAllOrdersMessage = LtWsMessage & {
138
173
  data?: { orders?: Record<string, unknown[]> | unknown[] }
139
174
  }
140
175
 
141
- /** @public */
176
+ /**
177
+ * Authenticated account trade-history frame. Trade arrays may be flat or
178
+ * market-indexed depending on whether the frame is an initial snapshot or
179
+ * update.
180
+ *
181
+ * @public
182
+ */
142
183
  export type LtWsAccountAllTradesMessage = LtWsMessage & {
143
184
  type: 'subscribed/account_all_trades' | 'update/account_all_trades'
144
185
  trades?: Record<string, unknown[]> | unknown[]
@@ -150,7 +191,10 @@ export type LtWsAccountAllTradesMessage = LtWsMessage & {
150
191
  }
151
192
 
152
193
  /**
153
- * `user_stats/{account_index}` frame; decimal strings in USD.
194
+ * `user_stats/{account_index}` frame; decimal strings in USD. The top-level
195
+ * figures span the whole account (`available_balance` = total withdrawable,
196
+ * including isolated positions' excess margin); `cross_stats` scopes them to
197
+ * the cross pool, whose `available_balance` is the free tradable collateral.
154
198
  * @public
155
199
  */
156
200
  export type LtWsUserStatsMessage = LtWsMessage & {
@@ -158,9 +202,21 @@ export type LtWsUserStatsMessage = LtWsMessage & {
158
202
  collateral?: string
159
203
  portfolio_value?: string
160
204
  available_balance?: string
205
+ cross_stats?: {
206
+ collateral: string
207
+ portfolio_value: string
208
+ available_balance: string
209
+ }
161
210
  }
162
211
  }
163
212
 
213
+ /**
214
+ * Authenticated account-position frame. Position and funding maps are keyed
215
+ * by Lighter market index; the provider normalizes the payload before emitting
216
+ * account updates.
217
+ *
218
+ * @public
219
+ */
164
220
  export type LtWsAccountAllPositionsMessage = LtWsMessage & {
165
221
  type: 'subscribed/account_all_positions' | 'update/account_all_positions'
166
222
  positions?: Record<string, unknown>
@@ -0,0 +1,52 @@
1
+ import { PerpsError } from '@lifi/perps-sdk'
2
+ import { PerpsErrorCode } from '@lifi/perps-types'
3
+ import Big from 'big.js'
4
+
5
+ /**
6
+ * Exact-decimal parse of a venue decimal string; null when absent or
7
+ * unparsable, so callers can gate on presence before deriving.
8
+ * @public
9
+ */
10
+ export const toBigOrNull = (value: string | undefined): Big | null => {
11
+ if (value === undefined) {
12
+ return null
13
+ }
14
+ try {
15
+ return new Big(value)
16
+ } catch {
17
+ return null
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Parse a required venue decimal and identify the bad field on failure.
23
+ * @public
24
+ */
25
+ export const toRequiredBig = (
26
+ value: string | undefined,
27
+ field: string
28
+ ): Big => {
29
+ const parsed = toBigOrNull(value)
30
+ if (parsed !== null) {
31
+ return parsed
32
+ }
33
+ throw new PerpsError(
34
+ PerpsErrorCode.SDKError,
35
+ `Lighter field \`${field}\` is not a valid decimal.`
36
+ )
37
+ }
38
+
39
+ /** Parse a required venue decimal that must be greater than zero. */
40
+ export const toPositiveRequiredBig = (
41
+ value: string | undefined,
42
+ field: string
43
+ ): Big => {
44
+ const parsed = toRequiredBig(value, field)
45
+ if (parsed.lte(0)) {
46
+ throw new PerpsError(
47
+ PerpsErrorCode.SDKError,
48
+ `Lighter field \`${field}\` must be greater than zero.`
49
+ )
50
+ }
51
+ return parsed
52
+ }
@@ -9,6 +9,7 @@ export {
9
9
  } from './activityCursor.js'
10
10
  export { LighterApiClient } from './apiClient.js'
11
11
  export { assetMarginModeInt, isAssetMarginEnabled } from './assetCollateral.js'
12
+ export { toBigOrNull, toRequiredBig } from './decimal.js'
12
13
  export { fetchDetailedAccount } from './fetchDetailedAccount.js'
13
14
  export { lighterAsset } from './lighterAsset.js'
14
15
  export { estimateLiquidationPrice } from './liquidation.js'
@@ -24,10 +25,11 @@ export {
24
25
  mapStatusReason,
25
26
  mapTriggerOrder,
26
27
  } from './mapOrder.js'
27
- export { mapPosition } from './mapPosition.js'
28
+ export { leverageFromImf, mapPosition } from './mapPosition.js'
28
29
  export { mapInterval } from './ohlcvInterval.js'
29
30
  export { formatOrderPrice, formatOrderSize } from './orderFormatting.js'
30
31
  export { toIsoFromMs, toIsoFromSeconds } from './time.js'
32
+ export { positionMarginConstraints } from './transferMargin.js'
31
33
  export {
32
34
  leverageToFraction,
33
35
  mapOrderTypeToInt,
@@ -1,7 +1,13 @@
1
1
  import type { Asset } from '@lifi/perps-types'
2
2
  import { LIGHTER_PROVIDER_KEY } from '../constants.js'
3
3
 
4
- /** @public */
4
+ /**
5
+ * Create the SDK asset descriptor for a Lighter asset identifier and display
6
+ * symbol. Lighter assets do not provide a package-level logo URI, so this
7
+ * helper leaves that field empty.
8
+ *
9
+ * @public
10
+ */
5
11
  export const lighterAsset = (id: string, displaySymbol: string): Asset => ({
6
12
  providerId: LIGHTER_PROVIDER_KEY,
7
13
  id,
@@ -1,4 +1,4 @@
1
- import type { MarketDisplay, Position } from '@lifi/perps-types'
1
+ import type { PerpsMarketDisplay, Position } from '@lifi/perps-types'
2
2
  import type { LtAccountPosition } from '../types/index.js'
3
3
  import { mapPosition } from './mapPosition.js'
4
4
 
@@ -11,7 +11,7 @@ import { mapPosition } from './mapPosition.js'
11
11
  */
12
12
  export const mapOpenPositions = (
13
13
  positions: LtAccountPosition[],
14
- resolveMarket: (marketId: number) => MarketDisplay
14
+ resolveMarket: (marketId: number) => PerpsMarketDisplay
15
15
  ): Position[] =>
16
16
  positions
17
17
  .filter((p) => Number.parseFloat(p.position) !== 0)
@@ -1,7 +1,29 @@
1
- import type { MarketDisplay, Position } from '@lifi/perps-types'
1
+ import type { PerpsMarketDisplay, Position } from '@lifi/perps-types'
2
2
  import { MarginMode, PositionSide } from '@lifi/perps-types'
3
+ import Big from 'big.js'
3
4
  import type { LtAccountPosition } from '../types/index.js'
4
5
  import { LT_MARGIN_MODE_ISOLATED } from '../types/index.js'
6
+ import { toPositiveRequiredBig, toRequiredBig } from './decimal.js'
7
+
8
+ /**
9
+ * Display leverage from an IMF percent string: `100 / IMF` in exact decimal
10
+ * arithmetic before conversion to `number`. This value is for displaying the
11
+ * venue setting; provider risk calculations consume the original decimal IMF
12
+ * instead. `undefined` for a non-positive or unparsable IMF.
13
+ * @public
14
+ */
15
+ export const leverageFromImf = (imf: string): number | undefined => {
16
+ let parsed: Big
17
+ try {
18
+ parsed = new Big(imf)
19
+ } catch {
20
+ return undefined
21
+ }
22
+ if (parsed.lte(0)) {
23
+ return undefined
24
+ }
25
+ return new Big(100).div(parsed).toNumber()
26
+ }
5
27
 
6
28
  /**
7
29
  * Map a raw Lighter account position to the generic Position type.
@@ -10,33 +32,37 @@ import { LT_MARGIN_MODE_ISOLATED } from '../types/index.js'
10
32
  */
11
33
  export const mapPosition = (
12
34
  pos: LtAccountPosition,
13
- market: MarketDisplay
35
+ market: PerpsMarketDisplay
14
36
  ): Position => {
15
- const size = parseFloat(pos.position)
37
+ const size = toRequiredBig(pos.position, 'position')
16
38
  const isIsolated = pos.margin_mode === LT_MARGIN_MODE_ISOLATED
17
-
18
- // `allocated_margin` is only populated for isolated positions (always "0"
19
- // on cross accounts). For cross, derive margin as
20
- // `position_value × initial_margin_fraction / 100` (IMF is in percent).
21
- const positionValue = Math.abs(parseFloat(pos.position_value))
22
- const imf = parseFloat(pos.initial_margin_fraction)
39
+ const positionValue = toRequiredBig(
40
+ pos.position_value,
41
+ 'position_value'
42
+ ).abs()
43
+ const imf = toPositiveRequiredBig(
44
+ pos.initial_margin_fraction,
45
+ 'initial_margin_fraction'
46
+ )
47
+ const initialMarginRequirement = positionValue.times(imf).div(100)
23
48
  const marginUsed = isIsolated
24
49
  ? pos.allocated_margin
25
- : ((positionValue * imf) / 100).toString()
50
+ : initialMarginRequirement.toFixed()
26
51
 
27
52
  return {
28
53
  market,
29
54
  side: pos.sign >= 0 ? PositionSide.LONG : PositionSide.SHORT,
30
- size: Math.abs(size).toString(),
55
+ size: size.abs().toFixed(),
31
56
  entryPrice: pos.avg_entry_price,
32
57
  markPrice:
33
- pos.position_value === '0' || size === 0
58
+ positionValue.eq(0) || size.eq(0)
34
59
  ? '0'
35
- : (parseFloat(pos.position_value) / Math.abs(size)).toString(),
60
+ : positionValue.div(size.abs()).toFixed(),
36
61
  liquidationPrice: pos.liquidation_price,
37
62
  unrealizedPnl: pos.unrealized_pnl,
38
- leverage: imf > 0 ? Math.round(100 / imf) : 1,
63
+ leverage: leverageFromImf(pos.initial_margin_fraction) ?? 1,
39
64
  marginUsed,
65
+ initialMarginRequirement: initialMarginRequirement.toFixed(),
40
66
  marginMode: isIsolated ? MarginMode.ISOLATED : MarginMode.CROSS,
41
67
  }
42
68
  }
@@ -20,7 +20,12 @@ const LIGHTER_SUPPORTED_INTERVALS: Record<string, string> = {
20
20
  '1w': '1w',
21
21
  }
22
22
 
23
- /** @public */
23
+ /**
24
+ * Validate and map an SDK OHLCV interval to Lighter's resolution string.
25
+ * Unsupported intervals raise a validation error instead of being rounded.
26
+ *
27
+ * @public
28
+ */
24
29
  export const mapInterval = (interval: string): string => {
25
30
  const resolution = LIGHTER_SUPPORTED_INTERVALS[interval]
26
31
  if (!resolution) {
@@ -0,0 +1,31 @@
1
+ import {
2
+ type Position,
3
+ type PositionMarginConstraints,
4
+ positionSupportsMarginAdjustment,
5
+ } from '@lifi/perps-types'
6
+ import { toPositiveRequiredBig } from './decimal.js'
7
+
8
+ const AMOUNT_INCREMENT = '0.000001'
9
+
10
+ /**
11
+ * Lighter's exact isolated-margin requirement. Lighter publishes no separate
12
+ * notional floor, so the provider-normalized initial-margin requirement is
13
+ * the retained amount.
14
+ *
15
+ * @see https://docs.lighter.xyz/trading/liquidations-and-llp-insurance-fund
16
+ * @public
17
+ */
18
+ export function positionMarginConstraints(
19
+ position: Position
20
+ ): PositionMarginConstraints | undefined {
21
+ if (!positionSupportsMarginAdjustment(position)) {
22
+ return undefined
23
+ }
24
+ return {
25
+ minimumMarginRequirement: toPositiveRequiredBig(
26
+ position.initialMarginRequirement,
27
+ 'initialMarginRequirement'
28
+ ).toFixed(),
29
+ amountIncrement: AMOUNT_INCREMENT,
30
+ }
31
+ }
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Lighter wire encoding for the WASM signer: scaled-integer decimal values,
3
- * basis-point margin fractions, and order-type/TIF/expiry resolution
4
- * mirroring the lighter-go signer's rules.
2
+ * Lighter wire encoding for the WASM signer: basis-point margin fractions and
3
+ * order-type/time-in-force/expiry resolution mirroring lighter-go's rules.
5
4
  */
6
5
 
7
6
  import { PerpsError } from '@lifi/perps-sdk'
@@ -20,8 +19,15 @@ import {
20
19
  } from '../types/action.js'
21
20
 
22
21
  /**
23
- * Lighter uses basis-points (1/10000) for margin fractions.
24
- * Convert to a decimal multiplier: 1000 → 0.1 (10x leverage).
22
+ * Convert Lighter's initial-margin fraction to the largest whole-number
23
+ * leverage exposed by the provider.
24
+ *
25
+ * Lighter expresses margin fractions in basis points (`10_000` = 100%), so
26
+ * the conversion is `floor(10_000 / fraction)`. Non-finite or non-positive
27
+ * input uses the safe `1x` fallback.
28
+ *
29
+ * @param fraction - Lighter's basis-point initial-margin fraction.
30
+ * @returns The floored maximum leverage, or `1` for invalid input.
25
31
  * @public
26
32
  */
27
33
  export const marginFractionToMaxLeverage = (fraction: number): number => {
@@ -32,7 +38,15 @@ export const marginFractionToMaxLeverage = (fraction: number): number => {
32
38
  }
33
39
 
34
40
  /**
35
- * Convert leverage to margin fraction in basis points for WASM signer.
41
+ * Convert requested leverage to Lighter's basis-point margin fraction.
42
+ *
43
+ * The signer expects `round(10_000 / leverage)`, where the result is the
44
+ * margin requirement in basis points.
45
+ *
46
+ * @param leverage - Requested leverage multiplier.
47
+ * @returns The rounded basis-point margin fraction.
48
+ * @throws {PerpsError} With `ValidationError` when leverage is less than
49
+ * or equal to zero.
36
50
  * @public
37
51
  */
38
52
  export const leverageToFraction = (leverage: number): number => {
@@ -46,7 +60,14 @@ export const leverageToFraction = (leverage: number): number => {
46
60
  }
47
61
 
48
62
  /**
49
- * Map our OrderType enum to Lighter's integer order type.
63
+ * Map a LI.FI `OrderType` value to Lighter's integer wire type.
64
+ *
65
+ * Supported mappings are LIMIT → 0, MARKET → 1, STOP_MARKET → 2,
66
+ * STOP_LIMIT → 3, TAKE_PROFIT_MARKET → 4, and TAKE_PROFIT_LIMIT → 5.
67
+ * Omitted or unknown values fall back to LIMIT.
68
+ *
69
+ * @param type - LI.FI order type value.
70
+ * @returns The corresponding Lighter order-type integer.
50
71
  * @public
51
72
  */
52
73
  export const mapOrderTypeToInt = (type?: string): number => {
@@ -62,13 +83,18 @@ export const mapOrderTypeToInt = (type?: string): number => {
62
83
  }
63
84
 
64
85
  /**
65
- * Lighter pairs each `order_type` with a permitted set of `time_in_force`
66
- * values: market-style orders (MARKET, STOP_MARKET, TAKE_PROFIT_MARKET) are
67
- * IOC by definition; limit-style orders may carry any TIF. The Go WASM
68
- * signer rejects mismatches with `OrderTimeInForce is not valid`. Resolve
69
- * the caller's optional `timeInForce` against the order type so unspecified
70
- * values land on the correct default and explicit values are validated up
71
- * front.
86
+ * Resolve a Lighter `time_in_force` value that is valid for an order type.
87
+ *
88
+ * Market-style orders (MARKET, STOP_MARKET, TAKE_PROFIT_MARKET) are IOC-only.
89
+ * Limit-style orders default to GTC when no TIF is supplied. An explicit
90
+ * non-IOC TIF on a market-style order raises a validation error. For
91
+ * limit-style orders, unknown TIF strings use the GTC fallback from
92
+ * {@link mapTimeInForceToInt}.
93
+ *
94
+ * @param orderTypeInt - Lighter order-type integer.
95
+ * @param tif - Optional LI.FI time-in-force value.
96
+ * @returns The validated Lighter time-in-force integer.
97
+ * @throws {PerpsError} With `ValidationError` for a market-style/TIF mismatch.
72
98
  * @public
73
99
  */
74
100
  export const resolveTimeInForce = (
@@ -93,7 +119,13 @@ export const resolveTimeInForce = (
93
119
  }
94
120
 
95
121
  /**
96
- * Map our TimeInForce enum to Lighter's integer time-in-force.
122
+ * Map a LI.FI time-in-force value to Lighter's integer wire value.
123
+ *
124
+ * IOC maps to 0, GTC maps to 1, and POST_ONLY maps to 2. Omitted or unknown
125
+ * values fall back to GTC.
126
+ *
127
+ * @param tif - LI.FI time-in-force value.
128
+ * @returns The corresponding Lighter time-in-force integer.
97
129
  * @public
98
130
  */
99
131
  export const mapTimeInForceToInt = (tif?: string): number => {
@@ -106,14 +138,17 @@ export const mapTimeInForceToInt = (tif?: string): number => {
106
138
  }
107
139
 
108
140
  /**
109
- * Pick the `order_expiry` value Lighter expects for a given TIF on a
110
- * regular limit order. `lighter-go/types/txtypes/create_order.go:121-128`
111
- * enforces:
112
- * - IOC limit orders → `OrderExpiry == NilOrderExpiry (0)`
113
- * - non-IOC limit orders → `OrderExpiry != NilOrderExpiry`
114
- * For non-IOC we pass the `-1` sentinel, which the WASM signer rewrites
115
- * to `now + 28d` (absolute ms). Trigger orders take a different rule —
116
- * see {@link LT_DEFAULT_ORDER_EXPIRY} call sites.
141
+ * Select the `order_expiry` sentinel required for a regular Lighter limit
142
+ * order from its resolved time-in-force.
143
+ *
144
+ * IOC uses Lighter's nil expiry (`0`). Every other TIF uses
145
+ * `LT_DEFAULT_ORDER_EXPIRY` (`-1`), which the WASM signer expands to its
146
+ * canonical future expiry (`now + 28d`) as an absolute Unix-millisecond
147
+ * timestamp. Trigger-order expiry handling is provider-specific; see
148
+ * {@link LT_DEFAULT_ORDER_EXPIRY} and its call sites.
149
+ *
150
+ * @param tifInt - Lighter time-in-force integer.
151
+ * @returns `0` for IOC, otherwise the default-expiry sentinel.
117
152
  * @public
118
153
  */
119
154
  export const orderExpiryForTif = (tifInt: number): number => {
@@ -9,6 +9,7 @@ import {
9
9
  ReconnectingWebSocket,
10
10
  resolveRetryPolicy,
11
11
  resolveSubscribeQuote,
12
+ toPerpsMarketDisplay,
12
13
  WsProviderBase,
13
14
  type WsProviderFactory,
14
15
  wsLog,
@@ -51,6 +52,7 @@ import {
51
52
  mapFill,
52
53
  mapMarketContext,
53
54
  mapPosition,
55
+ toRequiredBig,
54
56
  } from '../utils/index.js'
55
57
 
56
58
  // Public channels: `marketsContext` (market_stats/all + spot_market_stats/all),
@@ -225,6 +227,12 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
225
227
  return undefined
226
228
  }
227
229
 
230
+ /**
231
+ * Subscribe to a live quote stream resolved through the registered Lighter
232
+ * market registry. Returns an unsubscribe function; quote subscription
233
+ * requires the provider to be constructed through `lighterWsProvider` with a
234
+ * bound {@link PerpsSDKClient}.
235
+ */
228
236
  async subscribeQuote(
229
237
  params: ProviderGetQuoteParams,
230
238
  onQuote: QuoteListener
@@ -588,23 +596,51 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
588
596
  return
589
597
  }
590
598
  const stats = msg.stats
591
- const portfolio = Number.parseFloat(stats?.portfolio_value ?? '')
592
- const available = Number.parseFloat(stats?.available_balance ?? '')
593
- if (!Number.isFinite(portfolio) || !Number.isFinite(available)) {
599
+ if (stats === undefined) {
594
600
  return
595
601
  }
596
- // Identities of the REST 'net' semantics: `available_balance` nets locked
597
- // margin out with PnL marked in, and `collateral` is gross, PnL-exclusive.
598
- const collateral = Number.parseFloat(stats?.collateral ?? '')
602
+
603
+ const portfolio = toRequiredBig(
604
+ stats.portfolio_value,
605
+ 'stats.portfolio_value'
606
+ )
607
+ const collateral = toRequiredBig(stats.collateral, 'stats.collateral')
608
+
609
+ let available: Big
610
+ let marginUsed: Big
611
+ if (stats.cross_stats === undefined) {
612
+ // Compatibility with legacy gateways that omitted the entire
613
+ // cross_stats object.
614
+ available = toRequiredBig(
615
+ stats.available_balance,
616
+ 'stats.available_balance'
617
+ )
618
+ marginUsed = portfolio.minus(available)
619
+ } else {
620
+ const crossCollateral = toRequiredBig(
621
+ stats.cross_stats.collateral,
622
+ 'stats.cross_stats.collateral'
623
+ )
624
+ const crossPortfolio = toRequiredBig(
625
+ stats.cross_stats.portfolio_value,
626
+ 'stats.cross_stats.portfolio_value'
627
+ )
628
+ available = toRequiredBig(
629
+ stats.cross_stats.available_balance,
630
+ 'stats.cross_stats.available_balance'
631
+ )
632
+ const isolatedMargin = collateral.minus(crossCollateral)
633
+ const crossMargin = crossPortfolio.minus(available)
634
+ marginUsed = isolatedMargin.plus(crossMargin)
635
+ }
636
+
599
637
  this.emit(`accountSummary:${address}`, {
600
638
  channel: 'accountSummary',
601
639
  data: {
602
640
  portfolioValue: portfolio.toString(),
603
641
  availableMargin: available.toString(),
604
- marginUsed: Math.max(portfolio - available, 0).toString(),
605
- unrealizedPnl: Number.isFinite(collateral)
606
- ? (portfolio - collateral).toString()
607
- : '0',
642
+ marginUsed: marginUsed.toString(),
643
+ unrealizedPnl: portfolio.minus(collateral).toString(),
608
644
  },
609
645
  })
610
646
  }
@@ -649,7 +685,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
649
685
  } else {
650
686
  const market = this.registry?.get(String(p.market_id))
651
687
  if (market) {
652
- state.set(p.market_id, mapPosition(p, market))
688
+ state.set(p.market_id, mapPosition(p, toPerpsMarketDisplay(market)))
653
689
  }
654
690
  }
655
691
  }