Core concepts
Mandates
A mandate is the boundary the owner signs once. It decides what an agent may do, how much it may spend and for how long.
What a mandate contains
Each mandate belongs to one vault and is tied to one session key. The agent signs with that session key, never with the owner's wallet.
- Action: SWAP or TRANSFER.
- Target: the approved venue for a swap, or the approved recipient address for a transfer.
- Per-action cap: the largest amount a single attempt may move.
- Total budget: the most the mandate may spend in its lifetime. The amount already spent is tracked against it.
- Expiry: 1 hour, 6 hours, 24 hours, 3 days or 7 days.
Amounts
Amounts are USDC with 6 decimals and are handled as exact integers in base units, never as floating-point numbers. 2.5 USDC is stored as 2500000.
The per-action cap cannot exceed the total budget, and no amount may be above the 10,000 USDC safety limit. The browser and the server use the same validation schema, so their rules cannot drift apart.
Status
A mandate is ACTIVE, REVOKED or EXPIRED. The status is calculated every time it is read instead of being stored, so it is never out of date.
Expiry uses the server clock, not the clock of the browser that created the mandate. Revoking sets a timestamp and takes effect immediately.
Creating a mandate
The owner wallet signs the request. The vault is derived on the server from the signing wallet, so a client cannot attach a mandate to someone else's vault. The session key must be different from the owner wallet.
{
"sessionPubkey": "<agent session public key>",
"config": {
"action": "TRANSFER",
"recipient": "<approved recipient address>",
"maxPerAction": "5",
"maxTotal": "50",
"expiresInHours": "24"
}
}Revoking
Only the owner can revoke a mandate. Ownership is checked inside the database query. A mandate that does not exist, is already revoked or belongs to another wallet all return the same not-found message, which prevents guessing which mandate IDs exist.
POST /api/mandates/{id}/revoke