FORENSIAWeb3 forensicsOpen workspace

An experiment: locating one older wallet transaction by its nonce

Notes on a non-public demonstration capability, not available on the public site. It searches for at most one earlier nonce change when ordinary history is missing. It is not wallet indexing.

· Forensia · 6 min read · Tracing, Evidence

This post describes an experiment, not a release. The capability below is a non-public demonstration capability. It is not available on the public site at web3.forensia.ai, the public profile does not run it, and nothing here should be read as an announcement of a shipped feature.

The problem it addresses is common. You look up a wallet address and the result shows no activity. That does not mean the wallet never did anything. It usually means the lookup only read a recent slice of the chain, and the wallet was last active before that slice. Meanwhile the wallet itself reports a counter showing it has sent transactions at some point.

The experiment uses that counter. When ordinary history coverage is insufficient, a development build can ask the network what the counter was at a handful of earlier moments, narrow down the block where it last changed, and read that one block. The result is at most one recovered block, clearly marked as partial history.

#Plain languageFinding the page where a counter last ticked

Every wallet on an Ethereum-style network carries a number called a nonce. It starts at zero and goes up by one each time the wallet sends a transaction. It never goes down. Think of the trip counter on a rental car that is written into a daily log. If the counter reads 7 today and you want to know when the seventh trip happened, you open the log somewhere in the past. If the counter there already reads 7, the trip was earlier. If it reads 6, the trip was later. A few such looks and you have the exact page.

The search looks at the counter on a small, fixed number of past pages, stops at the page where it last changed, and keeps only the transactions on that page that involve the wallet.

Two cautions belong in the plain version. First, the search finds one page, the most recent change, and stops. It is not a history of the wallet. Second, the counter only records things the wallet sent. Money arriving at the wallet does not move it, so incoming-only activity is invisible to this method.

#How it worksWhere this appears, and where it does not

There is no button for this and no setting in the workspace. On a build where it is permitted, it runs as part of an ordinary address lookup when all of the following hold. On the public site the first condition is never met, so the path does not run.

  • The deployment profile is not the public one (in the code, deploymentProfile() is not public-free).
  • No history index is configured for the network, and indexed transfer pages were not available for the lookup.
  • The subject is a wallet, not a contract.
  • The latest nonce of the wallet is greater than zero.

When it does run on such a build, there is nothing extra to click. A recovered block is merged into the same list the lookup already uses, so any matching transaction shows up in Activity like any other. A sentence describing the recovery is appended to the coverage statement on Overview and in Sources under What was covered. A search that was cut short adds a warning that historical activity recovery is incomplete.

#Technical detailThe bounded nonce search in detail

Reads and budgets

The lookup has already fixed an upper boundary: the latest block it read. Every historical read is a JSON-RPC eth_getTransactionCount call for the subject address at an explicit block number. Each response is kept as an evidence record with a note that it is used only to locate candidate blocks.

BoundValue in the code
Deadline for the whole history stage15 seconds
Timeout for one read2,500 ms, or the time left before the deadline if shorter
Locator read ceiling30 nonce reads, plus one read at the boundary block
Transition blocks recovered1
Probe distances behind the upper boundary64, then 4,096, then 262,144 blocks, then genesis

Probing near before far

The search starts with the nonce at the boundary block as its target. It reads the nonce at 64 blocks back. If that value is already lower than the target, the change lies inside those 64 blocks. If not, it widens to 4,096 blocks back, then 262,144, and only then to block zero. Heights already read are cached and are not charged twice.

The ordering matters. An earlier version read genesis state first, which meant a provider that keeps recent historical state but not genesis state could not support the search at all. Probing near ranges first lets a recent transition be found without an archive-wide read. A regression test demonstrates that case. It was not established that this caused the original empty result on the live provider.

If even the genesis read is not lower than the target, the search ends with no candidate. A nonce that was already present at genesis is not a transaction.

Bisection and the candidate block

Once a lower nonce has been seen at some height, the search bisects between that height (low) and the nearest height known to hold the target (high). At each midpoint, a value equal to or above the target moves high down, and a lower value moves low up. When the two are adjacent, high is the candidate: the first sampled block at which the nonce had reached the target.

History recovery statuses
stop: "sample-limit"       // one candidate found, the limit for this caller
      "no-earlier-nonce"   // no lower nonce observed, nothing to locate
      "request-budget"     // the 30-read ceiling was reached
      "source-unavailable" // a read failed or samples contradicted each other
      "time-budget"        // set by the caller when the 15 s deadline passes

When the search refuses to answer

  • A sampled nonce higher than the nonce at the boundary is a contradiction.
  • A sample that breaks ordering against any cached sample (a lower block showing a higher nonce, or a higher block showing a lower one) is a contradiction.
  • A contradiction discards every candidate from the search, including any found earlier. A plain transport failure does not.
  • A failed read never becomes a zero. A non-integer or negative value is rejected as invalid.

From candidate block to activity

A nonce transition is a location, not a finding. The candidate block is fetched with eth_getBlockByNumber with full transaction objects, and its returned number must equal the requested height. Only transactions whose from or to equals the subject address become activity rows, and they then go through the same receipt checks as any other row. If the block cannot be read, the status becomes source-unavailable.

The notes record one real local run. For the Ethereum wallet 0x94Ada573d9f9b02c55bC33C479a54776D1Bf7452, the search returned status sample-limit with candidate block 25,994,464 after 23 history-stage requests, and the block yielded transaction 0x351035eb60de36d50a6d491308a03971a0b8d68975610177de908e4eb96b555f, an outgoing 1.074634 ETH transfer with a successful captured receipt. This is one observed run, not a performance figure. The nonce reads and the candidate block were answered by different hosts through the existing fallback routing. That is not agreement between independent providers.

The separate index hint path

The nonce search runs only when no history index is configured. A non-public build can instead be pointed at an operator-authorized, Blockscout-v2-compatible index, which requires an explicit rights setting and a matching chain ID and is disabled entirely in the public profile. That path makes one GET to /addresses/{address}/transactions with a 4 second timeout and a 1 MiB response cap, considers at most 50 entries, and keeps the two newest candidate block numbers. Index entries are hints only: each block is still fetched over RPC and filtered to exact subject matches.

#LimitsWhat one recovered block does not tell you

  • At most one transition block is recovered. Older transactions are not enumerated.
  • Incoming-only activity does not change the nonce and is not found.
  • Token transfer history is not recovered by this path.
  • A nonce change is not a transfer. Under EIP-7702, processing a delegation authorization increments the nonce of the authorizing wallet, so nonce counts cannot stand in for a count of outgoing transfers or a list of all wallet activity.
  • Agreement between sampled heights does not show that every height in between is consistent or canonical.
  • The source can refuse historical state, return conflicting values or run out the budget. Each of those ends the search as incomplete, and an incomplete search does not establish an empty history.
  • A result with no activity is still not evidence that a wallet was inactive.