Skip to content
WARDEN

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#

solidity
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#

FieldTypeDefault in the dashboardMeaning
validAfteruint640The agent cannot act before this unix time. 0 means from registration.
validUntiluint640The agent cannot act after this unix time. 0 means no expiry. Must be later than validAfter (InvalidValidity).
anyRecipientboolfalseWhen true, the agent may pay or call any address except the account and the factory.
limitsLimit[]one USDC limitOne entry per token the agent may spend. Listing a token twice reverts with DuplicateLimit.

Limit#

FieldTypeMeaning
tokenaddressThe ERC-20, or address(0) for ETH.
perTxuint128Largest amount one action may spend, in token units. 0 means nothing can be spent.
perPerioduint128Largest total within one window. Ignored when period is 0.
perioduint32Window 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.
  • 0x00000000 stands for calldata shorter than four bytes, such as a plain ETH send through call.

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:

  • value counts against the ETH limit.
  • ERC-20 calldata on target counts against the limit for target as a token: transfer, transferFrom, approve, increaseAllowance, and the ERC-1363 transferAndCall, transferFromAndCall and approveAndCall variants. 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:

  1. The caller is a registered agent.
  2. The account is not paused.
  3. Now is at or after validAfter.
  4. Now is at or before validUntil (unless it is 0).
  5. The recipient or target is allowed, and for ERC-20 calls so is the address inside the calldata.
  6. The function is allowed on that target.
  7. Every token the action spends has a limit.
  8. Every amount is at most perTx.
  9. Every amount fits in what is left of its window.
  10. No amount is at or above its approval threshold. If one is, the result is NeedsApproval.
  11. 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#

OrderReasonMeaningWhat to do
1NOT_AGENTThe 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.
2PAUSEDThe owner has paused the account. Every agent is frozen until it is unpaused.Ask the owner to unpause. Owner withdrawals still work while paused.
3NOT_YET_VALIDThe current time is before the policy's valid-from time.Wait until the valid-from time, or have the owner move it earlier.
4EXPIREDThe current time is after the policy's valid-until time.Have the owner extend valid-until or set it to no expiry.
5TARGET_NOT_ALLOWEDThe 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.
6SELECTOR_NOT_ALLOWEDThe contract is allowlisted for named functions only, and this call uses another one.Call an allowed function, or have the owner add this selector.
7TOKEN_NOT_ALLOWEDThe policy has no limit for this token, so the agent cannot spend it at all.Have the owner add a limit for the token.
8OVER_PER_TXThe 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.
9OVER_PERIODThis 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.
11INSUFFICIENT_BALANCEThe 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 perTx never triggers. A payment that large is denied with OVER_PER_TX before the threshold is tested. The dashboard warns about this.
  • perPeriod below perTx means no single payment can reach perTx.
  • period = 0 means only the per-payment cap applies, and nothing is tracked across payments. A leaked key can then spend perTx as 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 perPeriod below what was already spent this window blocks the agent until the window ends.
  • anyRecipient with 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#

solidity
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.