# Ethereum policy rules

This page covers every Ethereum operation and criterion type available for policies.
For shared concepts like evaluation logic and allowlist vs denylist patterns, see the [rules reference](/docs/configuration/policies/rules-reference).

## Operations

### Backend wallet operations

| Operation | Description | Available criteria |
| :--- | :--- | :--- |
| `signEvmTransaction` | Sign a raw EVM transaction | `ethValue`, `evmAddress`, `evmData` |
| `sendEvmTransaction` | Sign and broadcast an EVM transaction | `ethValue`, `evmAddress`, `evmNetwork`, `evmData` |
| `signEvmMessage` | Sign a personal message (EIP-191) | `evmMessage` |
| `signEvmTypedData` | Sign EIP-712 typed data | `evmTypedDataVerifyingContract`, `evmTypedDataField` |
| `signEvmHash` | Sign a raw hash | None (accept or reject only) |

### gas sponsorship operations

| Operation | Description | Available criteria |
| :--- | :--- | :--- |
| `sponsorEvmTransaction` | Sponsor an EVM UserOperation via the [paymaster](/docs/products/infrastructure/paymaster/ethereum) | `ethValue`, `evmAddress`, `evmNetwork`, `evmData` |

## Criteria

### `ethValue`

Compare the transaction value in wei.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'ethValue'` | Criterion type |
| `operator` | `'<='` | `'>='` | `'<'` | `'>'` | Comparison operator |
| `ethValue` | string | Value in wei (numeric string) |

```ts
{
  type: 'ethValue',
  operator: '<=',
  ethValue: '1000000000000000000', // 1 ETH
}
```

### `evmAddress`

Check the transaction recipient against an address list. Use `'in'` for an allowlist or `'not in'` for a denylist.

:::info
EVM addresses are stored as [EIP-55 checksummed](https://eips.ethereum.org/EIPS/eip-55) strings. Always provide addresses in checksummed form (mixed-case) — a lowercase or incorrectly cased address will not match.
:::

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'evmAddress'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `addresses` | string\[] | EVM addresses (hex, `0x` prefix) |

:::code-group
```ts [Allowlist]
{
  type: 'evmAddress',
  operator: 'in',
  addresses: [
    '0x000000000000000000000000000000000000dEaD',
    '0x1234567890abcdef1234567890abcdef12345678',
  ],
}
```

```ts [Denylist]
{
  type: 'evmAddress',
  operator: 'not in',
  addresses: [
    '0xBadAddress0000000000000000000000000000001',
  ],
}
```
:::

### `evmNetwork`

Restrict to specific EVM chains by chain ID. Only available for `sendEvmTransaction`.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'evmNetwork'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `chainIds` | number\[] | Chain IDs (positive integers) |

```ts
{
  type: 'evmNetwork',
  operator: 'in',
  chainIds: [1, 137, 8453], // Ethereum, Polygon, Base
}
```

### `evmMessage`

Match a personal-sign message against a regex pattern. Only available for `signEvmMessage`.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'evmMessage'` | Criterion type |
| `operator` | `'match'` | Regex match operator |
| `pattern` | string | RE2 regex pattern |

```ts
{
  type: 'evmMessage',
  operator: 'match',
  pattern: '^Sign in to MyApp:',
}
```

### `evmData`

