Specification 1.0.0 / bitcoin mainnet
Normative rules
Rules are grouped by scope and numbered so they can be cited. R-CAR applies to every payload on the carrier. R-20, R-NAME and R-OPI apply to one protocol each. Every rule reflects behaviour implemented in the organisation's own readers.
Conventions
Must and must not are absolute. Rejected means the reader refuses to apply the payload and records the reason; it never means the Bitcoin transaction is invalid. All three protocols are interpretations layered on top of Bitcoin, so a rejected payload is still a confirmed transaction.
Reason codes in monospace are the exact codes the readers emit. Two families appear: protocol_mismatch for a payload that does not obey the carrier or protocol shape, and upstream_schema for a payload whose shape is right but whose field values are not usable.
Amounts are written as whole atomic units. There is no decimals field on any of these three protocols; the registry records decimals: none for all of them.
R-CAR, the carrier
- R-CAR-1
A data carrier output is a transaction output whose scriptPubKey begins with
0x6a,OP_RETURN. The OP Inscriptions scanner additionally accepts a leadingOP_0, that is a script beginning0x00 0x6a. The OP-20 and OP Names readers do not: for them the first byte must be0x6a. - R-CAR-2
The payload is the concatenation, in script order, of the data pushed by every push operation after
OP_RETURN. Push boundaries carry no meaning to the protocols; a payload split across two pushes decodes identically to the same bytes in one push. - R-CAR-3
Recognised push encodings are
OP_0(0x00, zero bytes), a direct push (0x01to0x4b, where the opcode is the length),OP_PUSHDATA1(0x4c, one length byte),OP_PUSHDATA2(0x4d, two little endian length bytes) andOP_PUSHDATA4(0x4e, four little endian length bytes). - R-CAR-4
Any other opcode inside the carrier is fatal for OP-20 and OP Names: the carrier is rejected with
protocol_mismatchand no payload is produced. The OP Inscriptions scanner instead skips the unrecognised byte and continues, because its job is to catalogue historical bytes rather than to authorise a state change. - R-CAR-5
A push whose declared length runs past the end of the script is rejected. A truncated
PUSHDATA1,PUSHDATA2orPUSHDATA4length prefix is rejected for the same reason. - R-CAR-6
The OP Names reader refuses an assembled payload larger than 4096 bytes and records
payload_too_large. This is an implementation safety bound, not a network rule. - R-CAR-7
OP-20 and OP Names payloads must decode as strict UTF-8. Bytes that are not valid UTF-8 cannot carry either protocol. OP Names records
invalid_utf8evidence only when the bytes plausibly attempted the protocol, that is when they contain the byte0x2eor the Latin-1 reading containsopns. Otherwise the output is ignored entirely. - R-CAR-8
A carrier output must carry zero satoshis. Value assigned to it is destroyed and is not recoverable by any means.
- R-CAR-9
For the widest relay, keep the entire scriptPubKey at 83 bytes or fewer and use exactly one carrier output per transaction. This is node policy, not consensus: a larger or repeated carrier is still valid if mined. See relay and standardness.
- R-CAR-10
The carrier assigns no ownership. Where a protocol has an owner, that owner comes from a different, spendable output of the same transaction.
R-20, OP-20
Registry id op_return, display name OP-20, aliases op-return, op20, op-20. Ownership model recorded as hybrid, because a balance can be held by an address and also be pinned to a specific transfer UTXO.
Encoding
Full script hex: 6a347b2270223a226f702d3230222c226f70223a226d696e74222c227469636b223a2250495045222c22616d74223a2231303030227d
- R-20-1
The payload must begin with a JSON object. Only whitespace may precede the opening brace; any other leading text means the payload is not OP-20. The reader takes the first complete top level object and keeps whatever follows as an uninterpreted suffix.
- R-20-2
A transaction must contain exactly one OP-20 carrier. Two OP-20 payloads in one transaction are rejected with
protocol_mismatch. Other OP_RETURN outputs that are not OP-20 do not interfere. - R-20-3
The field
pmust equal exactly"op-20". Any other value, including"op20", means the payload is not OP-20 and is ignored rather than rejected. - R-20-4
The field
op, compared case insensitively, must bedeploy,mintortransfer. Anything else is rejected withprotocol_mismatch. - R-20-5
Allowed fields are exact per operation.
deploy:p, op, tick, max, lim, add.mint:p, op, tick, amt, add.transfer:p, op, tick, amt, add. Any other field is rejected withupstream_schema. There is no comment, memo or extension field. - R-20-6
A repeated top level key is rejected with
protocol_mismatch, beforeJSON.parsesilently keeps the last value. This is checked textually against the raw payload. - R-20-7
The token reference is derived from
tickby applying NFC normalisation, trimming, then lowercasing. The value must round trip as UTF-8, must be at most 400 bytes before and after normalisation, must not contain/or\, and must not be.or... The ticker as written is preserved separately for display and is bounded at 191 characters. - R-20-8
The stable asset identity is
op_return:op20:b64.<base64url(reference)>, where the reference is the exact normalised lowercase text from R-20-7. An identity whose decoded reference is not already in that exact form is an alias and is rejected. - R-20-9
deployrequiresmaxandlim.maxmust match^(0|[1-9]\d{0,77})(\.\d{1,77})?$and its whole part must be positive; a fractional part is truncated and the coercionlegacy-mysql-bigint-truncationis recorded.limmust be a positive whole atomic amount with no fraction at all. - R-20-10
mintandtransferrequireamt. It must match^(0|[1-9]\d{0,77})(\.(0{1,77}))?$: only an all zero fraction is tolerated, and it is truncated with the coercionlegacy-zero-fraction-truncated-by-mysql-bigint. A non zero fraction is rejected withupstream_schema. The resulting amount must be positive; zero is rejected except in a specific reconciliation case where a zero amount transfer is explicitly permitted. - R-20-11
addis an optional explicit recipient address. Its presence changes the shape of a transfer, see R-20-12. - R-20-12
A transfer with
addis a single direct balance changing event. A legacy transfer withoutaddis two stage: creating the transfer UTXO publishes a protocol operation that changes no balance, and spending that UTXO later publishes the balance changing transfer. An indexer that collapses the two stages will report balances that never existed. - R-20-13
Bytes after the first JSON object are retained as a suffix, with their byte length and text recorded. They are not protocol fields and must not be interpreted.
- R-20-14
Marketplace sale and purchase records are emitted separately from balance movement, with no wallet deltas of their own, because the associated transfer settlement already changed the balances.
- R-20-15
An OP-20 balance is an indexer interpretation. Bitcoin consensus knows nothing about it. Two indexers that disagree about a payload will disagree about the balance, and neither is wrong at the Bitcoin layer.
R-NAME, OP Names
Registry id op_names, display name OP Names, aliases op-names, opnames. Ownership model UTXO. A name is a singleton ownership position, never a fungible balance.
The organisation also indexes the Ordinals based Sats Names System under the separate marketplace protocol id names. That protocol originated outside this organisation, uses inscriptions rather than OP_RETURN, and has its own rules. Do not resolve an op_names identity through an SNS index, or the reverse.
Recognised carriers
- R-NAME-1
Three carrier shapes are recognised. Strict JSON
{"p":"opns","op":"reg","name":...}, optionally withadd. Strict JSON{"p":"opns","op":"transfer","name":...}. Legacy raw UTF-8 registration text containing exactly one literal dot. - R-NAME-2
Allowed JSON fields are exact.
reg:p, op, name, add.transfer:p, op, name. Any other field isunknown_field. A repeated top level key is rejected.namemust be a string.add, when present on areg, must be a string of at most 128 characters, otherwiseinvalid_explicit_address. - R-NAME-3
The transaction must contain exactly one OP_RETURN output and exactly one recognised carrier. If either count differs the result is
ambiguous_op_returnand nothing is applied. This is stricter than OP-20: an unrelated second data output is enough to void the whole transaction for OP Names. - R-NAME-4
An identity is derived from the name as follows. The value must be valid UTF-8. It must contain no C0 or DEL control characters, no
/or\, and no Unicode dot lookalike from the setU+00B7 U+0387 U+2022 U+2024 U+2027 U+2219 U+22C5 U+3002 U+30FB U+FE52 U+FF0E U+FF61 U+FF65. Apply NFC, trim, then lowercase. The result must be at most 255 UTF-8 bytes and must split on exactly one literal dot into a non empty label and a non empty namespace. The namespace must match^[a-z0-9]+$and be at most 63 UTF-8 bytes. - R-NAME-5
The stable asset identity is
op_names:name:b64.<base64url(normalised UTF-8 name)>. Non exact base64url encodings and identities that do not round trip are rejected. - R-NAME-6
Plain text with zero literal dots, or with two or more, is not an OP Names carrier at all. It is not recorded as evidence and is not an invalid registration; the reader simply does not claim it.
- R-NAME-7
A recognised payload with an unusable identity is stored as unapplied evidence with an explicit reason and never becomes an asset. Invalid rows are never normalised into an identity that could shadow a valid first registration.
State transitions
- R-NAME-8
The first valid registration of a name wins permanently. A later registration of the same name is recorded as an audit event with reason
first_registration_already_existsand changes nothing. - R-NAME-9
A registration carrying a valid
addenters address custody with that address as owner. - R-NAME-10
A registration without
addderives its owner from the receipt output. The receipt is output 0 when the OP_RETURN sits at an index greater than 0, and output 1 otherwise. The derived owner is in address custody. - R-NAME-11
An explicit
transferis valid only when the name is currently in address custody and the previous output spent by input 0 belongs to exactly the current owner. The previous transaction is fetched only when this proof is needed. - R-NAME-12
An authorised explicit transfer moves the name into UTXO custody, located at its receipt outpoint.
- R-NAME-13
A name in UTXO custody follows a spend of its exact current outpoint to output 0 of the spending transaction. Several names can follow the same consolidating output and remain distinct identities.
- R-NAME-14
A missing, OP_RETURN, unspendable or ownerless receipt or follow output burns the name explicitly. A burned name remains a valid historical registration, has no owner and no current outpoint, and is not transferable.
- R-NAME-15
Valid names have perpetual protocol validity. No expiry rule exists. Pending and mempool coverage is reported as unavailable.
- R-NAME-16
OP Names state is independent. It is never resolved through the
op_returnorop20account authority, and it lives in dedicated storage sourced from sequential raw Bitcoin blocks rather than from legacy token tables.
R-OPI, OP Inscriptions
Registry id op_inscriptions, display name OP Inscriptions, aliases op-inscriptions, opinscriptions. Ownership model UTXO. This is the only protocol on this carrier that Bitcoin Universe Core executes rather than merely displays.
- R-OPI-1
A candidate output has a script beginning
0x6aor0x00 0x6a. The record identity istxid:vout. - R-OPI-2
The payload is the concatenation of every decoded push. Small number opcodes
OP_1toOP_16contribute their numeric value as a single byte. If no push decodes at all, the raw bytes followingOP_RETURNare used as the payload. - R-OPI-3
A payload is treated as text when its UTF-8 reading contains no replacement character and at least 75 percent of its characters are printable, counting tab, newline and carriage return as printable. NUL bytes are stripped first.
- R-OPI-4
A payload is an OP inscription when its JSON tag, read from
p,protocolorprotoand compared case insensitively, isop-inscriptions,op_inscriptionsorop-inscription. The operation is read fromop,operationoractionand defaults toinscribe. - R-OPI-5
One historical envelope is also accepted: a JSON object whose keys are drawn entirely from
add,address,addr,to,receiver,collection, and which yields a non empty address. This shape predates the explicit protocol tag. Arbitrary JSON in an OP_RETURN is not an OP inscription. - R-OPI-6
The scanner also labels foreign payloads for catalogue purposes:
op-20andop20as OP-20,op-names,op_namesandopnameas OP Names,mezcalas Mezcal, and bare text matching^[a-z0-9][a-z0-9_-]{0,63}\.[a-z0-9_-]{1,24}$as OP Names. These labels are catalogue metadata only. They do not make the payload an OP inscription, and they do not override the owning protocol's own reader, whose rules are stricter. A name that this label accepts may still be invalid under R-NAME-4. - R-OPI-7
Media content is recognised by sniffing the payload bytes and, where declared, an explicit MIME type. A recognised image, audio, video, PDF, text or JSON payload becomes renderable inscription content.
- R-OPI-8
Content larger than one carrier may be split across several OP_RETURN records. Chunks are grouped by a shared group identifier and a declared total. Index fields named
part,partNumber,chunkNumber,seqornare treated as one based and converted; an index equal to the total is also treated as one based. - R-OPI-9
A group assembles only when every index from 0 to total minus 1 is present exactly once. A partial group is never assembled and never rendered. Chunk data is joined in index order and decoded as hex, base64 or text according to the declared chunk encoding.
- R-OPI-10
An assembled record takes the identity
assembled:<groupId>:<total>with operationassembled-media, and records the identifiers of every source part. The individual parts remain available as their own records.
Durability and marketplace state
- R-OPI-11
Each block, its deterministic records and the indexing cursor commit in one database transaction. New rows are provisional, and finality advances monotonically only after the configured confirmation count.
- R-OPI-12
Every block is checked against a fresh height to hash lookup and its expected parent. Outside an isolated test network an independently hosted verifier must agree on each height to hash result before the block reaches storage.
- R-OPI-13
Restart reconciliation finds a common ancestor before any mutation. A bounded provisional suffix is deleted and replayed. A deep or already final divergence persists a halted state instead of changing authoritative rows, and that halt is never cleared automatically.
- R-OPI-14
A marketplace fill is recorded only after the exact receipt outpoint spend, the buyer receipt output and the seller payout are all confirmed on chain. The settlement confirmation minimum is 1.
- R-OPI-15
The reconciler reopens receipts that are unspent on the active chain and marks conflicting spends stale. This is the only one of the three protocols with automatic lifecycle reconciliation.
- R-OPI-16
There is no atomic listing update. Changing the terms of a listing requires cancelling it and listing again.
Invalid conditions
Collected reason codes and what each one means for state. None of these make the Bitcoin transaction invalid.
| Reason | Protocol | Trigger | Effect on state |
|---|---|---|---|
protocol_mismatch | OP-20, OP Names | malformed carrier, non push opcode, truncated push, unsupported operation, repeated field | nothing applied |
upstream_schema | OP-20 | unknown field, unusable ticker, amount outside the accepted numeric form | nothing applied |
payload_too_large | OP Names | assembled payload over 4096 bytes | evidence only |
invalid_utf8 | OP Names | payload is not valid UTF-8 but plausibly attempted the protocol | evidence only |
malformed_json | OP Names | payload mentions opns but is not valid JSON | evidence only |
unknown_field | OP Names | a field outside the allowed set for the operation | evidence only |
unsupported_operation | OP Names | op is neither reg nor transfer | evidence only |
missing_name | OP Names | name is absent or not a string | evidence only |
invalid_explicit_address | OP Names | add is not a string of at most 128 characters | evidence only |
invalid_op_name | OP Names | identity fails R-NAME-4 | evidence only |
ambiguous_op_return | OP Names | more than one OP_RETURN output or more than one recognised carrier | nothing applied |
first_registration_already_exists | OP Names | the name was already registered by an earlier valid write | audit event only |
missing_receipt_output | OP Names | the derived receipt index does not exist | name burned |
unspendable_receipt_output | OP Names | the receipt output is itself an OP_RETURN | name burned |
receipt_owner_unavailable | OP Names | no single address can be derived from the receipt output | name burned |
missing_follow_output | OP Names | a UTXO custody spend has no output 0 | name burned |
unspendable_follow_output | OP Names | output 0 of the spend is an OP_RETURN | name burned |
follow_owner_unavailable | OP Names | output 0 of the spend has no derivable address | name burned |
Every one of these outcomes is reproduced with real script hex on the test vectors page.