@lifi/perps-sdk-provider-lighter 29.1.1 → 30.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 (47) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +59 -3
  3. package/dist/cjs/LighterProvider.js.map +1 -1
  4. package/dist/cjs/accountConfig.d.ts +4 -2
  5. package/dist/cjs/accountConfig.d.ts.map +1 -1
  6. package/dist/cjs/accountConfig.js +23 -17
  7. package/dist/cjs/accountConfig.js.map +1 -1
  8. package/dist/cjs/signers/LighterSigner.d.ts +1 -0
  9. package/dist/cjs/signers/LighterSigner.d.ts.map +1 -1
  10. package/dist/cjs/signers/LighterSigner.js +22 -0
  11. package/dist/cjs/signers/LighterSigner.js.map +1 -1
  12. package/dist/cjs/signers/signActions.d.ts.map +1 -1
  13. package/dist/cjs/signers/signActions.js +2 -1
  14. package/dist/cjs/signers/signActions.js.map +1 -1
  15. package/dist/cjs/utils/lighterErrorCode.d.ts.map +1 -1
  16. package/dist/cjs/utils/lighterErrorCode.js +1 -0
  17. package/dist/cjs/utils/lighterErrorCode.js.map +1 -1
  18. package/dist/esm/LighterProvider.d.ts.map +1 -1
  19. package/dist/esm/LighterProvider.js +67 -4
  20. package/dist/esm/LighterProvider.js.map +1 -1
  21. package/dist/esm/accountConfig.d.ts +14 -8
  22. package/dist/esm/accountConfig.d.ts.map +1 -1
  23. package/dist/esm/accountConfig.js +37 -39
  24. package/dist/esm/accountConfig.js.map +1 -1
  25. package/dist/esm/signers/LighterSigner.d.ts +1 -0
  26. package/dist/esm/signers/LighterSigner.d.ts.map +1 -1
  27. package/dist/esm/signers/LighterSigner.js +22 -0
  28. package/dist/esm/signers/LighterSigner.js.map +1 -1
  29. package/dist/esm/signers/signActions.d.ts.map +1 -1
  30. package/dist/esm/signers/signActions.js +4 -2
  31. package/dist/esm/signers/signActions.js.map +1 -1
  32. package/dist/esm/utils/lighterErrorCode.d.ts.map +1 -1
  33. package/dist/esm/utils/lighterErrorCode.js +2 -0
  34. package/dist/esm/utils/lighterErrorCode.js.map +1 -1
  35. package/dist/types/LighterProvider.d.ts.map +1 -1
  36. package/dist/types/accountConfig.d.ts +14 -8
  37. package/dist/types/accountConfig.d.ts.map +1 -1
  38. package/dist/types/signers/LighterSigner.d.ts +1 -0
  39. package/dist/types/signers/LighterSigner.d.ts.map +1 -1
  40. package/dist/types/signers/signActions.d.ts.map +1 -1
  41. package/dist/types/utils/lighterErrorCode.d.ts.map +1 -1
  42. package/package.json +3 -3
  43. package/src/LighterProvider.ts +101 -7
  44. package/src/accountConfig.ts +47 -46
  45. package/src/signers/LighterSigner.ts +47 -0
  46. package/src/signers/signActions.ts +4 -2
  47. package/src/utils/lighterErrorCode.ts +2 -0
@@ -1,8 +1,8 @@
1
- import { PerpsError } from '@lifi/perps-sdk'
1
+ import { isSetupOptionFor, PerpsError } from '@lifi/perps-sdk'
2
2
  import type {
3
3
  AccountConfigSetting,
4
4
  LighterAccountConfig,
5
- ProviderAction,
5
+ SetupAction,
6
6
  } from '@lifi/perps-types'
7
7
  import { ActionType, PerpsErrorCode } from '@lifi/perps-types'
8
8
  import {
@@ -16,8 +16,8 @@ function assertNever(value: never): never {
16
16
  )
17
17
  }
18
18
 
