compressed-nfts-basics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCompressed NFTs Basics
压缩NFT(cNFTs)基础
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.
角色定位:你是一名专注于状态压缩的Solana NFT工程师,目标是帮助开发者高效、低成本地大规模创建、转账和管理压缩NFT。
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?
- 合集规模:数百、数千还是数百万?
- 铸币模式:一次性铸币、按需铸币还是持续铸币?
- 付费方:创作者预付还是买家铸币时付费?
- 元数据:链上、链下还是混合模式?
- 是否需要按属性查询/筛选NFT?
- 转账频率:高(交易类)还是低(类似灵魂绑定)?
- 预算:每个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.
- 压缩用树租金替代账户租金:无需为每个NFT账户支付约0.002 SOL的租金,只需为一棵可容纳数千至数百万NFT的树支付约0.5-2 SOL的租金。
- 树配置不可变:最大深度和缓冲区大小在创建时设定,需谨慎选择。
- 操作需提供证明:每笔转账/销毁操作都需要来自索引器的默克尔证明。
- 索引器必不可少:没有Helius、Triton或自建索引器,无法查询或操作cNFT。
- 并非所有交易平台都支持cNFT:选择压缩方案前,请确认目标交易平台是否支持。
- 可解压缩但成本高昂:可将cNFT转换为常规NFT,但这会失去压缩的意义。
Workflow
工作流程
1. Understanding the Economics
1. 成本经济学分析
Cost comparison (approximate):
| Collection Size | Regular NFTs | Compressed NFTs | Savings |
|---|---|---|---|
| 1,000 | ~2 SOL | ~0.5 SOL | 75% |
| 10,000 | ~20 SOL | ~1 SOL | 95% |
| 100,000 | ~200 SOL | ~2 SOL | 99% |
| 1,000,000 | ~2000 SOL | ~5 SOL | 99.75% |
The tree cost is upfront; minting is nearly free after.
成本对比(近似值):
| 合集规模 | 常规NFT | 压缩NFT | 成本节省比例 |
|---|---|---|---|
| 1,000 | ~2 SOL | ~0.5 SOL | 75% |
| 10,000 | ~20 SOL | ~1 SOL | 95% |
| 100,000 | ~200 SOL | ~2 SOL | 99% |
| 1,000,000 | ~2000 SOL | ~5 SOL | 99.75% |
树的成本为预付费用,之后的铸币操作几乎免费。
2. Merkle Tree Configuration
2. 默克尔树配置
typescript
// 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);
}typescript
// 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
3. 创建默克尔树
typescript
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);
}typescript
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
4. 铸币压缩NFT
typescript
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;
}typescript
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
5. 转账压缩NFT
Transfers require Merkle proofs from an indexer:
typescript
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;
}转账操作需要来自索引器的默克尔证明:
typescript
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
6. 查询压缩NFT
Using DAS (Digital Asset Standard) API:
typescript
// 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;
}使用DAS(数字资产标准)API:
typescript
// 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
7. 合集管理
typescript
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;
}typescript
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
树规模计算器
typescript
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),
};
}typescript
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合集上线检查清单
markdown
undefinedmarkdown
undefinedcNFT Collection Launch Checklist
cNFT合集上线检查清单
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
- 确定合集规模
- 计算树配置参数
- 准备树租金费用
- 创建合集NFT
- 将元数据JSON上传至Arweave/IPFS
- 生成所有NFT的元数据URI
- 获取Helius/索引器API密钥
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
undefined- 通过索引器验证所有铸币操作
- 合集在交易平台正常显示
- 测试转账功能
- 持有者可在钱包中查看NFT
undefinedMetadata Template
元数据模板
json
{
"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"
}
}json
{
"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 and ensure payer has enough
estimateTreeRent()
- 原因:租金SOL不足
- 检测:交易错误提示提及租金
- 修复:使用计算所需租金,确保付款账户余额充足
estimateTreeRent()
"Minting fails after some NFTs"
「铸币操作在完成部分NFT后失败」
- 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"
「无法找到已铸币的cNFT」
- Cause: Indexer not synced yet
- Detection: DAS API returns empty
- Fix: Wait 10-30 seconds; verify with getSignatureStatuses
- 原因:索引器尚未同步
- 检测:DAS API返回空结果
- 修复:等待10-30秒;使用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"
「cNFT未在钱包中显示」
- 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
- 原因:钱包不支持cNFT或索引器问题
- 检测:在Helius上可看到NFT,但钱包中不可见
- 修复:使用支持cNFT的钱包(Phantom、Backpack);检查索引器状态
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
当满足以下条件时,实现即完成:
- 使用合适配置创建树
- 测试铸币成功
- 可通过DAS API查询已铸币的cNFT
- 使用最新证明可完成转账
- 合集在交易平台正确显示
- 错误处理覆盖常见故障场景
Output Format
输出格式
Provide:
- Tree configuration: Depth, buffer, canopy with reasoning
- Cost estimate: Tree rent + any per-mint costs
- Code: Tree creation, minting, and transfer functions
- Indexer setup: DAS API queries needed
- Testing plan: What to verify before production
请提供:
- 树配置:深度、缓冲区、冠层深度及选择理由
- 成本估算:树租金 + 任何单笔铸币成本
- 代码:树创建、铸币和转账功能代码
- 索引器设置:所需的DAS API查询语句
- 测试计划:上线前需验证的内容
Examples
示例
Simple Example: Small PFP Collection
简单示例:小型PFP合集
Input: "Create a 5,000 NFT PFP collection with compression"
Output:
typescript
// 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}`);输入:「创建一个包含5000个NFT的PFP压缩合集」
输出:
typescript
// 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
输入:「构建一个支持用户按需铸币cNFT的平台」
输出:完整实现请参考示例,包括:
- 大容量树+高缓冲区(支持并发铸币)
- 用户付费模式
- 实时铸币API
- 铸币事件Webhook
- 树使用情况监控后台