@lifi/perps-sdk-provider-lighter 21.0.0 → 22.1.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 (111) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +53 -26
  3. package/dist/cjs/LighterProvider.js.map +1 -1
  4. package/dist/cjs/accountConfig.js +3 -4
  5. package/dist/cjs/accountConfig.js.map +1 -1
  6. package/dist/cjs/constants.d.ts +3 -1
  7. package/dist/cjs/constants.d.ts.map +1 -1
  8. package/dist/cjs/constants.js +4 -1
  9. package/dist/cjs/constants.js.map +1 -1
  10. package/dist/cjs/index.d.ts +1 -1
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +2 -1
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/signers/signActions.d.ts.map +1 -1
  15. package/dist/cjs/signers/signActions.js +13 -5
  16. package/dist/cjs/signers/signActions.js.map +1 -1
  17. package/dist/cjs/types/account.d.ts +3 -3
  18. package/dist/cjs/types/account.d.ts.map +1 -1
  19. package/dist/cjs/types/action.d.ts +2 -0
  20. package/dist/cjs/types/action.d.ts.map +1 -1
  21. package/dist/cjs/types/action.js +3 -1
  22. package/dist/cjs/types/action.js.map +1 -1
  23. package/dist/cjs/types/fill.d.ts +2 -2
  24. package/dist/cjs/types/fill.d.ts.map +1 -1
  25. package/dist/cjs/types/order.d.ts +4 -0
  26. package/dist/cjs/types/order.d.ts.map +1 -1
  27. package/dist/cjs/utils/apiClient.d.ts +1 -1
  28. package/dist/cjs/utils/apiClient.d.ts.map +1 -1
  29. package/dist/cjs/utils/apiClient.js +23 -10
  30. package/dist/cjs/utils/apiClient.js.map +1 -1
  31. package/dist/cjs/utils/lighterErrorCode.d.ts +3 -0
  32. package/dist/cjs/utils/lighterErrorCode.d.ts.map +1 -0
  33. package/dist/cjs/utils/lighterErrorCode.js +17 -0
  34. package/dist/cjs/utils/lighterErrorCode.js.map +1 -0
  35. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  36. package/dist/cjs/utils/mapFill.js +2 -2
  37. package/dist/cjs/utils/mapFill.js.map +1 -1
  38. package/dist/cjs/utils/mapPosition.js +1 -1
  39. package/dist/cjs/utils/mapPosition.js.map +1 -1
  40. package/dist/esm/LighterProvider.d.ts.map +1 -1
  41. package/dist/esm/LighterProvider.js +79 -35
  42. package/dist/esm/LighterProvider.js.map +1 -1
  43. package/dist/esm/accountConfig.js +5 -7
  44. package/dist/esm/accountConfig.js.map +1 -1
  45. package/dist/esm/constants.d.ts +24 -3
  46. package/dist/esm/constants.d.ts.map +1 -1
  47. package/dist/esm/constants.js +24 -3
  48. package/dist/esm/constants.js.map +1 -1
  49. package/dist/esm/index.d.ts +1 -1
  50. package/dist/esm/index.d.ts.map +1 -1
  51. package/dist/esm/index.js +1 -1
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/signers/signActions.d.ts.map +1 -1
  54. package/dist/esm/signers/signActions.js +22 -8
  55. package/dist/esm/signers/signActions.js.map +1 -1
  56. package/dist/esm/types/account.d.ts +15 -4
  57. package/dist/esm/types/account.d.ts.map +1 -1
  58. package/dist/esm/types/action.d.ts +13 -0
  59. package/dist/esm/types/action.d.ts.map +1 -1
  60. package/dist/esm/types/action.js +13 -0
  61. package/dist/esm/types/action.js.map +1 -1
  62. package/dist/esm/types/fill.d.ts +2 -2
  63. package/dist/esm/types/fill.d.ts.map +1 -1
  64. package/dist/esm/types/order.d.ts +12 -0
  65. package/dist/esm/types/order.d.ts.map +1 -1
  66. package/dist/esm/utils/apiClient.d.ts +15 -7
  67. package/dist/esm/utils/apiClient.d.ts.map +1 -1
  68. package/dist/esm/utils/apiClient.js +41 -18
  69. package/dist/esm/utils/apiClient.js.map +1 -1
  70. package/dist/esm/utils/lighterErrorCode.d.ts +7 -0
  71. package/dist/esm/utils/lighterErrorCode.d.ts.map +1 -0
  72. package/dist/esm/utils/lighterErrorCode.js +26 -0
  73. package/dist/esm/utils/lighterErrorCode.js.map +1 -0
  74. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  75. package/dist/esm/utils/mapFill.js +3 -2
  76. package/dist/esm/utils/mapFill.js.map +1 -1
  77. package/dist/esm/utils/mapPosition.js +1 -1
  78. package/dist/esm/utils/mapPosition.js.map +1 -1
  79. package/dist/types/LighterProvider.d.ts.map +1 -1
  80. package/dist/types/constants.d.ts +24 -3
  81. package/dist/types/constants.d.ts.map +1 -1
  82. package/dist/types/index.d.ts +1 -1
  83. package/dist/types/index.d.ts.map +1 -1
  84. package/dist/types/signers/signActions.d.ts.map +1 -1
  85. package/dist/types/types/account.d.ts +15 -4
  86. package/dist/types/types/account.d.ts.map +1 -1
  87. package/dist/types/types/action.d.ts +13 -0
  88. package/dist/types/types/action.d.ts.map +1 -1
  89. package/dist/types/types/fill.d.ts +2 -2
  90. package/dist/types/types/fill.d.ts.map +1 -1
  91. package/dist/types/types/order.d.ts +12 -0
  92. package/dist/types/types/order.d.ts.map +1 -1
  93. package/dist/types/utils/apiClient.d.ts +15 -7
  94. package/dist/types/utils/apiClient.d.ts.map +1 -1
  95. package/dist/types/utils/lighterErrorCode.d.ts +7 -0
  96. package/dist/types/utils/lighterErrorCode.d.ts.map +1 -0
  97. package/dist/types/utils/mapFill.d.ts.map +1 -1
  98. package/package.json +2 -2
  99. package/src/LighterProvider.ts +130 -57
  100. package/src/accountConfig.ts +8 -8
  101. package/src/constants.ts +26 -2
  102. package/src/index.ts +1 -0
  103. package/src/signers/signActions.ts +28 -11
  104. package/src/types/account.ts +15 -4
  105. package/src/types/action.ts +15 -0
  106. package/src/types/fill.ts +3 -2
  107. package/src/types/order.ts +13 -0
  108. package/src/utils/apiClient.ts +47 -17
  109. package/src/utils/lighterErrorCode.ts +30 -0
  110. package/src/utils/mapFill.ts +5 -3
  111. package/src/utils/mapPosition.ts +1 -1
