Reference

Indexer semantics

Tandem state is a pure function of confirmed blocks. That sentence carries more weight than it first appears to: it decides how confirmation works, what a reorganization must undo, and why an unconfirmed transaction can never be a Tandem record, only a guess about one.

Protocol finalized. Mainnet is not active.

Where history starts

1. Block scope

An indexer does not scan the chain for Tandem activity. It is told exactly one INIT txid, finds it, and only then has a history to index at all.

The configured INIT transaction is located by exact txid on the active chain. Blocks before its confirmation block are outside this namespace's rooted history and are never examined for Tandem content.

When the configured txid confirms, an indexer first validates that transaction as the configured INIT. If it is valid, its payload supplies H_open and H_close, and the indexer then classifies every transaction in that block in transaction-index order, including transactions positioned before and after the INIT itself. The configured transaction is classified as INIT. No CREATE in that block can be valid, because the 1,008-block INIT lead places H_open at least that far later.

An invalid INIT is not a retry.

If the configured transaction is invalid, the indexer records its deterministic invalid event, sets the protocol identifier to FAILED_INIT, and classifies no other transaction under this namespace while that invalid INIT remains on the active chain. It still computes roots from the INIT block onward, with an empty object set and zero counters, and later blocks have empty event sets. A reorganization that disconnects the configured transaction rolls this back; if the same txid later confirms on the new branch, validation starts again with its new confirmation context.

There is no fallback and no discovery. If the configured INIT never confirms, this protocol identifier simply never activates, and no amount of Tandem-shaped traffic on the chain changes that.

The strongest rule on this page

2. Confirmation

Tandem does not have a confirmation depth. It has a confirmation predicate, and the predicate is stricter than the usual one.

Confirmation provenance

Every predecessor carrier and every noncarrier funding input required by a valid operation MUST have been confirmed in a block strictly earlier than the block containing the operation. tandem.md section 5.3

Read that again with the word strictly in mind. A prevout created earlier in the same block fails. It is confirmed by any ordinary definition, it appears at a lower transaction index, and Bitcoin consensus is perfectly happy with the spend. Tandem still rejects it, with UNCONFIRMED_OR_SAME_BLOCK_PREVOUT, code 0x0013.

The reason is determinism under partial views. If same-block chaining were permitted, an indexer would have to model intra-block UTXO creation to decide validity, and any difference in how two implementations ordered or batched that work could produce different answers. Requiring an earlier block means the predecessor set for a block is fully determined before the block is opened.

Same-block chaining is rejected In block n, a funding transaction creates an output which a Tandem operation in the same block tries to spend. That operation is rejected. In block n plus one, the same operation spending the same output is accepted, because the funding output was confirmed in a strictly earlier block. Block n funding tx, index 3 operation, index 7 REJECTED UNCONFIRMED_OR_SAME_BLOCK_PREVOUT Block n, then block n + 1 funding tx, block n operation, block n+1 ACCEPTED provenance established
The only difference between the two sides is a block boundary. Nothing about the transactions themselves changed.

Three further consequences follow. Mempool presence never establishes provenance. First-seen time never establishes provenance. Transaction arrival order never establishes provenance. An indexer that leans on any of the three will eventually disagree with one that does not.

REFUND adds a second, separate timing rule. Its input sequence must be exactly decimal 52,560 with the BIP68 disable and type flags clear, and Bitcoin consensus relative-locktime rules must consider the input mature in the containing block. The delay is measured from the confirmation height of the exact carrier being spent, so every confirmed MARK or ROTATE replaces the carrier and restarts the clock. A pair that keeps adding chapters keeps pushing their own recovery path further away.

The block loop

3. Connecting a block

Connecting a block is a fixed sequence, and every step is required to be atomic with respect to the others.

  1. Apply transactions in transaction-index order, zero-based and including the coinbase.
  2. For each transaction, run the dispatch in rule INV-1 against active object state as it exists immediately before that transaction.
  3. Record every emitted event.
  4. Apply each event's exact state delta.
  5. Compute the event root over this block's event leaves.
  6. Compute the object-state root over every surviving object's snapshot leaf.
  7. Compute the three post-block counters.
  8. Compute the chained block root from the previous chained root and the values above.

Event ordering inside a block is by the tuple (tx_index, event_index, sub_index). For exactly one remaining marker, event_index is that marker's actual vout. For multiple markers it is the lowest candidate vout. For an event with no marker, including a missing configured INIT marker, a REFUND, or a markerless exit, it is 0xffffffff. sub_index is zero for an ordinary single event; when one transaction consumes several active carriers, each consumed object gets its own terminal event, ordered by unsigned lexicographic binary object key with consecutive sub_index values starting at zero.

A block with no Tandem activity is still a block.

Every block from the INIT confirmation block onward contributes a chained root, even when its event set is empty. The empty event root is SHA256("TANDEM/EVENT-EMPTY\0" || namespace_commitment). Skipping empty blocks produces a different chained root and therefore permanent disagreement with a conforming implementation.

Undoing history

4. Reorganization

Tandem state is derived, so a reorganization is not a special case to be patched around. It is the ordinary case run backwards.

Disconnecting a block reverses its state mutations and its roots in exact reverse order, and this must complete before any replacement branch is applied. Partially applying a new branch over a partially rolled back old one is the single most likely way to produce state that no other implementation will ever reproduce.

