- The user’s L1 wallet — any EVM-compatible signer that owns the funds.
- An SDK-managed API key — a Lighter-native (Schnorr-style) keypair the SDK creates and registers on-chain once.
REGISTER_API_KEY step). This page explains what the two signatures are and what the API key can and cannot do — Setup covers the step-by-step API.
The L1 (user) wallet signature
The user’s EVM-compatible signer — provided to the SDK viauserWallet (createPerpsClient) or setUserWallet() — is asked to sign only two things on Lighter:
DEPOSIT— the on-chain EVM transaction(s) that fund the account (and create it on first deposit).- The
REGISTER_API_KEYcountersignature — an EIP-191 message authorizing the new API key. This is the L1 half of the dual-signed registration: the new API key signs the on-chainChangePubKeyblob, and the L1 wallet countersigns an EIP-191 message proving the account owner approves that key. (See Setup for the full dual-signing flow.)
The API key
The API key is a Lighter-native keypair (Schnorr-style — not an EVM key). The SDK generates it, and its public key is registered on-chain in one of the account’s key slots viaREGISTER_API_KEY. Lighter verifies every trade against that registered public key. Signing happens inside a Go WASM signer that produces opaque { txType, txInfo, txHash } blobs.
What the API key does:
- Signs every trading and operational action —
placeOrder,placeTriggerOrder,cancelOrders,modifyOrders,updateLeverage,updatePositionMargin, andWITHDRAWAL.
- It is not the L1 wallet — it cannot sign the
DEPOSITtransaction or the EIP-191 countersignature for its own registration. Those require the account owner’s EVM wallet. - It cannot redirect funds — withdrawals are constrained to the account owner’s address by the backend, so a leaked API key cannot drain funds elsewhere.
- It is not an EVM key — it’s a Lighter-native (Schnorr-style) keypair that means nothing to an EVM chain; don’t reuse it as a wallet key.
How the API key is created
The SDK generates a fresh Lighter-native keypair client-side (LighterSigner.generateAPIKey()), then registers its public key on-chain at a key slot — slot 42 by default (DEFAULT_API_KEY_INDEX) — through the dual-signed REGISTER_API_KEY step. Re-registering the same slot overwrites the previous key.
How the API key is stored
The keypair is persisted through the SDK’sLighterKeyStore over a pluggable StorageAdapter (default: browser localStorage; pass a custom adapter for non-browser environments). Each record holds { accountIndex, apiKeyIndex, apiKeyPrivateKey, apiKeyPublicKey }.
- Namespace:
lifi-perps-lighter-key:<l1-address>— keyed per L1 address. - Scope: client-side only. The API key private key is never transmitted to the LI.FI backend.
checkSetup() re-surfaces REGISTER_API_KEY and the user registers a fresh keypair at the same slot.
Who signs what
| Action | Signer |
|---|---|
placeOrder, placeTriggerOrder, cancelOrders, modifyOrders, updateLeverage, updatePositionMargin, WITHDRAWAL | SDK-managed API key (WASM signer, no popup) |
DEPOSIT | User’s L1 wallet (EVM transaction) |
REGISTER_API_KEY | Both — the new API key signs the ChangePubKey blob, the L1 wallet countersigns (EIP-191) |
WASM signer dispatch
Trading blobs are produced by a Go WASM module loaded once per process. Each action maps to a specific WASM signing call; the SDK’s high-level methods handle this dispatch automatically.| Action | WASM call |
|---|---|
PLACE_ORDER | SignCreateOrder |
CANCEL_ORDER | SignCancelOrder |
CANCEL_ALL_ORDERS | SignCancelAllOrders |
MODIFY_ORDER | SignModifyOrder |
UPDATE_LEVERAGE | SignUpdateLeverage |
UPDATE_POSITION_MARGIN | SignUpdateMargin |
WITHDRAWAL | SignWithdraw |
TRANSFER (internal, fastwithdraw) | SignTransfer |
REGISTER_API_KEY | SignChangePubKey (dual-signed — see Setup) |