Skip to main content

Agent Authorization

Multi-key signing support for market makers.

Overview​

Agent authorization allows a dedicated signing key (agent) to place and cancel orders on behalf of a trading wallet. This is useful for:

  • Security: Keep trading wallet keys in cold storage, use hot keys for signing
  • Operational: Multiple team members can sign with different keys
  • Automation: Automated systems can sign with dedicated keys

Two Authorization Models​

Hypercall has two separate delegation systems depending on the product:

ProductDelegation TypeStorageHow to Set Up
Options (REST API)Agent authorizationHypercall engine statePOST /approve-agent
PerpsAPI walletOn-chain (your Account contract)hc_update_api_wallet directive

These are independent systems. Approving an agent for options trading does not authorize it for perps, and vice versa.

Options: Agent Authorization (Off-Chain)​

Agents sign PlaceOrder and CancelOrder EIP-712 messages on behalf of a trading wallet. The API checks the agent against the engine's authorization state before the order reaches the engine.

Perps: API Wallet (On-Chain)​

API wallets sign perp orders and cancels in the HypercallApiSign EIP-712 domain. Hypercall checks the signer against your Account contract (isApiWalletActive()) before admitting the order. On the TEE route (the app default, and opt-in for API clients), a secure enclave holding a separate Hyperliquid trading key for your Account signs and sends the native order; on the smart contract route, an advanced setting in the app, the order goes through your Account contract. See EIP-712 Signing for the signing reference and Perps for the flow.

To add or remove an API wallet, submit an hc_update_api_wallet directive signed by the account's manager wallet.

This mirrors Hyperliquid's API wallet model. Hyperliquid documents these as API wallets, also called agent wallets, in Nonces and API wallets, and documents approveAgent in the Exchange endpoint.

Nonce Replay Protection​

Nonce tracking follows the Hyperliquid nonce model. All signed actions (orders, agent approval/revocation, QP handshakes) share a per-signer nonce space. The engine stores the 100 highest nonces per signer address. A new nonce is accepted if:

  1. !stored_set.contains(nonce): it is not a duplicate
  2. nonce > min(stored_set) once the set holds 100 entries: it is greater than the smallest stored nonce
  3. nonce is within (T - 2 days, T + 1 day) of the server timestamp

Out-of-order nonces are allowed (e.g., nonce 105 then 102 both work if both are above the set minimum). The set is bounded at 100 entries, so the oldest entries are evicted as new ones are added. This prevents account bricking from a single large nonce.

The nonce signer is the address that signed the EIP-712 message: the API wallet for orders, the recovered signer for agent auth, the QP wallet for handshakes. Clients should use Date.now() (millisecond epoch) as the nonce seed and increment monotonically.

Options Agent Authorization​

Direct Signing​

If signer == wallet, the order is always authorized (self-signing).

Agent Signing​

If signer != wallet, the signer must be an authorized agent:

  • Agent must be present in engine-owned authorization state
  • The authorization must not be expired
  • Engine snapshots and the engine journal are the durable restart sources

Approve Agent​

Endpoint: POST /approve-agent

Request: ApproveAgentRequest

{
"agent": "0x...",
"nonce": 1,
"signature": "0x..."
}

Signing:

  • Wallet owner signs the ApproveAgent message
  • Wallet is derived from recovered signature
  • Agent is the agent field in the request

EIP-712 struct:

struct ApproveAgent {
address agent;
uint64 nonce;
}

Response: ApproveAgentResponse

{
"success": true,
"error": null
}

Notes:

  • Agent authorization is journaled engine state and survives restarts
  • Approvals made through this endpoint do not expire

Revoke Agent​

Endpoint: DELETE /revoke-agent

Request: RevokeAgentRequest

{
"agent": "0x...",
"nonce": 2,
"signature": "0x..."
}

Signing:

  • Wallet owner signs the RevokeAgent message
  • Wallet is derived from recovered signature

EIP-712 struct:

struct RevokeAgent {
address agent;
uint64 nonce;
}

Response: RevokeAgentResponse