What a reorganization can change
ValueCan changeWhy
INIT validityYesThe configured txid may be removed from the active chain, or confirm at a different height with a different lead
CREATE founding statusYesFounding depends only on confirmation height, and height can change
Active outpointsYesA disconnected operation returns its predecessor carrier to active status
Terminal statusYesA terminal object becomes active again if the spend that terminated it is removed
All three countersYesThey are post-block values derived from the surviving object set
Every subsequent chained rootYesEach root feeds the next, so one changed block invalidates the whole tail
An object's genesis outpointNo, while it existsIt is permanently the CREATE outpoint; a reorganization removes the object rather than repointing it

The specification is explicit that results depend only on the configured binding, the bytes of the specification, and the blocks on the active chain. Arrival time, API ordering, cache contents, content availability, and any other implementation's parsed output have no role. That is what makes rollback safe to reason about: there is no hidden state to restore, only derived state to recompute.

Tandem defines no reorganization depth limit. In practice a deployment has a natural floor, because blocks before the configured INIT block are outside the namespace and there is nothing there to roll back to. Rolling back past that point is not a deeper reorganization, it is the end of this protocol identifier's history.

The part people get wrong

5. Mempool

There is no such thing as an unconfirmed Tandem record. The specification says mempool transactions never change protocol state or counters, and roots use confirmed state only.

This is stronger than a caching policy. An unconfirmed transaction cannot be evaluated against the Tandem rules at all, because two of them depend on information that only a block provides:

  • Confirmation provenance requires the containing block's height, in order to test that every required prevout confirmed strictly earlier.
  • REFUND maturity requires the containing block, because relative locktime is evaluated against it.

A third dependency is subtler. The dispatch in rule INV-1 joins input prevouts against active object state as it exists immediately before this transaction in block transaction order. Outside a block there is no such order, so there is no well-defined predecessor state to join against.

If you surface mempool observations, label them as observations.

A product may reasonably want to show a user that a transaction they just broadcast has been seen. That is a user-interface courtesy, not protocol state. Keep it in its own storage, keep it out of every root and counter, and never let it satisfy a precondition for constructing the next operation. An implementation that lets a mempool sighting stand in for a confirmed predecessor will build transactions that fail confirmation provenance the moment they are mined.

A useful discipline is structural rather than procedural: give mempool observations a separate table with no foreign key into protocol state, expose them only on routes that are named as unconfirmed, and never touch that table during a reorganization rollback. Then a mistake in the mempool path cannot corrupt derived state, because there is no path from one to the other.

Comparing two views

6. Two-pipeline agreement

Because the whole state at a height compresses to a chained root, two implementations can compare their entire view in a fixed-size signed message. That message is the agreement envelope, and its shape is published in this repository as schemas/agreement-envelope.schema.json.

SEMANTIC FIELDS must match

  • protocol_id
  • height
  • block_hash
  • event_root
  • object_state_root
  • chained_root
  • founding_created
  • all_objects
  • active_objects

RELEASE FIELDS must not

  • parser_commit
  • indexer_commit
  • parser_binary_sha256
  • indexer_binary_sha256

These identify which build produced the tuple. Two genuinely independent implementations are expected to differ here. Requiring them to match would defeat the entire point.

The signature covers the RFC 8785 JCS serialization of the tuple object only. The envelope fields around it, including key_id, are not signed. A consumer must therefore treat key_id as a hint about which key to try, never as an authenticated claim, and must resolve signer authorization through its own trust policy. Schema validity and signer authorization are separate decisions.

The useful posture for a product is to fail closed. If two authorized pipelines do not agree at a height, do not present state for that height and do not let a user construct a state-changing transaction against it. Disagreement is exactly the condition the whole arrangement exists to detect, so treating it as a transient error defeats it.

When a real mismatch happens, compare in a fixed order: event root first, then object-state root, then the counters, then the chained root. The first one that differs localizes the divergence, and the chained root differing while all three components match points at the chaining input rather than at this block's content.

Implementations

7. The reference indexer

Two public repositories implement the semantics above. Neither is a released product, and describing them as one would be wrong.

Read their status claims carefully

index-tandem describes itself as an implementation scaffold with executable protocol boundaries and unit tests. Its own documentation states it does not claim a live Bitcoin Core connection, ZMQ delivery, a migrated database, a signet replay, or a production signature ceremony, and that readiness fails closed until those dependencies and a signing key are verified at runtime. Several capabilities exist as implemented and tested boundaries that nothing currently calls, which its documentation lists under what still needs a driver.

The practical consequence for an integrator is that you should read the indexer's readiness output rather than assume it. Its readiness evaluation is a fixed ordered list of gates covering configuration, database, Bitcoin Core availability, network match, initial block download, node height, protocol tip presence, tip staleness, checkpoint completeness, and signer availability, and it reports every failing gate rather than just the first. A route that fails closed returns an explicit unavailable status rather than stale data, which is the behaviour you want.

No Universe product implements a trade path for Tandem.

The Bitcoin Universe capability snapshot records 38 protocols, 29 of them with a marketplace entry. Tandem has no entry in that snapshot at all, so no Universe surface implements a buy, sell, list, or transfer action for a Tandem object, and no indexer, freshness, confirmation, or reorganization policy is recorded for it there. Treat any claim of a Tandem market as unsupported.