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

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_authorizations table
  • is_active = true
  • expires_at is NULL or in the future

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 persistent (stored in DB)
  • Authorization does not expire unless expires_at is set (not yet implemented)

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:

  • Sets is_active = false in 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:

  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
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_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 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 = true
  • expires_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

  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
  4. Expiration: Set expires_at for temporary agent authorizations (when implemented)

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

"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_authorizations table
  • is_active = false
  • expires_at in the past (when enforced)

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