Notes:

  • Applies a journaled RevokeAgent command to engine-owned authorization state
  • Trading API authorization checks read backend state and enforce revocations for trading requests. Trollbox posting may cache positive agent authorization for longer, so a recently revoked agent may still be able to post until that cache expires or best-effort invalidation reaches the relevant edge location. This cache does not authorize trading API mutations.
  • Agent can no longer sign for the wallet after revocation

Revoke All Agents​

Endpoint: DELETE /revoke-all-agents

Request: { "nonce": 3, "signature": "0x..." }, signed by the wallet owner over RevokeAllAgents(uint64 nonce). Removes every agent for the recovered wallet.

Get Authorized Agents​

Endpoint: GET /authorized-agents?wallet=...

Query parameters:

  • wallet (required)

Response:

{
"agents": [
"0x...",
"0x..."
]
}

Notes:

  • Returns only active, non-expired agents

Agent Usage​

Signing Orders with Agent​

Once an agent is approved:

  1. Sign order with agent wallet: Use agent wallet to sign PlaceOrder / CancelOrder messages
  2. Set wallet field: Set wallet field to the trading wallet address (not agent address)
  3. Middleware verification: Middleware verifies agent is authorized for that wallet

Example:

// Agent wallet signs the order
const agentSigner = new ethers.Wallet(agentPrivateKey);

const message = {
wallet: "0x...", // Trading wallet (not agent)
symbol: "BTC-20250131-100000-C",
side: "Buy",
size: "0.1",
price: "100.0",
tif: "gtc",
clientId: "mm-1",
nonce: 1
};

// Sign with agent wallet
const signature = await agentSigner._signTypedData(domain, types, message);

// Send request. The REST body uses snake_case `client_id`.
const { clientId, ...rest } = message;
const response = await fetch('/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...rest,
client_id: clientId,
signature
})
});

Bulk Orders with Agent​

Bulk endpoints verify agent authorization per-item:

  • Each order in POST /bulk_order can be signed by a different agent
  • Agent authorization is checked per-item
  • If any item fails agent auth, that item returns error in BulkOrderResult

Authorization Checks​

Middleware (Single Orders)​

Endpoints:

  • POST /order
  • DELETE /order
  • DELETE /order_cloid

Check: signature_and_agent_middleware verifies:

  1. Signature recovery succeeds
  2. Signer is authorized (signer == wallet OR agent authorized)

Handler (Bulk Orders)​

Endpoints:

  • POST /bulk_order
  • DELETE /bulk_order
  • DELETE /bulk_order_cloid

Check: Per-item verification in handler:

  1. Signature recovery per-item
  2. Agent authorization check per-item

Authorization Storage​

Agent authorization is engine-owned state. ApproveAgent and RevokeAgent commands are journaled, restored through engine replay, and exposed through the read snapshot for API checks.

Authorization Logic​

Signer authorization is explicit OR logic:

  • Direct signing: signer == wallet.
  • Agent signing: the engine snapshot contains an authorization record for wallet_address == <wallet> and agent_address == <signer>, and expires_at is absent or in the future.

Expiration​

The engine supports an expires_at on an authorization and treats an expired agent as unauthorized. POST /approve-agent does not set one, so agents approved through the API stay active until revoked.

Security Considerations​

  1. Agent key security: Protect agent private keys (use hardware wallets or secure key management)
  2. Regular audits: Review authorized agents via GET /authorized-agents regularly
  3. Revoke unused agents: Revoke agents that are no longer needed

Best Practices​

  1. Use agents for automation: Keep trading wallet keys in cold storage, use agents for automated systems
  2. Limit agent scope: Only approve agents that need access
  3. Monitor agent usage: Track which agents are placing orders
  4. Revoke promptly: Revoke agents immediately when no longer needed

Common Issues​

"Signer 0x... is not authorized to act for wallet 0x..."​

Cause: Agent not approved or authorization expired/revoked.

Solution: Approve agent via POST /approve-agent or sign with wallet directly.

Agent Authorization Not Working​

Causes:

  • Agent not present in engine-owned authorization state
  • Authorization was revoked
  • Authorization is expired

Solution: Check agent status via GET /authorized-agents?wallet=....