Validate transaction calldata against a contract ABI. Match by function name and optionally constrain arguments.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'evmData'` | Criterion type |
| `operator` | `'=='` | `'in'` | `'not in'` | `'<'` | `'<='` | `'>'` | `'>='` | `'match'` | Comparison operator |
| `abi` | string | Contract ABI as JSON string |
| `functionName` | string | Function name to match |
| `args` | object | Argument constraints (optional) |

**Match function name only:**

```ts
{
  type: 'evmData',
  operator: '==',
  abi: JSON.stringify([{
    type: 'function',
    name: 'transfer',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ type: 'bool' }],
  }]),
  functionName: 'transfer',
}
```

**Match function name and constrain arguments:**

The `args` field lets you validate decoded function arguments by name or position (`_0`, `_1`, ...). The criterion's `operator` applies to each arg constraint.

```ts
// Only allow transfer() where amount <= 1000 USDC (6 decimals)
{
  type: 'evmData',
  operator: '<=',
  abi: JSON.stringify([{
    type: 'function',
    name: 'transfer',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ type: 'bool' }],
  }]),
  functionName: 'transfer',
  args: {
    amount: '1000000000', // 1000 USDC in smallest unit
  },
}
```

```ts
// Only allow transfer() to specific addresses
{
  type: 'evmData',
  operator: 'in',
  abi: JSON.stringify([{
    type: 'function',
    name: 'transfer',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ type: 'bool' }],
  }]),
  functionName: 'transfer',
  args: {
    to: [
      '0x000000000000000000000000000000000000dEaD',
      '0x1234567890abcdef1234567890abcdef12345678',
    ],
  },
}
```

### `evmTypedDataVerifyingContract`

Check the EIP-712 verifying contract address. Only available for `signEvmTypedData`.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'evmTypedDataVerifyingContract'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `addresses` | string\[] | Verifying contract addresses (hex, `0x` prefix) |

```ts
{
  type: 'evmTypedDataVerifyingContract',
  operator: 'in',
  addresses: ['0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'],
}
```

### `evmTypedDataField`

Validate a specific field within EIP-712 typed data. Only available for `signEvmTypedData`.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'evmTypedDataField'` | Criterion type |
| `operator` | `'in'` | `'<='` | `'match'` | Comparison operator |
| `fieldPath` | string | Dot-notation path to the field (e.g., `order.buyer`) |
| `values` | string\[] | Allowed values (for `in` operator) |
| `value` | string | Comparison value (for `<=` or `match` operators) |

```ts
// Only allow typed data where order.buyer is a known address
{
  type: 'evmTypedDataField',
  operator: 'in',
  fieldPath: 'order.buyer',
  values: ['0x000000000000000000000000000000000000dEaD'],
}
```

## Examples

### Address allowlist

Only accept transactions to known addresses on specific networks.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow transfers to treasury and vault',
  rules: [
    {
      action: 'accept',
      operation: 'sendEvmTransaction',
      criteria: [
        {
          type: 'evmAddress',
          operator: 'in',
          addresses: [
            '0x000000000000000000000000000000000000dEaD', // Treasury
            '0x1234567890abcdef1234567890abcdef12345678', // Vault
          ],
        },
        {
          type: 'evmNetwork',
          operator: 'in',
          chainIds: [1, 137, 8453], // Ethereum, Polygon, Base
        },
      ],
    },
  ],
})
```

### Address denylist

Block transactions to known malicious addresses while allowing everything else.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Block transfers to known bad addresses',
  rules: [
    {
      action: 'accept',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'evmAddress',
          operator: 'not in',
          addresses: [
            '0xBadAddress0000000000000000000000000000001',
            '0xBadAddress0000000000000000000000000000002',
          ],
        },
      ],
    },
  ],
})
```

### Transaction value limit

Reject transactions above a threshold.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Reject transactions above 1 ETH',
  rules: [
    {
      action: 'reject',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'ethValue',
          operator: '>',
          ethValue: '1000000000000000000', // 1 ETH in wei
        },
      ],
    },
  ],
})
```

### Restrict to ERC-20 transfers

Only allow `transfer` calls by matching calldata against an ABI.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow ERC-20 transfer calls',
  rules: [
    {
      action: 'accept',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'evmData',
          operator: '==',
          abi: JSON.stringify([
            {
              type: 'function',
              name: 'transfer',
              inputs: [
                { name: 'to', type: 'address' },
                { name: 'amount', type: 'uint256' },
              ],
              outputs: [{ type: 'bool' }],
            },
          ]),
          functionName: 'transfer',
        },
      ],
    },
  ],
})
```

### Restrict ERC-20 transfers with argument constraints

Only allow `transfer` calls where the `amount` is under 1000 USDC and the `to` address is in an allowlist. The `args` field constrains decoded function arguments — use the argument name from the ABI or a positional key (`_0`, `_1`, ...).

