Drops Protocol documentation

Indexer semantics

Validation, indexing and finality

Two implementations that apply the same rules in a different order can still agree on results, but only if every rule is a hard rejection. Drops is designed that way on purpose: there is no repair step, no partial acceptance, and no ordering-dependent outcome.

1. Verification order

The reference implementation performs these checks in this order. The order matters only for which reason string you get back, never for whether a candidate is accepted.

  1. Witness shape

    The input witness must hold exactly three items: signature, leaf script, control block. Each must be an even-length hex string. A final item beginning 0x50 is an annex and disqualifies the input.

  2. Signature length

    64 or 65 bytes. Nothing else.

  3. Leaf parse

    Decompile the leaf. Ten chunks, five pushes at even positions, the OP_DROP and OP_CHECKSIG sequence at odd positions. Then field-by-field validation: marker, content type, hash length, body length and encodability, key point validity. Then re-serialize and compare byte for byte.

    Failure here reports that the leaf did not satisfy specification section 3.

  4. Body hash

    SHA-256 the body push and compare against the hash push.

  5. Previous output

    Fetch the spent output. It must be a 34-byte native P2TR script. If it cannot be fetched, the reason is missing_previous_output, which is an unresolved candidate rather than a proven rejection.

  6. Control block structure

    Length at least 33, (length - 33) a multiple of 32, and at most 128 path hashes.

  7. Leaf version

    controlBlock[0] & 0xfe must be 0xc0.

  8. Internal key

    Bytes 1 through 32 must be a valid x-only point.

  9. Merkle fold and tweak

    Compute the tapleaf hash, fold the path with sorted TapBranch hashing, and tweak the internal key.

  10. Parity and key equality

    Output key parity must equal controlBlock[0] & 1, and the resulting x-only key must equal the 32 bytes of the spent script. The comparison is constant-time in the reference implementation.

Errors are rejections

Any exception raised anywhere in steps 5 through 10 is reported as Taproot commitment could not be verified and treated as failure. An implementation that treats a thrown error as anything other than "not a Drop" has a vulnerability, not a bug.

2. Confirmation policy

6Minimum confirmations on mainnet, enforced
tip − (N−1)Finalized target height for depth N
0Mempool parsers for Drops artifacts
1Scanner writer, chain-wide

A Drops artifact is recorded only when its reveal transaction has reached the deployment's configured confirmation depth. On mainnet that depth has a hard floor of six confirmations, and the reference indexer refuses to start with a shallower mainnet value. Deeper values are accepted. Shallower values remain available on regtest, where they exist so tests can exercise boundary behaviour.

For a configured depth N, the scanner advances to a finalized target of tip - (N - 1) and persists records only up to that height. Nothing below the target is treated as state.

The effective depth is not a secret and should not be guessed: a conforming service exposes it in its health and status responses, and the Drops health payload carries it as confirmationsRequired. Historical one-confirmation behaviour is not compatible with the current policy and must be treated as transition input, never as authority to weaken the floor.

2.1 Mempool

There is no mempool parser for Drops artifacts in the reference baseline. An unconfirmed reveal simply does not exist as far as the Drops view is concerned. This is a deliberate asymmetry with the OP_DROP token layer, which does publish provisional observations, clearly separated and never affecting authoritative balances.

OP_DROP provisional activity is labelled finality: provisional and carries a chainState of mempool, mined, finalized or reorged. A Drops implementation that wants to show pending activity should build the same explicit separation rather than blending it into recorded state.

3. Reorganisation behaviour

Confirmation depth reduces the chance of a reorganisation touching recorded state; it does not eliminate it. The rule is that records are immutable and projections are rebuilt.

How a reorganisation is handled A chain reorganisation is detected against the persisted block journal. If the rollback depth is within the automatic reorg depth budget and the estimated rebuild row count is within the rebuild budget, the indexer rolls back the affected blocks, rebuilds the custody and balance projections from the surviving immutable rows, and resumes. If either budget is exceeded, or the common ancestor is ambiguous, or the rebuild estimate fails, the indexer stops and sets an operator-required state with a specific code rather than guessing. Detect Compare the persisted block journal against the chain Check the budgets Rollback depth within limit? Estimated rebuild rows within limit? Both within budget: automatic Roll back, rebuild projections from immutable rows, resume Otherwise: stop and ask Operator-required state with a specific code, no guessing Records are never edited in place. A projection such as custody or a token balance is derived, so it is rebuilt rather than patched. Marketplace settlement rollback removes block-bound settlement evidence, marks the intent reorged, and preserves rebroadcast lineage. Failing closed is the design. An indexer that silently guesses at an ambiguous ancestor produces a history nobody can reproduce.
Automatic within budget, operator-required beyond it. The budget exists so that a deep or expensive rebuild is a decision, not a surprise.
Operator-required codes and their recovery class
CodeRecovery classMeaning
automatic_reorg_ancestor_ambiguouschain_reorgThe common ancestor could not be determined without guessing.
automatic_reorg_depth_exceededchain_reorgThe rollback is deeper than the automatic budget allows.
automatic_rebuild_estimate_failedchain_reorgThe cost of the rebuild could not be estimated.
automatic_rebuild_budget_exceededchain_reorgThe rebuild would touch more rows than the budget allows.
bip110_assessment_identity_mismatchbip110_assessmentThe recorded BIP-110 assessment identity does not match the current one.

A change to the confirmation depth that intersects already-indexed history is treated the same way: it is a policy reconciliation that must be audited and replayed, under the recovery class confirmation_policy. Silently reinterpreting existing rows under a new depth is not permitted.

4. One writer, many readers

The reference deployment separates the read path from the chain scanner. Read replicas serve recorded state; a single scanner holds a writer lock and advances the shared chain cursor. That separation is what lets maintenance and catch-up happen without presenting partial scanner work as confirmed state.

A scanner reports a leadership state, one of not_applicable, disabled, acquiring, leader, contended, unavailable, lost or released. An indexer whose scanner does not hold leadership must not claim readiness; the reference health endpoint returns 503 in that case.

Indexer state is reported as one of disabled, api_only, writer_lock_unavailable, leadership_lost, operator_required, paused, unavailable, syncing or synced. Only synced means the recorded view is current to the finalized target.

4.1 Custody integrity

Because custody is a projection, it can drift from the immutable records if something goes wrong. A conforming implementation should therefore audit it rather than assume it. The reference health payload reports a custody integrity block with counters including unresolved legacy records, active records missing an owner or a custody outpoint, burned-state mismatches, custody mismatches, event discontinuities, transfer-count mismatches, spent-outpoint counts, failed transitions, reconciliation backlog and retries, quarantined records, and the age of the oldest unresolved record. A single custodyHealthy boolean summarises them.

Publishing these counters is part of the contract. An indexer that cannot say how far its projection has drifted is asking to be trusted rather than verified.

5. Determinism requirements

  1. V-1

    Two implementations reading the same chain at the same height with the same confirmation depth must record the same set of Drop identities.

  2. V-2

    They may disagree about sequence and displayName, because those depend on where each implementation started scanning.

  3. V-3

    An implementation must not use a wallet's claim, an API response from another service, or a label in its own database as evidence. The only evidence is chain data.

  4. V-4

    An implementation should record why a candidate was rejected, and must not let a recorded rejection affect any other record's state.

  5. V-5

    An implementation must publish its confirmation depth, its network, and its indexed height alongside any data it serves, so a reader can tell what the answer is relative to.

The conformance checklist turns these into a list you can work through, and test vectors gives concrete cases for each rejection.