Overview

This page provides comprehensive examples and implementations for EVM-based policy configurations. These policies control EVM transaction signing, sending, message signing, and smart account operations. For general policy concepts and setup instructions, see the Policies Overview.

Policy evaluation example

project-policy.json
{
  "description": "An example project level policy",
  "scope": "project",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "ethValue",
          "ethValue": "1000000000000000000",
          "operator": "<="
        }
      ]
    },
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "ethValue",
          "ethValue": "2000000000000000000",
          "operator": "<="
        },
        {
          "type": "evmAddress",
          "addresses": [
            "0x123"
          ],
          "operator": "in"
        }
      ]
    }
  ]
}

Example policies

Allowlist

The following example demonstrates a policy that allows signing transactions only to specific EVM addresses. Transactions to any address outside of this list will automatically be deleted by the policy engine.
allow-list-policy.json
{
  "description": "Allowlist policy example",
  "rules": [
    {
      "action": "accept",
      "criteria": [
        {
          "addresses": [
            "0xffffffffffffffffffffffffffffffffffffffff",
            "0x1111111111111111111111111111111111111111"
          ],
          "operator": "in",
          "type": "evmAddress"
        }
      ],
      "operation": "signEvmTransaction"
    }
  ],
  "scope": "project"
}
The above policy treats the set of addresses as an allowlist, only accepting sign transaction requests to an EVM address that is in the set.

Denylist

The following example demonstrates a policy that rejects signing transactions to specific EVM addresses. Transactions with to field set to any address outside of this list will be accepted.
deny-list-policy.json
{
  "description": "Denylist policy example",
  "rules": [
    {
      "action": "accept",
      "criteria": [
        {
          "addresses": [
            "0xffffffffffffffffffffffffffffffffffffffff",
            "0x1111111111111111111111111111111111111111"
          ],
          "operator": "not in",
          "type": "evmAddress"
        }
      ],
      "operation": "signEvmTransaction"
    }
  ],
  "scope": "project"
}
The above policy treats the set of addresses as a denylist, rejecting any sign transaction to an address that is not in the set.

Transaction limits

The following example demonstrates a policy that only permits signing transactions with a value of 2000000000000000000 wei (2 ETH) or less.
transaction-limit-policy.json
{
  "description": "Transaction limit policy",
  "scope": "project",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "ethValue",
          "ethValue": "2000000000000000000",
          "operator": "<="
        }
      ]
    }
  ]
}

Network restrictions

The following example demonstrates a policy that only permits sending transactions on the Base Sepolia network.
restricted-network-policy.json
{
  "description": "Restricted network policy",
  "scope": "project",
  "rules": [
    {
      "action": "accept",
      "operation": "sendEvmTransaction",
      "criteria": [
        {
          "type": "evmNetwork",
          "networks": ["base-sepolia"],
          "operator": "in"
        }
      ]
    }
  ]
}

Multi-rule policies

Learn more on combining multiple rules in a single policy.
  • Allowlist first: A policy that checks the allowlist first, then the transaction limit.
  • Allowlist second: A policy that checks the transaction value first, then uses a combined rule to check both the transaction value and the allowlist.

Allowlist first

The following example demonstrates a policy that contains both an allowlist and a transaction limit.
combined-policy-1.json
{
  "description": "Allowlist then value limit",
  "scope": "project",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "evmAddress",
          "addresses": [
            "0xffffffffffffffffffffffffffffffffffffffff"
          ],
          "operator": "in"
        }
      ]
    },
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "ethValue",
          "ethValue": "2000000000000000000",
          "operator": "<="
        }
      ]
    }
  ]
}
In the example above, assume a user sends a sign transaction request with a value of 4000000000000000000 wei (4 ETH) to the address 0x123:
  1. The transaction will be rejected against the first rule, as the address is not in the allowlist. However, the criteria still is not met and the engine will evaluate the transaction against the second rule.
  2. The transaction will be rejected against the second rule, as the value is greater than 2000000000000000000 wei (2 ETH).

Allowlist second

Let’s take a look at another combined policy example where we define the allowlist as the second rule instead of the first.
combined-policy-2.json
{
  "description": "Value limit then allowlist",
  "scope": "project",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "ethValue",
          "ethValue": "1000000000000000000",
          "operator": "<="
        }
      ]
    },
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "ethValue",
          "ethValue": "2000000000000000000",
          "operator": "<="
        },
        {
          "type": "evmAddress",
          "addresses": [
            "0xffffffffffffffffffffffffffffffffffffffff"
          ],
          "operator": "in"
        }
      ]
    }
  ]
}
In the above example, if a user sends a transaction with a value of 1500000000000000000 wei (1.5 ETH) to the address 0x123:
  1. The transaction will be rejected against the first rule, as the value is greater than 1000000000000000000 wei. However, the criteria still is not met and the engine will continue evaluating the transaction against the second rule.
  2. The transaction matches against the second rule, as the value is less than or equal to 2000000000000000000 wei AND the address is in the allowlist. The transaction will be accepted.

