Following spent Bitcoin outputs, one checked step at a time
Forensia can follow a Bitcoin transaction’s spent outputs into the transactions that spent them. It is limited to four checks and two hops, and it never says who owns anything.
A Bitcoin transaction pays out to one or more outputs. Later, each of those outputs may be spent by another transaction. If you want to know where a payment went next, you have to look up each output, find the transaction that spent it, and confirm that the two records really point at each other.
Forensia now does that lookup for you, on request, one transaction at a time. It is a guided and deliberately small feature: at most four checks, including the starting transaction, and at most two hops beyond it. It works on confirmed Bitcoin mainnet transactions only.
It does not run in the background, it does not retry on its own, and it does not tell you who controls an address or how much of a payment belongs to whom.
#Plain languageA paper trail where both ends must agree
Think of a numbered cloakroom ticket. The ticket stub says coat 14 was collected by a person holding slip 3. That is only useful if slip 3, when you find it, also says it collected coat 14. If the two halves disagree, you do not have a link. You have a discrepancy.
Bitcoin works the same way. An output is identified by the transaction that created it and its position in that transaction. The transaction that spends it names that exact transaction and position as one of its inputs. Forensia accepts a link only when the parent side and the child side agree on the transaction, the positions and the amount.
Every link ends up in one of three states that are shown separately: matched, not checked yet, or stopped. An unchecked output is unchecked. It is not treated as unspent, and a stopped branch is not treated as the end of the money.
#How it worksUsing Follow spent outputs in the workspace
Look up a Bitcoin transaction ID. The ordinary result stays where it is. Below it there is a collapsed section called Follow spent outputs.
- Open Follow spent outputs and press Check starting transaction. Forensia fetches the current spend status of each output of your transaction.
- Read the list. Each spent output appears as a row showing the parent transaction and output position, the child transaction and input position, the hop number and the amount in satoshis.
- Press Check next transaction to check the next queued child. The status line names the transaction being checked and tells you that no other branch is running.
- Watch the counter: matched outputs, unchecked, stopped, and steps used out of 4. When the fourth step is used, the button reads Check limit reached.
- Open Exact transaction references on any row for the full hashes, or Limits and captured sources for each response, its capture time and its SHA-256 digest.
- Press Save trace if you want the completed steps kept in this browser, or Prepare trace backup and then Download trace JSON for a file.
Save trace is separate from Save result. Once a trace is saved, each later attempt is written to the browser before the request is sent, and its result is saved automatically. If you reload during a check, reopening the trace from Saved Bitcoin traces shows One check needs review. Nothing is retried for you. Mark check interrupted records that no result is claimed and keeps that attempt counted against the four.
#Technical detailWhat each check requests and compares
Each step is one POST to the existing investigation route with the mode set to bitcoin-branch and a 64-character transaction ID. The route applies the same origin, source-policy, verification, lease and concurrency gates as a normal lookup. The client cannot choose the upstream URL or the limits.
On the server, captureBitcoinBranch sends exactly two GET requests to the configured Esplora-style Bitcoin indexer, in parallel, and waits for both to settle before the lease is released:
GET tx/{txid} transaction, with previous-output references
GET tx/{txid}/outspends current spending transaction and input for each outputThe decoder then reads a small, fixed set of fields and refuses anything that does not fit.
| Field read | Rule applied |
|---|---|
| txid, status.confirmed | Must equal the requested transaction and be confirmed |
| vin, vout | 1 to 64 inputs and 1 to 32 outputs, otherwise the step stops |
| vin[].is_coinbase | A block-reward transaction is a tracing boundary |
| vin[].txid, vin[].vout, vin[].prevout.value | Required for every input. Duplicate previous-output references are rejected |
| outspends[i].spent, txid, vin | One entry per output. A missing spent flag is rejected |
| fee | Input total must equal output total plus fee, in integer satoshis |
| status.block_hash, status.block_height | Recorded for ordering checks |
Amounts are parsed as safe integers and capped at 2,100,000,000,000,000 satoshis. The result is a record with the schema forensia.bitcoin-branch.v1, its own capture time, the two source artifacts and a manifest digest over them.
The link rule
A link is a tuple of parent transaction, output index, child transaction, input index, value and depth. When both the parent and the child have been captured, the link is marked matched only if all of the following hold: the child input at the stated index names the parent transaction and output index, exactly one child input references that outpoint, the input value equals the parent output value, the child block height is not lower than the parent block height, and if the heights are equal the block hashes are the same. Anything else becomes stopped, with the reason that the spending input, amount or block position conflicts with the parent output.
For the starting transaction there is one more check: if the original lookup recorded a block hash, the new capture must report the same one.
Queue order and budgets
The queue is breadth-first in output order. It is not ordered by value and it is not a risk ranking. If two outputs are spent by the same child, the child is fetched once, but each parent-output to child-input pair is matched on its own. A child that points back to its own transaction or to the root is stopped as a cyclic reference without being queried. A child whose incoming link did not match keeps its source references for review and is not expanded.
| Limit in BITCOIN_BRANCH_LIMITS | Value |
|---|---|
| pages (steps, including the starting transaction) | 4 |
| depth (hops beyond the start) | 2 |
| requestsPerPage | 2, so at most 8 source requests in total |
| inputs per transaction | 64 |
| outputs per transaction | 32 |
| sourceBytes per response | 262,144 (256 KiB) |
A failed or cancelled attempt consumes a step. A cancelled request that was already sent may still count toward source limits. Outputs found at hop three are listed as stopped with the note that the child was not queried.
Saved traces
Saved traces live in a separate IndexedDB database in your browser: at most five traces, 10 MiB in total and 2 MiB per package. A backup carries the original result, the ordered attempts and source references, not raw response bodies. On reopen or import the queue, the matches and the counters are recomputed from the recorded references. Flags inside a file are never trusted. The original investigation, its capture time and its digest are never modified by a branch check.
#LimitsWhat a matched spend does not tell you
- A matched link shows that one output was consumed by one input. It does not show that the sender and the next recipient are the same party, or different parties.
- It does not allocate value. When a child transaction has several inputs and outputs, this view does not say which output received the coins you were following.
- An output reported unspent is an observation at capture time, not a permanent endpoint.
- Not checked yet means the budget or depth ran out. It says nothing about whether the output was spent.
- Spend status is read now, so it can differ from what the original capture saw.
- Links across different blocks are ordered by height. Links inside one block carry no wall-clock interval.
- The data comes from one configured indexer. It is not independent validation of Bitcoin consensus.
- Saved and imported traces are unsigned metadata. Their hashes detect inconsistency, not forgery, and they cannot be replayed against response bodies that are not in the file.
- Unconfirmed transactions, other networks, background jobs, automatic retries and complete address history are out of scope.
The engineering notes record a review run on the early transaction f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16. Four deliberate checks produced three matched outputs, one unchecked and two stopped at the depth limit. That is the normal shape of a result here: some links confirmed, and a clear list of what was not examined.