@lifi/perps-sdk-provider-lighter 4.0.0 → 5.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 (82) hide show
  1. package/README.md +16 -3
  2. package/dist/cjs/LighterProvider.d.ts +3 -0
  3. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  4. package/dist/cjs/LighterProvider.js +35 -29
  5. package/dist/cjs/LighterProvider.js.map +1 -1
  6. package/dist/cjs/constants.d.ts +19 -1
  7. package/dist/cjs/constants.d.ts.map +1 -1
  8. package/dist/cjs/constants.js +21 -1
  9. package/dist/cjs/constants.js.map +1 -1
  10. package/dist/cjs/index.d.ts +2 -1
  11. package/dist/cjs/index.d.ts.map +1 -1
  12. package/dist/cjs/index.js +9 -1
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts +3 -1
  15. package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  16. package/dist/cjs/signers/LighterReadOnlyTokenManager.js +4 -3
  17. package/dist/cjs/signers/LighterReadOnlyTokenManager.js.map +1 -1
  18. package/dist/cjs/signers/LighterSigner.d.ts +4 -0
  19. package/dist/cjs/signers/LighterSigner.d.ts.map +1 -1
  20. package/dist/cjs/signers/LighterSigner.js +26 -3
  21. package/dist/cjs/signers/LighterSigner.js.map +1 -1
  22. package/dist/cjs/signers/signActions.d.ts.map +1 -1
  23. package/dist/cjs/signers/signActions.js +30 -0
  24. package/dist/cjs/signers/signActions.js.map +1 -1
  25. package/dist/cjs/signers/wasmLoader.d.ts +3 -1
  26. package/dist/cjs/signers/wasmLoader.d.ts.map +1 -1
  27. package/dist/cjs/signers/wasmLoader.js.map +1 -1
  28. package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
  29. package/dist/cjs/websocket/LighterWsProvider.js +5 -5
  30. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
  31. package/dist/esm/LighterProvider.d.ts +15 -0
  32. package/dist/esm/LighterProvider.d.ts.map +1 -1
  33. package/dist/esm/LighterProvider.js +37 -31
  34. package/dist/esm/LighterProvider.js.map +1 -1
  35. package/dist/esm/constants.d.ts +81 -1
  36. package/dist/esm/constants.d.ts.map +1 -1
  37. package/dist/esm/constants.js +50 -0
  38. package/dist/esm/constants.js.map +1 -1
  39. package/dist/esm/index.d.ts +2 -1
  40. package/dist/esm/index.d.ts.map +1 -1
  41. package/dist/esm/index.js +1 -1
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts +9 -1
  44. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  45. package/dist/esm/signers/LighterReadOnlyTokenManager.js +5 -4
  46. package/dist/esm/signers/LighterReadOnlyTokenManager.js.map +1 -1
  47. package/dist/esm/signers/LighterSigner.d.ts +21 -6
  48. package/dist/esm/signers/LighterSigner.d.ts.map +1 -1
  49. package/dist/esm/signers/LighterSigner.js +42 -10
  50. package/dist/esm/signers/LighterSigner.js.map +1 -1
  51. package/dist/esm/signers/signActions.d.ts.map +1 -1
  52. package/dist/esm/signers/signActions.js +42 -0
  53. package/dist/esm/signers/signActions.js.map +1 -1
  54. package/dist/esm/signers/wasmLoader.d.ts +6 -2
  55. package/dist/esm/signers/wasmLoader.d.ts.map +1 -1
  56. package/dist/esm/signers/wasmLoader.js.map +1 -1
  57. package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
  58. package/dist/esm/websocket/LighterWsProvider.js +5 -5
  59. package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
  60. package/dist/types/LighterProvider.d.ts +15 -0
  61. package/dist/types/LighterProvider.d.ts.map +1 -1
  62. package/dist/types/constants.d.ts +81 -1
  63. package/dist/types/constants.d.ts.map +1 -1
  64. package/dist/types/index.d.ts +2 -1
  65. package/dist/types/index.d.ts.map +1 -1
  66. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts +9 -1
  67. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  68. package/dist/types/signers/LighterSigner.d.ts +21 -6
  69. package/dist/types/signers/LighterSigner.d.ts.map +1 -1
  70. package/dist/types/signers/signActions.d.ts.map +1 -1
  71. package/dist/types/signers/wasmLoader.d.ts +6 -2
  72. package/dist/types/signers/wasmLoader.d.ts.map +1 -1
  73. package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
  74. package/package.json +3 -3
  75. package/src/LighterProvider.ts +53 -40
  76. package/src/constants.ts +104 -1
  77. package/src/index.ts +12 -0
  78. package/src/signers/LighterReadOnlyTokenManager.ts +13 -5
  79. package/src/signers/LighterSigner.ts +67 -12
  80. package/src/signers/signActions.ts +57 -0
  81. package/src/signers/wasmLoader.ts +6 -2
  82. package/src/websocket/LighterWsProvider.ts +5 -5
