Ethras Blog
From Transaction Hash to Finality: Build an Ethereum Receipt Tracker
Track submitted Ethereum transactions through pending, replacement, execution, and finality while keeping retries from creating duplicate business actions.
An RPC timeout is a dangerous place to make a business decision. The transaction might have reached a node even though your application never received the answer. Signing another transaction immediately can turn one intended action into two.
A receipt tracker should preserve uncertainty until it has evidence to resolve it. The following design suggestions apply to ordinary Ethereum transactions sent by an externally owned account. Wallet abstractions and other networks may require additional states.
Persist the operation before submission
eth_sendRawTransaction submits signed transaction bytes and returns a transaction hash. That response does not establish successful execution. Its role is described in the execution API method reference.
Our recommendation is to record a stable business operation ID, chain ID, sender, nonce, expected transaction hash, and submission attempts before broadcasting. Use your signing library to derive the hash from the exact signed transaction. Retain the signed payload only where necessary for controlled recovery, with appropriate access controls, rather than placing it in routine logs.
Keep operation identity separate from transaction identity. One operation can have several deliberately signed replacement hashes. A transaction hash by itself cannot explain whether a later transaction was a speed-up, a cancellation attempt, or an unrelated action.
Ethereum transactions include a sender nonce and pass through broadcast and inclusion stages. See the official transaction overview. We recommend one nonce coordinator per sending account so parallel workers do not assign nonces independently.
Use evidence-based states
The following is a suggested application state model, rather than a list of RPC return values:
| State | Evidence to record | Suggested next action |
|---|---|---|
| Submission uncertain | Request timed out or response was lost | Check the known hash and submission record |
| Pending observed | Endpoint knows the transaction without block inclusion | Continue bounded tracking |
| Receipt observed | Receipt includes a containing block | Evaluate execution and canonicality |
| Replacement suspected | Another candidate shares sender and nonce | Reconcile candidates before concluding |
| No longer observed | A previously visible candidate is absent | Preserve uncertainty and investigate |
| Finality policy met | Canonical inclusion satisfies the chosen policy | Complete the business workflow once |
Avoid treating “no longer observed” as proof that a transaction was dropped everywhere. Keep the observation source and time so that an absence can be interpreted in context.
Geth's txpool documentation allows multiple transactions with the same sender and nonce, and identifies txpool methods as non-standard. Public endpoints need not expose them. We recommend designing recovery around your own candidate records, using pool inspection only when your integration explicitly supports it.
Interpret a receipt precisely
eth_getTransactionReceipt returns a receipt or null. A null answer means that endpoint did not return a receipt; it does not identify whether the cause is pending, unknown, or another visibility issue. For modern Ethereum receipts, status: "0x1" indicates execution success and status: "0x0" indicates failure. These are receipt fields inside a successful RPC result. See the receipt API reference.
Our recommendation is to validate the requested hash, returned transaction hash, block hash, block number, and status before changing workflow state. Persist the observation instead of keeping it only in a browser component.
A successful receipt is also narrower than many business operations. Your application may need to verify the intended contract, decoded events, and application postconditions. Do that using the expected operation context. For execution failure, retain gas and receipt information and offer a separate diagnosis path; do not label it a transient network error and repeat the action automatically.
Recheck the containing block
Inclusion and finality are different stages. Ethereum's Gasper documentation explains how fork choice and finality contribute different consensus properties.
We recommend storing the receipt's block number and block hash together. During tracking, compare the canonical block at that height with the recorded hash. A mismatch should move the candidate back into reconciliation. Re-query the receipt and check other candidates before updating downstream systems.
Choose a finality policy explicitly. A count of blocks after inclusion can be a product policy, but should not be named protocol finality. If you use a finalized-head query, verify endpoint support and establish that the containing block belongs to that finalized chain. Do not promise a fixed wall-clock completion time.
Design downstream writes to tolerate reconciliation. For example, keep a provisional operation record until the selected policy is met, then finalize it through an idempotent transition keyed by the business operation ID.
Recover without creating a second action
Our suggested recovery order after a timeout is to inspect the existing operation record, query its known hashes, reconcile the sender's nonce and candidate transactions, and only then decide whether rebroadcast or replacement is appropriate.
Rebroadcasting identical signed bytes preserves the transaction hash. Signing different contents or choosing a fresh nonce creates a different candidate and requires an explicit business decision. Replacement acceptance depends on node policy, so verify the actual rules before building automatic fee increases.
Do not infer successful replacement solely because the account's confirmed nonce advanced. That observation does not identify which transaction consumed it or whether the intended operation succeeded. For accounts using EIP-7702, processing an authorization can also advance the account's nonce, even inside another sender's transaction. Reconcile that authorization activity as well as the account's own transactions, and obtain the relevant receipt evidence.
Finally, use bounded polling with backoff, persist progress across restarts, and let a user revisit an unresolved operation without resubmitting it. A reliable transaction screen can say “still checking.” That honest state is more useful than a success banner based only on a hash, or a retry button that quietly signs a second action.
Share this article
Share on XReview your account and post on X. Opening X or copying text does not send a post.