Before and after: what one transaction changed
When a provider supplies it, Forensia shows balances, nonces, code and storage before and after an Ethereum transaction. It is one source’s report, not a replay, and it omits intermediate steps.
Most of what you read about a transaction is a list of things that happened along the way: calls, transfers, events. A different and often simpler question is what was actually different once it finished. Which accounts ended up with a different balance. Whether any contract’s stored data changed.
Forensia can now show that as a before-and-after comparison for an Ethereum transaction. For each account the provider reports, you see the value before the transaction and the value after it, for balance, nonce, code and individual storage slots.
It depends on the provider. This information comes from a tracing method that many nodes do not serve, so the section often reads as unavailable. When that happens the workspace says so and keeps the failed response as a record. It does not estimate the changes another way.
#Plain languageA bank statement with two columns
Think of an account statement that shows only an opening balance and a closing balance for one day. It tells you the net result with certainty about the endpoints. It does not show each purchase, the order they happened in, or a payment that was made and then refunded before the day ended.
The before-and-after view is that statement for one transaction, across every account the source chose to report. Four kinds of thing can appear:
- Balance: the amount of ETH held. It includes the fee the sender paid, so the sender’s balance changes even when nothing else does.
- Nonce: a counter on the account. For a wallet it normally goes up by one per transaction sent.
- Code: the program stored at the address, shown as a size and a fingerprint.
- Storage: numbered slots where a contract keeps its data, shown as raw values with no names attached.
An account can also be marked as created or deleted when it appears on only one side.
#How it worksOpening the comparison under the transaction steps
- Look up an Ethereum transaction hash. On Overview, open "Inspect transaction steps".
- Open the disclosure "Before and after this transaction". Opening it does not fetch anything.
- Read the status line. "Transaction boundary changes returned" is followed by the number of accounts. Other lines explain why nothing is shown.
- Open an account. Its heading is "Account changes", "Created account" or "Deleted account", with a count of changed fields.
- Read each change. Headings are "ETH balance · includes fees", "Account nonce", "Account code" and "Unnamed storage slot", each with a Before and an After value.
- Open "Sources and interpretation limits" to reach the five source records: network check, block check before trace, receipt check, state trace and block recheck.
For a failed transaction a line reminds you that fees and protocol-level account changes can still persist, and that they do not establish successful calls or transfers. A value missing from one side reads "Not present in this side of the response".
The review transaction 0x22fc4a1bba0e2a711a5852f5c835eea4cb764f989895b1b48ec73d84dab8ed95 returned 30 accounts in a recorded capture, at block 26,126,887, transaction position 235. A lookup of the wrap transaction 0x7742b66e9c5d18919daf9a64bea9c28eee7b8481d9877c1fb70ac780eec12a87 on the same day got a method-not-found refusal for the state trace and showed unavailable. Both outcomes are normal.
#Technical detailThe five-read sequence and the diff rules
Acquisition
The state capture runs for Ethereum mainnet only (chain ID 1), and only once the lookup has established the transaction as succeeded or failed. It makes five JSON-RPC reads in a fixed order and stops at the first one that fails a check.
| # | Method | Must hold |
|---|---|---|
| 1 | eth_chainId | Equals the lookup’s chain ID |
| 2 | eth_getBlockByNumber (number, false) | Block number and hash match; the hash at the recorded index is this transaction; it appears exactly once |
| 3 | eth_getTransactionReceipt | Transaction hash, block hash, block number and index match; status is 0x1 or 0x0 as already recorded |
| 4 | debug_traceTransaction | Parses as a strict, bounded state diff |
| 5 | eth_getBlockByNumber (number, false) | Same checks as read 2, repeated after the trace |
Read 4 uses these options, taken from the code:
{ "tracer": "prestateTracer", "timeout": "3s",
"tracerConfig": { "diffMode": true, "disableCode": false, "disableStorage": false } }The second block read exists to catch a chain reorganization during the sequence. If the block no longer holds this transaction at this position after the trace, the result is a conflict and no changes are accepted.
Endpoint handling is strict. A fallback endpoint may be tried on the first read only. Whichever endpoint answers is pinned for the rest, so a refusal of the trace is never patched over with another node’s answer. When a saved result is validated, all source records must name the same endpoint host.
Budgets and bounds
| Limit | Value |
|---|---|
| Overall deadline for the sequence | 15 seconds |
| Per-request limit | 5 seconds for the trace, 2.5 seconds for the others |
| Requests in the sequence | At most 6 (five reads plus one optional start-code read) |
| Response size | 1 MiB (1,048,576 bytes) per response |
| Accounts across both sides | 64 |
| Storage entries across both sides | 256 |
| Code, per value | 49,152 bytes |
| Code, total | 262,144 bytes |
Exceeding a bound gives the status limit and nothing is displayed. There is no partial diff, because in this format an omission carries meaning and a truncated response would change it. The reads also count against the lookup’s overall request budget and respect the source switches that disable a provider.
Parsing
The response must be an object with exactly two keys, pre and post. Each account key must be a 20-byte address and may not repeat in a different letter case. An account may contain only balance, nonce, code and storage. Balance must be a hex quantity, nonce a non-negative safe integer, code bounded hex, and each storage key and value a full 32-byte word. An unknown key or a malformed value gives the status invalid. Balances are kept as exact integers and formatted to 18 decimal places for display.
Turning pre and post into changes
- An account only in post is created. An account only in pre is deleted. An account in both is modified.
- For a modified account, a balance, nonce or code field missing from post means unchanged under this tracer’s rules, and no change is listed.
- For storage, a slot missing from one side is read as zero, and a change is listed only if the two sides differ. This zero rule applies to storage in this diff format and to nothing else.
- A missing balance, nonce or code field is never filled in with zero or empty.
- Accounts and slots are sorted for display. Code is shown as a byte count and its Keccak-256 hash.
Statuses
| Status | Meaning |
|---|---|
| captured | All five reads passed and the diff parsed |
| unavailable | The source did not serve the trace or another read failed |
| unsupported | Not Ethereum mainnet |
| invalid | The response had a shape the parser could not safely interpret |
| limit | A bound was exceeded; no partial diff |
| conflict | Network, receipt or block-position records disagreed |
Rechecking a saved result
Under Sources, Capture integrity & local evidence, Check local evidence re-reads the saved response bodies in your browser, up to 1 MiB each, and runs the same validators used at capture. Key order in the JSON does not matter. Exact integer values, storage entries and omissions do. A missing or expired body leaves the check incomplete, never matched, and a contradiction in any available body is reported even if an earlier one is missing. Older saved results are not backfilled with state they never captured.
#LimitsWhat the two columns leave out
- This is a report from one source at the transaction’s boundaries. Forensia does not re-execute the transaction and does not verify the report independently.
- It is not an opcode-by-opcode history. Writes that were made and then reverted, and values that changed and changed back, do not appear.
- It does not say which call caused which change. The sources attach to the transaction as a whole, never to a guessed internal step.
- The account list is not an inventory of every account touched. Accounts that were read but not changed are normally absent.
- Storage slots have no variable names. Forensia does not guess what a slot means.
- Code is not matched to verified source.
- A balance difference includes fees and other transaction effects. It is not the same thing as an amount transferred.
- A nonce change alone does not count ordinary transfers. Delegation authorizations can also affect it.
- An empty result does not mean no code ran or that nothing changed along the way.
- A failed transaction can still show fee and nonce effects. These do not establish successful calls.
- Supported for Ethereum only, and only when the provider serves the trace.
- It says nothing about who owns an account or whether a change was safe or intended.