package/src/constants.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FeeTier } from '@lifi/perps-types'
1
+ import type { FeeTier, LighterProviderKey } from '@lifi/perps-types'
2
2
 
3
3
  /**
4
4
  * Lighter provider key as it appears on `Provider.key` from the backend.
@@ -24,6 +24,109 @@ export const LIGHTER_SPOT_CATEGORY_ID = 'spot'
24
24
  export const DEFAULT_LIGHTER_REST_URL = 'https://mainnet.zklighter.elliot.ai'
25
25
  export const DEFAULT_LIGHTER_WS_URL = 'wss://mainnet.zklighter.elliot.ai/stream'
26
26
 
27
+ /**
28
+ * zkLighter L2 signing chain id fed to {@link LighterSigner} on mainnet.
29
+ *
30
+ * @public
31
+ */
32
+ export const DEFAULT_LIGHTER_SIGNER_CHAIN_ID = 304
33
+
34
+ /**
35
+ * Base URL the mainnet Lighter zkLighter explorer resolves a tx hash against
36
+ * (`${base}${txHash}`).
37
+ *
38
+ * @public
39
+ */
40
+ export const DEFAULT_LIGHTER_EXPLORER_TX_BASE_URL =
41
+ 'https://app.lighter.xyz/explorer/logs/'
42
+
43
+ /**
44
+ * The venue-specific facts that distinguish one Lighter deployment from
45
+ * another. One provider + WS pair is built per instance from these; a client
46
+ * may register several so long as their `providerKey`s differ.
47
+ *
48
+ * @public
49
+ */
50
+ export interface LighterInstanceConfig {
51
+ /** Wire-visible `Provider.key` and the plugin `type`; unique per instance. */
52
+ providerKey: LighterProviderKey
53
+ restUrl: string
54
+ wsUrl: string
55
+ /**
56
+ * zkLighter L2 signing chain id to construct this instance's
57
+ * {@link LighterSigner} with.
58
+ */
59
+ signerChainId: number
60
+ /**
61
+ * Explorer tx base URL for transfer-activity links (`${base}${txHash}`).
62
+ * When omitted, transfer links are not emitted for this instance.
63
+ */
64
+ explorerTxBaseUrl?: string
65
+ }
66
+
67
+ /**
68
+ * The built-in mainnet Lighter instance — the defaults `lighterProvider()` and
69
+ * `lighterWsProvider()` apply when constructed with no instance overrides.
70
+ *
71
+ * @public
72
+ */
73
+ export const LIGHTER_MAINNET_INSTANCE: LighterInstanceConfig = {
74
+ providerKey: LIGHTER_PROVIDER_KEY,
75
+ restUrl: DEFAULT_LIGHTER_REST_URL,
76
+ wsUrl: DEFAULT_LIGHTER_WS_URL,
77
+ signerChainId: DEFAULT_LIGHTER_SIGNER_CHAIN_ID,
78
+ explorerTxBaseUrl: DEFAULT_LIGHTER_EXPLORER_TX_BASE_URL,
79
+ }
80
+
81
+ /**
82
+ * Provider key for the Lighter instance running on Robinhood chain.
83
+ *
84
+ * @public
85
+ */
86
+ export const LIGHTER_RH_PROVIDER_KEY = 'lighter-rh'
87
+
88
+ /** @public */
89
+ export const LIGHTER_RH_REST_URL = 'https://api.rh.lighter.xyz'
90
+ /** @public */
91
+ export const LIGHTER_RH_WS_URL = 'wss://api.rh.lighter.xyz/stream'
92
+
93
+ /**
94
+ * Deployment-supplied facts the RH instance cannot source from public
95
+ * documentation and the caller must confirm.
96
+ *
97
+ * @public
98
+ */
99
+ export interface LighterRhInstanceOverrides {
100
+ /**
101
+ * zkLighter L2 signing chain id for the RH instance. Unverified from public
102
+ * sources — must be the value confirmed against lighter-go / RH support, not
103
+ * mainnet's 304 nor the RH L1 chain id 4663.
104
+ */
105
+ signerChainId: number
106
+ /**
107
+ * RH zkLighter explorer tx base URL. Omit until confirmed; transfer links are
108
+ * then left unset rather than pointed at the mainnet explorer.
109
+ */
110
+ explorerTxBaseUrl?: string
111
+ }
112
+
113
+ /**
114
+ * Build the `lighter-rh` {@link LighterInstanceConfig}. The signing chain id is
115
+ * a required argument because it is not verifiable from public sources — the
116
+ * caller must pass the value confirmed against lighter-go / RH support.
117
+ *
118
+ * @public
119
+ */
120
+ export const lighterRhInstance = (
121
+ overrides: LighterRhInstanceOverrides
122
+ ): LighterInstanceConfig => ({
123
+ providerKey: LIGHTER_RH_PROVIDER_KEY,
124
+ restUrl: LIGHTER_RH_REST_URL,
125
+ wsUrl: LIGHTER_RH_WS_URL,
126
+ signerChainId: overrides.signerChainId,
127
+ explorerTxBaseUrl: overrides.explorerTxBaseUrl,
128
+ })
129
+
27
130
  /** @internal */
