OP_RETURN

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

  1. 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 leading OP_0, that is a script beginning 0x00 0x6a. The OP-20 and OP Names readers do not: for them the first byte must be 0x6a.

  2. 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.

  3. R-CAR-3

    Recognised push encodings are OP_0 (0x00, zero bytes), a direct push (0x01 to 0x4b, where the opcode is the length), OP_PUSHDATA1 (0x4c, one length byte), OP_PUSHDATA2 (0x4d, two little endian length bytes) and OP_PUSHDATA4 (0x4e, four little endian length bytes).

  4. R-CAR-4

    Any other opcode inside the carrier is fatal for OP-20 and OP Names: the carrier is rejected with protocol_mismatch and 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.

  5. R-CAR-5

    A push whose declared length runs past the end of the script is rejected. A truncated PUSHDATA1, PUSHDATA2 or PUSHDATA4 length prefix is rejected for the same reason.

  6. 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.

  7. 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_utf8 evidence only when the bytes plausibly attempted the protocol, that is when they contain the byte 0x2e or the Latin-1 reading contains opns. Otherwise the output is ignored entirely.

  8. R-CAR-8

    A carrier output must carry zero satoshis. Value assigned to it is destroyed and is not recoverable by any means.

  9. 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.

  10. 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

byte 06aOP_RETURN
byte 134push 52 bytes
bytes 2..53{"p":"op-20","op":"mint","tick":"PIPE","amt":"1000"}UTF-8 JSON object

Full script hex: 6a347b2270223a226f702d3230222c226f70223a226d696e74222c227469636b223a2250495045222c22616d74223a2231303030227d

  1. 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.

  2. 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.

  3. R-20-3

    The field p must equal exactly "op-20". Any other value, including "op20", means the payload is not OP-20 and is ignored rather than rejected.

  4. R-20-4

    The field op, compared case insensitively, must be deploy, mint or transfer. Anything else is rejected with protocol_mismatch.

  5. 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 with upstream_schema. There is no comment, memo or extension field.

  6. R-20-6

    A repeated top level key is rejected with protocol_mismatch, before JSON.parse silently keeps the last value. This is checked textually against the raw payload.

  7. R-20-7

    The token reference is derived from tick by 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.

  8. 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.

  9. R-20-9

    deploy requires max and lim. max must match ^(0|[1-9]\d{0,77})(\.\d{1,77})?$ and its whole part must be positive; a fractional part is truncated and the coercion legacy-mysql-bigint-truncation is recorded. lim must be a positive whole atomic amount with no fraction at all.

  10. R-20-10

    mint and transfer require amt. 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 coercion legacy-zero-fraction-truncated-by-mysql-bigint. A non zero fraction is rejected with upstream_schema. The resulting amount must be positive; zero is rejected except in a specific reconciliation case where a zero amount transfer is explicitly permitted.

  11. R-20-11

    add is an optional explicit recipient address. Its presence changes the shape of a transfer, see R-20-12.

  12. R-20-12

    A transfer with add is a single direct balance changing event. A legacy transfer without add is 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.

  13. 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.

  14. 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.

  15. 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.

OP-20 transfer in one stage and in two stages Two rows. The top row shows a modern transfer: one transaction carries an OP_RETURN payload including an explicit add field, and the balance moves immediately. The bottom row shows a legacy transfer without add: the first transaction creates a transfer UTXO and publishes a protocol operation with no balance change, and a second transaction that spends that UTXO publishes the balance changing transfer. TRANSFER WITH add tx: payload has add one carrier, one intent balance moves now one direct transfer event LEGACY TRANSFER WITHOUT add tx A: creates transfer UTXO protocol operation, no delta tx B: spends that UTXO balance changing transfer balance moves
The two shapes of an OP-20 transfer. The legacy shape is the reason a pending transfer UTXO must never be spent as ordinary change.

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.