Message signing restrictions

The following example demonstrates how to guarantee any attempt to sign a message will conform to a specific template. When composing a regular expression in the match field, any valid re2 regular expression syntax will be accepted.
accept-sign-message-policy.json
{
  "description": "Accept sign message policy",
  "scope": "project",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmMessage",
      "criteria": [
        {
          "type": "evmMessage",
          "match": "^I solemnly swear that I,(.*), am up to no good\.$"
        }
      ]
    }
  ]
}

Limiting USDC Spend

This policy restricts USDC transactions on the Base network to transfers of 10,000 tokens or less. It applies to both signing and sending transactions to the USDC contract address, using the ERC20 ABI to validate that only transfer function calls with a value parameter under the specified limit are permitted.
limit-usdc-spend-policy.json
{
  "description": "Limit USDC Spend",
  "scope": "account",
  "rules": [
    {
      "action": "accept",
      "operation": "sendEvmTransaction",
      "criteria": [
        {
          "type": "evmNetwork",
          "networks": ["base"],
          "operator": "in"
        },
        {
          "type": "evmAddress",
          "addresses": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
          "operator": "in"
        },
        {
          "type": "evmData",
          "abi": "erc20",
          "conditions": [
            {
              "function": "transfer",
              "params": [
                {
                  "name": "value",
                  "operator": "<=",
                  "value": "10000"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "evmAddress",
          "addresses": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
          "operator": "in"
        },
        {
          "type": "evmData",
          "abi": "erc20",
          "conditions": [
            {
              "function": "transfer",
              "params": [
                {
                  "name": "value",
                  "operator": "<=",
                  "value": "10000"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Transaction data restrictions with custom ABI

When working with smart contracts that aren’t covered by standard token interfaces (ERC20, ERC721, ERC1155), you need to provide a custom ABI to enable the policy engine to decode and validate transaction data. This is essential for:
  • Custom smart contracts: Protocols with unique function signatures and parameters
  • Complex DeFi interactions: Multi-step operations, custom swaps, or governance functions
  • Proprietary contracts: Internal business logic that requires specific validation rules
  • Non-standard token implementations: Tokens with additional features beyond basic standards
The custom ABI allows the policy engine to understand the function being called and validate specific parameters according to your security requirements.
custom-abi-policy.json
{
  "description": "Proprietary Staking Contract",
  "scope": "account",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTransaction",
      "criteria": [
        {
          "type": "evmAddress",
          "addresses": ["0x1234567890123456789012345678901234567890"],
          "operator": "in"
        },
        {
          "type": "evmData",
          "abi": [
            {
              "type": "function",
              "name": "stakeTokens",
              "inputs": [
                {
                  "name": "amount",
                  "type": "uint256",
                  "internalType": "uint256"
                },
                {
                  "name": "duration",
                  "type": "uint256",
                  "internalType": "uint256"
                },
                {
                  "name": "beneficiary",
                  "type": "address",
                  "internalType": "address"
                }
              ],
              "outputs": [
                {
                  "name": "stakeId",
                  "type": "uint256",
                  "internalType": "uint256"
                }
              ],
              "stateMutability": "nonpayable"
            }
          ],
          "conditions": [
            {
              "function": "stakeTokens",
              "params": [
                {
                  "name": "amount",
                  "operator": "<=",
                  "value": "1000000000000000000000"
                },
                {
                  "name": "duration",
                  "operator": ">=",
                  "value": "86400"
                },
                {
                  "name": "beneficiary",
                  "operator": "in",
                  "values": [
                    "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
                    "0x1111111111111111111111111111111111111111"
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
This policy restricts the stakeTokens function to:
  • Maximum stake amount of 1000 tokens (assuming 18 decimals)
  • Minimum duration of 1 day (86400 seconds)
  • Only allow staking for pre-approved beneficiary addresses

Disable signing arbitrary hashes

The example below demonstrates a policy to prevent fraud by rejecting any attempt to sign a hash (i.e., undefined or arbitrary input data) on behalf of an account.
reject-sign-hash-policy.json
{
  "description": "Reject sign hash policy",
  "scope": "project",
  "rules": [
    {
      "action": "reject",
      "operation": "signEvmHash"
    }
  ]
}

EIP-712 verifying contract allowlist

The following example demonstrates a policy that prevents signing typed data for the zero address. This ensures that typed data signatures cannot be created for invalid or burn addresses.
sign-typed-data-verifying-contract-policy.json
{
  "description": "Prevent typed data signing for zero address",
  "scope": "account",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTypedData",
      "criteria": [
        {
          "type": "evmTypedDataVerifyingContract",
          "addresses": ["0x0000000000000000000000000000000000000000"],
          "operator": "not in"
        }
      ]
    }
  ]
}
This policy uses the verifying contract address from the EIP-712 domain to ensure that typed data signatures are not created for the zero address. You can extend this to create an allowlist by using the "in" operator with trusted contract addresses, or a denylist by using "not in" with untrusted addresses.

EIP-712 field restrictions

The following example demonstrates a more advanced policy that validates specific fields within typed data. This policy restricts an arbitrary Payment data type to a specific address, message content, and amount:
sign-typed-data-field-policy.json
{
  "description": "Restrict Payment data type",
  "scope": "account",
  "rules": [
    {
      "action": "accept",
      "operation": "signEvmTypedData",
      "criteria": [
        {
          "type": "evmTypedDataField",
          "types": {
            "primaryType": "Payment",
            "types": {
              "EIP712Domain": [
                { "name": "name", "type": "string" },
                { "name": "version", "type": "string" },
                { "name": "chainId", "type": "uint256" },
                { "name": "verifyingContract", "type": "address" }
              ],
              "Payment": [
                { "name": "to", "type": "address" },
                { "name": "amount", "type": "uint256" },
                { "name": "message", "type": "string" },
              ]
            }
          },
          "conditions": [
            {
              "path": "to",
              "operator": "in",
              "value": ["0x1234567890123456789012345678901234567890", "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"]
            },
            {
              "path": "message",
              "match": "^hello.*"
            },
            {
              "path": "amount",
              "operator": "<=",
              "value": "100"
            }
          ]
        }
      ]
    }
  ]
}
This policy uses the evmTypedDataField criterion to inspect the actual data being signed. The evmTypedDataField criterion supports conditions on numerical values, addresses and strings.

Smart Account Operations

The following policy restricts Smart Account operations to USDC transactions on the Base network, with transfers of 10,000 tokens or less. It applies to both prepare and send operations to the USDC contract address, using the ERC20 ABI to validate that only transfer function calls with a value parameter under the specified limit are permitted.
smart-account-operations-policy.json
{
  "description": "Smart Account USDC Limits",
  "scope": "account",
  "rules": [
    {
      "action": "accept",
      "operation": "prepareUserOperation",
      "criteria": [
        {
          "type": "evmNetwork",
          "networks": ["base"],
          "operator": "in"
        },
        {
          "type": "evmAddress",
          "addresses": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
          "operator": "in"
        },
        {
          "type": "evmData",
          "abi": "erc20",
          "conditions": [
            {
              "function": "transfer",
              "params": [
                {
                  "name": "value",
                  "operator": "<=",
                  "value": "10000"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "action": "accept",
      "operation": "sendUserOperation",
      "criteria": [
        {
          "type": "evmAddress",
          "addresses": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
          "operator": "in"
        },
        {
          "type": "evmData",
          "abi": "erc20",
          "conditions": [
            {
              "function": "transfer",
              "params": [
                {
                  "name": "value",
                  "operator": "<=",
                  "value": "10000"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Complete implementation example

The following example shows how to create and manage multiple EVM policies programmatically:
    import { CdpClient } from "@coinbase/cdp-sdk";
    import dotenv from "dotenv";

    dotenv.config();

    const cdp = new CdpClient();

    const account = await cdp.evm.createAccount();

    const policyId = "" // Paste the policy ID created on portal.

    // Update the account to add the account policy.
    const updatedAccount = await cdp.evm.updateAccount({
      address: account.address,
      update: {
        accountPolicy: policyId,
      }
    })

    console.log("Updated account %s with policy: %s", updatedAccount.address, updatedAccount.policies);

    // Create another account with policy immediately applied to it
    const otherAccount = await cdp.evm.createAccount({
      name: "OtherPolicyAccount",
      accountPolicy: policyId
    });
    console.log("Other account address:", otherAccount.address);

    // Create project policy example
    const projectPolicy = await cdp.policies.createPolicy({
      policy: {
        scope: "project",
        description: "Project Transaction Limit Example",
        rules: [
          {
            action: "accept",
            operation: "signEvmTransaction",
            criteria: [
              {
                type: "ethValue",
                ethValue: "5000000000000000000", // 5 ETH in wei
                operator: "<=",
              },
              {
                type: "evmAddress",
                addresses: ["0x000000000000000000000000000000000000dEaD"],
                operator: "in",
              },
            ],
          },
        ],
      },
    });
    console.log("Created project policy:", projectPolicy.id);