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
Authorization Model
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 listed in
agent_authorizationstable is_active = trueexpires_atisNULLor in the future
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 persistent (stored in DB)
- Authorization does not expire unless
expires_atis set (not yet implemented)
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:
- Sets
is_active = falsein database (soft delete) - Agent can no longer sign for the wallet after revocation
Get Authorized Agents
Endpoint: GET /authorized-agents?wallet=...
Query parameters:
wallet(required)
Response:
{
"agents": [
"0x...",
"0x..."
]
}
Notes:
- Returns only active, non-expired agents
- Ordered by
created_at DESC
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
const response = await fetch('/order', {
method: 'POST',
body: JSON.stringify({
...message,
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 Database
Schema
Table: agent_authorizations
Columns:
wallet_address(BYTEA, PK)agent_address(BYTEA, PK)expires_at(TIMESTAMPTZ, nullable)is_active(BOOLEAN)created_at(TIMESTAMPTZ)
Primary key: (wallet_address, agent_address)
Query Logic
Agent is authorized if:
wallet_address == <wallet>agent_address == <signer>is_active = trueexpires_at IS NULL OR expires_at > NOW()
Expiration
CURRENT STATUS: Expiration (expires_at) is stored but not yet enforced in all code paths.
Intended behavior: Agents with expires_at < NOW() should be treated as unauthorized.
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
- Expiration: Set
expires_atfor temporary agent authorizations (when implemented)
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
"Unauthorized: signer not authorized for wallet"
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 in
agent_authorizationstable is_active = falseexpires_atin the past (when enforced)
Solution: Check agent status via GET /authorized-agents?wallet=....