Checking one Across bridge journey between Ethereum and Base
How Forensia matches an Across deposit on one network to its fill on the other, using the full bridge identifier. It covers one protocol and two networks. It is not general cross-chain tracing.
A trail often stops at a bridge. Funds go into a bridge contract on one network, and something comes out on another network a little later. The two halves sit on separate ledgers, so a lookup of the first transaction cannot tell you where the second one is.
Forensia now has one narrow check for this. For a transaction on Ethereum or Base that used the Across bridge, the workspace can look on the other of those two networks for the matching record and show both halves side by side.
State the limit first. This is one bridge protocol, one event version and one pair of networks. It is not universal bridge tracing and it is not cross-chain tracing in general. A match links two protocol records. It does not show that the same coins moved, who controls either side, or that anything improper happened.
#Plain languageA claim ticket and its stub
Think of a coat check. You hand over a coat and receive a ticket. Later, someone presents the matching stub and a coat is handed back. Ticket and stub carry the same number and description, so you can pair them even though they were written at different counters.
An Across transfer works in a similar way. On the starting network the bridge writes a deposit record: a request to deliver a stated amount of a stated token to a stated recipient on another network. On the destination network, a third party called a relayer pays the recipient out of its own funds and the bridge writes a fill record that repeats the terms of the request. Forensia reads the deposit, searches a limited stretch of the destination network for a fill, and accepts a pairing only when every term of the request agrees.
The analogy also shows the limit. With a relayer it is not the same coat at all: the relayer supplies its own inventory. The check pairs paperwork.
#How it worksRunning the check from a transaction result
The check is available only when the result on screen is a transaction on Ethereum or Base. It lives on the Overview view as a collapsed question, and nothing is requested until you ask.
- Look up the transaction on Ethereum or Base. On Overview, open the disclosure titled Did this transaction bridge to another network?
- Choose Check Across journey. The source receipt is checked first, then a bounded window on the other network.
- Read the outcome. A match shows two labelled steps, Deposit on one network and Fill on the other, each with its transaction hash, block time and the party recorded in the event.
- Open Exact token & amount on either step for raw units, which are not converted with token decimals. Look up this fill transaction starts a separate lookup of the destination transaction.
- Open Sources & interpretation limits for the checked block range, both capture times, the check manifest and each source record with its response SHA-256.
Other outcomes are stated as plainly: No matching fill in the checked window, or These records do not identify one unique journey. If the fill paid a different recipient, amount or message than the request asked for, the panel says The fill used changed terms and shows the executed terms.
#Technical detailWhat the matcher reads and compares
The server call is POST /api/investigate in mode across-journey. The request carries the transaction hash and an anchor (block number and block hash) from the result you already captured. A receipt that now reports a different block stops the check as a conflict.
Recognized events and emitters
Only two event signatures are decoded, both from the bytes32 family of the Across V3 spoke pool interface, pinned at interface revision e814cb470b207bcba914922ae8c39b62f7a5bce5: FundsDeposited and FilledRelay. A log counts only if its address is the registered spoke pool for that chain: 0x5c7bcd6e7de5423a257d81b442095a1a6ced35c5 on Ethereum (chain 1) or 0x09aea4b2242abc8bb4bb78d537a67a245a7bec64 on Base (chain 8453). Topic 0 must equal the Keccak-256 of the full signature and the log must carry exactly four topics. After strict ABI decoding, the non-indexed arguments are re-encoded and compared with the original data bytes, which rejects non-canonical padding and trailing bytes. Legacy address-based events are not inferred from similar fields.
Four reads per receipt
Each receipt, source or candidate, is acquired with four logical reads through one pinned endpoint: eth_chainId, eth_getTransactionReceipt, then eth_getBlockByNumber twice for the containing block (transaction hashes only). The receipt status must be 0x1. Block hash and number must agree between the receipt and both block reads, the two block reads must return the same timestamp and transaction list with no duplicate hashes, and the transaction must sit at exactly its reported transactionIndex. Every log must repeat the receipt transaction hash, block hash, block number and transaction index, must not be marked removed, and logIndex must strictly increase.
The identity that must match
A deposit and a fill are paired by one key built from thirteen fields. Amounts are compared as exact integer strings. No symbol, rounded amount or time proximity is used.
originChainId, destinationChainId, depositId,
depositor, recipient, exclusiveRelayer,
inputToken, outputToken, inputAmount, outputAmount,
fillDeadline, exclusivityDeadline, messageHashFor a deposit, messageHash is derived from the message bytes: the zero word for an empty message, following the Across event convention, otherwise Keccak-256 of the bytes. The fill also carries relayExecutionInfo (updatedRecipient, updatedMessageHash, updatedOutputAmount, fillType). If those three updated values equal the original recipient, message hash and output amount, execution terms are reported as original. Otherwise they are reported as changed.
Choosing and scanning the destination window
No block range is typed by the user. The destination head is read with eth_getBlockByNumber at latest, then at most 16 further headers are read in a bisection on timestamp until the bracket around the source block time is 1,024 blocks or narrower. The window starts 128 blocks before the lower bracket and spans at most 5,000 blocks. The head is then read again and must return the same hash and timestamp.
Inside the window the scan issues one eth_getLogs call filtered to the destination spoke pool with three topics: the FilledRelay signature, the origin chain ID and the deposit ID. Each candidate transaction is then acquired independently with the four reads above, and the queried log must agree with the log in that receipt on block hash, block number, transaction index, address, topics and data. The upper boundary block is read before and after the scan and compared on height, hash, timestamp and full transaction list.
| Limit in the code | Value |
|---|---|
| Destination window | 5,000 blocks |
| Logs returned by the window query | 8 |
| Candidate receipts fetched | 4 |
| Logical requests inside the window scan | 20 |
| Total upstream requests for the whole check | 48, including fallback attempts |
| Total time | 25 seconds |
| Per-response size | 2 MiB |
Going over a limit rejects the check. It does not truncate the inventory and pick from what is left. Only the first request to each network may use the configured fallback endpoint. Later reads stay pinned to the host that answered.
Position consistency and outcomes
Before any match is returned, the positions of every log in every acquired receipt are compared, including logs from unrelated contracts. One height must have one block hash and timestamp, two transactions cannot share a transaction slot or a block-wide log index, and log order within a block must follow transaction order. A contradiction ends the check with no journey accepted.
| Result | Condition |
|---|---|
| matched-events | Exactly one fill with an identical relay identity, and the deposit is not duplicated |
| ambiguous | More than one matching fill, or duplicate deposit events |
| unresolved | No matching fill in the checked window |
| unsupported-route | The destination is not the other of Ethereum and Base |
| conflict, limit, invalid, unavailable | Source records disagreed, a bound was reached, records could not be decoded safely, or the source did not answer |
The engineering notes record one real two-sided control: Base deposit 0x60c7220dd59650ff4bde20ab4cafb34f21d56bfeab645de77132e5d8a354213d (deposit ID 6,296,002) matched to Ethereum fill 0x8c1f44a924c10a97019b3677652878f731f0189d942acc1ad0158132694466f6 with original execution terms.
#Technical detailChecking the events against the calls
Where the operator has enabled call tracing for the source, the check goes one step further. It asks the source for the transaction's call tree and lines every event in the receipt up against it, in order, rejecting events from calls that failed or were rolled back. The block is read again afterwards. A pass is labelled as matched to a provider-reported call. It is what the source reported about the calls, not an independent re-execution, and the historical code stays marked unverified.
If tracing is off, unavailable, over a limit, or returns something that cannot be read safely, no call match is accepted and the event match stands on its own. If the calls contradict the receipt, the result says so plainly and the event match should not be read as a verified path.
#LimitsWhat a matched journey does not establish
- Not general bridge tracing. Other bridges, other networks, and older Across event versions are not checked.
- Not the same coins. A relayer pays the recipient from its own inventory. No graph edge, owner grouping or causal claim is added.
- Not a failure verdict. An empty window means unresolved. The fill may be outside the window, in an unsupported event version, or unavailable from the source.
- Not a complete search. The 128 blocks before the bracket allow some fills that precede the deposit, but earlier prefills and later fills can fall outside the window.
- Not a clock. Block times on two networks are not ordered against each other, and a fill is allowed to precede its deposit.
- Not a replay. Receipts are source-reported. Consistency checks detect contradictory responses. They do not recompute block hashes, authenticate the provider, or establish finality or the final balance of the recipient.
- Fill types are named as the event reports them (fast fill, slow fill, or a fast fill replacing a slow-fill request), but refunds and relayer repayment are not checked. Whether a change of terms was authorized is not verified.