Bubblegum — TreeConfig & Leaf Schema
How compressed NFTs work — a small TreeConfig account governs a Merkle tree, and each NFT is a hashed leaf (LeafSchema) rather than its own account. Millions of NFTs for the cost of a few accounts.
What it is
Compressed NFTs (cNFTs) don’t get their own accounts. Instead, Metaplex Bubblegum stores each NFT as a hashed leaf in a Merkle tree, and a single small TreeConfig account governs the whole tree. A tree holding a million NFTs costs a few accounts’ worth of rent instead of a million — the core trick behind cheap, mass-scale NFT minting.
Why it exists
A normal NFT needs a mint, a token account, and a metadata account — too expensive for airdrops of millions. State compression hashes NFT data into a concurrent Merkle tree (stored off-chain, with only the 32-byte root and a change log on-chain). Ownership and metadata are proven against the root via Merkle proofs, so per-NFT on-chain storage drops to near zero.
Byte layout
TreeConfig — the per-tree authority account (an Anchor account, so it starts with an 8-byte discriminator):
| Offset | Length | Field | Type | Notes |
|---|---|---|---|---|
| 0 | 8 | discriminator | [u8; 8] |
Anchor account discriminator. |
| 8 | 32 | tree_creator |
Pubkey |
Who created the tree. |
| 40 | 32 | tree_delegate |
Pubkey |
Authorized to mint into the tree. |
| 72 | 8 | total_mint_capacity |
u64 LE |
Max leaves (set by tree depth). |
| 80 | 8 | num_minted |
u64 LE |
Leaves minted so far. |
| 88 | 1 | is_public |
bool |
Whether anyone may mint. |
| 89 | 1 | is_decompressible |
u8 enum |
Whether leaves can be decompressed to real NFTs. |
| 90 | 1 | version |
u8 enum |
Tree schema version. |
LeafSchema V1 — the per-NFT data that gets keccak-hashed into one 32-byte tree node (it is not stored as an account):
| Field | Type | Notes |
|---|---|---|
id |
Pubkey |
The asset id — a PDA from the tree + nonce. |
owner |
Pubkey |
Current owner. |
delegate |
Pubkey |
Approved delegate, if any. |
nonce |
u64 |
Leaf index / uniqueness nonce. |
data_hash |
[u8; 32] |
Hash of the NFT’s metadata. |
creator_hash |
[u8; 32] |
Hash of the creators array. |
The program computes keccak(id, owner, delegate, nonce, data_hash, creator_hash) → the 32-byte leaf node stored in the tree.
Where you see it
Large airdrops, gaming items, and any high-volume NFT collection (Drip, Helium, etc.). You don’t fetch a cNFT by account — you query an indexer (the DAS API) that reconstructs it from the tree and serves a Merkle proof.
Common gotchas
- The NFT has no account. You can’t
getAccountInfoa cNFT. Ownership lives as a hash in the tree; transfers update the leaf and the tree root. Tooling that assumes one-account-per-NFT doesn’t work. - You need a Merkle proof to act on one. Transferring or burning a cNFT requires submitting the leaf data plus a proof path against the current root — usually fetched from a DAS-API provider, not derived locally.
- The on-chain tree account is separate from TreeConfig. TreeConfig (this account) holds authority/counters; the actual concurrent Merkle tree (root + change log) lives in a separate account owned by the SPL Account Compression program.
data_hash/creator_hashare commitments, not the data. The real metadata lives off-chain; only its hash is on-chain. Verifying an NFT’s metadata means re-hashing the off-chain data and checking it matches.
See also
Last verified: 2026-05-20