> ## Documentation Index
> Fetch the complete documentation index at: https://public-perps-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing Model

> The two-part signing scheme on Hyperliquid: the user's L1 wallet and the SDK-managed agent

Trading on Hyperliquid uses a **two-part signing scheme**. Two different keys sign two different classes of action, and understanding the split is the key to understanding why trades don't trigger a wallet popup.

* The **user's L1 wallet** — any EVM-compatible signer the user connects. It owns the funds.
* An **SDK-managed agent** — a separate keypair the SDK creates and the user authorizes *once*.

The user approves the agent a single time; from then on the agent signs every trade, so there is no wallet prompt per order. Provisioning the agent is part of the one-time [Setup](/providers/hyperliquid/setup) flow — this page explains *what the two signatures are and why*, not the step-by-step API (which Setup covers).

## The L1 (user) wallet signature

This is a signature from the user's own **EVM-compatible** signer — MetaMask, a smart-contract wallet, a backend signer, anything that can produce an EVM signature. It is the key that ultimately owns the account and its balance. You hand it to the SDK via `userWallet` at construction (`createPerpsClient`) or later via [`setUserWallet()`](/sdk/trading/methods#setuserwallet).

These user-level messages are signed as EIP-712 typed data with the signature chain set to **Arbitrum** — Hyperliquid's L1 signature chain. The signer is just an EVM key; it is not tied to Ethereum the chain, protocol, or asset.

On Hyperliquid the L1 wallet is asked to sign **only the few actions the protocol requires the account owner to authorize directly**:

* **`APPROVE_AGENT`** — authorizes the agent keypair (the one-time bootstrap that makes popup-free trading possible).
* **`APPROVE_BUILDER_FEE`** — authorizes LI.FI's per-trade builder fee.
* **`WITHDRAWAL`** — moves funds off Hyperliquid. Agents **cannot** withdraw.
* **`SEND_ASSET`** — transfers balance between sub-accounts / venues. Agents **cannot** move assets.

These all either delegate authority or move real funds, so the protocol **requires** the account owner's own signature for them — they are mandatory and cannot be delegated to the agent.

## The agent signature

An **agent** (Hyperliquid also calls it an "API wallet" or "approved wallet") is a key the account owner has authorized to **place orders on its behalf** — but which **cannot withdraw or move funds**. That asymmetry is what makes it safe to keep an agent key on the client: the worst it can do is trade.

In `@lifi/perps-sdk` the agent is a standard **secp256k1 keypair** (the same curve EVM wallets use), generated and held by the SDK — distinct from the user's wallet. It signs every trading action as EIP-712 typed data with no wallet prompt: `placeOrder`, `placeTriggerOrder`, `cancelOrders`, `modifyOrders`, `updateLeverage`, and `updatePositionMargin`. The agent also signs the one-time `setReferrer` setup step, which is why that step is sequenced after `approveAgent`.

### How the agent is created

The SDK generates the agent keypair **client-side**, the first time an agent is needed (during the `APPROVE_AGENT` setup step). The agent's **public address** is what the user's wallet signs to approve; the **private key never leaves the client**. The LI.FI backend and Hyperliquid only ever see the agent's address, never its private key.

### How the agent is stored

The keypair is persisted through a pluggable **`StorageAdapter`**. The default is the browser's `localStorage`; pass your own adapter (via the provider/client config) for non-browser environments such as a server or a secure enclave.

* **Namespace:** `lifi-perps-agent:<l1-address>:hyperliquid` — one agent per L1 address per provider.
* **Scope:** client-side only. The agent private key is never transmitted to the LI.FI backend.

### Expiry and re-provisioning

An agent approval can lapse (Hyperliquid policy, or an optional TTL set at approval time), and a user on a new device won't have the stored key. When that happens, [`checkSetup()`](/providers/hyperliquid/setup) re-surfaces `APPROVE_AGENT` and the user re-approves once. Agents are **not** silently re-provisioned mid-trade — a trading call with no valid agent fails and points you back through Setup, so a re-approval is always an explicit, user-visible step.

## Who signs what

| Action                                                                                                      | Signer                                               |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `placeOrder`, `placeTriggerOrder`, `cancelOrders`, `modifyOrders`, `updateLeverage`, `updatePositionMargin` | SDK-managed agent (no popup)                         |
| `APPROVE_AGENT`, `APPROVE_BUILDER_FEE`                                                                      | User's L1 wallet (one-time setup)                    |
| `WITHDRAWAL`, `SEND_ASSET`                                                                                  | User's L1 wallet (always — agents cannot move funds) |

Approved agents appear in the account's `config.agents` array — see [Account Abstraction / Account Configuration](/providers/hyperliquid/account-abstraction#account-configuration).