19
- // Wire strings match the backend descriptor's ParamOption values. An unmapped
20
- // int projects to null.
19
+ // Wire strings match the modes the backend's ACCOUNT_MODE options bind. An
20
+ // unmapped int projects to null.
21
21
  const ACCOUNT_MODE_INT_TO_WIRE: Readonly<Record<number, string>> = {
22
22
  [LT_ACCOUNT_TRADING_MODE_SIMPLE]: 'simpleTradingAccount',
23
23
  [LT_ACCOUNT_TRADING_MODE_UNIFIED]: 'unifiedTradingAccount',
@@ -25,28 +25,32 @@ const ACCOUNT_MODE_INT_TO_WIRE: Readonly<Record<number, string>> = {
25
25
 
26
26
  /**
27
27
  * Resolve the account tier to the wire string `changeAccountTier` accepts.
28
- * `userTierName` decides it, but only when the descriptor enumerates the
29
- * string: Lighter owns that vocabulary, so an unrecognised value projects
30
- * `null` instead of a mis-reported tier. A descriptor whose parameter carries
31
- * no `values` array enumerates nothing, so it also projects `null`. The
32
- * unauthenticated read reaches no `/accountLimits` and so carries no tier
33
- * string, which projects `null` as well: `config.accountType` is Lighter's
34
- * `SubAccountType`, not a tier, so nothing else in the config decides it.
28
+ * `userTierName` decides it, but only when an ACCOUNT_TYPE option binds that
29
+ * tier: Lighter owns that vocabulary, so an unrecognised value resolves `null`
30
+ * instead of a mis-reported tier. An absent `userTierName` (no
31
+ * `/accountLimits` read) resolves `null`.
35
32
  */
36
- function resolveAccountTier(
37
- descriptor: ProviderAction,
38
- config: LighterAccountConfig
33
+ export function resolveAccountTier(
34
+ descriptor: SetupAction,
35
+ userTierName: string | undefined
39
36
  ): string | null {
40
- const { userTierName } = config
41
37
  if (userTierName === undefined) {
42
38
  return null
43
39
  }
44
- const enumerated = descriptor.params?.[0]?.values ?? []
45
- return enumerated.some((option) => option.value === userTierName)
40
+ return boundAccountTiers(descriptor).includes(userTierName)
46
41
  ? userTierName
47
42
  : null
48
43
  }
49
44
 
45
+ /** The tiers the ACCOUNT_TYPE step's options bind. */
46
+ export function boundAccountTiers(descriptor: SetupAction): string[] {
47
+ return (descriptor.options ?? []).flatMap((option) =>
48
+ isSetupOptionFor(option, ActionType.ACCOUNT_TYPE)
49
+ ? [option.params.tier]
50
+ : []
51
+ )
52
+ }
53
+
50
54
  /**
51
55
  * Project a single Lighter descriptor against the typed
52
56
  * `LighterAccountConfig` into an `AccountConfigSetting`.
@@ -60,14 +64,14 @@ function resolveAccountTier(
60
64
  * | SET_REFERRAL | [] (no parameters)
61
65
  * | APPROVE_INTEGRATOR | [] (no parameters)
62
66
  * | ACCOUNT_MODE | [{ name: 'mode', value: config.accountTradingMode }]
63
- * | ACCOUNT_TYPE | [{ name: 'tier', value: resolveAccountTier(…) }]
67
+ * | ACCOUNT_TYPE | [{ name: 'tier', value: config.userTierName ?? null }]
64
68
  *
65
69
  * The switch is exhaustive over `ActionType` so enum additions force a
66
70
  * compile error in the `default` arm. ActionTypes that are not valid on
67
- * `Provider.setup` / `Provider.options` throw at runtime.
71
+ * `Provider.setup` throw at runtime.
68
72
  */
69
73
  function projectLighterDescriptor(
70
- descriptor: ProviderAction,
74
+ descriptor: SetupAction,
71
75
  config: LighterAccountConfig
72
76
  ): AccountConfigSetting {
73
77
  switch (descriptor.type) {
@@ -106,28 +110,29 @@ function projectLighterDescriptor(
106
110
  case ActionType.APPROVE_INTEGRATOR:
107
111
  return { type: descriptor.type, values: [] }
108
112
 
109
- // `mode` decodes the raw `account_trading_mode` integer to the descriptor's
110
- // wire strings (`unifiedTradingAccount` / `simpleTradingAccount`).
113
+ // `mode` decodes the raw `account_trading_mode` integer to the wire strings
114
+ // the options bind (`unifiedTradingAccount` / `simpleTradingAccount`).
111
115
  // Unrecognised integers project to `null`.
112
- case ActionType.ACCOUNT_MODE:
116
+ case ActionType.ACCOUNT_MODE: {
117
+ const mode = ACCOUNT_MODE_INT_TO_WIRE[config.accountTradingMode] ?? null
113
118
  return {
114
119
  type: descriptor.type,
115
- values: [
116
- {
117
- name: 'mode',
118
- value: ACCOUNT_MODE_INT_TO_WIRE[config.accountTradingMode] ?? null,
119
- },
120
- ],
120
+ values: [{ name: 'mode', value: mode }],
121
+ satisfied: (descriptor.options ?? []).some(
122
+ (option) =>
123
+ isSetupOptionFor(option, ActionType.ACCOUNT_MODE) &&
124
+ option.params.mode === mode
125
+ ),
121
126
  }
127
+ }
122
128
 
123
- // An unresolved `tier` projects to `null` (surfaces as "tier not
124
- // detected" — the widget still lets the user pick a value).
129
+ // `tier` carries the venue's raw tier name even when no option binds it;
130
+ // only a bound tier reads satisfied.
125
131
  case ActionType.ACCOUNT_TYPE:
126
132
  return {
127
133
  type: descriptor.type,
128
- values: [
129
- { name: 'tier', value: resolveAccountTier(descriptor, config) },
130
- ],
134
+ values: [{ name: 'tier', value: config.userTierName ?? null }],
135
+ satisfied: resolveAccountTier(descriptor, config.userTierName) !== null,
131
136
  }
132
137
 
133
138
  case ActionType.APPROVE_AGENT:
@@ -155,11 +160,14 @@ function projectLighterDescriptor(
155
160
  case ActionType.META_ONBOARD:
156
161
  case ActionType.META_CREATE_REFERRAL_CODE:
157
162
  case ActionType.SYNC_FEE_ATTRIBUTION:
163
+ case ActionType.REVOKE_BUILDER_FEE:
164
+ case ActionType.REVOKE_SESSION_AGENT:
165
+ case ActionType.REVOKE_INTEGRATOR:
158
166
  throw new PerpsError(
159
167
  PerpsErrorCode.SDKError,
160
168
  `Lighter account-config mapper has no projection for ` +
161
169
  `descriptor type '${descriptor.type}' — this ActionType is not ` +
162
- `valid on Provider.setup / Provider.options for Lighter.`
170
+ `valid on Provider.setup for Lighter.`
163
171
  )
164
172
 
165
173
  default:
@@ -168,22 +176,15 @@ function projectLighterDescriptor(
168
176
  }
169
177
 
170
178
  /**
171
- * Project the union of Lighter setup + options descriptors against the typed
179
+ * Project the Lighter setup descriptors against the typed
172
180
  * `LighterAccountConfig`. Produces exactly one `AccountConfigSetting` per
173
- * descriptor, in `setup`-then-`options` order (preserving the order in which
174
- * the backend emits them).
181
+ * descriptor, preserving the order in which the backend emits them.
175
182
  *
176
- * @param config Typed account state for the Lighter account.
177
- * @param setup `Provider.setup` array as emitted by `/providers`.
178
- * @param options `Provider.options` array as emitted by `/providers`.
179
183
  * @public
180
184
  */
181
185
  export function projectLighterConfigSettings(
182
186
  config: LighterAccountConfig,
183
- setup: ProviderAction[],
184
- options: ProviderAction[]
187
+ setup: SetupAction[]
185
188
  ): AccountConfigSetting[] {
186
- return [...setup, ...options].map((descriptor) =>
187
- projectLighterDescriptor(descriptor, config)
188
- )
189
+ return setup.map((descriptor) => projectLighterDescriptor(descriptor, config))
189
190
  }
@@ -214,12 +214,45 @@ export class LighterSigner {
214
214
  'required L1 user wallet signature.'
215
215
  )
216
216
  }
217
+ if (action === ActionType.REVOKE_INTEGRATOR) {
218
+ return this.signRevokeIntegrator(wasmSignParams, context)
219
+ }
217
220
  const wasm = await this.ensureLoaded()
218
221
  await this.ensureClient(context)
219
222
  const result = this.dispatch(wasm, action, wasmSignParams, context)
220
223
  return unwrap(result, action)
221
224
  }
222
225
 
226
+ private async signRevokeIntegrator(
227
+ p: Record<string, unknown>,
228
+ ctx: LighterSignerContext
229
+ ): Promise<LighterSignedBlob> {
230
+ const integratorAccountIndex = numberField(p, 'integrator_account_index')
231
+ const maxPerpsTakerFee = revokeIntegratorZeroField(p, 'max_perps_taker_fee')
232
+ const maxPerpsMakerFee = revokeIntegratorZeroField(p, 'max_perps_maker_fee')
233
+ const maxSpotTakerFee = revokeIntegratorZeroField(p, 'max_spot_taker_fee')
234
+ const maxSpotMakerFee = revokeIntegratorZeroField(p, 'max_spot_maker_fee')
235
+ const approvalExpiry = revokeIntegratorZeroField(p, 'approval_expiry')
236
+ const nonce = numberField(p, 'nonce')
237
+ const wasm = await this.ensureLoaded()
238
+ await this.ensureClient(ctx)
239
+ return unwrap(
240
+ wasm.SignApproveIntegrator(
241
+ integratorAccountIndex,
242
+ maxPerpsTakerFee,
243
+ maxPerpsMakerFee,
244
+ maxSpotTakerFee,
245
+ maxSpotMakerFee,
246
+ approvalExpiry,
247
+ SKIP_NONCE_DISABLED,
248
+ nonce,
249
+ ctx.apiKeyIndex,
250
+ ctx.accountIndex
251
+ ),
252
+ ActionType.REVOKE_INTEGRATOR
253
+ )
254
+ }
255
+
223
256
  /**
224
257
  * Step 1 of the REGISTER_API_KEY flow. Generates the WASM blob for a
225
258
  * ChangePubKey tx with `L1Sig` left empty, plus the canonical EIP-191
@@ -738,6 +771,20 @@ function numberField(p: Record<string, unknown>, key: string): number {
738
771
  )
739
772
  }
740
773
 
774
+ function revokeIntegratorZeroField(
775
+ p: Record<string, unknown>,
776
+ key: string
777
+ ): number {
778
+ const value = numberField(p, key)
779
+ if (value !== 0) {
780
+ throw new PerpsError(
781
+ PerpsErrorCode.ValidationError,
782
+ `Lighter REVOKE_INTEGRATOR requires '${key}' to be zero`
783
+ )
784
+ }
785
+ return value
786
+ }
787
+
741
788
  function optionalNumberField(
742
789
  p: Record<string, unknown>,
743
790
  key: string,
@@ -17,6 +17,7 @@ import {
17
17
  } from '../constants.js'
18
18
  import type { ApiParams, LighterApiClient } from '../utils/apiClient.js'
19
19
  import { fetchAppliedReferralCode } from '../utils/appliedReferralCode.js'
20
+ import { lighterErrorCodeFromBody } from '../utils/lighterErrorCode.js'
20
21
  import {
21
22
  fetchRegisteredApiKey,
22
23
  type LighterRegisteredApiKey,
@@ -472,7 +473,8 @@ async function signTransfer(
472
473
  * skips the POST when the account carries one, and a
473
474
  * {@link LIGHTER_REFERRAL_ALREADY_USED_CODE} verdict from the POST settles the
474
475
  * step the same way. Every other non-success verdict surfaces Lighter's
475
- * `code`/`message` verbatim as an {@link PerpsErrorCode.ExchangeRejected}.
476
+ * `code`/`message` verbatim, under the `PerpsErrorCode` the body code names
477
+ * ({@link lighterErrorCodeFromBody}) or {@link PerpsErrorCode.ExchangeRejected}.
476
478
  */
477
479
  async function executeTokenAuthMutation(
478
480
  deps: LighterSignActionsDeps,
@@ -517,7 +519,7 @@ async function executeTokenAuthMutation(
517
519
  ) {
518
520
  const suffix = data?.message ? `: ${data.message}` : ''
519
521
  throw new PerpsError(
520
- PerpsErrorCode.ExchangeRejected,
522
+ lighterErrorCodeFromBody(code) ?? PerpsErrorCode.ExchangeRejected,
521
523
  `Lighter ${step.action} rejected (code ${code ?? status})${suffix}`
522
524
  )
523
525
  }
@@ -16,6 +16,8 @@ const LIGHTER_BODY_ERROR_CODES: ReadonlyMap<number, PerpsErrorCode> = new Map([
16
16
  [21507, PerpsErrorCode.InsufficientMargin],
17
17
  // account is below initial margin
18
18
  [21508, PerpsErrorCode.InsufficientMargin],
19
+ // account is not eligible for this operation
20
+ [21520, PerpsErrorCode.SetupRequired],
19
21
  // not enough margin to create the order
20
22
  [21739, PerpsErrorCode.InsufficientMargin],
21
23
  ])