Collections

Curated NFT collections with optional merkle proof enforcement

The ERC-721 Ethscriptions Collections protocol allows creators to build curated collections of ethscriptions with rich metadata and optional access control.

circle-info

Collections are an AppChain-only feature. They require smart contract execution on the L2.

Overview

  • Collection: A named set of ethscriptions with metadata (name, symbol, description, max supply)

  • Items: Individual ethscriptions added to a collection

  • Merkle Enforcement: Optional cryptographic restriction on which items can be added

Creating a Collection

Use the create_collection_and_add_self operation to create a collection and add the first item in one transaction:

data:image/png;rule=esip6;p=erc-721-ethscriptions-collection;op=create_collection_and_add_self;d=<base64-json>;base64,<image-bytes>
circle-info

The rule=esip6 parameter allows duplicate content. Without it, if the same data URI (including headers) was used in a previous ethscription, the new ethscription would be rejected as a duplicate. Uniqueness is based on SHA256 of the full data URI, not just the payload.

Where the base64-decoded d parameter contains:

{
  "metadata": {
    "name": "My Collection",
    "symbol": "MYC",
    "max_supply": "100",
    "description": "A curated collection of digital artifacts",
    "logo_image_uri": "",
    "banner_image_uri": "",
    "background_color": "",
    "website_link": "https://example.com",
    "twitter_link": "myhandle",
    "discord_link": "https://discord.gg/...",
    "merkle_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "initial_owner": "0x1234567890abcdef1234567890abcdef12345678"
  },
  "item": {
    "item_index": "0",
    "name": "Item #1",
    "background_color": "#FF0000",
    "description": "The first item in the collection",
    "attributes": [
      { "trait_type": "Rarity", "value": "Legendary" },
      { "trait_type": "Color", "value": "Red" }
    ],
    "merkle_proof": []
  }
}

Metadata Object Fields

All fields must be present in exact order. Use empty strings for optional values.

Field
Description

name

Collection name

symbol

Short symbol (e.g., "MYC")

max_supply

Maximum number of items (as string)

description

Collection description (can be empty)

logo_image_uri

Logo image as Data URI (can be empty)

banner_image_uri

Banner image as Data URI (can be empty)

background_color

Default background color (can be empty)

website_link

Project website URL (can be empty)

twitter_link

Twitter/X handle (can be empty)

discord_link

Discord invite URL (can be empty)

merkle_root

Merkle root for access control (use zero bytes32 for owner-only)

initial_owner

Address that will own the collection (lowercase)

Item Object Fields

Field
Description

item_index

Position in collection (0-indexed, as string)

name

Item name

background_color

Item-specific background color

description

Item description

attributes

Array of { trait_type, value } objects

merkle_proof

Array of proof hashes (for non-owner adds)

circle-exclamation

Adding Items to a Collection

After creating a collection, add items with add_self_to_collection:

Where the d parameter contains:

The collection_id is the L1 transaction hash of the collection creation.

Merkle Proof Enforcement

When a collection has a non-zero merkle_root, non-owners must provide a merkle proof to add items. This ensures only pre-approved items with exact metadata can be added.

How It Works

  1. Creator generates merkle tree from approved items

  2. Each leaf is computed from item metadata

  3. Creator sets merkle root when creating collection

  4. Non-owners provide proofs when adding items

Merkle Leaf Computation

Each leaf is computed as:

Merkle Tree Structure

For a 3-item collection, the tree looks like:

Where:

  • Proof for leaf0: [leaf1, leaf2]

  • Proof for leaf1: [leaf0, leaf2]

  • Proof for leaf2: [H(leaf0, leaf1)]

Pair Hashing

The merkle tree uses byte-wise ordering (same as OpenZeppelin):

This ensures consistent proof verification regardless of sibling order.

Adding Items with Proofs

Non-owners include the merkle proof in the item object:

Owner Bypass

Collection owners can always add items without providing merkle proofs. This allows:

  • Adding items not in the original tree

  • Making corrections

  • Flexibility for collection management

Example: Creating a Merkle-Enforced Collection

This walkthrough creates a 3-item collection where:

Item
Index
Added By
Merkle Proof Required?

Item 1 (Red)

0

Owner

No (owner bypass)

Item 2 (Green)

1

Non-owner

Yes

Item 3 (Blue)

2

Non-owner

Yes

Step 1: Compute Content Hashes

For each image, compute the keccak256 hash of the raw bytes:

Step 2: Build Merkle Leaves

Compute each leaf from the item metadata:

Step 3: Compute Merkle Root

Step 4: Create Collection (Owner)

The owner creates the collection with the merkle root and adds the first item:

  1. Send a 0 ETH transaction to any address

  2. Include the hex-encoded Data URI with op=create_collection_and_add_self

  3. The merkle_root is set to 0x06fbc22a...

  4. Save the transaction hash as collection_id

The owner doesn't need a merkle proof for their own item.

Step 5: Add Items (Non-Owner)

A different address adds items 2 and 3 with merkle proofs:

For Item 2 (index 1):

The proof must match exactly, and the metadata must match what was used to compute the leaf.

Operations Reference

Operation
Description

create_collection_and_add_self

Create collection and add first item

add_self_to_collection

Add item to existing collection

edit_collection

Update collection metadata

edit_collection_item

Update item metadata

transfer_ownership

Transfer collection ownership

renounce_ownership

Surrender ownership (to zero address)

remove_items

Delete items from collection

lock_collection

Prevent further additions

circle-info

ESIP-6 is optional. Add rule=esip6 to your data URI only if you need to allow duplicate content (e.g., sending the same JSON command multiple times). Without it, an ethscription with identical content to an existing one will not be created. For image-based operations, add it to the header: data:image/png;rule=esip6;p=.... For text-based operations: data:;rule=esip6,{...json...}.

Editing Collections

Update collection metadata with edit_collection. Send as a data URI:

JSON payload (all fields required; pass current values to keep them, empty strings will clear fields):

Only the collection owner can edit.

Editing Items

Update item metadata with edit_collection_item (all fields required):

Only the collection owner can edit items.

Removing Items

Remove items with remove_items using ethscription IDs (transaction hashes):

Only the collection owner can remove items.

Transferring Ownership

Transfer collection ownership with transfer_ownership:

Only the current owner can transfer ownership.

Renouncing Ownership

Permanently surrender ownership with renounce_ownership:

After renouncing, no one can edit the collection or add items (unless they have valid merkle proofs for a non-zero merkle root collection).

Locking Collections

Once locked, no more items can be added:

This is irreversible. Only the collection owner can lock.

Error Handling

Error
Cause

Invalid Merkle proof

Proof doesn't match root, or metadata differs from what was used to compute the leaf

Merkle proof required

Non-owner tried to add to a collection with zero merkle root (owner-only mode)

Item slot taken

Index already has an item

Collection locked

Cannot add to locked collection

Exceeds max supply

Collection is full

Not collection owner

Only owner can perform this operation

Security Considerations

  1. Content Hash Verification - The merkle leaf includes the content hash, ensuring exact image content is verified

  2. Metadata Binding - All metadata is bound to the merkle proof and cannot be changed after the tree is computed

  3. Owner Bypass - Collection owners can always add items, useful for corrections

  4. Locking - Once locked, no more items can be added even with valid proofs

  5. Zero Merkle Root - When merkle_root is zero, only the owner can add items

Generating Merkle Trees (TypeScript)

Below is complete TypeScript code using viemarrow-up-right for generating merkle trees and collection calldata.

Dependencies

Helper Functions

Complete Example

Sending Transactions

To create an ethscription, send a 0 ETH transaction with the hex calldata:

Last updated