PRC-721: Extended Inscription Envelope for Pepecoin
| 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
- If the tag key ends with
";br", decompress the value with Brotli before decoding CBOR - If decompression fails, ignore the tag (inscription remains valid)
- Tags without the suffix are raw CBOR — no decompression attempted
Inscriber Behavior
The inscriber should:
- Encode the data as CBOR
- Attempt Brotli compression
- Compare raw vs compressed size
- 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:
- Parse the
"parent"tag from the tag trailer - Check that the first reveal transaction in the child's chain has the parent inscription's UTXO as one of its inputs
- 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:
- Check if the inscription has a
"delegate"tag - If yes, look up the delegate inscription
- If the delegate itself has a delegate, follow the chain (recursive resolution)
- Serve the final delegate's content and content type
- If a delegate doesn't exist or has been burned, fall back to the original inscription's own content (if any)
- 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)