28
131
  export const DEFAULT_TRADES_LIMIT = 50
29
132
 
package/src/index.ts CHANGED
@@ -15,11 +15,23 @@
15
15
 
16
16
  export { projectLighterConfigSettings } from './accountConfig.js'
17
17
  export { getAccountSummary } from './accountSummary.js'
18
+ export type {
19
+ LighterInstanceConfig,
20
+ LighterRhInstanceOverrides,
21
+ } from './constants.js'
18
22
  export {
19
23
  DEFAULT_API_KEY_INDEX,
24
+ DEFAULT_LIGHTER_EXPLORER_TX_BASE_URL,
20
25
  DEFAULT_LIGHTER_REST_URL,
26
+ DEFAULT_LIGHTER_SIGNER_CHAIN_ID,
27
+ DEFAULT_LIGHTER_WS_URL,
28
+ LIGHTER_MAINNET_INSTANCE,
21
29
  LIGHTER_PROVIDER_KEY,
30
+ LIGHTER_RH_PROVIDER_KEY,
31
+ LIGHTER_RH_REST_URL,
32
+ LIGHTER_RH_WS_URL,
22
33
  LIGHTER_SPOT_CATEGORY_ID,
34
+ lighterRhInstance,
23
35
  } from './constants.js'
24
36
  export type {
25
37
  LighterPerpsProvider,
@@ -7,12 +7,11 @@ import {
7
7
  import type {
8
8
  ApproveReadOnlyTokenParams,
9
9
  LighterAccountConfig,
10
+ LighterProviderKey,
10
11
  } from '@lifi/perps-types'
11
12
  import { PerpsErrorCode } from '@lifi/perps-types'
12
13
  import type { Address } from 'viem'
13
- import { DEFAULT_LIGHTER_REST_URL } from '../constants.js'
14
-
15
- const STORAGE_PREFIX = 'lifi:perps:lighter:rotoken'
14
+ import { DEFAULT_LIGHTER_REST_URL, LIGHTER_PROVIDER_KEY } from '../constants.js'
16
15
 
17
16
  /**
18
17
  * Default token name persisted alongside Lighter's `tokens/create` row.
@@ -73,6 +72,13 @@ export type LighterTokenFetcher = (params: {
73
72
  /** @public */
74
73
  export interface LighterReadOnlyTokenManagerOptions {
75
74
  storage?: StorageAdapter
75
+ /**
76
+ * Provider instance key. Namespaces persisted tokens so two Lighter
77
+ * instances sharing a storage backend never serve each other's token for a
78
+ * coincident `(address, accountIndex)` pair, and stamps the `provider` of
79
+ * the {@link ApproveReadOnlyTokenResult} config. Defaults to `'lighter'`.
80
+ */
81
+ providerKey?: LighterProviderKey
76
82
  /** Lighter API host. Defaults to {@link DEFAULT_LIGHTER_REST_URL}. */
77
83
  lighterApiUrl?: string
78
84
  /** Override the multipart POST. Defaults to a `fetch`-based implementation. */
@@ -145,6 +151,7 @@ const isLighterReadOnlyToken = (
145
151
  */
146
152
  export class LighterReadOnlyTokenManager {
147
153
  private readonly storage: StorageAdapter
154
+ private readonly providerKey: LighterProviderKey
148
155
  private readonly lighterApiUrl: string
149
156
  private readonly fetcher: LighterTokenFetcher
150
157
  private readonly now: () => number
@@ -152,13 +159,14 @@ export class LighterReadOnlyTokenManager {
152
159
 
153
160
  constructor(options: LighterReadOnlyTokenManagerOptions = {}) {
154
161
  this.storage = options.storage ?? localStorageAdapter
162
+ this.providerKey = options.providerKey ?? LIGHTER_PROVIDER_KEY
155
163
  this.lighterApiUrl = options.lighterApiUrl ?? DEFAULT_LIGHTER_REST_URL
156
164
  this.fetcher = options.fetcher ?? defaultLighterTokenFetcher
157
165
  this.now = options.now ?? (() => Date.now())
158
166
  }
159
167
 
160
168
  private storageKey(address: Address, accountIndex: number): string {
161
- return `${STORAGE_PREFIX}:${address.toLowerCase()}:${accountIndex}`
169
+ return `lifi:perps:${this.providerKey}:rotoken:${address.toLowerCase()}:${accountIndex}`
162
170
  }
163
171
 
164
172
  private isExpired(token: LighterReadOnlyToken): boolean {
@@ -291,7 +299,7 @@ export class LighterReadOnlyTokenManager {
291
299
  return {
292
300
  token,
293
301
  config: {
294
- provider: 'lighter',
302
+ provider: this.providerKey,
295
303
  accountIndex: token.accountIndex,
296
304
  readOnlyTokenApproved: true,
297
305
  readOnlyTokenExpiry: token.expiry,
@@ -1,5 +1,8 @@
1
1
  import { ActionType } from '@lifi/perps-types'
2
- import { DEFAULT_LIGHTER_REST_URL } from '../constants.js'
2
+ import {
3
+ DEFAULT_LIGHTER_REST_URL,
4
+ DEFAULT_LIGHTER_SIGNER_CHAIN_ID,
5
+ } from '../constants.js'
3
6
  import {
4
7
  LT_ASSET_ID_USDC,
5
8
  LT_ROUTE_PERP,
@@ -49,14 +52,18 @@ export interface ChangePubKeyResult extends LighterSignedBlob {
49
52
  messageToSign: string
50
53
  }
51
54
 
55
+ /** @public */
56
+ export interface ApproveIntegratorResult extends LighterSignedBlob {
57
+ /** EIP-191 `L2ApproveIntegrator` L1 body the user's wallet must countersign to authorize the integrator approval. */
58
+ messageToSign: string
59
+ }
60
+
52
61
  /** @public */
53
62
  export interface ApiKeyPair {
54
63
  publicKey: string
55
64
  privateKey: string
56
65
  }
57
66
 
58
- const DEFAULT_CHAIN_ID = 304
59
-
60
67
  // Signing "unset" sentinels mirror lighter-go `types/txtypes/constants.go`.
61
68
  // Passing them yields an empty `L2TxAttributes` — no integrator fees, default
62
69
  // self-trade rules.
@@ -97,7 +104,7 @@ export class LighterSigner {
97
104
 
98
105
  constructor(config: LighterSignerConfig = {}) {
99
106
  this.apiUrl = config.apiUrl ?? DEFAULT_LIGHTER_REST_URL
100
- this.chainId = config.chainId ?? DEFAULT_CHAIN_ID
107
+ this.chainId = config.chainId ?? DEFAULT_LIGHTER_SIGNER_CHAIN_ID
101
108
  this.loaderOptions = {
102
109
  wasmBinaryUrl: config.wasmBinaryUrl,
103
110
  wasmExecJsUrl: config.wasmExecJsUrl,
@@ -133,8 +140,9 @@ export class LighterSigner {
133
140
  * `WasmBlobActionStep`. Returns the signed `{ txType, txInfo, txHash }`
134
141
  * triple the backend forwards to Lighter's `sendTx` endpoint.
135
142
  *
136
- * For REGISTER_API_KEY use `signChangePubKey` instead — it returns an
137
- * additional `messageToSign` the L1 wallet must countersign.
143
+ * For REGISTER_API_KEY use `signChangePubKey` and for APPROVE_INTEGRATOR
144
+ * use `signApproveIntegrator` instead — both return an additional
145
+ * `messageToSign` the L1 wallet must countersign.
138
146
  */
139
147
  async sign(
140
148
  action: ActionType,
@@ -147,6 +155,12 @@ export class LighterSigner {
147
155
  'must be coordinated by the caller.'
148
156
  )
149
157
  }
158
+ if (action === ActionType.APPROVE_INTEGRATOR) {
159
+ throw new Error(
160
+ 'Use signApproveIntegrator() for APPROVE_INTEGRATOR — sign() does ' +
161
+ 'not collect the required L1 user wallet signature.'
162
+ )
163
+ }
150
164
  const wasm = await this.ensureLoaded()
151
165
  await this.ensureClient(context)
152
166
  const result = this.dispatch(wasm, action, wasmSignParams, context)
@@ -211,10 +225,51 @@ export class LighterSigner {
211
225
  }
212
226
 
213
227
  /**
214
- * Step 2 of REGISTER_API_KEY — inject the L1 signature produced by the
215
- * user's Ethereum wallet into the ChangePubKey txInfo JSON. `L1Sig` is the
216
- * only field in txInfo that depends on the L1 signature; txHash does NOT
217
- * include it (so we do not recompute it).
228
+ * Sign an APPROVE_INTEGRATOR action with the stored API key and return the
229
+ * signed blob alongside the EIP-191 `L2ApproveIntegrator` L1 message the
230
+ * user's Ethereum wallet must countersign. Unlike {@link sign}, this exposes
231
+ * `messageToSign`: Lighter requires `L1Sig` (injected via
232
+ * {@link embedL1Signature} before submission) when the integrator account
233
+ * belongs to a different L1 address with non-zero fee caps.
234
+ */
235
+ async signApproveIntegrator(
236
+ wasmSignParams: Record<string, unknown>,
237
+ context: LighterSignerContext
238
+ ): Promise<ApproveIntegratorResult> {
239
+ const wasm = await this.ensureLoaded()
240
+ await this.ensureClient(context)
241
+ const result = this.dispatch(
242
+ wasm,
243
+ ActionType.APPROVE_INTEGRATOR,
244
+ wasmSignParams,
245
+ context
246
+ )
247
+ if (result.error) {
248
+ throw new Error(`Lighter SignApproveIntegrator failed: ${result.error}`)
249
+ }
250
+ if (
251
+ result.txType === undefined ||
252
+ result.txInfo === undefined ||
253
+ result.txHash === undefined ||
254
+ !result.messageToSign
255
+ ) {
256
+ throw new Error(
257
+ 'Lighter SignApproveIntegrator returned an incomplete result'
258
+ )
259
+ }
260
+ return {
261
+ txType: result.txType,
262
+ txInfo: result.txInfo,
263
+ txHash: result.txHash,
264
+ messageToSign: result.messageToSign,
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Inject the L1 signature produced by the user's Ethereum wallet into a
270
+ * signed txInfo JSON. `L1Sig` is the only field that depends on the L1
271
+ * signature; txHash does NOT include it (so we do not recompute it). Shared
272
+ * by the REGISTER_API_KEY (ChangePubKey) and APPROVE_INTEGRATOR flows.
218
273
  */
219
274
  embedL1Signature(txInfo: string, l1Signature: string): string {
220
275
  let parsed: Record<string, unknown>
@@ -222,7 +277,7 @@ export class LighterSigner {
222
277
  parsed = JSON.parse(txInfo) as Record<string, unknown>
223
278
  } catch (err) {
224
279
  throw new Error(
225
- `Failed to parse ChangePubKey txInfo as JSON: ${(err as Error).message}`
280
+ `Failed to parse signed txInfo as JSON: ${(err as Error).message}`
226
281
  )
227
282
  }
228
283
  parsed.L1Sig = l1Signature
@@ -291,7 +346,7 @@ export class LighterSigner {
291
346
  action: ActionType,
292
347
  p: Record<string, unknown>,
293
348
  ctx: LighterSignerContext
294
- ): SignResult {
349
+ ): SignResult & { messageToSign?: string } {
295
350
  const nonce = numberField(p, 'nonce')
296
351
  switch (action) {
297
352
  case ActionType.PLACE_ORDER:
@@ -81,6 +81,8 @@ export async function signWasmBlobActions(
81
81
  for (const step of steps) {
82
82
  if (step.action === ActionType.REGISTER_API_KEY) {
83
83
  signed.push(await signRegisterApiKey(deps, address, step, ctx))
84
+ } else if (step.action === ActionType.APPROVE_INTEGRATOR) {
85
+ signed.push(await signApproveIntegrator(deps, address, step, ctx))
84
86
  } else if (
85
87
  TOKEN_AUTH_MUTATION_KINDS.has(step.wasmSignParams.kind as string)
86
88
  ) {
@@ -196,6 +198,61 @@ async function signRegisterApiKey(
196
198
  }
197
199
  }
198
200
 
201
+ /**
202
+ * APPROVE_INTEGRATOR flow:
203
+ * 1. Load the user's stored API key (no keypair generation).
204
+ * 2. Wasm-sign the `L2ApproveIntegrator` blob with that key, obtaining the
205
+ * EIP-191 L1 message the wallet must countersign.
206
+ * 3. Have the user's L1 Ethereum wallet sign that message.
207
+ * 4. Inject the L1 signature into the txInfo JSON as `L1Sig`.
208
+ *
209
+ * Requires the end-user's wallet in `ctx.userWallet`: Lighter rejects the tx
210
+ * without the user's `L1Sig` when the integrator account belongs to a
211
+ * different L1 address with non-zero fee caps.
212
+ */
213
+ async function signApproveIntegrator(
214
+ deps: LighterSignActionsDeps,
215
+ address: Address,
216
+ step: WasmBlobActionStep,
217
+ ctx: SignActionsContext | undefined
218
+ ): Promise<WasmBlobSignedActionStep> {
219
+ const walletSigner = ctx?.userWallet
220
+ if (!walletSigner) {
221
+ throw new PerpsError(
222
+ PerpsErrorCode.SDKError,
223
+ 'APPROVE_INTEGRATOR requires the end-user wallet — pass `userWallet` to ' +
224
+ 'createPerpsClient or call setUserWallet(walletClient).'
225
+ )
226
+ }
227
+
228
+ const apiKey = await requireApiKey(deps, address)
229
+ const signed = await deps.signer.signApproveIntegrator(step.wasmSignParams, {
230
+ apiKeyPrivateKey: apiKey.apiKeyPrivateKey,
231
+ apiKeyIndex: apiKey.apiKeyIndex,
232
+ accountIndex: apiKey.accountIndex,
233
+ })
234
+
235
+ const l1Signature = await walletSigner.signMessage({
236
+ account: walletSigner.account,
237
+ message: signed.messageToSign,
238
+ })
239
+
240
+ const txInfoWithL1Sig = deps.signer.embedL1Signature(
241
+ signed.txInfo,
242
+ l1Signature
243
+ )
244
+
245
+ return {
246
+ action: step.action,
247
+ wasmSignParams: step.wasmSignParams,
248
+ signedTx: {
249
+ txType: signed.txType,
250
+ txInfo: txInfoWithL1Sig,
251
+ txHash: signed.txHash,
252
+ },
253
+ }
254
+ }
255
+
199
256
  /**
200
257
  * Execute a token-authenticated Lighter mutation (`ACCOUNT_TYPE` →
201
258
  * `/changeAccountTier`, `SET_REFERRAL` → `/referral/use`) client-side.
@@ -163,9 +163,13 @@ export interface LighterWasmExports {
163
163
  * 10 args: integratorAccountIndex, maxPerpsTakerFee, maxPerpsMakerFee,
164
164
  * maxSpotTakerFee, maxSpotMakerFee, approvalExpiry, skipNonce, then the
165
165
  * trailing three. Fees are uint32 ppm of `FeeTick` (1_000_000); a non-nil
166
- * fee requires a non-nil integrator index.
166
+ * fee requires a non-nil integrator index. `messageToSign` is the EIP-191
167
+ * `L2ApproveIntegrator` L1 body the user's wallet must countersign — the
168
+ * venue rejects the tx without the resulting `L1Sig`.
167
169
  */
168
- SignApproveIntegrator: (...args: unknown[]) => SignResult
170
+ SignApproveIntegrator: (
171
+ ...args: unknown[]
172
+ ) => SignResult & { messageToSign?: string }
169
173
  /**
170
174
  * 5 args: accountTradingMode (0 = Classic/Simple, 1 = Unified), skipNonce,
171
175
  * then the trailing three. `accountTradingMode` is validated to {0, 1} by
@@ -185,7 +185,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
185
185
 
186
186
  constructor(
187
187
  wsUrl: string = DEFAULT_LIGHTER_WS_URL,
188
- providerKey = 'lighter',
188
+ providerKey: string = LIGHTER_PROVIDER_KEY,
189
189
  options: LighterWsProviderOptions = {},
190
190
  client?: PerpsSDKClient
191
191
  ) {
@@ -199,7 +199,7 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
199
199
  policy: resolveRetryPolicy(
200
200
  LIGHTER_RETRY_DEFAULTS,
201
201
  client?.config.retry,
202
- LIGHTER_PROVIDER_KEY
202
+ providerKey
203
203
  ),
204
204
  fetchImpl: client?.config.fetch,
205
205
  }
@@ -468,19 +468,19 @@ export class LighterWsProvider extends WsProviderBase<SubState> {
468
468
  try {
469
469
  msg = JSON.parse(raw) as LtWsMessage
470
470
  } catch {
471
- wsLog.parseFailure(LIGHTER_PROVIDER_KEY, raw)
471
+ wsLog.parseFailure(this.providerKey, raw)
472
472
  return
473
473
  }
474
474
 
475
475
  if (!isValidLighterFrame(msg)) {
476
- wsLog.parseFailure(LIGHTER_PROVIDER_KEY, raw)
476
+ wsLog.parseFailure(this.providerKey, raw)
477
477
  return
478
478
  }
479
479
 
480
480
  try {
481
481
  this.dispatch(msg)
482
482
  } catch (error) {
483
- wsLog.handlerFailure(LIGHTER_PROVIDER_KEY, error)
483
+ wsLog.handlerFailure(this.providerKey, error)
484
484
  }
485
485
  }
486
486