All articles
solanastate-compressionnftmerkle

State compression and cNFTs on Solana: when 99% cost reduction actually happens

Compressed NFTs save up to 99% on mint cost using Merkle trees. Here's the actual mechanism, the numbers, and when the trade-offs aren't worth it.

Solana storage costs money. Every account pays rent-exempt SOL proportional to its size — small for one account, ruinous when you want to mint a million NFTs and put each one in its own account. A traditional Metaplex NFT mint costs ~0.012 SOL in rent. At a million units, that's 12,000 SOL.

State compression is the trick that makes that million-mint use case affordable. Instead of one account per NFT, you store all of them inside a single on-chain Merkle tree, and keep the actual data off-chain. The tree only stores hashes.

The result: ~99% reduction in storage cost. A million cNFTs costs in the tens of SOL, not thousands.

The mechanism in two parts

On-chain: a concurrent Merkle tree account. Stores the tree's root hash, depth, max buffer size, and recent change log. The root hash represents the entire current state of every leaf in the tree.

Off-chain: the actual leaf data (NFT metadata, owner address, mint info) lives in transaction history. Anyone who indexes Solana history can reconstruct any leaf and prove its inclusion in the on-chain root.

When you mint a cNFT, you don't create an account — you append a leaf to the tree. The tree's root updates. The metadata is in the transaction logs, indexable forever.

Tree sizing math

A tree has three configurable parameters: maxDepth, maxBufferSize, and canopyDepth. Two of them are about how big the tree can get; the third is the consequential one.

Tree capacity = 2^maxDepth. So maxDepth=14 holds 16,384 leaves. maxDepth=20 holds ~1M. maxDepth=30 holds ~1B (the practical max).

maxBufferSize controls how many concurrent writes the tree handles before proofs go stale. Higher = more writes per slot, more storage cost.

canopyDepth is the gotcha. To prove a leaf belongs in the tree, you need a proof path from the leaf to the root. The deeper the canopy, the shorter the proof clients need to supply. Without canopy, every transfer requires you to attach the full proof (e.g. 30 hashes for depth-30) as additional instruction data. That blows the 1232-byte transaction limit.

Practical canopyDepth recommendations:

  • Depth 14 (16k leaves): canopy 10. Costs ~0.05 SOL.
  • Depth 20 (1M leaves): canopy 14. Costs ~0.4 SOL.
  • Depth 30 (1B leaves): canopy 17. Costs ~3 SOL.

The reader-side cost

Here's the trade-off that gets glossed over in "99% cost reduction" headlines:

Reading a cNFT is no longer a simple getAccountInfo. The data lives in transaction history, which means clients need a DAS-API indexer (Helius, Triton, QuickNode all provide this) to query cNFTs by owner, by collection, by mint, etc.

typescript
// Read cNFTs by owner via Helius DAS
const res = await fetch(`https://mainnet.helius-rpc.com/?api-key=${KEY}`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "getAssetsByOwner",
    params: {
      ownerAddress: "...",
      page: 1,
      limit: 100,
    },
  }),
})
const { result: { items } } = await res.json()

You don't need this for regular NFTs — a plain getProgramAccounts works. With cNFTs, the indexer is load-bearing infrastructure for your app, and you're paying the indexer's monthly bill in addition to the network savings.

The breakeven

Rough rule: compress when you'd mint >1000 NFTs from the same logical project. Below that threshold, the savings don't justify the indexer dependency, the more complex client code, or the transfer-proof gymnastics.

Concrete examples:

  • Airdrop 1M loyalty NFTs: compress. Saves ~12k SOL.
  • Mint 10k PFP collection: borderline. Compress if you expect <5% trade volume; traditional if you expect heavy marketplace activity (transfer proof overhead matters more).
  • Mint 50 limited-edition NFTs: regular Metaplex. The savings would be ~0.5 SOL — not worth the trade-off.
  • Ticketing for a 100k-person event: compress. Tickets are read-once, transferred rarely, retired quickly.
  • Account-bound credential (one per user, never transferred): consider Token-2022 NonTransferable instead — cheaper per-account semantics without the tree.

What state compression doesn't do

  • It doesn't compress fungible tokens. Token-2022 mints are still one account per holder. The compression framework is currently NFT-shaped (one-of-N, owner-tracked leaves).
  • It doesn't make transfers cheaper. A cNFT transfer still costs the standard fee, plus the compute for the Merkle proof verification. It's only the creation that's ~99% cheaper.
  • It doesn't obscure data. cNFT data is in public transaction logs, indexed by anyone who runs DAS-API. Confidentiality requires the separate Token-2022 confidential transfer extension.

References

State compression isn't a default — it's a specific optimisation that pays off massively at scale and barely at all otherwise. Use the scale check above before you compress anything.

State compression and cNFTs on Solana: when 99% cost reduction actually happens | devrels.xyz