Transaction Simulation Best Practices for AI Agents
How to safely simulate Ethereum transactions before an agent signs anything.
Transaction simulation for AI agents (quick definition)
Transaction simulation is the process of executing a transaction locally before signing, allowing AI agents to verify behavior, detect malicious actions, and enforce safety policies without broadcasting on-chain.
AI agents that can sign and broadcast Ethereum transactions are powerful. They are also one of the fastest ways to lose funds if they are not tightly constrained. Unlike humans, AI agents:
- Act continuously and at high speed
- Can be manipulated via prompt injection
- Do not intuitively recognize when something looks wrong
Because of this, transaction simulation is not optional. It is one of the primary runtime safety layers before on-chain execution, not a guarantee of safety.
This guide explains how to treat simulation as a mandatory runtime gate, not a debugging convenience. For transactions your agent constructs itself, validate the typed params and simulate. For transactions supplied by a third party, decode calldata first, then run the same policy checks and simulation.
This applies to Ethereum and EVM-compatible L2s, where the same simulation patterns and RPC methods are used.
What is transaction simulation?
Transaction simulation is the process of executing a transaction without broadcasting it, typically using eth_call or a fork, to observe whether it would revert and what state changes it would produce.
Why is transaction simulation critical for AI agents?
In practice that view can be:
- Live state via eth_call (single contract call, no fork required)
- Mainnet fork (e.g. Anvil, Foundry)
- Hosted simulator with traces and diffs (e.g. Tenderly)
For AI agents, simulation is critical because it lets you:
- Preview approvals, transfers, and other state changes (especially when you decode calldata or inspect traces)
- Detect unintended side effects before execution
- Verify that what you are about to sign matches the agent's stated intent
Without that loop, an agent can easily approve unlimited spending, target the wrong spender or recipient, or interact with malicious or misleading contracts, often while passing naive spend cap checks.
What are the biggest risks?
The most common failures are intent mismatches, not low-level bugs. For example:
- Approving a malicious spender while the UI or model narrative says swap
- Sending funds to the wrong recipient due to manipulated or hallucinated addresses
- Interacting with proxies or upgraded contracts whose behavior no longer matches assumptions
- Executing technically valid transactions that violate user expectations or policy
These paths often slip through simple spend limits because the shape of the transaction looks allowed.
What does "fail closed" mean in AI agent security?
Fail closed means that if a transaction cannot be verified as safe under defined policies, it must not execute.
In AI agents, simulation and policy checks enforce this by blocking any transaction that fails validation before signing.
Many systems simulate for visibility but still execute when something looks off. Simulation plus automated checks is how you enforce that in software, not as a human checklist.
How do you safely simulate transactions for an AI agent?
To safely simulate transactions for an AI agent:
- Construct or decode the intended call
- Validate parameters against policy (allowlists, caps, intent)
- Simulate using eth_call or a fork
- Enforce invariants and fail closed if any check fails
- Only then sign and broadcast
How to enforce simulation programmatically
Simulation should be mandatory in your agent loop: decode or construct the intended call, run policy checks, simulate, enforce invariants, log, then, and only then, sign.
Below is a minimal, realistic viem pattern for a known contract function (here, ERC-20 approve). For arbitrary raw calldata, you decode first (see decoding calldata), then simulate or trace.
This example uses live-state simulation through an RPC provider because it is lightweight enough to run inside an agent's normal signing flow. For deeper multi-step analysis, use a mainnet fork such as Anvil.
If the agent intends to call approve(spender, amount), construct that call explicitly and simulate before signing.
// This only simulates against Ethereum mainnet state.
// It does not broadcast or modify state unless the returned request
// is later passed to a wallet client, e.g. walletClient.writeContract(request).
import { createPublicClient, http, parseAbi, type Address, getAddress } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http(process.env.ETH_RPC_URL!), // Never commit secrets or .env files
})
// Minimal ERC-20 ABI: enough to simulate `approve(address,uint256)`.
const erc20Abi = parseAbi([
'function approve(address spender, uint256 amount) returns (bool)',
])
const MAX_UINT256 = 2n ** 256n - 1n
function assertSafeApprove(
spender: Address,
expectedSpender: Address,
amount: bigint,
) {
// Normalize and validate addresses (throws if invalid)
const spenderNorm = getAddress(spender)
const expectedNorm = getAddress(expectedSpender)
// The spender encoded in the tx must match the spender the agent expected.
if (spenderNorm !== expectedNorm) {
throw new Error('Blocked: spender mismatch')
}
if (amount === MAX_UINT256) {
throw new Error('Blocked: unlimited approval')
}
}
export async function simulateAndGateApprove(params: {
token: Address
from: Address
spender: Address
expectedSpender: Address
amount: bigint
}) {
// Policy checks run before the transaction reaches a signer.
assertSafeApprove(params.spender, params.expectedSpender, params.amount)
const { result, request } = await client.simulateContract({
address: params.token,
abi: erc20Abi,
functionName: 'approve',
args: [params.spender, params.amount],
account: params.from,
})
if (result !== true) throw new Error('Blocked: simulation failed')
return request // pass this to signer only after checks pass
}
const request = await simulateAndGateApprove({
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', // agent wallet
spender: '0x6A000F20005980200259B80c5102003040001068',
expectedSpender: '0x6A000F20005980200259B80c5102003040001068',
amount: 25_000_000n, // 25 USDC (USDC has 6 decimals)
})
console.log('Simulation passed. Prepared request:', request)This example shows the minimal gating pattern. In production, you should enforce additional constraints:
- Restrict tokens to an allowlist (do not approve arbitrary contracts)
- Restrict or validate spenders (allowlist or strict intent matching)
- Set per-token approval caps (not just not unlimited)
- Normalize addresses safely (e.g. viem getAddress)
- Account for state drift (simulation only reflects a snapshot; execution may differ if state changes before inclusion)
Important: simulateContract uses eth_call-style execution against current RPC state. It proves this call doesn't revert right now and returns the function result. It does not provide a full multi-step state diff.
For full state diffs or multi-transaction analysis, use a fork (e.g. Anvil) or a tracing tool like Tenderly.
For a deeper "compare before/after on a fork" workflow, see simulating transactions.
What should an AI agent check before executing a transaction?
At minimum, automate checks such as:
- Token approvals (flag effectively unlimited or absurdly large values)
- Recipient/spender vs an allowlist or a model-derived expected address
- Native ETH and token transfer amounts vs caps
- Calls to unknown or very new contracts (policy, delay, or human gate)
- Unexpected multi-step patterns (nested calls, sweepers, permit + transfer)
- Balance or allowance deltas beyond expected slippage when trace or diff data is available
- Decode calldata when the agent did not construct the transaction itself (wallet, dapp, or third-party supplied hex)
Final takeaway
The risk is not what agents can do, but what they are allowed to execute.
Transaction simulation, combined with intent validation, is the last line of defense before irreversible execution.
Make it mandatory, logged, and enforced. Treat it as a hard security requirement, not a suggestion.
Building AI agents that interact with on-chain systems? I help teams review transaction flows, spot integration risks, and validate pre-launch security. I also offer private tooling and custom implementations for production use cases. Feel free to DM or reach out.