@lifi/perps-sdk-provider-lighter 20.0.0 → 22.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.
- package/dist/cjs/LighterProvider.d.ts.map +1 -1
- package/dist/cjs/LighterProvider.js +70 -24
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/constants.d.ts +1 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +2 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
- package/dist/cjs/signers/LighterReadOnlyTokenManager.js +1 -0
- package/dist/cjs/signers/LighterReadOnlyTokenManager.js.map +1 -1
- package/dist/cjs/signers/signActions.d.ts.map +1 -1
- package/dist/cjs/signers/signActions.js +13 -5
- package/dist/cjs/signers/signActions.js.map +1 -1
- package/dist/cjs/types/account.d.ts +8 -3
- package/dist/cjs/types/account.d.ts.map +1 -1
- package/dist/cjs/types/activity.d.ts +7 -6
- package/dist/cjs/types/activity.d.ts.map +1 -1
- package/dist/cjs/types/fill.d.ts +3 -1
- package/dist/cjs/types/fill.d.ts.map +1 -1
- package/dist/cjs/types/order.d.ts +6 -1
- package/dist/cjs/types/order.d.ts.map +1 -1
- package/dist/cjs/utils/apiClient.d.ts +2 -1
- package/dist/cjs/utils/apiClient.d.ts.map +1 -1
- package/dist/cjs/utils/apiClient.js +28 -12
- package/dist/cjs/utils/apiClient.js.map +1 -1
- package/dist/cjs/utils/fetchDetailedAccount.d.ts +2 -2
- package/dist/cjs/utils/fetchDetailedAccount.d.ts.map +1 -1
- package/dist/cjs/utils/fetchDetailedAccount.js +9 -3
- package/dist/cjs/utils/fetchDetailedAccount.js.map +1 -1
- package/dist/cjs/utils/lighterErrorCode.d.ts +3 -0
- package/dist/cjs/utils/lighterErrorCode.d.ts.map +1 -0
- package/dist/cjs/utils/lighterErrorCode.js +17 -0
- package/dist/cjs/utils/lighterErrorCode.js.map +1 -0
- package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -1
- package/dist/cjs/utils/ohlcvInterval.js +0 -1
- package/dist/cjs/utils/ohlcvInterval.js.map +1 -1
- package/dist/cjs/utils/wireList.d.ts +2 -0
- package/dist/cjs/utils/wireList.d.ts.map +1 -0
- package/dist/cjs/utils/wireList.js +6 -0
- package/dist/cjs/utils/wireList.js.map +1 -0
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +91 -32
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/constants.d.ts +14 -2
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +14 -2
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
- package/dist/esm/signers/LighterReadOnlyTokenManager.js +3 -0
- package/dist/esm/signers/LighterReadOnlyTokenManager.js.map +1 -1
- package/dist/esm/signers/signActions.d.ts.map +1 -1
- package/dist/esm/signers/signActions.js +22 -8
- package/dist/esm/signers/signActions.js.map +1 -1
- package/dist/esm/types/account.d.ts +15 -3
- package/dist/esm/types/account.d.ts.map +1 -1
- package/dist/esm/types/account.js +1 -0
- package/dist/esm/types/account.js.map +1 -1
- package/dist/esm/types/activity.d.ts +8 -6
- package/dist/esm/types/activity.d.ts.map +1 -1
- package/dist/esm/types/activity.js +1 -0
- package/dist/esm/types/activity.js.map +1 -1
- package/dist/esm/types/fill.d.ts +5 -1
- package/dist/esm/types/fill.d.ts.map +1 -1
- package/dist/esm/types/fill.js +1 -0
- package/dist/esm/types/fill.js.map +1 -1
- package/dist/esm/types/order.d.ts +15 -1
- package/dist/esm/types/order.d.ts.map +1 -1
- package/dist/esm/types/order.js +1 -0
- package/dist/esm/types/order.js.map +1 -1
- package/dist/esm/utils/apiClient.d.ts +23 -15
- package/dist/esm/utils/apiClient.d.ts.map +1 -1
- package/dist/esm/utils/apiClient.js +55 -26
- package/dist/esm/utils/apiClient.js.map +1 -1
- package/dist/esm/utils/fetchDetailedAccount.d.ts +3 -2
- package/dist/esm/utils/fetchDetailedAccount.d.ts.map +1 -1
- package/dist/esm/utils/fetchDetailedAccount.js +10 -3
- package/dist/esm/utils/fetchDetailedAccount.js.map +1 -1
- package/dist/esm/utils/lighterErrorCode.d.ts +7 -0
- package/dist/esm/utils/lighterErrorCode.d.ts.map +1 -0
- package/dist/esm/utils/lighterErrorCode.js +26 -0
- package/dist/esm/utils/lighterErrorCode.js.map +1 -0
- package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -1
- package/dist/esm/utils/ohlcvInterval.js +1 -2
- package/dist/esm/utils/ohlcvInterval.js.map +1 -1
- package/dist/esm/utils/wireList.d.ts +10 -0
- package/dist/esm/utils/wireList.d.ts.map +1 -0
- package/dist/esm/utils/wireList.js +10 -0
- package/dist/esm/utils/wireList.js.map +1 -0
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/constants.d.ts +14 -2
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
- package/dist/types/signers/signActions.d.ts.map +1 -1
- package/dist/types/types/account.d.ts +15 -3
- package/dist/types/types/account.d.ts.map +1 -1
- package/dist/types/types/activity.d.ts +8 -6
- package/dist/types/types/activity.d.ts.map +1 -1
- package/dist/types/types/fill.d.ts +5 -1
- package/dist/types/types/fill.d.ts.map +1 -1
- package/dist/types/types/order.d.ts +15 -1
- package/dist/types/types/order.d.ts.map +1 -1
- package/dist/types/utils/apiClient.d.ts +23 -15
- package/dist/types/utils/apiClient.d.ts.map +1 -1
- package/dist/types/utils/fetchDetailedAccount.d.ts +3 -2
- package/dist/types/utils/fetchDetailedAccount.d.ts.map +1 -1
- package/dist/types/utils/lighterErrorCode.d.ts +7 -0
- package/dist/types/utils/lighterErrorCode.d.ts.map +1 -0
- package/dist/types/utils/ohlcvInterval.d.ts.map +1 -1
- package/dist/types/utils/wireList.d.ts +10 -0
- package/dist/types/utils/wireList.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/LighterProvider.ts +153 -62
- package/src/constants.ts +15 -2
- package/src/index.ts +1 -0
- package/src/signers/LighterReadOnlyTokenManager.ts +3 -0
- package/src/signers/signActions.ts +28 -11
- package/src/types/account.ts +17 -3
- package/src/types/activity.ts +9 -6
- package/src/types/fill.ts +6 -1
- package/src/types/order.ts +17 -1
- package/src/utils/apiClient.ts +68 -25
- package/src/utils/fetchDetailedAccount.ts +13 -6
- package/src/utils/lighterErrorCode.ts +30 -0
- package/src/utils/ohlcvInterval.ts +1 -2
- package/src/utils/wireList.ts +9 -0
package/src/types/activity.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Activity history shapes returned by Lighter's REST API
|
|
2
2
|
// (deposits, withdrawals, funding payments, liquidations, transfers).
|
|
3
|
+
// Lighter serializes an empty list as JSON `null`, so list members are nullable.
|
|
3
4
|
|
|
4
5
|
import type { LtAccountPosition } from './account.js'
|
|
5
6
|
|
|
@@ -27,7 +28,7 @@ export interface LtDepositHistoryItem {
|
|
|
27
28
|
*/
|
|
28
29
|
export interface LtDepositHistoryResponse {
|
|
29
30
|
code: number
|
|
30
|
-
deposits: LtDepositHistoryItem[]
|
|
31
|
+
deposits: LtDepositHistoryItem[] | null
|
|
31
32
|
cursor?: string
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -56,7 +57,7 @@ export interface LtWithdrawHistoryItem {
|
|
|
56
57
|
*/
|
|
57
58
|
export interface LtWithdrawHistoryResponse {
|
|
58
59
|
code: number
|
|
59
|
-
withdraws: LtWithdrawHistoryItem[]
|
|
60
|
+
withdraws: LtWithdrawHistoryItem[] | null
|
|
60
61
|
cursor?: string
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -80,6 +81,8 @@ export interface LtPositionFunding {
|
|
|
80
81
|
rate: string
|
|
81
82
|
position_size: string
|
|
82
83
|
position_side: string
|
|
84
|
+
/** Funding-fee discount Lighter applied to `change`, as a decimal string. */
|
|
85
|
+
discount?: string
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
/**
|
|
@@ -90,7 +93,7 @@ export interface LtPositionFunding {
|
|
|
90
93
|
*/
|
|
91
94
|
export interface LtPositionFundingsResponse {
|
|
92
95
|
code: number
|
|
93
|
-
position_fundings: LtPositionFunding[]
|
|
96
|
+
position_fundings: LtPositionFunding[] | null
|
|
94
97
|
next_cursor?: string
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -142,7 +145,7 @@ export type LtLiquidationPosition = Pick<
|
|
|
142
145
|
* @public
|
|
143
146
|
*/
|
|
144
147
|
export interface LtLiquidationInfo {
|
|
145
|
-
positions: LtLiquidationPosition[]
|
|
148
|
+
positions: LtLiquidationPosition[] | null
|
|
146
149
|
risk_info_before: {
|
|
147
150
|
// Lighter's OpenAPI does not require this member, so an account without
|
|
148
151
|
// cross exposure may omit it.
|
|
@@ -176,7 +179,7 @@ export interface LtLiquidation {
|
|
|
176
179
|
*/
|
|
177
180
|
export interface LtLiquidationsResponse {
|
|
178
181
|
code: number
|
|
179
|
-
liquidations: LtLiquidation[]
|
|
182
|
+
liquidations: LtLiquidation[] | null
|
|
180
183
|
next_cursor?: string
|
|
181
184
|
}
|
|
182
185
|
|
|
@@ -212,6 +215,6 @@ export interface LtTransfer {
|
|
|
212
215
|
*/
|
|
213
216
|
export interface LtTransferHistoryResponse {
|
|
214
217
|
code: number
|
|
215
|
-
transfers: LtTransfer[]
|
|
218
|
+
transfers: LtTransfer[] | null
|
|
216
219
|
cursor?: string
|
|
217
220
|
}
|
package/src/types/fill.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Trade (fill) shapes returned by Lighter's `/api/v1/trades` endpoint.
|
|
2
|
+
// Lighter serializes an empty list as JSON `null`, so list members are nullable.
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Public trade/fill row returned by Lighter's `/api/v1/trades` endpoint.
|
|
@@ -54,6 +55,10 @@ export type LtTrade = {
|
|
|
54
55
|
// older trade rows that predate the field.
|
|
55
56
|
taker_initial_margin_fraction_before?: number
|
|
56
57
|
maker_initial_margin_fraction_before?: number
|
|
58
|
+
/** Version of the resting ask order the trade matched against. */
|
|
59
|
+
ask_order_version?: number
|
|
60
|
+
/** Version of the resting bid order the trade matched against. */
|
|
61
|
+
bid_order_version?: number
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
/**
|
|
@@ -65,5 +70,5 @@ export type LtTrade = {
|
|
|
65
70
|
export interface LtTradesResponse {
|
|
66
71
|
code: number
|
|
67
72
|
next_cursor: string
|
|
68
|
-
trades: LtTrade[]
|
|
73
|
+
trades: LtTrade[] | null
|
|
69
74
|
}
|
package/src/types/order.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Order shapes returned by Lighter's REST API.
|
|
2
|
+
// Lighter serializes an empty list as JSON `null`, so list members are nullable.
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Order payload returned by Lighter's REST API. Amounts and prices are decimal
|
|
@@ -46,6 +47,8 @@ export type LtOrder = {
|
|
|
46
47
|
timestamp: number
|
|
47
48
|
created_at: number
|
|
48
49
|
updated_at: number
|
|
50
|
+
/** Lighter bit flags recording the order's wire options. */
|
|
51
|
+
order_flags?: number
|
|
49
52
|
/**
|
|
50
53
|
* Unix timestamp in microseconds. Lighter documents no unit, and its public
|
|
51
54
|
* trade rows report this member in microseconds while the sibling
|
|
@@ -63,7 +66,20 @@ export type LtOrder = {
|
|
|
63
66
|
export interface LtOrdersResponse {
|
|
64
67
|
code: number
|
|
65
68
|
next_cursor: string
|
|
66
|
-
orders: LtOrder[]
|
|
69
|
+
orders: LtOrder[] | null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Order-lookup response from Lighter's `accountOrders` path. The lookup takes
|
|
74
|
+
* up to 20 `client_order_indexes` rather than a page, so it carries no cursor.
|
|
75
|
+
* It reaches the last 10K active orders with no time bound, and the last 1K
|
|
76
|
+
* inactive orders from the past 24 hours.
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
export interface LtAccountOrdersResponse {
|
|
81
|
+
code: number
|
|
82
|
+
orders: LtOrder[] | null
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
/**
|
package/src/utils/apiClient.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
LIGHTER_SUCCESS_CODES,
|
|
10
10
|
LIGHTER_TOKEN_REVOKED_CODE,
|
|
11
11
|
} from '../constants.js'
|
|
12
|
+
import { lighterErrorCodeFromBody } from './lighterErrorCode.js'
|
|
12
13
|
|
|
13
14
|
/** @internal */
|
|
14
15
|
export type ApiParams = Record<string, string | number | boolean>
|
|
@@ -48,6 +49,28 @@ const isLighterAuthRejection = (status: number, data: unknown): boolean =>
|
|
|
48
49
|
const isLighterTokenRevoked = (data: unknown): boolean =>
|
|
49
50
|
lighterBodyErrorCode(data) === LIGHTER_TOKEN_REVOKED_CODE
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* A CR or LF in a token would split the request when it reaches a
|
|
54
|
+
* `fetchImpl` that does not validate header values itself. A blank token
|
|
55
|
+
* would dispatch an `Authorization` header that carries no credential at
|
|
56
|
+
* all, which Lighter answers as a generic rejection.
|
|
57
|
+
*/
|
|
58
|
+
const assertHeaderSafe = (authToken: string): string => {
|
|
59
|
+
if (authToken.trim().length === 0) {
|
|
60
|
+
throw new PerpsError(
|
|
61
|
+
PerpsErrorCode.ValidationError,
|
|
62
|
+
'Lighter auth token is blank and cannot go in a header'
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
if (/[\r\n]/.test(authToken)) {
|
|
66
|
+
throw new PerpsError(
|
|
67
|
+
PerpsErrorCode.ValidationError,
|
|
68
|
+
'Lighter auth token carries a line break and cannot go in a header'
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
return authToken
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
/** @internal */
|
|
52
75
|
export const LIGHTER_RETRY_DEFAULTS: ResolvedRetryPolicy = {
|
|
53
76
|
enabled: true,
|
|
@@ -115,9 +138,17 @@ export interface LighterApiClientOptions {
|
|
|
115
138
|
*
|
|
116
139
|
* Auth-gated endpoints (accountLimits, accountActiveOrders, deposit/history,
|
|
117
140
|
* withdraw/history, positionFunding, liquidations, transfer/history) take the
|
|
118
|
-
* Lighter read-only token
|
|
119
|
-
*
|
|
120
|
-
*
|
|
141
|
+
* Lighter read-only token in the `Authorization` header, which is the only
|
|
142
|
+
* auth channel Lighter's OpenAPI spec declares. The header makes such a read
|
|
143
|
+
* CORS-preflighted; both Lighter hosts answer the `OPTIONS` probe with
|
|
144
|
+
* `Authorization` in `Access-Control-Allow-Headers` and
|
|
145
|
+
* `Access-Control-Max-Age: 86400`, so a browser sends one preflight per day
|
|
146
|
+
* per origin.
|
|
147
|
+
*
|
|
148
|
+
* The `/api/v1/changeAccountTier` and `/api/v1/referral/use` mutations send
|
|
149
|
+
* the same header with a fresh per-call token. Their `OPTIONS` response is
|
|
150
|
+
* not yet probed against a live host, so a browser may reject the preflight
|
|
151
|
+
* even though the GET paths pass.
|
|
121
152
|
*
|
|
122
153
|
* Lighter signals rate limits through HTTP 429 or HTTP 405. This client never
|
|
123
154
|
* retries such a response. It throws `RateLimitExceeded` and holds all network
|
|
@@ -198,29 +229,24 @@ export class LighterApiClient {
|
|
|
198
229
|
return this.getChecked<T>(path, params)
|
|
199
230
|
}
|
|
200
231
|
|
|
201
|
-
/**
|
|
202
|
-
* Auth-gated GET. The token is appended as the `auth` query parameter (per
|
|
203
|
-
* Lighter's OpenAPI spec); the `Authorization` header is intentionally NOT
|
|
204
|
-
* used — Lighter rejects it.
|
|
205
|
-
*/
|
|
232
|
+
/** Auth-gated GET carrying the Lighter token in the `Authorization` header. */
|
|
206
233
|
async getAuthed<T>(
|
|
207
234
|
path: string,
|
|
208
235
|
authToken: string,
|
|
209
236
|
params: ApiParams = {}
|
|
210
237
|
): Promise<T> {
|
|
211
|
-
const { status, data } = await this.
|
|
212
|
-
|
|
213
|
-
auth: authToken,
|
|
238
|
+
const { status, data } = await this.sendGet<unknown>(path, params, {
|
|
239
|
+
Authorization: assertHeaderSafe(authToken),
|
|
214
240
|
})
|
|
215
241
|
if (isLighterTokenRevoked(data)) {
|
|
216
242
|
throw new LighterTokenRevokedError(
|
|
217
|
-
PerpsErrorCode.
|
|
243
|
+
PerpsErrorCode.Unauthorized,
|
|
218
244
|
`Lighter reports a revoked auth token for ${path}`
|
|
219
245
|
)
|
|
220
246
|
}
|
|
221
247
|
if (isLighterAuthRejection(status, data)) {
|
|
222
248
|
throw new LighterAuthRejectedError(
|
|
223
|
-
PerpsErrorCode.
|
|
249
|
+
PerpsErrorCode.Unauthorized,
|
|
224
250
|
`Lighter rejected the auth token for ${path}`
|
|
225
251
|
)
|
|
226
252
|
}
|
|
@@ -236,12 +262,20 @@ export class LighterApiClient {
|
|
|
236
262
|
async getWithStatus<T>(
|
|
237
263
|
path: string,
|
|
238
264
|
params?: ApiParams
|
|
265
|
+
): Promise<{ status: number; data: T }> {
|
|
266
|
+
return this.sendGet<T>(path, params)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private async sendGet<T>(
|
|
270
|
+
path: string,
|
|
271
|
+
params?: ApiParams,
|
|
272
|
+
headers?: Record<string, string>
|
|
239
273
|
): Promise<{ status: number; data: T }> {
|
|
240
274
|
this.assertRequestAllowed()
|
|
241
275
|
const url = this.buildUrl(path, params)
|
|
242
276
|
const response = await fetchWithRetry(
|
|
243
277
|
url,
|
|
244
|
-
{},
|
|
278
|
+
headers === undefined ? {} : { headers },
|
|
245
279
|
{
|
|
246
280
|
policy: this.policy,
|
|
247
281
|
fetchImpl: this.fetchWithHold,
|
|
@@ -254,13 +288,15 @@ export class LighterApiClient {
|
|
|
254
288
|
}
|
|
255
289
|
|
|
256
290
|
/**
|
|
257
|
-
* Form-encoded POST to a Lighter mutation endpoint
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
291
|
+
* Form-encoded POST to a Lighter mutation endpoint, carrying the Lighter
|
|
292
|
+
* token in the `Authorization` header. Single-shot — never retried, since
|
|
293
|
+
* these are money/state writes whose outcome is unknown on a transport
|
|
294
|
+
* failure. Surfaces the raw `{status, body}` pair so the caller can map
|
|
295
|
+
* Lighter's per-endpoint business-rule `code` to a domain error verbatim.
|
|
261
296
|
*/
|
|
262
297
|
async postForm<T>(
|
|
263
298
|
path: string,
|
|
299
|
+
authToken: string,
|
|
264
300
|
params: ApiParams
|
|
265
301
|
): Promise<{ status: number; data: T }> {
|
|
266
302
|
const body = new URLSearchParams()
|
|
@@ -269,7 +305,10 @@ export class LighterApiClient {
|
|
|
269
305
|
}
|
|
270
306
|
const response = await this.fetchWithHold(`${this.baseUrl}${path}`, {
|
|
271
307
|
method: 'POST',
|
|
272
|
-
headers: {
|
|
308
|
+
headers: {
|
|
309
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
310
|
+
Authorization: assertHeaderSafe(authToken),
|
|
311
|
+
},
|
|
273
312
|
body: body.toString(),
|
|
274
313
|
signal: this.signal,
|
|
275
314
|
})
|
|
@@ -279,27 +318,31 @@ export class LighterApiClient {
|
|
|
279
318
|
}
|
|
280
319
|
|
|
281
320
|
private async getChecked<T>(path: string, params?: ApiParams): Promise<T> {
|
|
282
|
-
const { status, data } = await this.
|
|
321
|
+
const { status, data } = await this.sendGet<unknown>(path, params)
|
|
283
322
|
this.assertOk(path, status, data)
|
|
284
323
|
return data as T
|
|
285
324
|
}
|
|
286
325
|
|
|
287
326
|
/**
|
|
288
327
|
* Post-parse validation shared by every checked read: rejects a non-2xx HTTP
|
|
289
|
-
* status and a 200 body carrying a non-success `code`.
|
|
290
|
-
*
|
|
328
|
+
* status and a 200 body carrying a non-success `code`. A body `code` that
|
|
329
|
+
* names a nonce, margin, or balance rejection sets the matching
|
|
330
|
+
* `PerpsErrorCode`; every other rejection is `ThirdPartyError`. Callers that
|
|
331
|
+
* surface a distinct auth-rejection error must run that check before this one.
|
|
291
332
|
*/
|
|
292
333
|
private assertOk(path: string, status: number, data: unknown): void {
|
|
334
|
+
const errorCode = lighterBodyErrorCode(data)
|
|
335
|
+
const code =
|
|
336
|
+
lighterErrorCodeFromBody(errorCode) ?? PerpsErrorCode.ThirdPartyError
|
|
293
337
|
if (status < 200 || status >= 300) {
|
|
294
338
|
throw new PerpsError(
|
|
295
|
-
|
|
339
|
+
code,
|
|
296
340
|
`Lighter API request failed: ${status} — ${JSON.stringify(data).slice(0, 200)}`
|
|
297
341
|
)
|
|
298
342
|
}
|
|
299
|
-
const errorCode = lighterBodyErrorCode(data)
|
|
300
343
|
if (errorCode !== undefined) {
|
|
301
344
|
throw new PerpsError(
|
|
302
|
-
|
|
345
|
+
code,
|
|
303
346
|
`Lighter API error for ${path}: code ${errorCode} — ${JSON.stringify(data).slice(0, 200)}`
|
|
304
347
|
)
|
|
305
348
|
}
|
|
@@ -2,22 +2,24 @@ import { PerpsError } from '@lifi/perps-sdk'
|
|
|
2
2
|
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
|
-
import type { LtDetailedAccount } from '../types/index.js'
|
|
5
|
+
import type { LtAccount, LtDetailedAccount } from '../types/index.js'
|
|
6
6
|
import type { LighterApiClient } from './apiClient.js'
|
|
7
|
+
import { wireList } from './wireList.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Look up the Lighter account for an L1 address via `/api/v1/account`,
|
|
10
11
|
* discriminating Lighter's account-not-found body code from generic failures.
|
|
12
|
+
* The returned account carries real arrays for `positions` and `assets`.
|
|
11
13
|
*
|
|
12
14
|
* @public
|
|
13
15
|
*/
|
|
14
16
|
export const fetchDetailedAccount = async (
|
|
15
17
|
client: LighterApiClient,
|
|
16
18
|
address: Address
|
|
17
|
-
): Promise<
|
|
19
|
+
): Promise<LtAccount> => {
|
|
18
20
|
const { status, data } = await client.getWithStatus<{
|
|
19
21
|
code: number
|
|
20
|
-
accounts?: LtDetailedAccount[]
|
|
22
|
+
accounts?: LtDetailedAccount[] | null
|
|
21
23
|
message?: string
|
|
22
24
|
}>('/api/v1/account', { by: 'l1_address', value: address })
|
|
23
25
|
|
|
@@ -35,12 +37,17 @@ export const fetchDetailedAccount = async (
|
|
|
35
37
|
)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const accounts = data?.accounts
|
|
39
|
-
|
|
40
|
+
const accounts = wireList(data?.accounts)
|
|
41
|
+
const account = accounts[0]
|
|
42
|
+
if (account === undefined) {
|
|
40
43
|
throw new PerpsError(
|
|
41
44
|
PerpsErrorCode.AccountNotFound,
|
|
42
45
|
`No Lighter account found for address: ${address}`
|
|
43
46
|
)
|
|
44
47
|
}
|
|
45
|
-
return
|
|
48
|
+
return {
|
|
49
|
+
...account,
|
|
50
|
+
positions: wireList(account.positions),
|
|
51
|
+
assets: wireList(account.assets),
|
|
52
|
+
}
|
|
46
53
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
2
|
+
|
|
3
|
+
/** Lighter body `code` values that name a rejection a `PerpsErrorCode` describes. */
|
|
4
|
+
const LIGHTER_BODY_ERROR_CODES: ReadonlyMap<number, PerpsErrorCode> = new Map([
|
|
5
|
+
// invalid nonce
|
|
6
|
+
[21104, PerpsErrorCode.InvalidNonce],
|
|
7
|
+
// batch transaction nonce is not increasing
|
|
8
|
+
[21105, PerpsErrorCode.InvalidNonce],
|
|
9
|
+
// not enough collateral
|
|
10
|
+
[21301, PerpsErrorCode.InsufficientBalance],
|
|
11
|
+
// not enough asset balance
|
|
12
|
+
[21304, PerpsErrorCode.InsufficientBalance],
|
|
13
|
+
// not enough asset balance for fee
|
|
14
|
+
[21305, PerpsErrorCode.InsufficientBalance],
|
|
15
|
+
// account is below maintenance margin
|
|
16
|
+
[21507, PerpsErrorCode.InsufficientMargin],
|
|
17
|
+
// account is below initial margin
|
|
18
|
+
[21508, PerpsErrorCode.InsufficientMargin],
|
|
19
|
+
// not enough margin to create the order
|
|
20
|
+
[21739, PerpsErrorCode.InsufficientMargin],
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Maps a Lighter body error `code` to the `PerpsErrorCode` it names. Returns
|
|
25
|
+
* `undefined` for every other code so the status-driven layer decides.
|
|
26
|
+
*/
|
|
27
|
+
export const lighterErrorCodeFromBody = (
|
|
28
|
+
code: number | undefined
|
|
29
|
+
): PerpsErrorCode | undefined =>
|
|
30
|
+
code === undefined ? undefined : LIGHTER_BODY_ERROR_CODES.get(code)
|
|
@@ -6,7 +6,7 @@ import { PerpsErrorCode } from '@lifi/perps-types'
|
|
|
6
6
|
* Map our `OhlcvInterval` literal to Lighter's `resolution` enum.
|
|
7
7
|
*
|
|
8
8
|
* Lighter exposes only the timeframes listed below. SDK intervals without a
|
|
9
|
-
* direct match (3m, 2h, 8h, 3d, 1M) raise a validation error rather than
|
|
9
|
+
* direct match (3m, 2h, 8h, 3d, 1w, 1M) raise a validation error rather than
|
|
10
10
|
* silently rounding — the caller picks a supported timeframe.
|
|
11
11
|
*/
|
|
12
12
|
const LIGHTER_SUPPORTED_INTERVALS: Partial<Record<OhlcvInterval, string>> = {
|
|
@@ -18,7 +18,6 @@ const LIGHTER_SUPPORTED_INTERVALS: Partial<Record<OhlcvInterval, string>> = {
|
|
|
18
18
|
'4h': '4h',
|
|
19
19
|
'12h': '12h',
|
|
20
20
|
'1d': '1d',
|
|
21
|
-
'1w': '1w',
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a Lighter wire list member. Lighter serializes an empty list as JSON
|
|
3
|
+
* `null` rather than `[]`, so every list the SDK reads off a REST body passes
|
|
4
|
+
* through this at the response boundary and every mapper downstream works on a
|
|
5
|
+
* real array.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export const wireList = <T>(value: T[] | null | undefined): T[] => value ?? []
|