Normative document

Atomicals + ARC-20 specification

The rules below are stated as the indexer enforces them. The normative source is the Bitcoin Universe Atomicals indexer, a pinned fork of atomicals-electrumx; file references name modules inside it. The protocol originated with the upstream Atomicals project, and where this fork adds or narrows behavior the rule says so.

Rule identifiers (E envelope, I identifiers, R commit and reveal, T tickers, F direct mints, D dmint deploys, M dmint claims, B bitwork, C coloring, S state, N names, X invalidity) are stable within document version 2.0.0.

1. The atom envelope

Atomicals operations travel inside taproot leaf scripts revealed in witness data. The carrier is an ordinary Bitcoin transaction; consensus never sees the protocol.

Anatomy of the atom envelope in a tapscript A row of labelled script segments: a 32 byte pubkey push, OP_CHECKSIG, OP_IF, a push of the four bytes atom, a push of the operation code such as dmt, one or more payload pushes holding CBOR, and OP_ENDIF. 0x20 <32B key>pubkey push OP_CHECKSIGnot parsed OP_IF0x63 "atom"0461746f6d "dmt"op push CBOR pushesconcatenated OP_ENDIF0x68 taproot leaf script, revealed in the witness of the spending transaction the five byte marker the indexer scans for
Fig. 1: the atom envelope. The indexer requires a witness element of at least 39 bytes beginning with a 32 byte pubkey push, then finds the first OP_IF and expects the marker 0461746f6d immediately after it.
  1. A witness element is considered only if it is at least 39 bytes long and its first byte is 0x20, a 32 byte push. Source: parse_protocols_operations_from_witness_for_input in electrumx/lib/util_atomicals.py.
  2. After the key push the parser scans forward to the first OP_IF (0x63). The five bytes immediately following it MUST be 0461746f6d, a four byte push of ASCII atom. Bytes between the key push and OP_IF, typically OP_CHECKSIG, are not interpreted, and a leading OP_FALSE is not required by this implementation.
  3. The operation name follows as a single push: three letter operations as 0x03 plus name, two letter as 0x02 plus name, one letter as 0x01 plus name. An unknown code causes the envelope to be skipped, not the transaction to be rejected.
  4. The payload is the concatenation of the data of every subsequent push, including OP_PUSHDATA1, OP_PUSHDATA2, and OP_PUSHDATA4, up to OP_ENDIF. This is how payloads exceed the 520 byte single push limit.
  5. All inputs are scanned in order; the first successfully parsed envelope wins and its input index is recorded. Most operations are valid only when that index is 0, see rule R1.
  6. Payment markers use a separate carrier: an OP_RETURN output of the form 6a 04 "atom" 01 "p"|"d" 24 <36 byte atomical id>, where p marks a subrealm payment and d a dmitem payment. See rule N10.

2. CBOR payload

  1. The payload bytes MUST decode as a single CBOR map. Any other CBOR type, or undecodable bytes, invalidates the operation.
  2. The reserved top level keys meta, args, ctx, and init MUST be maps when present. Values inside them are restricted to integers, floats, strings, lists, and nested maps. Byte strings are rejected inside meta and ctx always, and inside args before activation height 828128. init may always carry bytes.
  3. Functional parameters live in args. Recognized keys include request_ticker, request_realm, request_subrealm, request_container, request_dmitem, mint_ticker, claim_type, parent_realm, parent_container, parents, i, bitworkc, bitworkr, mint_bitworkc, mint_bitworkr, mint_height, mint_amount, max_mints, md, bv, bci, bri, bcs, brs, maxg, proof, and main.
  4. At most one name request may appear per mint. More than one of request_realm, request_subrealm, request_container, request_ticker, or request_dmitem in the same args invalidates the mint.
  5. On an nft, ft, or dft mint, every top level payload key other than args is stored as the object's initial state, equivalent to a first mod. File content conventionally lives under named keys with metadata such as $ct for content type alongside raw bytes.
  6. args.parents, when present, maps compact atomical ids to minimum satoshi values. Each listed parent atomical MUST be spent among the transaction's inputs carrying at least that total input value, or the mint is invalid.