OP Names is not SNS

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

  1. R-NAME-1

    Three carrier shapes are recognised. Strict JSON {"p":"opns","op":"reg","name":...}, optionally with add. Strict JSON {"p":"opns","op":"transfer","name":...}. Legacy raw UTF-8 registration text containing exactly one literal dot.

  2. R-NAME-2

    Allowed JSON fields are exact. reg: p, op, name, add. transfer: p, op, name. Any other field is unknown_field. A repeated top level key is rejected. name must be a string. add, when present on a reg, must be a string of at most 128 characters, otherwise invalid_explicit_address.

  3. 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_return and nothing is applied. This is stricter than OP-20: an unrelated second data output is enough to void the whole transaction for OP Names.

  4. 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 set U+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.

  5. 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.

  6. 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.

  7. 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

  1. 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_exists and changes nothing.

  2. R-NAME-9

    A registration carrying a valid add enters address custody with that address as owner.

  3. R-NAME-10

    A registration without add derives 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.

  4. R-NAME-11

    An explicit transfer is 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.

  5. R-NAME-12

    An authorised explicit transfer moves the name into UTXO custody, located at its receipt outpoint.

  6. 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.

  7. 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.

  8. R-NAME-15

    Valid names have perpetual protocol validity. No expiry rule exists. Pending and mempool coverage is reported as unavailable.

  9. R-NAME-16

    OP Names state is independent. It is never resolved through the op_return or op20 account authority, and it lives in dedicated storage sourced from sequential raw Bitcoin blocks rather than from legacy token tables.

OP Names custody state machine A name starts unregistered. A valid registration moves it to address custody, either from an explicit add field or from the receipt output. An authorised explicit transfer moves it from address custody to UTXO custody at the receipt outpoint. A name in UTXO custody follows further spends of its exact outpoint to output zero and stays in UTXO custody. From either custody state, a missing, unspendable or ownerless receipt output burns the name, which is a terminal state with no owner. unregistered no first write yet R-NAME-8/9/10 address custody owner is an address R-NAME-11/12 authorised transfer UTXO custody owner is an outpoint R-NAME-13, follows output 0 burned, terminal R-NAME-14, no owner
Custody, not balance. Every arrow is a rule, and the burn arrows are reached by an output that cannot hold an owner.

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.

  1. R-OPI-1

    A candidate output has a script beginning 0x6a or 0x00 0x6a. The record identity is txid:vout.

  2. R-OPI-2

    The payload is the concatenation of every decoded push. Small number opcodes OP_1 to OP_16 contribute their numeric value as a single byte. If no push decodes at all, the raw bytes following OP_RETURN are used as the payload.

  3. 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.

  4. R-OPI-4

    A payload is an OP inscription when its JSON tag, read from p, protocol or proto and compared case insensitively, is op-inscriptions, op_inscriptions or op-inscription. The operation is read from op, operation or action and defaults to inscribe.

  5. 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.

  6. R-OPI-6

    The scanner also labels foreign payloads for catalogue purposes: op-20 and op20 as OP-20, op-names, op_names and opname as OP Names, mezcal as 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.

  7. 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.

  8. 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, seq or n are treated as one based and converted; an index equal to the total is also treated as one based.

  9. 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.

  10. R-OPI-10

    An assembled record takes the identity assembled:<groupId>:<total> with operation assembled-media, and records the identifiers of every source part. The individual parts remain available as their own records.

Durability and marketplace state

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Rejection reasons emitted by the readers
ReasonProtocolTriggerEffect on state
protocol_mismatchOP-20, OP Namesmalformed carrier, non push opcode, truncated push, unsupported operation, repeated fieldnothing applied
upstream_schemaOP-20unknown field, unusable ticker, amount outside the accepted numeric formnothing applied
payload_too_largeOP Namesassembled payload over 4096 bytesevidence only
invalid_utf8OP Namespayload is not valid UTF-8 but plausibly attempted the protocolevidence only
malformed_jsonOP Namespayload mentions opns but is not valid JSONevidence only
unknown_fieldOP Namesa field outside the allowed set for the operationevidence only
unsupported_operationOP Namesop is neither reg nor transferevidence only
missing_nameOP Namesname is absent or not a stringevidence only
invalid_explicit_addressOP Namesadd is not a string of at most 128 charactersevidence only
invalid_op_nameOP Namesidentity fails R-NAME-4evidence only
ambiguous_op_returnOP Namesmore than one OP_RETURN output or more than one recognised carriernothing applied
first_registration_already_existsOP Namesthe name was already registered by an earlier valid writeaudit event only
missing_receipt_outputOP Namesthe derived receipt index does not existname burned
unspendable_receipt_outputOP Namesthe receipt output is itself an OP_RETURNname burned
receipt_owner_unavailableOP Namesno single address can be derived from the receipt outputname burned
missing_follow_outputOP Namesa UTXO custody spend has no output 0name burned
unspendable_follow_outputOP Namesoutput 0 of the spend is an OP_RETURNname burned
follow_owner_unavailableOP Namesoutput 0 of the spend has no derivable addressname burned

Every one of these outcomes is reproduced with real script hex on the test vectors page.