Skip to content
compressed-nfts-basics logo

Compressed NFTs Basics

compressed-nfts-basics

Build with Solana compressed NFTs (cNFTs) - Merkle trees, minting at scale, transfers, and metadata. Use when creating large NFT collections or reducing mint costs.

SKILL.md

Full skill instructions

Compressed NFTs Basics

Role framing: You are a Solana NFT engineer specializing in state compression. Your goal is to help developers create, transfer, and manage compressed NFTs cost-effectively at scale.

Initial Assessment

  • What's the collection size: hundreds, thousands, or millions?
  • Minting pattern: all at once, on-demand, or continuous?
  • Who pays: creator upfront or buyers on mint?
  • Metadata: on-chain, off-chain, or hybrid?
  • Do you need to query/​filter NFTs by attributes?
  • Transfer frequency: high (trading) or low (soulbound-ish)?
  • Budget: what's acceptable cost per NFT?

Core Principles

  • Compression trades account rent for tree rent: Instead of paying ~0.002 SOL per NFT account, pay ~0.5-2 SOL for a tree that holds thousands-millions.
  • Trees are immutable config: Max depth and buffer size are set at creation. Choose wisely.
  • Proofs are required for operations: Every transfer/​burn needs a Merkle proof from an indexer.
  • Indexers are essential: Without Helius, Triton, or your own, you can't query or operate on cNFTs.
  • Not all marketplaces support cNFTs: Verify listing venue support before choosing compression.
  • Decompression is possible but costly: Can convert cNFT to regular NFT, but defeats the purpose.

Workflow

1. Understanding the Economics

Cost comparison (approximate):

Collection SizeRegular NFTsCompressed NFTsSavings
1,000~2 SOL~0.5 SOL75%
10,000~20 SOL~1 SOL95%
100,000~200 SOL~2 SOL99%
1,000,000~2000 SOL~5 SOL99.75%

The tree cost is upfront; minting is nearly free after.

2. Merkle Tree Configuration

// Tree parameters
interface TreeConfig {
  maxDepth: number;      // Max NFTs = 2^maxDepth
  maxBufferSize: number; // Concurrent operations buffer
  canopyDepth: number;   // Proof size optimization
}

// Common configurations:
const TREE_CONFIGS = {
  small: {      // Up to 16,384 NFTs
    maxDepth: 14,
    maxBufferSize: 64,
    canopyDepth: 11,
    approxCost: '0.5 SOL',
  },
  medium: {     // Up to 1,048,576 NFTs
    maxDepth: 20,
    maxBufferSize: 256,
    canopyDepth: 14,
    approxCost: '1.5 SOL',
  },
  large: {      // Up to 1 billion NFTs
    maxDepth: 30,
    maxBufferSize: 2048,
    canopyDepth: 17,
    approxCost: '5+ SOL',
  },
};

// Calculate tree capacity
function getTreeCapacity(maxDepth: number): number {
  return Math.pow(2, maxDepth);
}

// Calculate approximate rent
async function estimateTreeRent(
  connection: Connection,
  maxDepth: number,
  maxBufferSize: number,
  canopyDepth: number
): Promise<number> {
  const space = getConcurrentMerkleTreeAccountSize(
    maxDepth,
    maxBufferSize,
    canopyDepth
  );
  return connection.getMinimumBalanceForRentExemption(space);
}

3. Creating a Merkle Tree

import {
  createTree,
  mplBubblegum,
} from '@metaplex-foundation/​mpl-bubblegum';
import { generateSigner, createSignerFromKeypair } from '@metaplex-foundation/​umi';
import { createUmi } from '@metaplex-foundation/​umi-bundle-defaults';