3. Identifiers

  1. An atomical id is 36 bytes: the commit transaction id (32 bytes) plus the commit output index (4 bytes, little endian). The commit outpoint is the prevout of the input that carried the reveal envelope. Compact text form is <txid_hex>i<index>, with the index bounded to 0 through 100000.
  2. A location id has the same 36 byte shape but names where an atomical currently sits: the transaction id and output index of the UTXO holding it.
  3. On mint the atomical is imprinted at output 0 of the reveal transaction. The reveal location index is always 0.
  4. A secondary reference form is the Crockford base 32 encoding of the byte reversed commit transaction id, lowercased, suffixed i0.

4. Commit and reveal

Commit and reveal timeline A block height axis. A commit transaction at height h locks the envelope and a reveal spends it. General reveals are valid up to h plus 100 blocks, name reveals only to h plus 3 blocks, and subname payments from h plus 3 to h plus 15. commit height h reveal names: reveal by h+3 general mints: reveal by h+100 · subname payment window: h+3 to h+15 height
Fig. 2: commit and reveal windows. The commit is the transaction that created the outpoint spent by the reveal's envelope input; windows are measured between their block heights.
  1. Every mint operation (nft, ft, dft, dmt, and this fork's def and new) MUST carry its envelope on input 0 of the reveal transaction.
  2. The commit transaction MUST itself be indexed and MUST be at or after the protocol activation height 808080.
  3. General window: a mint reveal is invalid unless commit_height >= reveal_height - 100.
  4. Name window: mints requesting any name (realm, subrealm, container, ticker, dmitem) are additionally invalid unless commit_height >= reveal_height - 3. Late reveals are recorded with status expired_revealed_late.
  5. From activation height 822800 the commit outpoint index MUST be 0 for name type mints and for dmt mints.
  6. The reveal transaction MUST have at least one output; the mint imprints on output 0, see rule I3.
  7. dmt claims are exempt from R3 and R4; their height gates are M3 and M4 instead.

5. Operations

Recognized operation codes
OpPush hexMeaningFamily
nft036e6674Mint a non-fungible tokenNFT
ft026674Mint a fungible token with a direct fixed supplyARC-20
dft03646674Deploy a decentralized mint fungible tokenARC-20
dmt03646d74Mint units of a deployed dft tokenARC-20
mod036d6f64Modify general statestate
evt03657674Event or message, no state changestate
dat03646174Store data on a transactiondata
sl02736cSeal an NFT and lock it foreverNFT
x0178Splat: separate co-located NFTs into distinct outputstransfer
y0179Split: skip output value before coloring, per tokentransfer
z017aCustom color: direct token value at chosen outputs, from height 848484transfer
def, new03646566, 036e6577AVM protocol definition and contract instantiation. These are additions in the Bitcoin Universe fork's AVM support, not part of the original upstream operation set.AVM

A transaction that spends atomicals-bearing UTXOs with no envelope at all is a plain transfer, and the coloring rules in section 8 still apply to it.

6. ARC-20 issuance

6.1 Tickers

  1. A ticker MUST match ^[a-z0-9]{1,21}$: lowercase letters and digits only, 1 to 21 characters. No hyphens, no uppercase, no unicode.
  2. Ticker claims resolve by earliest commit: among valid candidates the one with the lowest commit transaction number wins. Bitwork is an admission requirement, never a tiebreaker.
  3. A ticker request MUST carry commit bitwork (args.bitworkc) with a prefix of at least 4 hex characters, like all top level name types. See rule N2.
  4. A ticker claim becomes verified once its commit is at least 3 blocks deep; until then it is pending. A later mint attempt is rejected outright if any existing candidate committed earlier.

6.2 Direct mints (ft)

  1. An ft mint MUST include a valid args.request_ticker and MUST NOT set args.i; fungible tokens cannot be immutable.
  2. The entire fixed supply equals the satoshi value of output 0 of the reveal transaction. Every supply unit is one colored satoshi from birth.
  3. The minted token UTXO is recorded at reveal output 0 with token value equal to satoshi value.

6.3 Decentralized mint deploys (dft)

  1. A dft deploy MUST include a valid request_ticker and integers mint_amount in 546 to 100000000, mint_height in 0 to 10000000, and max_mints of at least 1. max_mints is capped at 500000 before activation height 828128 and at 21000000 from it.
  2. Optional mint_bitworkc and mint_bitworkr MUST be valid bitwork strings if present; they set the proof-of-work every future claim's commit or reveal transaction id must satisfy.
  3. args.i is forbidden. args.md, the mint mode, MUST be 0, 1, or absent, and md = 1 selects perpetual mode which is legal only from height 828128.
  4. Perpetual mode requires a bitwork vector bv matching ^[a-z0-9]+$ of length at least 4; at least one of bci (commit increment) or bri (reveal increment), each an integer in 0 to 64; a matching start bcs or brs in 64 to 256, defaulting to 64, whenever an increment is set; max_mints of at most 100000 per difficulty phase; and an optional global cap maxg in 1 to 21000000. mint_bitworkc and mint_bitworkr MUST NOT be set in perpetual mode.
  5. Declared maximum supply: in fixed mode mint_amount × max_mints; in perpetual mode with maxg, mint_amount × maxg; in perpetual mode without maxg, unbounded.
  6. The deploy creates no token UTXO. Supply exists only as claims are minted under the M rules.

6.4 Mint claims (dmt)

  1. A claim names its token with args.mint_ticker, which MUST resolve to a verified deploy of the decentralized subtype.
  2. The envelope MUST be on input 0, and from height 822800 the commit outpoint index MUST be 0.
  3. Both the claim's block height and its commit transaction's height MUST be at or above the deploy's mint height. An earlier claim is a premature mint and is invalid.
  4. Output 0 of the claim MUST carry exactly mint_amount satoshis. Any other value invalidates the claim.
  5. In fixed mode the claim is valid only while the number of prior valid claims is below max_mints. If mint_bitworkc is set the commit transaction id MUST satisfy it; if mint_bitworkr is set the reveal transaction id MUST satisfy it.
  6. In perpetual mode the required bitwork for the current claim is derived from the phase schedule in rule B4, checked against the commit transaction id for bci and the reveal transaction id for bri. From height 828628 a transaction id that satisfies the next longer prefix of bv is also accepted, so a miner who overshoots a phase boundary does not lose the work. Where maxg is set, claims stop at the global cap.
  7. A valid claim writes a token UTXO at output 0 with token value equal to satoshi value. At each block boundary the indexer re-counts claims from durable state and fails hard on any count above the cap, so no inflation is possible.

7. Bitwork proof-of-work

  1. A bitwork string is <prefix> or <prefix>.<ext>, where the prefix is 1 to 64 lowercase hex characters and ext is an integer 0 to 15. Example: 123af.15.
  2. A transaction id satisfies bitwork when its hex form starts with the prefix, and, when ext is present, the hex digit immediately after the prefix has numeric value at least ext. An ext of 0 is equivalent to no extension and an ext of 15 is equivalent to one more prefix character.
  3. A bitwork string encodes a difficulty target of 16 × prefix_length + ext.
  4. Perpetual dmint difficulty: with prior_mints claims recorded, current_target = start + floor(prior_mints / max_mints) × increment. The required string is derived by padding bv with zeros to 32 characters, taking floor(target / 16) characters, and appending .<target mod 16> when that remainder is nonzero. Valid ranges are start 64 to 256, increment 1 to 64, and max_mints 1 to 100000.
  5. Request level bitwork on any mint: args.bitworkc is validated against the commit transaction id and args.bitworkr against the reveal transaction id. A declared but unmet bitwork invalidates the whole operation.

8. ARC-20 coloring rules

Coloring decides, for every transaction that spends token bearing UTXOs, which outputs inherit which token value. Two era switches matter: FIFO ordering from height 819181, and partial coloring plus the z operation from height 848484. Source: electrumx/lib/atomicals_blueprint_builder.py. The simulator executes exactly these rules.

An ARC-20 transfer splitting colored sats by output order Two inputs on the left, the first carrying 8000 units of token A and the second 2000 units of token B. Three outputs on the right of 6000, 2000, and 2000 sats. Token A fills outputs 0 and 1 and token B then colors output 2. inputs (FIFO order) A: 8000 in 0 B: 2000 in 1 outputs (colored left to right) out 0 · 6000 sats A: 6000 units out 1 · 2000 sats A: 2000 units out 2 · 2000 sats B: 2000 units Token A, first seen on input 0, colors first and stops when exhausted. Token B then begins at the next output. Color follows output order, never intent.
Fig. 3: an ARC-20 transfer splits colored sats by output order. Token A's 8000 units cover outputs 0 and 1 exactly, so token B starts at output 2. Had A's value not ended on an output boundary, rule C7 would have restarted every token from output 0.
  1. Value model. Each token bearing UTXO records a satoshi value and a token value. Classically they are equal, one unit to one satoshi. From height 848484 the token value may be lower than the satoshi value, a partially colored output, but never higher.
  2. Ordering. When several tokens enter one transaction they are processed first in, first out: by the first input index where each token appears, ties broken by ascending atomical id. Before height 819181 the order was ascending atomical id alone.
  3. Regular assignment. Each token, in order, colors outputs left to right starting at the output after the last one the previous token colored.
  4. Unspendable outputs. Outputs whose script is OP_RETURN, with or without a leading OP_FALSE, are skipped by regular assignment and never carry color. The split and custom color paths do not apply this filter, so directing value at an unspendable output under z destroys that value.
  5. Exact cover era, heights 819181 to 848483. An output could be colored only when the token's remaining value covered its entire satoshi value. Leftover value burned, and a token that could not be placed at all triggered rule C7.
  6. Partial coloring era, height 848484 onward. A token colors consecutive outputs and the final output it touches may be partially colored with whatever value remains. Value exceeding the total satoshis of the remaining outputs burns.
  7. Non-clean fallback. If any token fails to produce a clean assignment, or finds no colorable output at its starting position, the entire map is discarded and every token restarts assignment from output 0. In this fallback multiple tokens can color the same outputs, and leftovers burn per token.
  8. Split (y). With a split envelope on input 0 the payload maps each compact atomical id to a non-negative satoshi amount to skip: outputs are passed over until their cumulative satoshi value reaches that amount, then coloring proceeds as in C5 or C6. This is how two tokens sharing one input UTXO are separated. NFTs present in a split transaction are forced to output 0.
  9. Custom color (z), from height 848484. The payload maps each compact atomical id to a map of output index to token value. Requested values are clamped to the output's satoshi value and to the token's remaining value, so no inflation is possible; value assigned to no output burns; an output colored below its satoshi value is partial.
  10. NFT movement. NFTs move by input order: the n-th NFT bearing input maps to output n in the FIFO era. An out of range or unspendable target, or a split transaction, sends the NFT to output 0. The x splat operation gives each co-located NFT its own output in ascending id order, overflowing to output 0. NFTs are deliberately hard to burn by accident.
  11. Burns. Token value that lands on no spendable output under these rules is recorded as burned and permanently leaves the supply. Burned value is reported per token by the indexer.
  12. Inflation guard. The summed colored output value of a token can never exceed its summed input value; the indexer treats any violation as a fatal error, and its broadcast time validator rejects raw transactions whose blueprint would burn or inflate unexpectedly.

9. State, sealing, immutability

  1. mod updates an atomical's state. History folds in ascending transaction order: a payload with $a = 1 deletes the named keys, where a value of true deletes and a nested map recurses; any other payload sets keys by recursive merge. args and $a are never stored.
  2. evt records a message against an atomical without changing state, and like mints must ride input 0.
  3. An NFT minted with args.i = true is immutable, and no mod or evt is ever applied to it. Realms, subrealms, and containers can never be immutable, and neither can fungible tokens.
  4. sl permanently seals an NFT: the indexer records the sealed location and stops advancing the atomical's UTXO, so it can never move or change again.

10. Realms, containers, dmitems

  1. Name shapes, each segment 1 to 64 characters with no leading or trailing hyphen: realm ^[a-z][a-z0-9-]{0,63}$; subrealm, container, and dmitem ^[a-z0-9][a-z0-9-]{0,63}$. Names are ASCII at the protocol layer; unicode display forms are an application convention.
  2. Top level names (realm, container, ticker) MUST carry commit bitwork with a prefix of at least 4 hex characters. Subrealms and dmitems are exempt.
  3. Name races resolve by earliest commit transaction number among valid candidates, the same rule as tickers. A claim is verified when its commit is at least 3 blocks deep and it is the earliest candidate.
  4. A subrealm mint declares args.parent_realm and an args.claim_type of direct or rule. Direct claims require the parent realm to be spent in the same transaction. Rule claims are evaluated against the parent's subrealm rule set as of one block before the commit.
  5. A subname rule set is a list of 1 to 100 entries. Each entry carries a regex p of 1 to 100000 characters which MUST compile and MUST NOT contain parentheses, plus at least one of payment outputs o, bitworkc, or bitworkr, where a bitwork value may be the literal any. One invalid entry invalidates the entire rule set.
  6. Payment outputs o map an output script in hex to {"v": <sats>} or {"v": <amount>, "id": <compact atomical id>}. With id, payment is measured in colored units of that ARC-20 token at the paying output. The minimum payment value is 0, so free subnames are legal.
  7. Subname payment window: payment may be made from commit_height + 3 and MUST confirm by commit_height + 15, or the candidate expires.
  8. A container's dmint configuration requires v equal to the string "1", a 64 hex character merkle root, a non-negative mint_height, an optional boolean immutable, and a valid rule set per N5.
  9. A dmitem mint proves membership: args.proof is a non-empty list of {"p": bool, "d": <64 hex>} merkle steps, and args.main names a payload key whose bytes hash into the leaf, accepted under the four combinations of any or specific bitwork against the container's merkle root. Both the commit height and the current height must be at or above the container's mint height.
  10. Subname payments are matched only in transactions carrying the OP_RETURN marker of rule E6, and never in a transaction that also carries a split operation, because a split can reassign ARC-20 value within the paying transaction.

11. Activation heights, Bitcoin mainnet

HeightNameEffect
808080ActivationAtomicals parsing and indexing begins
819181DmintContainer dmitems; FIFO input ordering for transfers
822800CommitzCommit outpoint index must be 0 for name type and dmt mints
828128Densitymax_mints cap raised to 21000000; perpetual mint mode; bytes permitted in args
828628Bitwork rolloverPerpetual mints may satisfy the next higher difficulty prefix
848484Custom coloringPartial coloring of outputs; the z operation
900000AVMdef and new handling, Bitcoin Universe fork

12. Invalidity conditions

A non-exhaustive checklist of conditions that void an operation. The transaction itself remains a valid Bitcoin transaction; only the protocol meaning is lost.

  1. Envelope structural failures: witness element under 39 bytes, missing 32 byte key push, a marker other than atom after OP_IF, an unknown operation code, a payload that is not a CBOR map, or byte values where rule E8 forbids them.
  2. Mint placement failures: envelope not on input 0; commit not indexed or before height 808080; reveal outside the 100 block general window; name reveal outside the 3 block window; a nonzero commit index after height 822800 for name type or dmt mints; a transaction with no outputs.
  3. Name failures: a malformed ticker, realm, container, or dmitem string; multiple name requests in one mint; missing or short commit bitwork on a top level name; an earlier committed candidate already exists; an immutable flag on a realm, subrealm, container, or fungible token.
  4. Deploy failures: mint_amount outside 546 to 100000000; max_mints outside its era cap; mint_height outside 0 to 10000000; malformed mint bitwork; perpetual mode before height 828128, without a valid bv, with static mint bitwork, or with out of range bci, bri, bcs, brs, or maxg.
  5. Claim failures: an unknown or unverified ticker; a claim or its commit before the deploy's mint height; an output 0 value that is not exactly mint_amount; a mint count at or above the cap; a commit or reveal transaction id failing the required or phase derived bitwork.
  6. Payment failures: a subname payment inside a split transaction; a payment after commit_height + 15; a payment output below the rule's value or in the wrong token; a missing OP_RETURN marker.
  7. Declared but unmet request bitwork on any operation, and unmet args.parents minimums per rule E12.
  8. Coloring never fails a transaction. A spend that violates no Bitcoin rule always confirms, and the coloring rules then decide assignment and burns. Invalid protocol intent shows up as burned or unmoved value, not as a rejected transaction. This is the single most important safety fact for wallet authors.

Attribution and scope. Sections 1 to 11 describe behavior implemented in the atomicals-electrumx lineage and are believed to match upstream Atomicals behavior, except where a rule is marked as a Bitcoin Universe fork addition. Where upstream documentation and this indexer's code disagree, this document follows the code and says so. The full list of Universe-specific indexing decisions is on the reference page.