Skip to main content
Policies are the rules that constrain what an agent can do within a pact. Every operation the runtime submits is evaluated against the policies you define.

How the engine works

The policy engine evaluates every operation against all applicable policies and outputs one of three decisions: allow, require_approval, or deny.

Evaluation order (within a policy)

  1. Check deny_if — if hit, deny immediately
  2. Check review_if / always_review — if hit, pause for owner approval
  3. Otherwise, allow

Default-deny semantics

Pact-level policies use fail-closed semantics: if the operation does not match any policy’s when conditions, it is automatically denied. Every operation the agent needs to perform must be explicitly covered by a policy.

Final decision

When multiple policies apply:
  • Any deny → deny
  • No deny, but any review triggered → require_approval
  • Otherwise → allow

Policy structure

Each policy in the policies array has this shape:
{
  "name": "<human-readable name>",
  "type": "transfer | contract_call | message_sign",
  "rules": {
    "effect": "allow",
    "when": { ... },
    "deny_if": { ... },
    "review_if": { ... },
    "always_review": true | false
  }
}
FieldRequiredDescription
nameYesHuman-readable label for this policy
typeYesOperation type: transfer, contract_call, or message_sign
rules.effectYesAlways "allow" for pact-level policies
rules.whenYes (unless always_review: true)Allowlist conditions — which chains, tokens, or contracts are permitted
rules.deny_ifNoHard-block conditions — operations that exceed these limits are denied
rules.review_ifNoSoft-block conditions — operations exceeding these thresholds pause for owner approval
rules.always_reviewNoWhen true, every matched operation requires owner approval
Constraints:
  • deny_if takes priority over review_if / always_review
  • deny effect policies cannot have review_if or always_review
  • allow + review_if requires a non-empty when
  • allow requires either a non-empty when or always_review: true

Transfer policies

Use "type": "transfer" to control token transfers.

when — allowlist conditions

All fields are AND conditions. All specified fields must match.
FieldTypeDescription
chain_instring[]Restrict to specific chains, e.g. ["BASE_ETH", "ETH"]
token_inChainTokenRef[]Restrict to specific tokens — [{"chain_id": "BASE_ETH", "token_id": "BASE_USDC"}]
destination_address_inChainAddressRef[]Restrict to specific destination addresses — [{"chain_id": "BASE_ETH", "address": "0x..."}]

deny_if — hard-block limits

FieldTypeDescription
amount_gtstring (decimal)Deny if a single transfer’s token amount exceeds this
amount_usd_gtstring (decimal)Deny if a single transfer’s USD value exceeds this
usage_limits.rolling_1hobjectRolling 1-hour window limits
usage_limits.rolling_24hobjectRolling 24-hour window limits
usage_limits.rolling_7dobjectRolling 7-day window limits
usage_limits.rolling_30dobjectRolling 30-day window limits
Each rolling window supports: amount_gt, amount_usd_gt, tx_count_gt.

review_if — approval thresholds

Supports the same fields as when (chain_in, token_in, destination_address_in), plus:
FieldTypeDescription
amount_gtstring (decimal)Require approval if token amount exceeds this
amount_usd_gtstring (decimal)Require approval if USD value exceeds this

Examples

{
  "name": "usdc-on-base",
  "type": "transfer",
  "rules": {
    "effect": "allow",
    "when": {
      "chain_in": ["BASE_ETH"],
      "token_in": [{"chain_id": "BASE_ETH", "token_id": "BASE_USDC"}]
    }
  }
}

Contract call policies

Use "type": "contract_call" to control smart contract interactions. EVM and Solana use different target fields.

when — allowlist conditions (EVM)

FieldTypeDescription
chain_instring[]Restrict to specific chains
target_inContractTargetRef[]Allowlist by contract address and optionally function selector — [{"chain_id": "SETH", "contract_addr": "0x...", "function_id": "0x38ed1739"}]. Omit function_id to allow all functions on the contract.
params_matchParamMatchRule[]Match on decoded calldata parameters (EVM only — requires function_abis)

when — allowlist conditions (Solana)

FieldTypeDescription
chain_instring[]Restrict to specific chains
program_inProgramRef[]Allow if the transaction involves any of the listed programs
program_all_inProgramRef[]Allow only if the transaction involves all of the listed programs

params_match rule

Used to match on decoded function parameters. Requires function_abis to be set.
FieldTypeDescription
param_namestringParameter name as defined in the ABI
opstringeq, neq, in, not_in, gt, gte, lt, lte
valueanyValue to compare against
params_match is only supported on EVM chains. Multiple rules are AND conditions.

function_abis

Required when using params_match. Provide the ABI fragment for each function selector you reference:
"function_abis": [
  {
    "type": "function",
    "selector": "0x38ed1739",
    "inputs": [
      {"name": "amountIn", "type": "uint256"},
      {"name": "recipient", "type": "address"}
    ]
  }
]

deny_if — hard-block limits

