PRC-721: Extended Inscription Envelope for Pepecoin

Status: Draft Version: 1.1 Date: 2026-03-19 Github View on GitHub
Feature Spec Indexer
Parent/child
Delegate
Metadata
Properties
Body compression

Abstract

This specification is developed as part of ord-pepecoin, the reference indexer for Pepecoin inscriptions.

PRC-721 extends the existing Pepecoin inscription format (P2SH scriptSig with countdown) to support parent/child provenance, delegate inscriptions, structured properties, and compressed metadata, while maintaining full backwards compatibility with existing parsers and indexers.

Tags are appended after the body countdown reaches zero. Old parsers stop at countdown 0 and never see the tags — inscription numbers remain consistent across all indexer versions.

Inspired by ordinals/ord, but designed specifically for non-SegWit scriptSig chains. PRC-721 uses string tag keys ("parent", "delegate") rather than numeric tags to avoid collision with countdown integers, and introduces compression and space optimizations tailored to Pepecoin's 1650-byte scriptSig limit.

Motivation

Pepecoin inscriptions ("pepinals") currently support basic content embedding via the "ord" countdown envelope. This is sufficient for standalone inscriptions but lacks:

  • Provenance — no way to link child inscriptions to a parent (e.g., items in a collection)
  • Delegates — no way to reference another inscription's content (e.g., 10,000 PFPs pointing to a shared base image, saving fees)
  • Metadata — no extensible field for structured data
  • Properties — no standard way to attach titles and traits to inscriptions for display and filtering by indexers and explorers

No scriptSig-based chain has cleanly implemented these features. PRC-721 solves this without breaking existing inscriptions or indexers.

Existing Envelope Format

The current format uses a countdown from npieces-1 to 0:

OP_PUSHBYTES_3 "ord"              # protocol marker
OP_PUSHNUM_N                      # npieces (total body chunks)
OP_PUSHBYTES_<len> <content_type> # MIME type
OP_PUSHNUM_<N-1>   <chunk_1>      # countdown N-1
OP_PUSHNUM_<N-2>   <chunk_2>      # countdown N-2
...
OP_PUSHNUM_1        <chunk_N-1>   # countdown 1
OP_PUSHBYTES_0      <chunk_N>     # countdown 0 (final chunk)

Parsers read the countdown until it reaches 0, then stop. Any data after countdown 0 is ignored by current parsers.

PRC-721 Extension

Envelope Structure

PRC-721 adds an optional tag trailer after the final body chunk. The trailer consists of string-keyed tag/value pairs:

OP_PUSHBYTES_3 "ord"              # protocol marker
OP_PUSHNUM_N                      # npieces
OP_PUSHBYTES_<len> <content_type> # MIME type
OP_PUSHNUM_<N-1>   <chunk_1>      # body countdown
...
OP_PUSHBYTES_0      <chunk_N>     # countdown 0 (body complete)
# ──── tag trailer (optional, new parsers only) ────
OP_PUSHBYTES_6  "parent"          # tag key
OP_PUSHBYTES_36 <parent_id>       # tag value: 32-byte txid LE + 4-byte vout LE
OP_PUSHBYTES_8  "delegate"        # tag key
OP_PUSHBYTES_36 <delegate_id>     # tag value: 32-byte txid LE + 4-byte vout LE

Tag Format

Each tag is a pair of consecutive pushes:

Push Content
Tag key UTF-8 string identifying the tag (e.g., "parent", "delegate")
Tag value Raw bytes, interpretation depends on the tag key

Unknown tags MUST be ignored by parsers (forward compatibility).

Defined Tags

parent — Parent Inscription

Links this inscription as a child of an existing inscription (provenance/collections).

  • Key: "parent" (6 bytes)
  • Value: 36 bytes — inscription ID (32-byte txid little-endian + 4-byte output index little-endian)
  • Repeatable: Yes — an inscription may have multiple parents
  • Validation: The first reveal transaction in the inscription's chain MUST spend the parent inscription's UTXO as one of its inputs. The tag alone is not sufficient — the indexer must verify the spend (cryptographic provenance).
OP_PUSHBYTES_6  "parent"
OP_PUSHBYTES_36 <32-byte txid LE><4-byte vout LE>

delegate — Delegate Inscription

