Where does it stop?
The contract’s own code decided to stop. That happens when the transaction runs, so a mined transaction has a hash and a receipt with status reverted. You can also meet the same revert earlier, when the gas is estimated.
Two ways to meet the same revert
- The estimate fails first. With a local account and no gas limit, viem asks the node to estimate the gas. The revert happens there, and you get the reason before anything is sent: nothing is mined and no gas is spent.
- The transaction is mined and reverts. With a gas limit you set, or when the state changed after the estimate, the transaction is sent and mined, and the receipt says reverted. The gas is spent.
An address-only account, such as a wallet or a dev node that signs for you, does not go through viem’s estimate: viem hands the transaction to the wallet or node, which decides about gas. In our Anvil run it was accepted and got a hash.
Why does it happen?
- A failed
requireor an explicitrevert. The contract refuses: it is paused, a limit is exceeded, an allowance or a balance is too low. - Wrong arguments or wrong state. The call is valid, but not for the contract’s state now.
- The state changed since the estimate. Another transaction got there first.
How do I fix it?
- Read the reason. viem prints it for an estimate. For a mined transaction, read it from the trace or decode it with the contract’s ABI.
- Run the call before you send it. A simulation returns the revert and its reason and costs nothing:
import { createPublicClient, http, parseAbi } from 'viem';import { mainnet } from 'viem/chains';
declare const account: `0x${string}`;declare const vault: `0x${string}`;const publicClient = createPublicClient({ chain: mainnet, transport: http() });const abi = parseAbi(['function withdraw(uint256 amount)']);
// Run the call against the current state first: a revert comes back with its reason and costs nothing.const { request } = await publicClient.simulateContract({ account, address: vault, abi, functionName: 'withdraw', args: [1n] });- In an agent, simulate inside the tool and return the reason to the model, so it can change the amount or stop, instead of sending a transaction that is bound to revert.
What does it look like in a trace?
When the estimate fails, the send span ends with an error and no hash, like any refusal. Since hashspan 1.1.0 its error.type is ExecutionRevertedError, the name viem gives a revert. When the transaction is mined, the confirm span carries the reason:
- blockchain.tx.status
- reverted
- blockchain.tx.revert.reason
- Withdrawals are paused
- blockchain.tx.gas.used
- 21,054
- blockchain.tx.fee
- 42,108,000,000,000
- error.type
- reverted
The replay uses the state of the previous block, so a transaction that depended on another one in the same block can show no reason or a different one. The long version, with the trace of an agent whose withdrawal reverted, is in your agent’s trace stops at the tool call, and a query for them is in find reverted transactions. A revert without any reason, with gas used equal to the limit, is out of gas.