Policy reference
This page lists everything setAgent stores for an agent, how the account evaluates an action against it, and every reason it can refuse. The behaviour described here is the contract's; the dashboard and the landing demo run the same rules in TypeScript.
setAgent#
function setAgent(
address agent,
Policy calldata policy,
address[] calldata targets,
Selector[] calldata selectors,
Threshold[] calldata thresholds
) external;Only the owner can call it. It registers the agent or replaces its whole configuration and emits AgentSet(agent, policyHash). The agent cannot be address(0), the account itself or the factory (InvalidAgent).
Fields#
Policy#
| Field | Type | Default in the dashboard | Meaning |
|---|---|---|---|
validAfter | uint64 | 0 | The agent cannot act before this unix time. 0 means from registration. |
validUntil | uint64 | 0 | The agent cannot act after this unix time. 0 means no expiry. Must be later than validAfter (InvalidValidity). |
anyRecipient | bool | false | When true, the agent may pay or call any address except the account and the factory. |
limits | Limit[] | one USDC limit | One entry per token the agent may spend. Listing a token twice reverts with DuplicateLimit. |
Limit#
| Field | Type | Meaning |
|---|---|---|
token | address | The ERC-20, or address(0) for ETH. |
perTx | uint128 | Largest amount one action may spend, in token units. 0 means nothing can be spent. |
perPeriod | uint128 | Largest total within one window. Ignored when period is 0. |
period | uint32 | Window length in seconds. 0 disables the window cap and leaves only perTx. |
Amounts are in the token's smallest unit: 5 USDC is 5000000 because USDC has 6 decimals. The SDK and the dashboard convert from human amounts for you.
Allowlist#
targets is a list of addresses the agent may pay or call when anyRecipient is false.
selectors is a list of { target, selector } pairs that restrict calls to named functions:
- A target with no selector entries accepts any function.
- A target with one or more entries accepts only those.
0x00000000stands for calldata shorter than four bytes, such as a plain ETH send throughcall.
Thresholds#
thresholds is a list of { token, amount }. An action spending at least amount of token waits for the owner. 0, or no entry, means never.
How calls are counted#
call(target, value, data, memo) can move value in two ways, and both count:
valuecounts against the ETH limit.- ERC-20 calldata on
targetcounts against the limit fortargetas a token:transfer,transferFrom,approve,increaseAllowance, and the ERC-1363transferAndCall,transferFromAndCallandapproveAndCallvariants. Approvals count at their full amount, so an agent cannot approve its way around a cap.
For those token calls, the recipient or spender inside the calldata must be allowlisted too. Otherwise allowlisting a token contract would let the agent transfer it to anyone.
On a target that is one of the agent's limited tokens, only the token functions above are accepted, unless you allowlist other selectors for it. A non-standard function that moves the token cannot bypass the cap. Token calldata too short to read is refused with SELECTOR_NOT_ALLOWED.
Agents can never call the account itself or the factory, and pay to address(0) or to the account is refused.
Order of checks#
The account tests an action in a fixed order and reports the first rule it breaks, so an action that breaks several rules always gets the same answer:
- The caller is a registered agent.
- The account is not paused.
- Now is at or after
validAfter. - Now is at or before
validUntil(unless it is0). - The recipient or target is allowed, and for ERC-20 calls so is the address inside the calldata.
- The function is allowed on that target.
- Every token the action spends has a limit.
- Every amount is at most
perTx. - Every amount fits in what is left of its window.
- No amount is at or above its approval threshold. If one is, the result is
NeedsApproval. - The account holds enough of every token it would send.
The balance check comes after the threshold on purpose: a payment can be parked while the account is short and funded before you approve it. approve runs the list again without step 10.
Denial reasons#
| Order | Reason | Meaning | What to do |
|---|---|---|---|
| 1 | NOT_AGENT | The caller has no policy on this account, or it was revoked. | Register the agent's address on the account, or call from the agent key you registered. |
| 2 | PAUSED | The owner has paused the account. Every agent is frozen until it is unpaused. | Ask the owner to unpause. Owner withdrawals still work while paused. |
| 3 | NOT_YET_VALID | The current time is before the policy's valid-from time. | Wait until the valid-from time, or have the owner move it earlier. |
| 4 | EXPIRED | The current time is after the policy's valid-until time. | Have the owner extend valid-until or set it to no expiry. |
| 5 | TARGET_NOT_ALLOWED | The recipient or contract is not on the agent's allowlist. The account itself and the factory are never allowed. | Have the owner add the address to the allowlist. |
| 6 | SELECTOR_NOT_ALLOWED | The contract is allowlisted for named functions only, and this call uses another one. | Call an allowed function, or have the owner add this selector. |
| 7 | TOKEN_NOT_ALLOWED | The policy has no limit for this token, so the agent cannot spend it at all. | Have the owner add a limit for the token. |
| 8 | OVER_PER_TX | The amount is larger than the most this agent may spend in one transaction. | Split the payment, or have the owner raise the per-payment cap. |
| 9 | OVER_PERIOD | This payment would take the agent past its total for the current window. | Wait for the next window, or have the owner raise the period cap. |
| 11 | INSUFFICIENT_BALANCE | The account does not hold enough of the token to cover the payment. | Have the owner fund the account. |
Step 10, between OVER_PERIOD and INSUFFICIENT_BALANCE, is the approval threshold: an action that passes every cap and is at or above the threshold returns NeedsApproval instead of a reason.
Every refusal reverts with Denied(bytes32 reason), where the reason is the name above as left-aligned ASCII, for example bytes32("OVER_PER_TX"). Decode it with a plain bytes-to-string conversion; no lookup table is needed. check returns the same value without reverting.
Edge cases#
- A threshold above
perTxnever triggers. A payment that large is denied withOVER_PER_TXbefore the threshold is tested. The dashboard warns about this. perPeriodbelowperTxmeans no single payment can reachperTx.period = 0means only the per-payment cap applies, and nothing is tracked across payments. A leaked key can then spendperTxas many times as the balance allows. Use a period unless you mean it.- Windows are fixed, not rolling. See Concepts.
- Editing a policy keeps the window. Lowering
perPeriodbelow what was already spent this window blocks the agent until the window ends. anyRecipientwith a token that has no limit is only protected against the recognised token selectors. Prefer an allowlist.- Revoking clears the policy and allowlists and makes pending requests unapprovable. The spending window is kept.
- Request lifetime is
requestTtl, between 1 second and 30 days, 24 hours by default.
Reading a policy back#
function getAgent(address agent) external view returns (
Policy memory policy, address[] memory targets, Selector[] memory selectors, Threshold[] memory thresholds
);
function spending(address agent, address token) external view returns (uint64 windowStart, uint128 spent, uint256 remaining);
function policyHash(address agent) external view returns (bytes32);spending returns remaining = type(uint256).max for a token with no window, and zeros for a token with no limit.