@@ -6,9 +6,11 @@ import {
6
6
  import { PerpsErrorCode } from '@lifi/perps-types'
7
7
  import {
8
8
  LIGHTER_INVALID_AUTH_CODE,
9
+ LIGHTER_INVALID_SIGNATURE_CODE,
9
10
  LIGHTER_SUCCESS_CODES,
10
11
  LIGHTER_TOKEN_REVOKED_CODE,
11
12
  } from '../constants.js'
13
+ import { lighterErrorCodeFromBody } from './lighterErrorCode.js'
12
14
 
13
15
  /** @internal */
14
16
  export type ApiParams = Record<string, string | number | boolean>
@@ -40,19 +42,33 @@ export class LighterAuthRejectedError extends PerpsError {}
40
42
  */
41
43
  export class LighterTokenRevokedError extends PerpsError {}
42
44
 
43
- const isLighterAuthRejection = (status: number, data: unknown): boolean =>
44
- status === 401 ||
45
- status === 403 ||
46
- lighterBodyErrorCode(data) === LIGHTER_INVALID_AUTH_CODE
45
+ const isLighterAuthRejection = (status: number, data: unknown): boolean => {
46
+ if (status === 401 || status === 403) {
47
+ return true
48
+ }
49
+ const code = lighterBodyErrorCode(data)
50
+ return (
51
+ code === LIGHTER_INVALID_AUTH_CODE ||
52
+ code === LIGHTER_INVALID_SIGNATURE_CODE
53
+ )
54
+ }
47
55
 
48
56
  const isLighterTokenRevoked = (data: unknown): boolean =>
49
57
  lighterBodyErrorCode(data) === LIGHTER_TOKEN_REVOKED_CODE
50
58
 
51
59
  /**
52
60
  * A CR or LF in a token would split the request when it reaches a
53
- * `fetchImpl` that does not validate header values itself.
61
+ * `fetchImpl` that does not validate header values itself. A blank token
62
+ * would dispatch an `Authorization` header that carries no credential at
63
+ * all, which Lighter answers as a generic rejection.
54
64
  */
55
65
  const assertHeaderSafe = (authToken: string): string => {
66
+ if (authToken.trim().length === 0) {
67
+ throw new PerpsError(
68
+ PerpsErrorCode.ValidationError,
69
+ 'Lighter auth token is blank and cannot go in a header'
70
+ )
71
+ }
56
72
  if (/[\r\n]/.test(authToken)) {
57
73
  throw new PerpsError(
58
74
  PerpsErrorCode.ValidationError,
@@ -136,6 +152,11 @@ export interface LighterApiClientOptions {
136
152
  * `Access-Control-Max-Age: 86400`, so a browser sends one preflight per day
137
153
  * per origin.
138
154
  *
155
+ * The `/api/v1/changeAccountTier` and `/api/v1/referral/use` mutations send
156
+ * the same header with a fresh per-call token. Their `OPTIONS` response is
157
+ * not yet probed against a live host, so a browser may reject the preflight
158
+ * even though the GET paths pass.
159
+ *
139
160
  * Lighter signals rate limits through HTTP 429 or HTTP 405. This client never
140
161
  * retries such a response. It throws `RateLimitExceeded` and holds all network
141
162
  * dispatch until `Retry-After` expires (capped at five minutes), or for 60
@@ -226,13 +247,13 @@ export class LighterApiClient {
226
247
  })
227
248
  if (isLighterTokenRevoked(data)) {
228
249
  throw new LighterTokenRevokedError(
229
- PerpsErrorCode.ThirdPartyError,
250
+ PerpsErrorCode.Unauthorized,
230
251
  `Lighter reports a revoked auth token for ${path}`
231
252
  )
232
253
  }
233
254
  if (isLighterAuthRejection(status, data)) {
234
255
  throw new LighterAuthRejectedError(
235
- PerpsErrorCode.ThirdPartyError,
256
+ PerpsErrorCode.Unauthorized,
236
257
  `Lighter rejected the auth token for ${path}`
237
258
  )
238
259
  }
@@ -274,13 +295,15 @@ export class LighterApiClient {
274
295
  }
275
296
 
276
297
  /**
277
- * Form-encoded POST to a Lighter mutation endpoint. Single-shot — never
278
- * retried, since these are money/state writes whose outcome is unknown on a
279
- * transport failure. Surfaces the raw `{status, body}` pair so the caller can
280
- * map Lighter's per-endpoint business-rule `code` to a domain error verbatim.
298
+ * Form-encoded POST to a Lighter mutation endpoint, carrying the Lighter
299
+ * token in the `Authorization` header. Single-shot — never retried, since
300
+ * these are money/state writes whose outcome is unknown on a transport
301
+ * failure. Surfaces the raw `{status, body}` pair so the caller can map
302
+ * Lighter's per-endpoint business-rule `code` to a domain error verbatim.
281
303
  */
282
304
  async postForm<T>(
283
305
  path: string,
306
+ authToken: string,
284
307
  params: ApiParams
285
308
  ): Promise<{ status: number; data: T }> {
286
309
  const body = new URLSearchParams()
@@ -289,7 +312,10 @@ export class LighterApiClient {
289
312
  }
290
313
  const response = await this.fetchWithHold(`${this.baseUrl}${path}`, {
291
314
  method: 'POST',
292
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
315
+ headers: {
316
+ 'content-type': 'application/x-www-form-urlencoded',
317
+ Authorization: assertHeaderSafe(authToken),
318
+ },
293
319
  body: body.toString(),
294
320
  signal: this.signal,
295
321
  })
@@ -306,20 +332,24 @@ export class LighterApiClient {
306
332
 
307
333
  /**
308
334
  * Post-parse validation shared by every checked read: rejects a non-2xx HTTP
309
- * status and a 200 body carrying a non-success `code`. Callers that surface a
310
- * distinct auth-rejection error must run that check before this one.
335
+ * status and a 200 body carrying a non-success `code`. A body `code` that
336
+ * names a nonce, margin, or balance rejection sets the matching
337
+ * `PerpsErrorCode`; every other rejection is `ThirdPartyError`. Callers that
338
+ * surface a distinct auth-rejection error must run that check before this one.
311
339
  */
312
340
  private assertOk(path: string, status: number, data: unknown): void {
341
+ const errorCode = lighterBodyErrorCode(data)
342
+ const code =
343
+ lighterErrorCodeFromBody(errorCode) ?? PerpsErrorCode.ThirdPartyError
313
344
  if (status < 200 || status >= 300) {
314
345
  throw new PerpsError(
315
- PerpsErrorCode.ThirdPartyError,
346
+ code,
316
347
  `Lighter API request failed: ${status} — ${JSON.stringify(data).slice(0, 200)}`
317
348
  )
318
349
  }
319
- const errorCode = lighterBodyErrorCode(data)
320
350
  if (errorCode !== undefined) {
321
351
  throw new PerpsError(
322
- PerpsErrorCode.ThirdPartyError,
352
+ code,
323
353
  `Lighter API error for ${path}: code ${errorCode} — ${JSON.stringify(data).slice(0, 200)}`
324
354
  )
325
355
  }
@@ -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)
@@ -107,9 +107,11 @@ export const mapFill = (
107
107
 
108
108
  // Lighter publishes both counterparties' position-before snapshots on every
109
109
  // trade row; reading the wrong one mis-classifies when they differ.
110
- const startPosition = isMaker
111
- ? trade.maker_position_size_before
112
- : trade.taker_position_size_before
110
+ // Both keys are `omitempty`: an absent snapshot means flat.
111
+ const startPosition =
112
+ (isMaker
113
+ ? trade.maker_position_size_before
114
+ : trade.taker_position_size_before) ?? '0'
113
115
  const entryQuoteBefore = isMaker
114
116
  ? trade.maker_entry_quote_before
115
117
  : trade.taker_entry_quote_before
@@ -60,7 +60,7 @@ export const mapPosition = (
60
60
  : positionValue.div(size.abs()).toFixed(),
61
61
  liquidationPrice: pos.liquidation_price,
62
62
  unrealizedPnl: pos.unrealized_pnl,
63
- accruedFunding: pos.total_funding_paid_out,
63
+ accruedFunding: pos.total_funding_paid_out ?? '0',
64
64
  leverage: leverageFromImf(pos.initial_margin_fraction) ?? 1,
65
65
  marginUsed,
66
66
  initialMarginRequirement: initialMarginRequirement.toFixed(),