You price a 500,000 asset drop on Token Metadata and the rent line kills the plan.
Someone says "compress them" and the quote drops to a few SOL.
The official cost tables put a million of them near 5 SOL in total. The catch is operational.
Cheap assets change how you read, transfer, and support them. Run this checklist before you pick a tree.
When Compression Is the Right Call
Start with per-asset rent, straight from the official docs:
- Core: about 0.003 SOL per mint.
- Token Metadata, by the same page: about 0.022 SOL per mint.
- Compressed, by the Bubblegum cost table: about 0.0000053 SOL each at one million assets, or 5.31 SOL in total.
Per asset, that's over 99% cheaper than Core by those two pages. Core and Token Metadata put each asset in on-chain accounts that pay rent forever. A compressed asset is a hash stored as a leaf in an on-chain tree. The full data lives in the transaction log, and an indexer serves it back.
Run the figures against a 100,000 asset project. Core lands near 300 SOL in rent and Token Metadata near 2,200 SOL. The same cost table puts 100,000 compressed assets under 1 SOL.
So why doesn't everyone compress? You give up three things:
- Direct reads. No account exists to fetch. Every lookup goes through a DAS indexer.
- Simple transfers. Every write needs a proof fetched moments before.
- Broad support. Some wallets and markets lag on the newest compressed standard.
Our rule, with the Hashlings mint and staking experience as the reference case: under a few thousand assets that must trade on every market, use Core. Tens of thousands or more, or anything free like tickets, loyalty, or game items, compress.
Need a second opinion on your mix? Our Solana development service covers both paths.
Size the Tree Before You Mint
A Bubblegum tree has three settings you choose once. The create trees page is blunt: "Tree parameters (max depth, max buffer size, canopy depth) are immutable after creation."
Max depth sets capacity at two to the power of the depth. Depth 14 holds 16,384 leaves and depth 20 holds 1,048,576.
Max buffer size is the concurrency limit. The concurrent tree docs explain that the program fast-forwards stale proofs through a change log.
When more writes land in a block than the buffer allows, the extras fail and must retry.
Canopy depth caches the top levels of the tree on chain. The canopy page states the trade: with a canopy of n levels on a depth d tree, a proof shrinks to d minus n nodes of 32 bytes each. More canopy means more rent and smaller transactions.
The recommended settings table on the create trees page gives you anchors:
- Depth 14, canopy 8, buffer 64: about 0.34 SOL for 16,384 slots.
- Depth 20, canopy 13, buffer 1024: about 8.5 SOL for 1,048,576 slots.
The samples target the versions pinned below, the latest on the package registry as of 2026-10-27.
npm install @metaplex-foundation/mpl-bubblegum@6.0.0 \
@metaplex-foundation/umi-bundle-defaults@1.6.0 \
@metaplex-foundation/digital-asset-standard-api@2.1.1import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import { generateSigner } from '@metaplex-foundation/umi';
import { createTreeV2, mplBubblegum } from '@metaplex-foundation/mpl-bubblegum';
// Use an RPC that supports DAS. Public endpoints do not index compressed assets.
const umi = createUmi('https://YOUR_DAS_RPC').use(mplBubblegum());
// Attach a signer from your key service here, never from a file in the repo.
const merkleTree = generateSigner(umi);
const builder = await createTreeV2(umi, {
merkleTree,
maxDepth: 14, // 16,384 leaves
maxBufferSize: 64, // 64 concurrent writes per block
});
await builder.sendAndConfirm(umi);Create the tree on a test cluster first and read the rent it charged.
Mint With Bubblegum V2
Metaplex now labels Bubblegum v1 as legacy. The V2 overview adds Core collections, freeze and thaw, non-transferable assets, and royalty enforcement. V1 trees and V2 leaves don't mix, so pick V2 for any new tree.
Minting appends a leaf. The tree stores its rightmost proof on chain, so a mint needs no proof from you. That's why minting is the cheap, parallel part.
The mint page shows the shape. To mint into a Core collection, pass the collection address and sign as its authority.
import { publicKey, some } from '@metaplex-foundation/umi';
import {
mintV2,
parseLeafFromMintV2Transaction,
} from '@metaplex-foundation/mpl-bubblegum';
const coreCollection = publicKey('YOUR_CORE_COLLECTION');
const recipient = publicKey('RECIPIENT_WALLET');
const { signature } = await mintV2(umi, {
merkleTree: merkleTree.publicKey,
leafOwner: recipient,
coreCollection,
collectionAuthority: umi.identity,
metadata: {
name: 'Season Pass #1',
uri: 'https://example.com/pass-1.json',
sellerFeeBasisPoints: 500, // 5%, or omit to inherit from the collection
collection: some(coreCollection), // must match coreCollection
creators: [{ address: umi.identity.publicKey, verified: false, share: 100 }],
},
}).sendAndConfirm(umi, { confirm: { commitment: 'finalized' } });
// Parse only after finalized, not confirmed. The leaf id is the asset id.
const leaf = await parseLeafFromMintV2Transaction(umi, signature);
console.log('asset id', leaf.id);Two notes from the docs. Omit the seller fee on a collection mint and the leaf stores a sentinel, so the collection's royalty applies, but the creators list must then be empty. The asset ID comes from the tree address plus the leaf index, so store both with your order record.
For a drop of 100,000 assets, batch mints across several signers and watch for buffer-size failures. A retry queue with back-off is not optional here.
Read Assets With the DAS API
Nothing about a compressed asset sits in an account you can fetch directly. The Digital Asset Standard (DAS) API is a JSON-RPC extension that indexers expose. The Bubblegum docs are plain about it: "not all RPCs support the DAS API."
Three methods cover most product work. The field names below come from the official DAS client.
- getAssetsByOwner returns a wallet's assets, compressed or not, up to 1,000 per page.
- getAsset returns one asset by ID, including a compression block with the tree address, leaf ID, and data hash.
- getAssetProof returns the root, the proof path, the node index, and the tree ID for a transfer or burn.
import { dasApi } from '@metaplex-foundation/digital-asset-standard-api';
umi.use(dasApi());
// Inventory screen: page through a wallet.
const page1 = await umi.rpc.getAssetsByOwner({
owner: publicKey('USER_WALLET'),
limit: 1000,
page: 1,
});
const passes = page1.items.filter(
(a) => a.compression.compressed && a.grouping.some(
(g) => g.group_key === 'collection' && g.group_value === coreCollection,
),
);
// Detail screen: one asset.
const asset = await umi.rpc.getAsset(leaf.id);
console.log(asset.ownership.owner, asset.content.metadata.name);
// Before a write: the proof, fetched at the last moment.
const proof = await umi.rpc.getAssetProof(leaf.id);
console.log(proof.root, proof.proof.length, proof.node_index);Both owner and asset responses carry a last indexed slot field. Log it. When support asks why a user's pass "disappeared", the slot tells you whether the indexer was behind or the asset moved.
Pagination is 1-based and capped at 1,000 per page on Helius. For wallets with many assets, use the before and after cursors instead of page numbers, and never assume the grand total is cheap to compute.
Transfers Need a Proof
A transfer replaces a leaf. The program must verify the old leaf against the current root, so you send the proof nodes as extra accounts. The transfer page wraps the fetch in one helper.
import { getAssetWithProof, transferV2 } from '@metaplex-foundation/mpl-bubblegum';
// Fetch the proof right before you build the transaction, not earlier.
const assetWithProof = await getAssetWithProof(umi, leaf.id, {
truncateCanopy: true, // drop the nodes the on-chain canopy already holds
});
await transferV2(umi, {
...assetWithProof,
authority: umi.identity, // current leaf owner or delegate
newLeafOwner: publicKey('NEW_OWNER'),
coreCollection, // required when the asset is in a collection
}).sendAndConfirm(umi);Size is the limit to respect. Solana caps a transaction at 1,232 bytes. The create trees page notes that only 17 proof accounts fit in one transaction, and the canopy page says trees deeper than 24 need a canopy for that reason.
That's where canopy depth earns its rent. A depth 20 tree with no canopy sends 640 proof bytes per transfer. With canopy 14 it sends 192 bytes, leaving room for a marketplace or escrow instruction in the same transaction.
If your product only transfers through your own backend, a small canopy is fine. If third-party programs must compose with your assets, pay for the deeper canopy up front. You can't add it later.
Six Operational Gotchas
These are the ones that page an engineer at night.
- Indexer dependence. Your app is down when your DAS provider is down. Keep a second RPC that supports DAS configured and health checked.
- Stale proofs. Any write to the tree changes the root. The buffer fast-forwards recent proofs, but a proof fetched minutes ago can fail. Fetch, build, and send in one tight loop.
- Indexer lag. A finalized transfer can take a moment to show in the owner query. Read the last indexed slot and show "pending" instead of "gone".
- Immutable tree settings. Depth, buffer, and canopy are locked at creation. Plan for the collection you'll have in two years, not the drop next week.
- Wallet and market support. The V2 overview says three major wallets, Phantom among them, display V2 assets but don't transfer them yet. It also says Magic Eden and Tensor don't support V2 yet. Check that page again before launch, because it changes.
- Protocol fees. Some Bubblegum instructions charge a protocol fee. The amounts can change, so read the live page and build them into your per-asset budget.
One more that's less about code than ownership. The off-chain data sits in transaction history and indexer databases. If you rotate RPC vendors, confirm the new one has indexed your tree from creation.
We fold these into the web development side of a build too: inventory pages, transfer flows, and support tooling that read the indexed slot instead of guessing.
FAQ
How much does a compressed NFT cost to mint on Solana?
About five millionths of a SOL per asset at a million assets, by the official cost table, plus the tree rent you pay once. A depth 14 tree for 16,384 assets runs about a third of a SOL by the same docs. Protocol fees may apply on top for some instructions.
Can a compressed NFT be converted to a regular NFT?
Only on Bubblegum v1. The v1 decompress page describes redeeming a leaf for a voucher and turning it into a Token Metadata NFT, which then pays rent. The V2 FAQ states that V2 assets remain compressed. If some assets must become regular NFTs later, mint those on Core from the start.
Do I need Helius for the DAS API?
No. You need any RPC that implements the DAS methods, and the Bubblegum docs link to the current list of providers. Whatever you pick, configure a second provider, so an indexer outage doesn't take your inventory screen down.
Why does my compressed NFT transfer fail with a transaction too large error?
The proof doesn't fit in the 1,232 byte limit alongside your other instructions. Pass the truncate canopy option to the proof helper, so you only send the nodes the canopy doesn't hold. If that's not enough, the tree's canopy is too shallow for your use, and the only fix is a new tree.
Should I use Bubblegum V1 or V2 for a new project?
V2. Metaplex labels V1 as legacy and recommends V2 for new trees, which adds Core collections, freeze, and royalty enforcement. The trade-off today is wallet and marketplace support, so check the V2 overview for the current list before you commit.
Price Your Drop With Real Numbers
Compression is a cost decision with an operating bill attached. Get the tree settings right once and the per-asset price stays near zero. Get them wrong, and you're minting a second tree six months in.
If you're choosing between Core and compression for thousands or millions of assets, talk to us. We'll run your numbers on a test cluster and show you the proof sizes. Then try the tree settings yourself before you spend a single SOL on rent.