```ts
const erc20Abi = JSON.stringify([
  {
    type: 'function',
    name: 'transfer',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ type: 'bool' }],
  },
])

const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Allow USDC transfers under 1000 to approved addresses',
  rules: [
    // Rule 1: transfer amount must be <= 1000 USDC
    {
      action: 'accept',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'evmData',
          operator: '<=',
          abi: erc20Abi,
          functionName: 'transfer',
          args: {
            amount: '1000000000', // 1000 USDC (6 decimals)
          },
        },
        {
          type: 'evmData',
          operator: 'in',
          abi: erc20Abi,
          functionName: 'transfer',
          args: {
            to: [
              '0x000000000000000000000000000000000000dEaD', // Treasury
              '0x1234567890abcdef1234567890abcdef12345678', // Vault
            ],
          },
        },
      ],
    },
  ],
})
```

### Message signing restriction

Only allow messages that match a specific prefix pattern.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow sign-in messages',
  rules: [
    {
      action: 'accept',
      operation: 'signEvmMessage',
      criteria: [
        {
          type: 'evmMessage',
          operator: 'match',
          pattern: '^Sign in to MyApp:',
        },
      ],
    },
  ],
})
```

### Reject hash signing

Block all raw hash signing to prevent fraud. Since `signEvmHash` has no criteria, the rule matches unconditionally.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Block all raw hash signing',
  rules: [
    {
      action: 'reject',
      operation: 'signEvmHash',
    },
  ],
})
```

### EIP-712 typed data restriction

Only allow typed data signing for a known verifying contract, and validate a specific field value.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Restrict EIP-712 signing to known contract',
  rules: [
    {
      action: 'accept',
      operation: 'signEvmTypedData',
      criteria: [
        {
          type: 'evmTypedDataVerifyingContract',
          operator: 'in',
          addresses: ['0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'],
        },
        {
          type: 'evmTypedDataField',
          operator: 'in',
          fieldPath: 'order.buyer',
          values: ['0x000000000000000000000000000000000000dEaD'],
        },
      ],
    },
  ],
})
```

### Combined: value limit with address allowlist

Allow small transactions to anyone, but require allowlisted addresses for larger amounts.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Tiered value limits with address restrictions',
  rules: [
    // Rule 1: Accept any transaction under 1 ETH
    {
      action: 'accept',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'ethValue',
          operator: '<=',
          ethValue: '1000000000000000000', // 1 ETH
        },
      ],
    },
    // Rule 2: Accept up to 10 ETH, but only to allowlisted addresses
    {
      action: 'accept',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'ethValue',
          operator: '<=',
          ethValue: '10000000000000000000', // 10 ETH
        },
        {
          type: 'evmAddress',
          operator: 'in',
          addresses: ['0x000000000000000000000000000000000000dEaD'],
        },
      ],
    },
    // Anything above 10 ETH or to non-allowlisted addresses → rejected
  ],
})
```

### Complete implementation

End-to-end example: create a policy, sign a transaction that passes, then attempt one that gets rejected.

```ts
import Openfort from '@openfort/openfort-node'
import { parseEther } from 'viem'

const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY!, {
  walletSecret: process.env.OPENFORT_WALLET_SECRET,
})

// 1. Create a backend wallet
const account = await openfort.accounts.evm.backend.create()
console.log('Wallet:', account.address)

// 2. Create a policy that rejects transactions above 1 ETH
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Reject transactions above 1 ETH',
  rules: [
    {
      action: 'reject',
      operation: 'signEvmTransaction',
      criteria: [
        {
          type: 'ethValue',
          operator: '>',
          ethValue: '1000000000000000000', // 1 ETH
        },
      ],
    },
    {
      action: 'accept',
      operation: 'signEvmTransaction',
    },
  ],
})
console.log('Policy created:', policy.id)

// 3. Sign a small transaction — should succeed
const signed = await account.signTransaction({
  to: '0x000000000000000000000000000000000000dEaD',
  value: parseEther('0.5'),
  chainId: 1,
})
console.log('✅ Signed:', signed)

// 4. Attempt a large transaction — should be rejected
try {
  await account.signTransaction({
    to: '0x000000000000000000000000000000000000dEaD',
    value: parseEther('5'),
    chainId: 1,
  })
} catch (error) {
  console.log('❌ Rejected by policy:', error.message)
}
```

## Token decimals

| Token | Decimals | 1 unit in smallest denomination |
| :--- | :--- | :--- |
| ETH | 18 | 1 ETH = 1,000,000,000,000,000,000 wei |
| USDC | 6 | 1 USDC = 1,000,000 |
| USDT | 6 | 1 USDT = 1,000,000 |
