> ## Documentation Index
> Fetch the complete documentation index at: https://turnkey-0e7c1f5b-andrew-eip712-policy-examples.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Ethereum (EVM)

> This page provides examples of policies governing Ethereum (EVM) signing.

Note: see the [language section](/features/policies/language#ethereum) for more details.

#### Allow ABI-specific contract call parameters

For contract interactions, use
[Smart Contract Interfaces](/features/policies/smart-contract-interfaces) (ABI upload) rather than
raw calldata slicing. ABI-based policies use named arguments
(`eth.tx.contract_call_args['arg_name']`) instead of byte offsets — they're more readable, less
error-prone, and won't silently break if the contract encoding changes. Raw `eth.tx.data[...]`
slicing is a fallback for contracts where no ABI is available.

**Restrict a `transfer` call to a maximum amount and a specific recipient:**

```json theme={"system"}
{
  "policyName": "Limit WETH transfers",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.contract_call_args['wad'] < 1000000000000000000 && eth.tx.contract_call_args['dst'] == '0x08d2b0a37F869FF76BACB5Bab3278E26ab7067B7'"
}
```

**Restrict by function name or selector (also requires an ABI upload):**

```json theme={"system"}
{
  "policyName": "Allow only transfer calls to a contract",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.to == '<CONTRACT_ADDRESS>' && eth.tx.function_name == 'transfer'"
}
```

#### Iterating over contract call arguments

When a contract function takes an array parameter, use  `.count()`, `.all()`, and `.any()` to enforce conditions across every element,
rather than checking a single index.

**Syntax:**

* Array element count: `eth.tx.contract_call_args['arrayArg'].count()`
* All elements match: `eth.tx.contract_call_args['arrayArg'].all(item, <condition>)`
* Any element matches: `eth.tx.contract_call_args['arrayArg'].any(item, <condition>)`

<Note>
  For functions that take a `tuple array` (e.g. `executeBatch((address,uint256,bytes)[] calls)`), tuple fields are currently
  accessed by `position` rather than name: `call[0]` (first field), `call[1]` (second field), and so on.
  The positions correspond to the order of fields as defined in your ABI — substitute the correct indices for your own struct.
  Named access such as `call['target']` is not yet supported and produces `OUTCOME_ERROR`.
</Note>

**Enforce exact batch size**

Restrict signing to transactions containing exactly two calls:

```json theme={"system"}
{
  "policyName": "Allow executeBatch with exactly 2 calls",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.function_name == 'executeBatch' && eth.tx.contract_call_args['calls'].count() == 2"
}
```

**Whitelist all call targets in a batch**

Allow signing only when every call targets a specific contract and sends zero ETH.
call\[0] is the target address and call\[1] is the value, based on the field order in the executeBatch ABI:

```json theme={"system"}
{
  "policyName": "Allow executeBatch to whitelisted contract only",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.function_name == 'executeBatch' && eth.tx.contract_call_args['calls'].all(call, call[0] == '<CONTRACT_ADDRESS>' && call[1] == 0)"
}
```

**Combine batch size and target restrictions**

```json theme={"system"}
{
  "policyName": "Allow executeBatch — max 2 calls to whitelisted contract",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.function_name == 'executeBatch' && eth.tx.contract_call_args['calls'].count() <= 2 && eth.tx.contract_call_args['calls'].all(call, call[0] == '<CONTRACT_ADDRESS>' && call[1] == 0)"
}
```

**Whitelist recipients in a flat address array**

For functions that take a plain `address[]` parameter — such as a disperse-style multi-send contract — use
in to restrict signing to a known set of addresses:

```json theme={"system"}
{
  "policyName": "Allow disperse to whitelisted recipients only",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.function_name == 'disperse' && eth.tx.contract_call_args['recipients'].all(addr, addr in ['<ADDRESS_1>', '<ADDRESS_2>', '<ADDRESS_3>'])"
}
```

See [Smart Contract Interfaces](/features/policies/smart-contract-interfaces) for the full upload
walkthrough and Solana IDL support.

#### Allow ERC-20 transfers for a specific token smart contract (raw calldata fallback)

Use this pattern only when an ABI is unavailable. The selector `0xa9059cbb` is the 4-byte keccak256
hash of `transfer(address,uint256)`.

```json theme={"system"}
{
  "policyName": "Enable ERC-20 transfers for <CONTRACT_ADDRESS>",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.to == '<CONTRACT_ADDRESS>' && eth.tx.data[0..10] == '0xa9059cbb'"
}
```

#### Allow anyone to sign transactions for testnet (Sepolia)

```json theme={"system"}
{
  "policyName": "Allow signing ethereum sepolia transactions",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.chain_id == 11155111"
}
```

#### Allow ETH transactions with a specific nonce range

```json theme={"system"}
{
  "policyName": "Allow signing Ethereum transactions with an early nonce",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.tx.nonce <= 3"
}
```

#### Allow signing of EIP-712 payloads for Hyperliquid `ApproveAgent` operations

```json theme={"system"}
{
  "policyName": "Allow signing of EIP-712 Payloads for Hyperliquid `ApproveAgent` operations",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.eip_712.domain.name == 'HyperliquidSignTransaction' && eth.eip_712.primary_type == 'HyperliquidTransaction:ApproveAgent' && activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2'"
}
```

#### Inspect nested fields in EIP-712 message payloads

The `eth.eip_712.message` map supports nested field access using bracket notation and array iteration operators,
allowing policies to inspect and enforce conditions across typed data contents, beyond just the domain and primary type.

**Syntax:**

* Nested struct fields: `eth.eip_712.message['outerField']['innerField']`
* Array element fields: `eth.eip_712.message['arrayField'][0]['innerField']`
* Array iteration: `eth.eip_712.message['arrayField'].all(item, <condition>)`
* Array length: `eth.eip_712.message['arrayField'].count()`

**Example: Restrict Permit2 batch token approvals by domain, spender, and token**

Uniswap's [Permit2](https://github.com/Uniswap/permit2) `PermitBatchTransferFrom` type lets users sign a single message authorizing
multiple token transfers. The message contains a `permitted` array of `TokenPermissions` structs,
each with a `token` address and an `amount`.

```json theme={"system"}
{
  "types": {
    "EIP712Domain": [
      { "name": "name", "type": "string" },
      { "name": "chainId", "type": "uint256" },
      { "name": "verifyingContract", "type": "address" }
    ],
    "TokenPermissions": [
      { "name": "token", "type": "address" },
      { "name": "amount", "type": "uint256" }
    ],
    "PermitBatchTransferFrom": [
      { "name": "permitted", "type": "TokenPermissions[]" },
      { "name": "spender", "type": "address" },
      { "name": "nonce", "type": "uint256" },
      { "name": "deadline", "type": "uint256" }
    ]
  },
  "primaryType": "PermitBatchTransferFrom",
  "domain": { "name": "Permit2", "chainId": 1, "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3" },
  "message": {
    "permitted": [
      { "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "1000000000" }
    ],
    "spender": "0x2222222222222222222222222222222222222222",
    "nonce": "0",
    "deadline": "1992689033"
  }
}
```

The following policy pins the complete Permit2 domain, allows one token permission, and restricts
that permission to mainnet USDC and a known spender:

```json theme={"system"}
{
  "policyName": "Allow one mainnet USDC Permit2 permission for a known spender",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.domain.name == 'Permit2' && eth.eip_712.domain.version == '' && eth.eip_712.domain.chain_id == 1 && eth.eip_712.domain.verifying_contract == '0x000000000022D473030F116dDEE9F6B43aC78BA3' && eth.eip_712.primary_type == 'PermitBatchTransferFrom' && eth.eip_712.message['spender'] == '0x2222222222222222222222222222222222222222' && eth.eip_712.message['permitted'].count() == 1 && eth.eip_712.message['permitted'][0]['token'] == '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'"
}
```

<Note>
  Array elements can be accessed by index (`[0]`, `[1]`, etc.). When a policy checks only a
  specific index, also constrain the array length or validate every element with `.all()`.
</Note>

#### Iterating over array fields

<Warning>
  Each `EFFECT_ALLOW` policy is evaluated independently. The token-only example below does not cap
  the amount or batch length, and the batch-length example does not cap individual amounts. In
  production, combine every required domain, spender, token, amount, and count constraint in the
  same condition.
</Warning>

**Whitelist a specific token across all approvals with .all():**

```json theme={"system"}
{
  "policyName": "Allow Permit2 batch only for USDC",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.domain.name == 'Permit2' && eth.eip_712.domain.version == '' && eth.eip_712.domain.chain_id == 1 && eth.eip_712.domain.verifying_contract == '0x000000000022D473030F116dDEE9F6B43aC78BA3' && eth.eip_712.primary_type == 'PermitBatchTransferFrom' && eth.eip_712.message['spender'] == '0x2222222222222222222222222222222222222222' && eth.eip_712.message['permitted'].all(p, p['token'] == '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')"
}
```

**Allow Permit2 batches with two or fewer USDC token permissions:**

```json theme={"system"}
{
  "policyName": "Allow Permit2 batches with <= 2 USDC token permissions",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.domain.name == 'Permit2' && eth.eip_712.domain.version == '' && eth.eip_712.domain.chain_id == 1 && eth.eip_712.domain.verifying_contract == '0x000000000022D473030F116dDEE9F6B43aC78BA3' && eth.eip_712.primary_type == 'PermitBatchTransferFrom' && eth.eip_712.message['spender'] == '0x2222222222222222222222222222222222222222' && eth.eip_712.message['permitted'].count() <= 2 && eth.eip_712.message['permitted'].all(p, p['token'] == '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')"
}
```

**Allow one token permission for exactly 100 USDC:**

```json theme={"system"}
{
  "policyName": "Allow one exact 100 USDC Permit2 permission",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.domain.name == 'Permit2' && eth.eip_712.domain.version == '' && eth.eip_712.domain.chain_id == 1 && eth.eip_712.domain.verifying_contract == '0x000000000022D473030F116dDEE9F6B43aC78BA3' && eth.eip_712.primary_type == 'PermitBatchTransferFrom' && eth.eip_712.message['spender'] == '0x2222222222222222222222222222222222222222' && eth.eip_712.message['permitted'].count() == 1 && eth.eip_712.message['permitted'].all(p, p['token'] == '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' && p['amount'] == '100000000')"
}
```

<Note>
  Ethers and bigint-based Viem payloads serialize EIP-712 integers as decimal strings. Compare
  those values to quoted strings, as in `p['amount'] == '100000000'`. Numeric range comparisons
  require the submitted typed-data JSON to contain JSON numbers instead.
</Note>

<Warning>
  `PermitTransferFrom` and `PermitBatchTransferFrom` sign the permitted token amounts and spender,
  but not the eventual transfer recipient. To constrain the recipient, use a Permit2 witness type
  that commits to it or enforce the recipient when the signed permit is executed.
</Warning>

### Deny signing of `NO_OP` keccak256 payloads

```json theme={"system"}
{
  "policyName": "Deny NO_OP hash signing",
  "effect": "EFFECT_DENY",
  "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.hash_function == 'HASH_FUNCTION_NO_OP' && activity.params.encoding != 'PAYLOAD_ENCODING_EIP712'"
}
```

#### Allow signing of EIP-712 payloads for EIP-3009 transfers

This policy allows a Turnkey key to sign an EIP-3009 `TransferWithAuthorization` for exactly one
mainnet USDC to a known recipient. It pins the complete domain so that a contract with the same
domain name cannot reuse the policy.

```json theme={"system"}
{
  "policyName": "Allow one mainnet USDC via EIP-3009 to a known recipient",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && activity.params.encoding == 'PAYLOAD_ENCODING_EIP712' && eth.eip_712.domain.name == 'USD Coin' && eth.eip_712.domain.version == '2' && eth.eip_712.domain.chain_id == 1 && eth.eip_712.domain.verifying_contract == '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' && eth.eip_712.primary_type == 'TransferWithAuthorization' && eth.eip_712.message['from'] == '0x1111111111111111111111111111111111111111' && eth.eip_712.message['to'] == '0x2222222222222222222222222222222222222222' && eth.eip_712.message['value'] == '1000000'"
}
```

Replace the `from` address with the address controlled by the Turnkey key and the `to` address with
the allowed recipient. The example uses USDC's six decimals, so `1000000` is one USDC.

<Note>
  `validAfter`, `validBefore`, and `nonce` are also available under `eth.eip_712.message`. You can
  constrain a single authorization by comparing them to exact quoted values. Policies do not track
  previously used nonces or compare a validity field to the current time; replay protection and
  expiration are enforced by the token contract.
</Note>

#### Allow signing of EIP-712 payloads for EIP-2612 permits for USD Coin

```json theme={"system"}
{
  "policyName": "Allow signing of EIP-712 payloads for EIP-2612 Permits for USD Coin",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.eip_712.domain.name == 'USD Coin' && eth.eip_712.primary_type == 'Permit' && activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2'"
}
```

#### Allow signing of EIP-7702 authorizations

```json theme={"system"}
{
  "policyName": "Allow signing of EIP-7702 Authorizations",
  "effect": "EFFECT_ALLOW",
  "condition": "eth.eip_7702_authorization.address == '<ADDRESS>' && eth.eip_7702_authorization.chain_id == '<CHAIN_ID>' && eth.eip_7702_authorization.nonce == '<NONCE>' && activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2'"
}
```
