Skip to main content
Lighter uses a two-part signing scheme — the user approves a signer once, then trades execute without a per-trade wallet popup. Two keys sign two classes of action:
  • 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.
Registering the API key is part of the one-time Setup flow (the dual-signed 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 via setUserWallet() on PerpsClient — is used only when a provider descriptor declares signer: USER:
  • DEPOSIT — the on-chain EVM transaction(s) that fund the account (and create it on first deposit).
  • REGISTER_API_KEY — the native key signs ChangePubKey and the L1 wallet countersigns its EIP-191 authorization.
  • APPROVE_INTEGRATOR — the native key signs the approval blob and the L1 owner supplies the protocol-required EIP-191 signature.
  • TRANSFER — the internal step of a fast withdrawal: the native key signs the transfer and the L1 wallet signs an EIP-191 message that binds the destination to the owner.
Other trading and operational actions have signer: SDK: the plugin fulfils them with the API key.

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 via REGISTER_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, placeTwapOrder, cancelTwapOrder, cancelAllOrders, sendAsset, updateAssetCollateral, and standard WITHDRAWAL.
What the API key does not do:
  • It is not the L1 wallet — it cannot sign the DEPOSIT transaction 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. A fast withdrawal additionally needs the L1 wallet’s EIP-191 signature.
  • 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 through the dual-signed REGISTER_API_KEY step. The backend selects the key slot and sends it in wasmSignParams.api_key_index; the SDK signs whatever slot the backend names. Re-registering a slot overwrites the previous key.

How the API key is stored

Each lighterProvider() / lighterRhProvider() instance creates its own LighterKeyStore over the provider’s storage option. The default is encrypted browser localStorage; pass a custom adapter only for SSR, tests, or another persistence backend. Consumers do not construct or inject a key store separately. Each record holds { providerKey, accountIndex, apiKeyIndex, apiKeyPrivateKey, apiKeyPublicKey, appliedReferralCode? }. Records whose providerKey belongs to another provider instance are discarded.
  • Namespace: mainnet preserves lifi-perps-lighter-key:<l1-address>; additional deployments include the provider key, for example lifi-perps-lighter-key:lighter-rh:<l1-address>.
  • Scope: client-side only. The private key is never transmitted to the LI.FI backend.
If the local store is cleared (or the user is on a new device), the SDK has no signing key — checkSetup() re-surfaces REGISTER_API_KEY and the user registers a fresh keypair at the slot the backend assigns.

Who signs what

WASM signer dispatch

The provider instance owns the Go WASM signer and loads it once per process (see WASM signer for size, CSP, and warm-up). Each action maps to a specific WASM call; applications do not dispatch methods themselves.