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:
| Product | Delegation Type | Storage | How to Set Up |
|---|---|---|---|
| Options (REST API) | Agent authorization | Hypercall engine state | POST /approve-agent |
| Perps | API wallet | On-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:
!stored_set.contains(nonce): it is not a duplicatenonce > min(stored_set)once the set holds 100 entries: it is greater than the smallest stored noncenonceis 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
ApproveAgentmessage - Wallet is derived from recovered signature
- Agent is the
agentfield 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
RevokeAgentmessage - Wallet is derived from recovered signature
EIP-712 struct:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Response: RevokeAgentResponse
Notes:
- Applies a journaled
RevokeAgentcommand 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:
- Sign order with agent wallet: Use agent wallet to sign
PlaceOrder/CancelOrdermessages - Set wallet field: Set
walletfield to the trading wallet address (not agent address) - 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_ordercan 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 /orderDELETE /orderDELETE /order_cloid
Check: signature_and_agent_middleware verifies:
- Signature recovery succeeds
- Signer is authorized (signer == wallet OR agent authorized)
Handler (Bulk Orders)
Endpoints:
POST /bulk_orderDELETE /bulk_orderDELETE /bulk_order_cloid
Check: Per-item verification in handler:
- Signature recovery per-item
- 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>andagent_address == <signer>, andexpires_atis 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
- Agent key security: Protect agent private keys (use hardware wallets or secure key management)
- Regular audits: Review authorized agents via
GET /authorized-agentsregularly - Revoke unused agents: Revoke agents that are no longer needed
Best Practices
- Use agents for automation: Keep trading wallet keys in cold storage, use agents for automated systems
- Limit agent scope: Only approve agents that need access
- Monitor agent usage: Track which agents are placing orders
- 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=....