Allow ABI-specific contract call parameters
For contract interactions, use 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:
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>)
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.address[] parameter — such as a disperse-style multi-send contract — use
in to restrict signing to a known set of addresses:
Allow ERC-20 transfers for a specific token smart contract (raw calldata fallback)
Use this pattern only when an ABI is unavailable. The selector0xa9059cbb is the 4-byte keccak256
hash of transfer(address,uint256).
Allow anyone to sign transactions for testnet (Sepolia)
Allow ETH transactions with a specific nonce range
Allow signing of EIP-712 payloads for Hyperliquid ApproveAgent operations
Inspect nested fields in EIP-712 message payloads
Theeth.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()
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.
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().Iterating over array fields
Whitelist a specific token across all approvals with .all():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.Deny signing of NO_OP keccak256 payloads
Allow signing of EIP-712 payloads for EIP-3009 transfers
This policy allows a Turnkey key to sign an EIP-3009TransferWithAuthorization 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.
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.
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.