async function createMerkleTree(
  connection: Connection,
  payer: Keypair,
  config: TreeConfig
): Promise<PublicKey> {
  // Setup UMI
  const umi = createUmi(connection.rpcEndpoint)
    .use(mplBubblegum());

  const payerSigner = createSignerFromKeypair(umi, {
    publicKey: payer.publicKey,
    secretKey: payer.secretKey,
  });
  umi.use(payerSigner);

  // Generate tree keypair
  const merkleTree = generateSigner(umi);

  // Create tree
  await createTree(umi, {
    merkleTree,
    maxDepth: config.maxDepth,
    maxBufferSize: config.maxBufferSize,
    canopyDepth: config.canopyDepth,
    public: false, // Only tree authority can mint
  }).sendAndConfirm(umi);

  console.log('Tree created:', merkleTree.publicKey);

  return new PublicKey(merkleTree.publicKey);
}

4. Minting Compressed NFTs

import { mintV1 } from '@metaplex-foundation/​mpl-bubblegum';

interface CNFTMetadata {
  name: string;
  symbol: string;
  uri: string;
  sellerFeeBasisPoints: number;
  creators: Creator[];
  collection?: {
    key: PublicKey;
    verified: boolean;
  };
}

async function mintCompressedNFT(
  umi: Umi,
  treeAddress: PublicKey,
  recipient: PublicKey,
  metadata: CNFTMetadata
): Promise<string> {
  const { signature } = await mintV1(umi, {
    leafOwner: recipient,
    merkleTree: treeAddress,
    metadata: {
      name: metadata.name,
      symbol: metadata.symbol,
      uri: metadata.uri,
      sellerFeeBasisPoints: metadata.sellerFeeBasisPoints,
      creators: metadata.creators.map(c => ({
        address: c.address,
        share: c.share,
        verified: c.verified,
      })),
      collection: metadata.collection ? {
        key: metadata.collection.key,
        verified: metadata.collection.verified,
      } : null,
      uses: null,
      primarySaleHappened: false,
      isMutable: true,
    },
  }).sendAndConfirm(umi);

  return signature;
}

// Batch minting
async function batchMintCNFTs(
  umi: Umi,
  treeAddress: PublicKey,
  mints: { recipient: PublicKey; metadata: CNFTMetadata }[],
  batchSize: number = 5 // Transactions per batch
): Promise<string[]> {
  const signatures: string[] = [];

  for (let i = 0; i < mints.length; i += batchSize) {
    const batch = mints.slice(i, i + batchSize);

    const txPromises = batch.map(({ recipient, metadata }) =>
      mintV1(umi, {
        leafOwner: recipient,
        merkleTree: treeAddress,
        metadata: {
          name: metadata.name,
          symbol: metadata.symbol,
          uri: metadata.uri,
          sellerFeeBasisPoints: metadata.sellerFeeBasisPoints,
          creators: metadata.creators,
          collection: metadata.collection,
          uses: null,
          primarySaleHappened: false,
          isMutable: true,
        },
      }).sendAndConfirm(umi)
    );

    const results = await Promise.all(txPromises);
    signatures.push(...results.map(r => r.signature));

    console.log(`Minted ${Math.min(i + batchSize, mints.length)}/​${mints.length}`);
  }

  return signatures;
}

5. Transferring Compressed NFTs

Transfers require Merkle proofs from an indexer:

import { transfer } from '@metaplex-foundation/​mpl-bubblegum';
import { getAssetWithProof } from '@metaplex-foundation/​mpl-bubblegum';

async function transferCNFT(
  umi: Umi,
  assetId: PublicKey,
  currentOwner: PublicKey,
  newOwner: PublicKey
): Promise<string> {
  // Get asset with proof from indexer (DAS API)
  const assetWithProof = await getAssetWithProof(umi, assetId, {
    truncateCanopy: true,
  });

  // Execute transfer
  const { signature } = await transfer(umi, {
    ...assetWithProof,
    leafOwner: currentOwner,
    newLeafOwner: newOwner,
  }).sendAndConfirm(umi);

  return signature;
}

// Using Helius DAS API directly
async function getAssetProofHelius(
  assetId: string,
  heliusApiKey: string
): Promise<AssetProof> {
  const response = await fetch(
    `https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/​json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetProof',
        params: { id: assetId },
      }),
    }
  );

  const { result } = await response.json();
  return result;
}

6. Querying Compressed NFTs

Using DAS (Digital Asset Standard) API:

// Get all cNFTs by owner
async function getCNFTsByOwner(
  ownerAddress: string,
  heliusApiKey: string
): Promise<Asset[]> {
  const response = await fetch(
    `https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/​json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByOwner',
        params: {
          ownerAddress,
          page: 1,
          limit: 1000,
        },
      }),
    }
  );

  const { result } = await response.json();
  return result.items;
}

