Signing is the one irreversible step in a crypto transaction, and the confirmation screen is the last safety system in front of it. What simulation catches, what it can't, and the design rules I'd hold a signing flow to.
Learning in public. I haven’t shipped anything on-chain yet. These posts map what I know from production payments and ledgers onto how blockchains handle the same problems.
Most of my career has been about making the irreversible step safe. In a send pipeline
that's an idempotency key and a
rehearsed rollback. In a ledger it's
append-only rows and a reconciler. On /ask it's a refusal
gate that says "I don't know" rather than guess.
A crypto wallet has the starkest version of the problem I've studied. There's no rollback, no chargeback, no support ticket that reverses a transfer. The signature is the commit, and the screen in front of it is the last safety system the user has. I'm self-taught in this space, and these are the design rules I've arrived at reading about how real attacks work.
Rule 1: show outcomes, not instructions.
A raw transaction is a list of instructions: program IDs, account addresses, encoded data. Showing that to a person and asking them to approve it is blind signing. It looks transparent, and nobody can actually read it.
The fix is simulation. Before asking for a signature, run the transaction against
current chain state and show what would change: "You send 2 SOL, you receive 312 USDC."
Solana RPC nodes expose simulateTransaction, and the EVM has equivalent call and
trace tooling. Phantom took this seriously enough to acquire Blowfish, a
simulation-and-scam-detection company, in late 2024, and fold its warnings into the
wallet.
A balance diff is the most useful thing a confirmation screen can show. It's also not enough on its own, which is what the rest of these rules are about.
Rule 2: authority changes are first-class, even when no balance moves.
Some of the most damaging transactions move no money at signing time. On Solana, a token account has an owner authority that can be reassigned. A transaction that hands that authority to an attacker changes no balances, so a naive simulation diff shows nothing, and the user loses control of everything in the account.
The EVM has the same shape in token approvals: an approve for an unlimited amount, or
an off-chain signed permit, moves nothing today and authorizes a drain tomorrow.
So the screen needs a second section beside the balance diff: what this transaction lets someone else do later. Owner changes, delegate approvals, unlimited allowances, and closed accounts. These should be the loudest thing on the screen, not a footnote, because their blast radius is "everything", not "this amount".
Rule 3: a simulation is a prediction, so assert the outcome at execution.
Simulation runs against the state at preview time. The transaction executes against the state a few seconds later. An attacker who controls a contract can make it behave when simulated and then change its state before the real transaction lands. Security researchers have documented exactly this: a benign preview, a state change on the attacker's side, and the victim's signature arriving about thirty seconds later.
It's a time-of-check to time-of-use bug, and the fix is the database one: don't trust the check, enforce the invariant at commit. Phantom does this with Lighthouse, an open assertion program. Before submitting, the wallet can append instructions that check the post-transaction state, such as token balances or that an account's data hasn't changed, and make the whole transaction fail if reality differs from the preview. Phantom's developer docs warn apps that the on-chain transaction may contain these extra instructions, which is a fair price for turning a prediction into a guarantee.
That's the same move as a CHECK constraint versus validation in application code.
Application checks are a courtesy; the constraint is the guarantee.
Rule 4: show the expiry, and treat "never expires" as a warning.
A normal Solana transaction carries a recent blockhash and becomes unprocessable after roughly 150 blocks, about 30 seconds since the October 2026 move to 200ms slots. That deadline is a quiet safety feature: a signature you abandon can't be dug up later.
A transaction built on a durable nonce has no deadline. It stays valid until it's submitted or the nonce advances, so it can be signed today and executed next month against completely different state, long after the preview stopped meaning anything. There are legitimate uses for it (offline signing, slow multisigs), but a consumer wallet should say so plainly: this approval doesn't expire.
Rule 5: fail closed, and make friction proportional to risk.
If simulation fails, times out, or returns something the wallet can't interpret, the
safe default is to say so and make signing harder, not to fall back to "approve anyway"
with a smaller warning. My /ask agent refuses when retrieval is weak, and the logic is
the same: an unknown should look like an unknown, not like a yes.
The flip side is that friction is a budget. If every transaction carries a scary warning, users learn to click through all of them. The confirmation screen should be calm for a plain transfer to a known address, firmer for an unlimited approval, and genuinely in the way for an owner change to an address with a bad reputation. Reserve the loudest signal for the rarest and most dangerous actions, or it stops working.
The checklist.
If I were reviewing a signing flow, this is what I'd look for:
| On the screen | Why |
|---|---|
| Balance changes, in plain units | The outcome, not the instructions |
| Authority and approval changes, prominently | No balance moves, total exposure |
| Who's asking (domain, reputation) | Most drains start on a lookalike site |
| Expiry, or "doesn't expire" | A preview only means something for a short window |
| What happens if the preview is wrong | Execution-time assertions turn a prediction into a guarantee |
| A clear "couldn't verify" state | Fail closed instead of defaulting to approve |
Most of this list isn't about crypto. It's what I'd ask of any system with an irreversible commit: show the real effect, enforce it where it's written, make unknowns loud, and spend friction where the blast radius is.
Owen
Thanks for reading. Cheers.
If you spot something wrong in this post, or you’d have done it differently, tell me. I’d rather be corrected than confidently wrong. owenlrichards2000@gmail.com
— Owen