Open the codebase and search for the number of confirmations you wait for before treating a transaction as done. You will find a constant. It will be 1, 6, 12 or 64, it will have no comment explaining the choice, and it will be applied uniformly to a five dollar tip and a fifty thousand dollar settlement.
That constant is not a technical detail. It is a statement about how much money you are willing to lose to a reorg, and it belongs to whoever owns the product risk, not to whoever was writing the listener that afternoon.
Confirmed is a word your interface invented
No chain sends you an event that means confirmed. What you get is a transaction included in a block, and then more blocks built on top of it. Everything after that is interpretation performed by your software.
The interpretation differs fundamentally depending on the consensus you are reading:
Probabilistic finality. Under longest-chain rules, a transaction is never final. The probability of reversal falls as blocks accumulate, approaching zero without reaching it. Six confirmations is not a threshold the protocol recognises. It is a convention that means “the residual risk is small enough for most purposes”, and “most purposes” was defined by other people’s products, not yours.
Deterministic finality. Chains with an explicit finality gadget will, after some number of blocks or epochs, mark a block as final under an accountable rule: reverting it requires a quantifiable and punishable fraction of validators to misbehave. Here there genuinely is a line, and once you are past it your remaining risk is a governance and slashing question rather than a probability calculation.
Inherited finality. On a rollup, your transaction has two finality clocks: fast soft confirmation from the sequencer, and real finality once the data or proof lands on the base layer. These can be minutes apart or hours apart, and a product that treats the sequencer’s acknowledgement as settlement has chosen to trust the sequencer. Which may be entirely reasonable, and should be a decision somebody made on purpose.
Every confirmation threshold is a bet. The only unusual thing about most of them is that nobody remembers placing it.
One threshold for every action is the actual bug
A single global constant is wrong in both directions simultaneously. It is too slow for the small stuff, making your product feel broken when someone sends two dollars. And it is too fast for the large stuff, where the amount at risk justifies waiting considerably longer.
The useful frame is expected loss. If reversal at depth d has probability p(d), and the action is worth v, then the exposure is roughly p(d) times v, and the question is what exposure you accept per action. That turns the constant into a small table, which is the shape the problem actually has:
action depth rationale
display balance 0 optimistic, reversible in UI
unlock non-transferable 1 reversal costs us nothing real
credit account under $100 3 exposure below support cost
credit account $100-$10k 12 standard risk appetite
credit above $10k final wait for the finality gadget
withdraw to external chain final irreversible on our side
mint against a deposit final supply is permanent
Two things fall out of writing it this way. Anything irreversible on your side should wait for the strongest finality available, because you are giving up the ability to compensate. And the cheap actions can be near-instant, which is where most of the perceived speed of a product comes from.
Optimistic display is fine. Optimistic accounting is not.
The distinction worth holding onto is between what you show and what you commit. Showing a pending state immediately is good product design. Writing an irreversible ledger entry off the same event is how you end up with a balance you cannot explain.
The state machine that survives contact with reorgs has more than two states, and every one of them is reachable from the outside:
- submitted. Broadcast, not seen in a block. May never be. Can be replaced by the sender at a higher fee.
- included. In a block at depth 1. Genuinely likely to stick, genuinely able to vanish.
- credited. Past the depth you chose for this action’s value. Your ledger has moved.
- final. Past the chain’s own finality rule, where one exists.
- reverted. Was included, is no longer in the canonical chain. This state must exist in your schema, or your code will handle a reorg by having no opinion at all.
If reverted is not a state your system can represent, then a reorg does not cause a handled error. It causes a permanent inconsistency that somebody discovers in a reconciliation report three weeks later.
What to say to the person waiting
Depth is meaningless to a user, and a spinner with no horizon is the worst available option. Two things reliably help.
Translate blocks into time, and be honest about the variance. “Usually about two minutes” is more useful than “3 of 12 confirmations”, and “this is taking longer than usual, the network is congested” is better than either, because it tells the user the delay is not their fault and not a bug.
Then let them leave. Anything gated behind waiting should be something the user can walk away from and be notified about. A confirmation flow that requires the tab to stay open converts a protocol property into a usability failure, and it is the single most common way products make finality feel worse than it is.
Write the number down with a reason
The practical ask is small. Find the constant. Put a comment next to it naming who chose it, what exposure it accepts, and what would justify changing it. Then split it by action value, which is usually an afternoon of work.
That comment is what makes the number reviewable during the next incident, and it turns an inherited default into a decision your organisation can defend. Which is the difference between having a risk appetite and having a config file.