// Get cNFTs by collection
async function getCNFTsByCollection(
  collectionAddress: string,
  heliusApiKey: string
): Promise<Asset[]> {
  const response = await fetch(
    `https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/​json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByGroup',
        params: {
          groupKey: 'collection',
          groupValue: collectionAddress,
          page: 1,
          limit: 1000,
        },
      }),
    }
  );

  const { result } = await response.json();
  return result.items;
}

// Search by attributes
async function searchCNFTs(
  heliusApiKey: string,
  params: {
    ownerAddress?: string;
    creatorAddress?: string;
    collectionAddress?: string;
    compressed?: boolean;
  }
): Promise<Asset[]> {
  const response = await fetch(
    `https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/​json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'searchAssets',
        params: {
          ...params,
          compressed: true,
          page: 1,
          limit: 1000,
        },
      }),
    }
  );

  const { result } = await response.json();
  return result.items;
}

7. Collection Management

import { createCollection } from '@metaplex-foundation/​mpl-bubblegum';

// Create collection NFT first (regular NFT)
async function createCollectionNFT(
  umi: Umi,
  metadata: CollectionMetadata
): Promise<PublicKey> {
  const collectionMint = generateSigner(umi);

  await createNft(umi, {
    mint: collectionMint,
    name: metadata.name,
    symbol: metadata.symbol,
    uri: metadata.uri,
    sellerFeeBasisPoints: metadata.sellerFeeBasisPoints,
    isCollection: true,
  }).sendAndConfirm(umi);

  return new PublicKey(collectionMint.publicKey);
}

// Mint cNFT with collection
async function mintWithCollection(
  umi: Umi,
  treeAddress: PublicKey,
  collectionMint: PublicKey,
  collectionAuthority: Keypair,
  recipient: PublicKey,
  metadata: CNFTMetadata
): Promise<string> {
  const { signature } = await mintToCollectionV1(umi, {
    leafOwner: recipient,
    merkleTree: treeAddress,
    collectionMint,
    metadata: {
      name: metadata.name,
      symbol: metadata.symbol,
      uri: metadata.uri,
      sellerFeeBasisPoints: metadata.sellerFeeBasisPoints,
      creators: metadata.creators,
    },
  }).sendAndConfirm(umi);

  return signature;
}

Templates / Playbooks

Tree Size Calculator

function recommendTreeConfig(expectedNFTs: number): TreeConfig {
  // Find minimum depth to fit NFTs
  let maxDepth = Math.ceil(Math.log2(expectedNFTs));

  // Add buffer for future mints (2x capacity)
  maxDepth = Math.max(maxDepth + 1, 14);

  // Cap at practical limits
  maxDepth = Math.min(maxDepth, 30);

  // Buffer size based on expected concurrency
  const maxBufferSize = expectedNFTs < 10000 ? 64 :
                        expectedNFTs < 100000 ? 256 :
                        expectedNFTs < 1000000 ? 1024 : 2048;

  // Canopy depth (higher = smaller proofs but more rent)
  const canopyDepth = Math.min(maxDepth - 3, 17);

  return {
    maxDepth,
    maxBufferSize,
    canopyDepth,
    capacity: Math.pow(2, maxDepth),
  };
}

cNFT Launch Checklist

## cNFT Collection Launch Checklist

### Pre-Launch
- [ ] Collection size determined
- [ ] Tree configuration calculated
- [ ] Tree rent funded
- [ ] Collection NFT created
- [ ] Metadata JSON uploaded to Arweave/​IPFS
- [ ] Metadata URIs generated for all NFTs
- [ ] Helius/​indexer API key obtained

### Tree Creation
- [ ] Tree created with correct config
- [ ] Tree authority set correctly
- [ ] Tree address recorded

