The agent transacts. The chain enforces the fences.
A mandate is a typed delegation with a MINT budget, a CEL scope, and a block-height window. The agent acts; each action subtracts from the meter; out-of-scope actions are rejected at the gate; revocation slams the gate shut even when budget remains. No advisory spending limits — actual protocol-enforced bounds.
Bounded delegation
An agent can act for you — but inside fences.
A naïve delegation is a shared wallet — everything the agent can do, the principal is on the line for. A mandate is the opposite: a typed, on-chain bound. The agent can spend up to here; the agent can act within this scope; the agent can act between these blocks. Outside the fence, the chain refuses the action.
Step 1 · IssueMandate
IssueMandate sets budget, scope, and timelock.
The principal anchors a mandate naming the delegate DID, the MINT budget, a CEL scope expression, a not-before / not-after window, and an optional per-call cap. The chain assigns a mandate_id. The meter fills to full; the gate stands open within scope.
Step 2 · Coins roll one at a time
Each action subtracts from the meter.
Every transaction the delegate submits under the mandate decrements the meter atomically with the action. The action only commits if the spend fits. There is no path where the chain accepts the action and forgets to record the cost.
Step 3 · Scope check
Scope mismatch is rejected at the gate.
The action context is evaluated against the mandate's CEL expression. Wrong counterparty, wrong contract, wrong transaction kind — the predicate returns false and the chain rejects with `mandate_scope_mismatch` before the coin reaches the destination.
Step 4 · RecordMandateSpend
RecordMandateSpend writes to chain.
The accepted action and the meter decrement commit together. The chain stores a typed spend record bound to the mandate_id. Auditors can reconstruct exactly which actions burned which coins, in which block, against which scope.
Step 5 · RevokeMandate
RevokeMandate slams the gate.
The principal cancels in a single transaction. The gate locks shut in the block of inclusion. Any pending action referencing the mandate is rejected by the executor. Whatever budget remains returns to the principal. No exception path, no override.
Four typed transactions
Issue. Update. Spend. Revoke.
Mandates are not a service — they are four transaction kinds and a state-machine the chain enforces. IssueMandate grants; UpdateMandate can only tighten; RecordMandateSpend records the meter decrement atomically with the action; RevokeMandate slams the gate.
| tx | effect | payload |
|---|---|---|
IssueMandate | Grant a budget-, scope-, and time-bounded delegation. | delegate_did, budget_mint, scope_cel, not_before, not_after, per_call_cap? |
UpdateMandate | Tighten the bounds. Can shrink, cannot widen. | mandate_id, (new_budget≤old | new_scope⊆old | new_not_after≤old) |
RecordMandateSpend | Atomic spend record bound to the gated action. | mandate_id, amount, action_hash |
RevokeMandate | Slam the gate. Remaining budget returns to principal. | mandate_id |
Why this is not a spending limit
A wallet hint is a hope. A mandate is a protocol.
A spending limit lives in a wallet UI. Bypass the UI, bypass the limit. A Sigil mandate lives in consensus — the chain refuses the transaction before it commits. Same difference as 'please don't' versus 'cannot'.
Mandate not revoked.
Current height ∈ [not_before, not_after].
Action context satisfies CEL predicate.
Spend amount ≤ per-call cap.
Spend amount ≤ remaining budget.
Action + RecordMandateSpend commit atomically.
Let agents transact. Keep the fences.
Issue a mandate. Watch the meter. Revoke at will. Every coin passes the gate exactly once — or never passes at all.