FieldTypeDescription
amount_gtstringDeny if the operation’s native value exceeds this
amount_usd_gtstringDeny if the USD value exceeds this
usage_limitsobjectSame rolling window structure as transfer (rolling_1h, rolling_24h, rolling_7d, rolling_30d), each supporting amount_gt, amount_usd_gt, tx_count_gt

review_if — approval thresholds

Supports the same fields as when (chain_in, target_in, program_in, params_match), plus amount_gt and amount_usd_gt.

Examples

{
  "name": "uniswap-swap",
  "type": "contract_call",
  "rules": {
    "effect": "allow",
    "when": {
      "chain_in": ["BASE_ETH"],
      "target_in": [{"chain_id": "BASE_ETH", "contract_addr": "0x2626664c2603336E57B271c5C0b26F421741e481"}]
    },
    "deny_if": {
      "usage_limits": {"rolling_24h": {"tx_count_gt": 5}}
    }
  }
}

Message sign policies

Use "type": "message_sign" to control EIP-712 typed-data signing. There are no amount_gt / amount_usd_gt fields — rate limits use request_count_gt instead.

when — allowlist conditions

FieldTypeDescription
chain_instring[]Restrict to specific chains
primary_type_instring[]Match on the EIP-712 primaryType field, e.g. ["PermitSingle", "PermitBatch"]
source_address_inChainAddressRef[]Restrict to specific signing addresses. EVM: case-insensitive. Solana: case-sensitive.
domain_matchMatchRule[]Match on EIP-712 domain fields
message_matchMatchRule[]Match on EIP-712 message fields
All when fields are AND conditions.

Path syntax for domain_match and message_match

Use dot notation to address nested fields:
SyntaxMeaningExample
.Nested fielddetails.spender
[N]Array index (0-based)path[0].tokenIn
*All array elementsitems.*.token
.lengthArray lengthtransfers.length
Wildcard semantics — for multi-value paths like items.*.token:
  • eq, in, gt, gte, lt, lte: matches if any element satisfies the condition
  • neq, not_in: matches only if all elements satisfy the condition
Supported operators: eq, neq, in, not_in, gt, gte, lt, lte

deny_if — rate limits

FieldTypeDescription
usage_limits.rolling_1h.request_count_gtintegerDeny if signing requests in the past hour exceed this
usage_limits.rolling_24h.request_count_gtintegerDeny if signing requests in the past 24h exceed this
usage_limits.rolling_7d.request_count_gtintegerDeny if signing requests in the past 7 days exceed this
usage_limits.rolling_30d.request_count_gtintegerDeny if signing requests in the past 30 days exceed this

review_if — approval thresholds

Supports the same fields as when (chain_in, primary_type_in, source_address_in, domain_match, message_match).

Chain ID validation

For EVM chains, the engine checks that the request chain_id matches domain.chainId in the typed data. A mismatch results in an immediate deny with reason eip712_domain_chain_id_mismatch.

Examples

{
  "name": "permit2-allowlist",
  "type": "message_sign",
  "rules": {
    "effect": "allow",
    "when": {
      "chain_in": ["SETH"],
      "primary_type_in": ["PermitSingle", "PermitBatch"],
      "domain_match": [
        {"param_name": "name", "op": "eq", "value": "Permit2"},
        {"param_name": "verifyingContract", "op": "eq", "value": "0x000000000022d473030f116ddee9f6b43ac78ba3"}
      ],
      "message_match": [
        {"param_name": "details.spender", "op": "in", "value": ["0xabc...", "0xdef..."]},
        {"param_name": "details.token",   "op": "in", "value": ["0x111...", "0x222..."]}
      ]
    },
    "review_if": {
      "message_match": [
        {"param_name": "details.amount", "op": "gt", "value": "1000000000000000000"}
      ]
    },
    "deny_if": {
      "usage_limits": {
        "rolling_1h":  {"request_count_gt": 10},
        "rolling_24h": {"request_count_gt": 100}
      }
    }
  }
}

Completion conditions

Completion conditions determine when a pact automatically ends. At least one is required. The pact completes when any condition is satisfied, after which access is revoked immediately.
TypeThresholdDescription
tx_countstring (integer)Complete after N successful transactions — e.g. "1" for a one-time operation
amount_spentstring (decimal)Complete after cumulative token amount reaches this — e.g. "1000" for 1000 USDC
amount_spent_usdstring (decimal)Complete after cumulative USD spend reaches this — e.g. "500"
time_elapsedstring (seconds)Complete after N seconds from pact activation — e.g. "2592000" for 30 days
[
  {"type": "tx_count", "threshold": "12"},
  {"type": "time_elapsed", "threshold": "7776000"}
]
This pact ends after 12 transactions or 90 days, whichever comes first.

Amount units

amount_gt, amount_spent, and related fields use the token’s transfer unit — the same unit passed when submitting a transfer:
  • "1.5" for USDC means 1.5 USDC (not 1,500,000 micro-USDC)
  • "0.01" for ETH means 0.01 ETH (not wei)
USD-based conditions (amount_usd_gt, amount_spent_usd) only apply to tokens with available price data. For tokens without price data, use token-denominated limits (amount_gt, amount_spent) instead.