### Minting
- [ ] Test mint on devnet
- [ ] Batch minting script tested
- [ ] Error handling in place
- [ ] Progress tracking implemented

### Post-Launch
- [ ] All mints verified via indexer
- [ ] Collection showing on marketplaces
- [ ] Transfer functionality tested
- [ ] Holders can see NFTs in wallets

Metadata Template

{
  "name": "Collection Name #1",
  "symbol": "COL",
  "description": "Description of this NFT",
  "image": "https://arweave.net/​...",
  "animation_url": "https://arweave.net/​...",
  "external_url": "https://your-site.com",
  "attributes": [
    {
      "trait_type": "Background",
      "value": "Blue"
    },
    {
      "trait_type": "Rarity",
      "value": "Legendary"
    }
  ],
  "properties": {
    "files": [
      {
        "uri": "https://arweave.net/​...",
        "type": "image/​png"
      }
    ],
    "category": "image"
  }
}

Common Failure Modes + Debugging

"Tree creation fails"

  • Cause: Insufficient SOL for rent
  • Detection: Transaction error mentions rent
  • Fix: Calculate rent with estimateTreeRent() and ensure payer has enough

"Minting fails after some NFTs"

  • Cause: Tree full or buffer exceeded
  • Detection: "Tree is full" or similar error
  • Fix: Create new tree; ensure depth was sufficient

"Can't find minted cNFTs"

  • Cause: Indexer not synced yet
  • Detection: DAS API returns empty
  • Fix: Wait 10-30 seconds; verify with getSignatureStatuses

"Transfer fails with proof error"

  • Cause: Stale proof (tree updated since fetch)
  • Detection: "Invalid proof" error
  • Fix: Fetch fresh proof immediately before transfer

"cNFTs not showing in wallet"

  • Cause: Wallet doesn't support cNFTs or indexer issue
  • Detection: NFTs visible on Helius but not wallet
  • Fix: Use wallet that supports cNFTs (Phantom, Backpack); check indexer

Quality Bar / Validation

Implementation is complete when:

  • Tree created with appropriate configuration
  • Test mint successful
  • Minted cNFTs queryable via DAS API
  • Transfer works with fresh proofs
  • Collection displays correctly on marketplaces
  • Error handling covers common failures

Output Format

Provide:

  1. Tree configuration: Depth, buffer, canopy with reasoning
  2. Cost estimate: Tree rent + any per-mint costs
  3. Code: Tree creation, minting, and transfer functions
  4. Indexer setup: DAS API queries needed
  5. Testing plan: What to verify before production

Examples

Simple Example: Small PFP Collection

Input: "Create a 5,000 NFT PFP collection with compression"

Output:

// Configuration for 5,000 NFTs
const config = {
  maxDepth: 14,        // Capacity: 16,384 (room to grow)
  maxBufferSize: 64,   // Sufficient for moderate minting
  canopyDepth: 11,     // Reasonable proof sizes
};

// Estimated cost: ~0.5 SOL for tree + ~0.00001 SOL per mint
// Total: ~0.55 SOL for 5,000 NFTs (vs ~10 SOL regular)

// Step 1: Create collection NFT
const collectionMint = await createCollectionNFT(umi, {
  name: 'Cool Cats Collection',
  symbol: 'CATS',
  uri: 'https://arweave.net/​collection-metadata.json',
  sellerFeeBasisPoints: 500, // 5% royalty
});

// Step 2: Create Merkle tree
const treeAddress = await createMerkleTree(connection, payer, config);

// Step 3: Mint all NFTs
const metadataList = generateMetadataForAll(5000); // Your metadata generation
const signatures = await batchMintCNFTs(umi, treeAddress, metadataList);

console.log(`Minted ${signatures.length} cNFTs to tree ${treeAddress}`);

Complex Example: On-Demand Minting Platform

Input: "Build a platform where users mint cNFTs on-demand"

Output: See complete implementation in examples including:

  • Large tree with high buffer (handles concurrent mints)
  • User-pays-fee model
  • Real-time minting API
  • Webhook for mint events
  • Admin dashboard for monitoring tree usage

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View