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 userWallet (createPerpsClient) or setUserWallet() — is used only when a provider descriptor includes the public USER signer:
  • 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.
Other trading and operational actions are handled by the plugin’s public SDK signer.

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, and 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, 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 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 { accountIndex, apiKeyIndex, apiKeyPrivateKey, apiKeyPublicKey }.
  • 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. Each action maps to a specific WASM call; applications do not load the module or dispatch methods themselves.