Why
Most failed writes are knowable before gas or user attention is spent. Simulation cannot guarantee future state, but it turns avoidable failures into application errors rather than wallet surprises.
Simulation removes failures already implied by current state and exposes return data or revert reasons without spending gas. It does not reserve that state, guarantee ordering, or eliminate front-running. The useful product pattern is therefore simulate → explain → sign → monitor, not simulate → promise.
How it works
Wrap three writes with viem simulateContract: one success, one custom-error revert, and one state-dependent failure. Compare the predicted outcome with the receipt and surface decoded failure before requesting a signature.
PoC
Wrap three writes: a success, a custom-error revert, and a transaction that succeeds in simulation but fails after another transaction changes state. Decode the first two before requesting a signature and use the third to document the boundary of the guarantee.
const { request, result } = await publicClient.simulateContract(args)
// show decoded effect and warnings
const hash = await walletClient.writeContract(request)
Reference: viem simulateContract.
Review clarification
The message in one sentence, and what caused this card
Never ask a user to sign a transaction you could have known would fail or surprise them — check first, explain in plain words, sign, then keep watching, because the check is not a guarantee. The card was not born from theory; three expensive histories sit behind it:
- Blind signing kept burning people, up to $1.5B. Years of wallets showing unreadable hex built the wallet-drainer economy — thousands signing
setApprovalForAllor permits to phishing sites — and it climaxed with the Bybit hack (2025-02, ~$1.5B): professional multisig signers approved a tampered UI's malicious delegatecall. Every loss had the same shape: what the transaction would do was knowable before signing, and nobody's software said it out loud. That is the explain half. - Users literally pay for predictable failures. A revert still costs gas. The canonical case is the Otherside mint (2022): well over $150M burned in a gas war, a chunk of it on transactions that failed — money paid for nothing that a
simulateContractcall would have caught. That is the simulate half. - The market voted, but patched the wrong layer. Simulation became a product category — Tenderly, Blockaid, Pocket Universe, Rabby's built-in preview, MetaMask + Blockaid. But a wallet can only show a generic balance diff; only the application knows the domain meaning ("you will receive ~132 USDC, or this reverts because your allowance is 50 short").
The third write's formal name: TOCTOU
Time-of-check to time-of-use. Simulation is a check against latest; the signed transaction executes against future state, and the gap is the mempool. The moral is ancient and settled: a check is not a reservation — which is the card's "simulate → explain → sign → monitor, not simulate → promise" in one word. A worthwhile fourth write someday: block-environment drift — a contract branching on block.timestamp or basefee can pass simulation and legitimately fail with no adversary and no state change by anyone, a different edge of the guarantee than write #3.
Embedded wallets remove the second surface
With an external wallet, the prompt is an independent second surface where a bad transaction might still get caught. With an embedded wallet (embedded-wallet-policy) that surface does not exist — the app owns the entire consent moment, so simulate-and-explain stops being polish and becomes the only place informed consent can happen. This is what promotes the card from UX nicety to the seed of a shared service (Jayverse #6, Wallet & Simulation-before-sign).
You can only decode errors you know
viem decodes custom errors from the ABI it was given. A revert bubbling up from a nested third-party contract arrives as a raw selector — undecodable without that ABI. A service version needs an error-selector registry (own ABIs + a 4byte-style directory + an honest "unknown reason" fallback). "Decoded failure" quietly ranges from "insufficient allowance, need 50 more" to "something reverted" — show one of each.
The pipeline, and the KPI
This card and receipt-is-not-settlement are one product: the monitor leg of simulate → explain → sign → monitor is exactly that card's detected → included → safe → finalized (+ reorged) machine. Before the signature this card removes knowable failures; after it, the tier machine handles the unknowable ones. The production KPI falls out naturally: the false-promise rate — the share of transactions that passed simulation but failed on-chain, per contract and per market condition. That number is the honesty meter of the explain step, and the SLO the service should publish about itself.