State machine
Two states, three transitions, and one record. Everything else is derived at query time.
- Which fields are stored, and why depth and tier are not among them.
- What each of the three transitions writes: the ring appended, the carrier set, the status changed.
- Why RELIC is terminal, and what a relic keeps forever anyway.
- The order state is built in: blocks by height, transactions in block order, nothing from the mempool.
- Why artifacts sharing one carrier cannot be separated by a single transaction.
The artifact record
One artifact record, with its rings nested inside it, and that is the whole model. Anything a client shows you is either a field below or a subtraction from one. The public depth page takes the same record apart field by field, with the changing parts moving.
Artifact {
artifact_id
birth_txid, birth_height, birth_vout
endowment_sats
founding
status: ALIVE | RELIC
carrier: { txid, vout, height, value } | null
rings: Ring[]
}
Ring {
index
start_height, end_height
depth
carried_value
successor_txid | null
successor_vout | null
relic
}
Depth and tier are not in the record. They are computed when you ask, from the carrier height and the current tip. Nothing is written per block.
The two states
The whole difference is whether a carrier exists. Every row in the table follows from that one fact.
| ALIVE | RELIC | |
|---|---|---|
| carrier | An unspent outpoint | null |
| depth | tip_height - carrier.height | Not applicable |
| Can gain rings | Yes, one each time the carrier is spent | No |
| Can return to the other state | Becomes RELIC when a spend has no successor | Never. Terminal. |
| Rings readable | Yes | Yes, forever |
The three transitions
Birth: nothing to ALIVE
A valid SEED transaction creates the artifact. carrier is set to the named output, at the
height of the block containing the reveal. rings is empty. Depth starts at zero and increments
with every block that passes.
Conditions on SEED rules.
Move: ALIVE to ALIVE
A confirmed spend of the carrier ends the stretch.
- Append a ring with
start_height = carrier.height,end_height = spend_height, anddepth = spend_height - carrier.height. - Record
carried_valueas the value the carrier held. - Choose the successor with a valid KEEP entry, or the default rule.
- Set
carrierto the successor outpoint, with the spend block's height and the successor's value. - Record
successor_txidandsuccessor_vouton the ring.
Depth is now zero again, counting from the block that made the successor.
End: ALIVE to RELIC
Same spend, but no eligible successor exists: no valid KEEP entry, and no output that is both non OP_RETURN and at or above 10000 sats.
- Append a ring the same way, with
successor_txidandsuccessor_voutnull andrelic: true. - Set
carrierto null andstatustoRELIC.
There is no path out. A relic keeps its id, its rings, and its founding flag forever.
What does not change, ever
artifact_idbirth_txid,birth_height,birth_voutfounding- Any ring that has already been appended
The only exception is a reorg, which does not change these values so much as unwind the blocks that produced them. See Reorg behavior.
Everything happens in block order
State is a function of the blockchain and nothing else. Given the same chain, every correct implementation produces the same state.
- Process blocks in height order.
- Inside a block, process transactions in the order they appear in the block.
- Inside a transaction, the spend of an existing carrier and the creation of a new artifact are independent. Whether a spent input was a carrier is decided by the state before this transaction, and a newly created carrier is an output of this transaction, so it cannot also be spent by it.
- Nothing in the mempool affects state. An unconfirmed spend has not happened.
Bundles: several artifacts, one carrier
Successor routing can land two artifacts on the same output. From then on they share a carrier and move together. The public risks page states the same thing as a warning, because it surprises people at the moment they try to sell one artifact out of a group.
- All artifacts on a shared carrier have the same depth, because depth is a property of the outpoint.
- Spending that carrier ends every one of their stretches at once and engraves a ring on each.
- A KEEP entry names an input, and one input can name only one vout. So one transaction routes every artifact on that carrier to the same place.
Because a single spend of a carrier moves everything on it to one destination, you cannot split a bundle in one transaction. You split it across a sequence: move the bundle, then move the resulting carrier again, arranging along the way for the artifacts you want apart to end up on different carriers. Each step is a spend, so each step resets depth for everything it touches. Plan it before you need it, not during a sale.
Invalid events are part of the state
A transaction that carries a marker and fails a rule does not change any artifact, but it does record an
invalid event with a reason code. Those events are served by
GET /patina/invalid-events, and they are what makes a failed mint diagnosable instead of
mysterious.
Treat them as part of the state you must reproduce. Two implementations that agree on artifacts but disagree on invalid events do not agree. See Reason codes.