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.
Where it lands in Jayverse
- Wallet: implement simulate → explain → sign → monitor as the actual pipeline, not a slogan. Wrap every write jayverse-wallet sends through viem's simulateContract, decode the result, and show the plain-language outcome before the signature prompt — since it's an embedded wallet, the app owns the entire consent moment.
- Wallet: build the error-selector registry now, not after the first undecodable revert. Combine Jayverse's own ABIs with a 4byte-style directory and an explicit "unknown reason" fallback, so decoded failures don't silently collapse from "insufficient allowance, need 50 more" to "something reverted."
- gitboard: publish the false-promise rate as a standing metric. Track the share of transactions that passed simulation but failed on-chain, per contract, as the SLO that measures whether the explain step is honest.
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| blind signing | 내용을 모른 채 서명하기 · 지갑이 트랜잭션 의미를 설명 안 해줄 때 벌어지는 문제. "Blind signing kept burning people" |
| burn (money) | 돈이 날아가다, 소진되다 · 실패한 거래에 가스비만 날린 경우. "well over $150M burned in a gas war" |
| climax with | ~로 절정에 달하다 · 여러 사건이 누적되다 최악의 사건으로 정점을 찍을 때. "it climaxed with the Bybit hack" |
| bubble up (from) | (에러 등이) 아래에서 위로 전파되어 올라오다 · 중첩된 컨트랙트에서 에러가 올라올 때. "A revert bubbling up from a nested... contract" |
| gas war | 가스 전쟁 · 다들 먼저 처리되려고 가스비를 올려 경쟁하는 상황. "well over $150M burned in a gas war" |
| patch the wrong layer | 엉뚱한 계층을 땜질하다 · 문제의 진짜 원인이 아닌 곳을 고칠 때. "The market voted, but patched the wrong layer" |
| the seed of | ~의 씨앗, 출발점 · 작은 아이디어가 나중에 큰 것으로 자랄 때. "the seed of a shared service" |
| honesty meter | 정직함을 재는 척도(비유) · 예측이 얼마나 믿을 만한지 보여주는 지표. "the honesty meter of the explain step" |
| false-promise rate | 거짓 약속 비율 · 시뮬레이션은 통과했지만 실제로 실패한 거래의 비율. "the false-promise rate... is the honesty meter" |
| front-running | 선행매매 · 남의 거래를 미리 알고 앞서 끼어드는 행위. "does not... eliminate front-running" |
| SLO | 서비스 수준 목표(Service Level Objective) · 서비스가 스스로 공개해야 할 정직성 지표의 기준. "the SLO the service should publish about itself" |
| TOCTOU | 확인 시점과 사용 시점의 간극(Time-of-check to time-of-use) · 시뮬레이션 이후 실제 실행 사이에 상태가 바뀌는 문제. "Time-of-check to time-of-use." |
| 4byte directory | 이더리움 함수 셀렉터 조회용 공개 레지스트리 · 알려지지 않은 revert 이유를 해독할 때 참조하는 도구. "a 4byte-style directory" |