Points to another inscription whose content should be served in place of this one. Useful for collections where many inscriptions share the same visual content.

  • Key: "delegate" (8 bytes)
  • Value: 36 bytes — inscription ID (32-byte txid little-endian + 4-byte output index little-endian)
  • Repeatable: No — only the first "delegate" tag is used
  • Behavior: When serving /content/<id>, if the inscription has a delegate, the indexer serves the delegate's content and content type instead.
OP_PUSHBYTES_8  "delegate"
OP_PUSHBYTES_36 <32-byte txid LE><4-byte vout LE>

metadata — Arbitrary Metadata

Attach arbitrary structured data to an inscription using CBOR encoding. Metadata is intended for protocol-specific or metaprotocol data (e.g., PRC-20 state) that external tools may consume. It is not used by the indexer for display — use "properties" for that.

  • Key: "metadata", "metadata;br" (Brotli-compressed)
  • Value: CBOR or JSON encoded bytes
  • Repeatable: No — first occurrence wins, duplicates are ignored
  • Size: Recommended under 1 KB

CBOR is preferred for efficiency. JSON is also accepted — the parser distinguishes by checking the first byte: { or [ indicates JSON, otherwise CBOR. This allows simple inscribers to attach metadata without a CBOR library, at the cost of a few extra bytes.

OP_PUSHBYTES_8  "metadata"
OP_PUSHDATA1    <CBOR or JSON bytes>

properties — Protocol-Level Properties

Structured fields that the indexer treats as first-class attributes of the inscription. Separate from "metadata" so the indexer knows exactly where to find protocol-recognized fields without parsing through arbitrary user data.

  • Key: "properties", "properties;br" (Brotli-compressed)
  • Value: CBOR-encoded bytes only (RFC 8949) — JSON is not accepted
  • Repeatable: No — first occurrence wins
Key Type Description
title string Inscription title (displayed by indexer)
traits map Collection traits for filtering
OP_PUSHBYTES_10 "properties"
OP_PUSHDATA1    <CBOR bytes>
Why both metadata and properties?
  • Properties contain the public identity of the inscription — name and traits. Indexers and explorers use this directly for display and filtering.
  • Metadata is for protocol data — arbitrary key/value pairs consumed by external tools or metaprotocols, not necessarily part of the visual identity.

content-encoding — Body Compression

Indicates that the inscription body is compressed. The indexer decompresses the body before serving /content/.

  • Key: "content-encoding" (16 bytes)
  • Value: UTF-8 string — "br" (Brotli) or "gzip"
  • Repeatable: No — first occurrence wins
OP_PUSHBYTES_16 "content-encoding"
OP_PUSHBYTES_2  "br"

The indexer does not decompress the body. It stores the compressed bytes as-is and serves them with the corresponding HTTP Content-Encoding header (e.g., Content-Encoding: br). The browser handles decompression natively. This avoids decompression bomb risks at the server level.

Multi-Transaction Chains

For inscriptions that span multiple reveal transactions:

  • The body chunks are distributed across all transactions in the chain (as today)
  • The tag trailer (tags) goes exclusively in the final reveal transaction, after the last body chunk (countdown 0)
  • The parent UTXO (if any) is spent as an input in the first reveal transaction
  • If the final reveal transaction has no tags after countdown 0, the inscription has no PRC-721 features (standard inscription)
  • Parent spend validation only applies to the first reveal transaction — intermediate chain transactions are not checked

Compression

The ;br Suffix Pattern

Instead of using a separate encoding tag (which costs additional bytes), PRC-721 encodes the compression method directly in the tag key name using a ;br suffix:

Tag Key Encoding Purpose
"metadata" Raw CBOR Arbitrary protocol data
"metadata;br" Brotli-compressed CBOR Compressed protocol data
"properties" Raw CBOR Structured identity (title, traits)
"properties;br" Brotli-compressed CBOR Compressed structured identity

Parser Behavior

  1. If the tag key ends with ";br", decompress the value with Brotli before decoding CBOR
  2. If decompression fails, ignore the tag (inscription remains valid)
  3. Tags without the suffix are raw CBOR — no decompression attempted

Inscriber Behavior

The inscriber should:

  1. Encode the data as CBOR
  2. Attempt Brotli compression
  3. Compare raw vs compressed size
  4. Use the tag name ("properties" vs "properties;br") that produces the smallest result

Constraints

  • Maximum uncompressed size: 4,000 bytes
  • Maximum compression ratio: 30:1 (to prevent decompression bombs)

ScriptSig Space Budget

Pepecoin uses P2SH scriptSig inscriptions without SegWit. The IsStandard policy limits scriptSig to 1650 bytes. After signature and redeem script overhead (~150 bytes), roughly 1500 bytes are available for inscription payload per reveal transaction.

Chunk packing

Body data is split into 240-byte chunks. Each chunk encodes as 242 bytes (1-byte OP_PUSHDATA1 prefix + 240 bytes data + 1-byte countdown number), fitting ~6 chunks per reveal — approximately 1,440 bytes of body data per reveal transaction.

Practical example

A 10 KB image requires ~42 chunks across ~7 reveal transactions. A 50 KB image needs ~35 reveals. Multi-reveal chains are the norm on Pepecoin, not the exception.

PRC-721 tags (parent, properties, metadata) are appended after the final body chunk. They add at most 1–2 extra reveals to a chain that is already many transactions long. Compression (";br" suffix) helps keep this overhead minimal.

Backwards Compatibility

Practical context

In practice, indexer divergence already exists in the Pepecoin inscription ecosystem. The ordpep indexer supports 520-byte push data (the maximum allowed by consensus), while older apezord-based parsers assume 240-byte chunks. Any inscription using larger pushes already produces different results across indexers. The ordpep indexer is currently the only actively maintained Pepecoin inscription indexer — there is no fragmented ecosystem to break.

Old parsers reading PRC-721 inscriptions

Old parsers (without tag support) will process the body countdown to 0 and stop reading — they never see the tag trailer. Inscription numbers remain consistent across all indexer versions.

New parsers reading old inscriptions

New parsers will parse the body countdown to 0 as normal, find no remaining pushes, and index the inscription with no parent/delegate metadata. 100% compatible.

Inscription number consistency

Inscription numbers remain consistent across old and new indexers. Old parsers never skip PRC-721 inscriptions — they just don't extract the metadata.

Validation Rules

Parent Validation

A conforming indexer MUST verify parent/child relationships cryptographically:

  1. Parse the "parent" tag from the tag trailer
  2. Check that the first reveal transaction in the child's chain has the parent inscription's UTXO as one of its inputs
  3. Only if both conditions are met, record the parent/child relationship

The tag alone is not proof of parentage — anyone can write a tag. The UTXO spend proves ownership of the parent inscription at the time the child was created.

Delegate Resolution

When serving inscription content:

  1. Check if the inscription has a "delegate" tag
  2. If yes, look up the delegate inscription
  3. If the delegate itself has a delegate, follow the chain (recursive resolution)
  4. Serve the final delegate's content and content type
  5. If a delegate doesn't exist or has been burned, fall back to the original inscription's own content (if any)
  6. Enforce a maximum recursion depth of 10 to prevent infinite loops (A → B → A). If exceeded, serve the original inscription's content.

Parser Pseudocode

parse(sig_scripts):
  pushes = decode_all_push_data(sig_scripts)

  # Standard envelope parsing
  assert pushes[0] == "ord"
  npieces = to_number(pushes[1])
  content_type = pushes[2]

  # Body countdown
  body = []
  i = 3
  for countdown in (npieces-1)..0:
    assert to_number(pushes[i]) == countdown
    body.append(pushes[i+1])
    i += 2

  # PRC-721 tag trailer (optional)
  tags = {}
  while i + 1 < len(pushes):
    key = to_string(pushes[i])
    value = pushes[i+1]
    tags[key].append(value)    # support repeated tags
    i += 2

  return Inscription { content_type, body, tags }

Extensibility

New tags can be added without a protocol upgrade — old parsers and old PRC-721 parsers will simply ignore unknown tags. Tag keys are UTF-8 strings, values are raw bytes with interpretation defined per tag.

References

  • mvdnbrk/ord-pepecoin — Ordinals indexer for Pepecoin
  • ordinals/ord — Bitcoin inscription standard (Taproot/witness)
  • apezord/ord-dogecoin — P2SH scriptSig inscription indexer, fork of ordinals/ord v0.5. Basis for Pepecoin inscription support.
  • PRC-20 — Pepecoin fungible token standard (same mechanics as BRC-20 on Bitcoin and DRC-20 on Dogecoin, no formal spec exists for Pepecoin)