@lifi/perps-sdk-provider-lighter 1.6.0 → 1.7.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 +2 -0
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountConfig.d.ts.map +1 -1
- package/dist/cjs/accountConfig.js +15 -3
- package/dist/cjs/accountConfig.js.map +1 -1
- package/dist/cjs/constants.d.ts +2 -1
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +3 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/signers/LighterSigner.d.ts.map +1 -1
- package/dist/cjs/signers/LighterSigner.js +2 -0
- package/dist/cjs/signers/LighterSigner.js.map +1 -1
- package/dist/cjs/signers/signActions.d.ts +2 -0
- package/dist/cjs/signers/signActions.d.ts.map +1 -1
- package/dist/cjs/signers/signActions.js +37 -19
- package/dist/cjs/signers/signActions.js.map +1 -1
- package/dist/cjs/signers/wasmLoader.d.ts +1 -0
- package/dist/cjs/signers/wasmLoader.d.ts.map +1 -1
- package/dist/cjs/signers/wasmLoader.js +1 -0
- package/dist/cjs/signers/wasmLoader.js.map +1 -1
- package/dist/cjs/utils/apiClient.d.ts +4 -0
- package/dist/cjs/utils/apiClient.d.ts.map +1 -1
- package/dist/cjs/utils/apiClient.js +15 -0
- package/dist/cjs/utils/apiClient.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +2 -0
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountConfig.d.ts.map +1 -1
- package/dist/esm/accountConfig.js +27 -8
- package/dist/esm/accountConfig.js.map +1 -1
- package/dist/esm/constants.d.ts +9 -1
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +9 -1
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/signers/LighterSigner.d.ts.map +1 -1
- package/dist/esm/signers/LighterSigner.js +2 -0
- package/dist/esm/signers/LighterSigner.js.map +1 -1
- package/dist/esm/signers/signActions.d.ts +13 -5
- package/dist/esm/signers/signActions.d.ts.map +1 -1
- package/dist/esm/signers/signActions.js +68 -38
- package/dist/esm/signers/signActions.js.map +1 -1
- package/dist/esm/signers/wasmLoader.d.ts +6 -0
- package/dist/esm/signers/wasmLoader.d.ts.map +1 -1
- package/dist/esm/signers/wasmLoader.js +1 -0
- package/dist/esm/signers/wasmLoader.js.map +1 -1
- package/dist/esm/utils/apiClient.d.ts +10 -0
- package/dist/esm/utils/apiClient.d.ts.map +1 -1
- package/dist/esm/utils/apiClient.js +21 -0
- package/dist/esm/utils/apiClient.js.map +1 -1
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/accountConfig.d.ts.map +1 -1
- package/dist/types/constants.d.ts +9 -1
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/signers/LighterSigner.d.ts.map +1 -1
- package/dist/types/signers/signActions.d.ts +13 -5
- package/dist/types/signers/signActions.d.ts.map +1 -1
- package/dist/types/signers/wasmLoader.d.ts +6 -0
- package/dist/types/signers/wasmLoader.d.ts.map +1 -1
- package/dist/types/utils/apiClient.d.ts +10 -0
- package/dist/types/utils/apiClient.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/LighterProvider.ts +2 -0
- package/src/accountConfig.ts +30 -8
- package/src/constants.ts +10 -0
- package/src/signers/LighterSigner.ts +8 -0
- package/src/signers/signActions.ts +112 -44
- package/src/signers/wasmLoader.ts +7 -0
- package/src/utils/apiClient.ts +25 -0
|
@@ -11,20 +11,29 @@ import { ActionType, PerpsErrorCode, SigningMethod } from '@lifi/perps-types'
|
|
|
11
11
|
import type { Address } from 'viem'
|
|
12
12
|
import { parseAbi } from 'viem'
|
|
13
13
|
import { waitForTransactionReceipt } from 'viem/actions'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_API_KEY_INDEX,
|
|
16
|
+
LIGHTER_MUTATION_SUCCESS_CODE,
|
|
17
|
+
} from '../constants.js'
|
|
18
|
+
import type { ApiParams, LighterApiClient } from '../utils/apiClient.js'
|
|
15
19
|
import type { LighterApiKey, LighterKeyStore } from './LighterKeyStore.js'
|
|
16
20
|
import type { LighterSigner } from './LighterSigner.js'
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Per-batch dependencies the Lighter `signActions` implementation needs:
|
|
20
24
|
* a WASM signer (`LighterSigner`), the API-key store keyed on L1 address,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
25
|
+
* a REST client for the token-authenticated venue mutations that execute
|
|
26
|
+
* client-side, and a reference to the host SDK client for descriptor/account
|
|
27
|
+
* fetches within the REGISTER_API_KEY hybrid flow.
|
|
23
28
|
* @internal
|
|
24
29
|
*/
|
|
25
30
|
export interface LighterSignActionsDeps {
|
|
26
31
|
signer: LighterSigner
|
|
27
32
|
keyStore: LighterKeyStore
|
|
33
|
+
/** REST client bound to the user's Lighter base URL, used for the direct
|
|
34
|
+
* `changeAccountTier` / `referral/use` POSTs so their auth token never
|
|
35
|
+
* transits the LI.FI backend. */
|
|
36
|
+
apiClient: LighterApiClient
|
|
28
37
|
/**
|
|
29
38
|
* Resolve the user's Lighter `accountIndex` for an L1 address. Required
|
|
30
39
|
* by REGISTER_API_KEY so the ChangePubKey blob carries the right
|
|
@@ -34,15 +43,32 @@ export interface LighterSignActionsDeps {
|
|
|
34
43
|
resolveAccountIndex(address: Address): Promise<number>
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
/**
|
|
47
|
+
* `wasmSignParams.kind` values for the Lighter mutations that authenticate with
|
|
48
|
+
* a short-lived auth token instead of a wasm-signed transaction. Both execute
|
|
49
|
+
* client-side (SDK → Lighter REST) so the token never reaches the backend.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
const TOKEN_AUTH_MUTATION_KINDS = new Set(['changeAccountTier', 'referralUse'])
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Lifetime of the per-call auth token minted for a token-authenticated venue
|
|
56
|
+
* mutation. Minutes, not the read endpoints' hours: it authenticates one POST
|
|
57
|
+
* that completes immediately, so it never lingers usable.
|
|
58
|
+
*/
|
|
59
|
+
const TOKEN_AUTH_MUTATION_DEADLINE_SECONDS = 5 * 60
|
|
60
|
+
|
|
37
61
|
/**
|
|
38
62
|
* Sign a `WASM_BLOB` batch (Lighter). Ensures the user's API keypair is
|
|
39
63
|
* registered first — generating one and running the REGISTER_API_KEY
|
|
40
64
|
* hybrid flow via the L1 signer if not — then feeds each subsequent step
|
|
41
65
|
* through the WASM signer.
|
|
42
66
|
*
|
|
43
|
-
* `ACCOUNT_TYPE`
|
|
44
|
-
*
|
|
45
|
-
*
|
|
67
|
+
* The token-authenticated mutations (`ACCOUNT_TYPE` → `/changeAccountTier`,
|
|
68
|
+
* `SET_REFERRAL` → `/referral/use`) are NOT wasm-signed: they execute
|
|
69
|
+
* client-side here via a direct Lighter POST authenticated with a short-lived
|
|
70
|
+
* auth token, so the token never transits the LI.FI backend. They produce no
|
|
71
|
+
* backend-bound step and are omitted from the returned array.
|
|
46
72
|
* @internal
|
|
47
73
|
*/
|
|
48
74
|
export async function signWasmBlobActions(
|
|
@@ -55,8 +81,10 @@ export async function signWasmBlobActions(
|
|
|
55
81
|
for (const step of steps) {
|
|
56
82
|
if (step.action === ActionType.REGISTER_API_KEY) {
|
|
57
83
|
signed.push(await signRegisterApiKey(deps, address, step, ctx))
|
|
58
|
-
} else if (
|
|
59
|
-
|
|
84
|
+
} else if (
|
|
85
|
+
TOKEN_AUTH_MUTATION_KINDS.has(step.wasmSignParams.kind as string)
|
|
86
|
+
) {
|
|
87
|
+
await executeTokenAuthMutation(deps, address, step)
|
|
60
88
|
} else {
|
|
61
89
|
signed.push(await signStandardWasmAction(deps, address, step))
|
|
62
90
|
}
|
|
@@ -169,44 +197,94 @@ async function signRegisterApiKey(
|
|
|
169
197
|
}
|
|
170
198
|
|
|
171
199
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* Lighter's `/api/v1/changeAccountTier` is an HTTP-only mutation —
|
|
175
|
-
* Lighter does NOT consume a wasm-signed transaction here; it
|
|
176
|
-
* authenticates the request with the same auth token its read endpoints
|
|
177
|
-
* use, and enforces anti-replay business rules server-side. The backend
|
|
178
|
-
* declares the step as a `WasmBlobActionStep` with
|
|
179
|
-
* `wasmSignParams.kind = 'changeAccountTier'` and expects the SDK to create
|
|
180
|
-
* an auth token in lieu of a transaction signature; the executor reads
|
|
181
|
-
* `signedTx.txInfo` as the `auth` form parameter.
|
|
200
|
+
* Execute a token-authenticated Lighter mutation (`ACCOUNT_TYPE` →
|
|
201
|
+
* `/changeAccountTier`, `SET_REFERRAL` → `/referral/use`) client-side.
|
|
182
202
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
203
|
+
* Neither endpoint consumes a wasm-signed transaction — both authenticate with
|
|
204
|
+
* a Lighter auth token and enforce their business rules (open positions,
|
|
205
|
+
* pending orders, 24h tier cooldown) server-side. We mint a fresh short-lived
|
|
206
|
+
* token per call (never the stored read-only token, never persisted) and POST
|
|
207
|
+
* the mirror of the backend's form body directly to Lighter, so no auth token
|
|
208
|
+
* ever transits the LI.FI backend. A non-success verdict surfaces Lighter's
|
|
209
|
+
* `code`/`message` verbatim as an {@link PerpsErrorCode.ExchangeRejected}.
|
|
186
210
|
*/
|
|
187
|
-
async function
|
|
211
|
+
async function executeTokenAuthMutation(
|
|
188
212
|
deps: LighterSignActionsDeps,
|
|
189
213
|
address: Address,
|
|
190
214
|
step: WasmBlobActionStep
|
|
191
|
-
): Promise<
|
|
215
|
+
): Promise<void> {
|
|
192
216
|
const apiKey = await requireApiKey(deps, address)
|
|
193
|
-
const deadline =
|
|
217
|
+
const deadline =
|
|
218
|
+
Math.floor(Date.now() / 1000) + TOKEN_AUTH_MUTATION_DEADLINE_SECONDS
|
|
194
219
|
const authToken = await deps.signer.createAuthToken(deadline, {
|
|
195
220
|
apiKeyPrivateKey: apiKey.apiKeyPrivateKey,
|
|
196
221
|
apiKeyIndex: apiKey.apiKeyIndex,
|
|
197
222
|
accountIndex: apiKey.accountIndex,
|
|
198
223
|
})
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
224
|
+
|
|
225
|
+
const { path, params } = buildTokenAuthMutationRequest(step, authToken)
|
|
226
|
+
const { status, data } = await deps.apiClient.postForm<{
|
|
227
|
+
code?: number
|
|
228
|
+
message?: string
|
|
229
|
+
}>(path, params)
|
|
230
|
+
|
|
231
|
+
const code = data?.code
|
|
232
|
+
if (status < 200 || status >= 300 || code !== LIGHTER_MUTATION_SUCCESS_CODE) {
|
|
233
|
+
const suffix = data?.message ? `: ${data.message}` : ''
|
|
234
|
+
throw new PerpsError(
|
|
235
|
+
PerpsErrorCode.ExchangeRejected,
|
|
236
|
+
`Lighter ${step.action} rejected (code ${code ?? status})${suffix}`
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Map a token-authenticated mutation step to its Lighter endpoint and
|
|
243
|
+
* form body, mirroring the backend's request contract. The auth token is the
|
|
244
|
+
* `auth` field; the remaining fields come from `wasmSignParams`.
|
|
245
|
+
*/
|
|
246
|
+
function buildTokenAuthMutationRequest(
|
|
247
|
+
step: WasmBlobActionStep,
|
|
248
|
+
authToken: string
|
|
249
|
+
): { path: string; params: ApiParams } {
|
|
250
|
+
const kind = step.wasmSignParams.kind
|
|
251
|
+
if (kind === 'changeAccountTier') {
|
|
252
|
+
const { account_index, new_tier } = step.wasmSignParams as {
|
|
253
|
+
account_index?: number
|
|
254
|
+
new_tier?: string
|
|
255
|
+
}
|
|
256
|
+
if (account_index == null || new_tier == null) {
|
|
257
|
+
throw new PerpsError(
|
|
258
|
+
PerpsErrorCode.SDKError,
|
|
259
|
+
'Lighter ACCOUNT_TYPE wasmSignParams missing account_index or new_tier'
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
path: '/api/v1/changeAccountTier',
|
|
264
|
+
params: { auth: authToken, account_index, new_tier },
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (kind === 'referralUse') {
|
|
268
|
+
const { l1_address, referral_code, x } = step.wasmSignParams as {
|
|
269
|
+
l1_address?: string
|
|
270
|
+
referral_code?: string
|
|
271
|
+
x?: string
|
|
272
|
+
}
|
|
273
|
+
if (l1_address == null || referral_code == null || x == null) {
|
|
274
|
+
throw new PerpsError(
|
|
275
|
+
PerpsErrorCode.SDKError,
|
|
276
|
+
'Lighter SET_REFERRAL wasmSignParams missing l1_address, referral_code, or x'
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
path: '/api/v1/referral/use',
|
|
281
|
+
params: { auth: authToken, l1_address, referral_code, x },
|
|
282
|
+
}
|
|
209
283
|
}
|
|
284
|
+
throw new PerpsError(
|
|
285
|
+
PerpsErrorCode.SDKError,
|
|
286
|
+
`Lighter token-auth mutation: unknown wasmSignParams.kind '${String(kind)}'`
|
|
287
|
+
)
|
|
210
288
|
}
|
|
211
289
|
|
|
212
290
|
async function requireApiKey(
|
|
@@ -266,16 +344,6 @@ export async function signEvmTxActions(
|
|
|
266
344
|
)
|
|
267
345
|
}
|
|
268
346
|
|
|
269
|
-
if (walletSigner.chain?.id !== params.chainId) {
|
|
270
|
-
throw new PerpsError(
|
|
271
|
-
PerpsErrorCode.SDKError,
|
|
272
|
-
`EVM_TX leg '${step.action}' targets chain ${params.chainId}, but the ` +
|
|
273
|
-
`connected wallet is on chain ${walletSigner.chain?.id ?? 'unknown'}. ` +
|
|
274
|
-
`Switch the wallet to chain ${params.chainId} before signing — the ` +
|
|
275
|
-
"SDK broadcasts on the wallet's active chain and does not switch it."
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
347
|
const txHash = await walletSigner.writeContract({
|
|
280
348
|
address: params.to,
|
|
281
349
|
abi: parseAbi(params.abi),
|
|
@@ -166,6 +166,12 @@ export interface LighterWasmExports {
|
|
|
166
166
|
* fee requires a non-nil integrator index.
|
|
167
167
|
*/
|
|
168
168
|
SignApproveIntegrator: (...args: unknown[]) => SignResult
|
|
169
|
+
/**
|
|
170
|
+
* 5 args: accountTradingMode (0 = Classic/Simple, 1 = Unified), skipNonce,
|
|
171
|
+
* then the trailing three. `accountTradingMode` is validated to {0, 1} by
|
|
172
|
+
* lighter-go before signing.
|
|
173
|
+
*/
|
|
174
|
+
SignUpdateAccountConfig: (...args: unknown[]) => SignResult
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
/** @internal */
|
|
@@ -191,6 +197,7 @@ const WASM_FUNCTION_NAMES = [
|
|
|
191
197
|
'SignModifyOrder',
|
|
192
198
|
'SignUpdateMargin',
|
|
193
199
|
'SignApproveIntegrator',
|
|
200
|
+
'SignUpdateAccountConfig',
|
|
194
201
|
] as const
|
|
195
202
|
|
|
196
203
|
type GoClass = new () => {
|
package/src/utils/apiClient.ts
CHANGED
|
@@ -160,6 +160,31 @@ export class LighterApiClient {
|
|
|
160
160
|
return { status: response.status, data }
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Form-encoded POST to a Lighter mutation endpoint. Single-shot — never
|
|
165
|
+
* retried, since these are money/state writes whose outcome is unknown on a
|
|
166
|
+
* transport failure. Surfaces the raw `{status, body}` pair so the caller can
|
|
167
|
+
* map Lighter's per-endpoint business-rule `code` to a domain error verbatim.
|
|
168
|
+
*/
|
|
169
|
+
async postForm<T>(
|
|
170
|
+
path: string,
|
|
171
|
+
params: ApiParams
|
|
172
|
+
): Promise<{ status: number; data: T }> {
|
|
173
|
+
const body = new URLSearchParams()
|
|
174
|
+
for (const [k, v] of Object.entries(params)) {
|
|
175
|
+
body.set(k, String(v))
|
|
176
|
+
}
|
|
177
|
+
const fetchImpl = this.fetchImpl ?? fetch
|
|
178
|
+
const response = await fetchImpl(`${this.baseUrl}${path}`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
181
|
+
body: body.toString(),
|
|
182
|
+
signal: this.signal,
|
|
183
|
+
})
|
|
184
|
+
const data = (await response.json().catch(() => undefined)) as T
|
|
185
|
+
return { status: response.status, data }
|
|
186
|
+
}
|
|
187
|
+
|
|
163
188
|
private async getChecked<T>(path: string, params?: ApiParams): Promise<T> {
|
|
164
189
|
const { status, data } = await this.getWithStatus<unknown>(path, params)
|
|
165
190
|
this.assertOk(path, status, data)
|