UNPKG

@pod-protocol/cli

Version:
355 lines 17.4 kB
/** * ZK Compression Commands and Utilities * Handles compressed NFT operations and Merkle tree management */ import { Command } from "commander"; import chalk from 'chalk'; import boxen from 'boxen'; import * as emoji from 'node-emoji'; import { intro, outro, text, select, confirm } from '@clack/prompts'; import { createDASAPI, DASUtils } from '../services/das-api.js'; import { createSafeUmiOperations, loadWalletFromFile, validateTreeConfig, calculateTreeCapacity, calculateTreeStorageCost } from '../utils/wallet-adapter.js'; export const ALL_DEPTH_SIZE_PAIRS = [ { maxDepth: 14, maxBufferSize: 64, description: "Small collection (16K NFTs)", capacity: 16384, estimatedCost: 0.1 }, { maxDepth: 17, maxBufferSize: 64, description: "Medium collection (131K NFTs)", capacity: 131072, estimatedCost: 0.5 }, { maxDepth: 20, maxBufferSize: 256, description: "Large collection (1M NFTs)", capacity: 1048576, estimatedCost: 2.0 }, { maxDepth: 24, maxBufferSize: 1024, description: "Massive collection (16M NFTs)", capacity: 16777216, estimatedCost: 15.0 }, { maxDepth: 26, maxBufferSize: 2048, description: "Enterprise collection (67M NFTs)", capacity: 67108864, estimatedCost: 50.0 }, { maxDepth: 30, maxBufferSize: 2048, description: "Maximum collection (1B+ NFTs)", capacity: 1073741824, estimatedCost: 200.0 } ]; export function getConcurrentMerkleTreeAccountSize(maxDepth, maxBufferSize, canopyDepth) { return calculateTreeStorageCost(maxDepth, maxBufferSize, canopyDepth); } export function createZKCompressionCommands() { const zk = new Command('zk') .alias('compress') .description('ZK compression operations for NFTs and data'); // Create compressed NFT tree zk .command('create-tree') .description('Create a new Merkle tree for compressed NFTs') .option('--depth <number>', 'tree depth (3-30)', '14') .option('--buffer-size <number>', 'buffer size', '64') .option('--canopy-depth <number>', 'canopy depth for faster proof verification', '10') .option('--network <network>', 'target network', 'devnet') .action(async (options) => { intro(`${emoji.get('deciduous_tree')} Creating ZK Compression Merkle Tree`); const maxDepth = parseInt(options.depth); const maxBufferSize = parseInt(options.bufferSize); const canopyDepth = parseInt(options.canopyDepth); const config = { maxDepth, maxBufferSize, canopyDepth, wallet: 'current-wallet' }; const validation = validateTreeConfig(config); if (!validation.valid) { console.error(boxen(`${emoji.get('x')} Invalid Configuration:\n\n` + validation.errors.map(e => `• ${e}`).join('\n'), { padding: 1, borderStyle: 'round', borderColor: 'red', title: ' Configuration Error ' })); return; } const capacity = calculateTreeCapacity(maxDepth); const estimatedCost = calculateTreeStorageCost(maxDepth, maxBufferSize, canopyDepth); console.log(boxen(`${emoji.get('gear')} Tree Configuration:\n\n` + `${emoji.get('chart_with_upwards_trend')} Max Depth: ${chalk.cyan(maxDepth)}\n` + `${emoji.get('package')} Buffer Size: ${chalk.cyan(maxBufferSize)}\n` + `${emoji.get('umbrella')} Canopy Depth: ${chalk.cyan(canopyDepth)}\n` + `${emoji.get('hash')} Capacity: ${chalk.green(capacity.toLocaleString())} NFTs\n` + `${emoji.get('moneybag')} Estimated Cost: ${chalk.yellow(estimatedCost.toFixed(4))} SOL\n\n` + `${emoji.get('globe_with_meridians')} Network: ${chalk.blue(options.network)}`, { padding: 1, borderStyle: 'round', borderColor: 'blue', title: ' Tree Configuration ' })); const shouldCreate = await confirm({ message: 'Create this Merkle tree?', initialValue: false }); if (shouldCreate) { try { const wallet = loadWalletFromFile(); const { operations } = createSafeUmiOperations(wallet, `https://api.${options.network}.solana.com`); const result = await operations.createTree(config); const confirmation = await result.sendAndConfirm(); console.log(boxen(`${emoji.get('white_check_mark')} Tree Created Successfully!\n\n` + `${emoji.get('id')} Tree ID: ${chalk.green(result.publicKey)}\n` + `${emoji.get('link')} Signature: ${chalk.gray(confirmation.signature)}\n` + `${emoji.get('hash')} Capacity: ${chalk.cyan(capacity.toLocaleString())} NFTs\n\n` + `${emoji.get('information_source')} You can now mint compressed NFTs to this tree`, { padding: 1, borderStyle: 'round', borderColor: 'green', title: ' Tree Created ' })); outro(`${emoji.get('white_check_mark')} Merkle tree ready for compressed NFTs!`); } catch (error) { console.error(boxen(`${emoji.get('x')} Failed to create tree:\n\n${error.message}`, { padding: 1, borderStyle: 'round', borderColor: 'red', title: ' Error ' })); } } }); // Mint compressed NFT zk .command('mint') .description('Mint a compressed NFT') .option('--tree <treeId>', 'Merkle tree ID') .option('--name <name>', 'NFT name') .option('--symbol <symbol>', 'NFT symbol') .option('--uri <uri>', 'metadata URI') .option('--to <address>', 'recipient address') .action(async (options) => { intro(`${emoji.get('art')} Minting Compressed NFT`); let treeId = options.tree; let name = options.name; let symbol = options.symbol; let uri = options.uri; let recipient = options.to; if (!treeId) { treeId = await text({ message: 'Enter Merkle tree ID:', placeholder: 'tree_abc123...' }); } if (!name) { name = await text({ message: 'Enter NFT name:', placeholder: 'My Compressed NFT' }); } if (!symbol) { symbol = await text({ message: 'Enter NFT symbol:', placeholder: 'CNFT' }); } if (!uri) { uri = await text({ message: 'Enter metadata URI:', placeholder: 'https://example.com/metadata.json' }); } if (!recipient) { const wallet = loadWalletFromFile(); recipient = wallet.address.toString(); } try { const wallet = loadWalletFromFile(); const { operations } = createSafeUmiOperations(wallet, 'https://api.devnet.solana.com'); const mintConfig = { leafOwner: recipient, merkleTree: treeId, metadata: { name, symbol, uri, sellerFeeBasisPoints: 500 } }; const result = await operations.mintV1(mintConfig); const confirmation = await result.sendAndConfirm(); console.log(boxen(`${emoji.get('white_check_mark')} Compressed NFT Minted!\n\n` + `${emoji.get('art')} Name: ${chalk.cyan(name)}\n` + `${emoji.get('label')} Symbol: ${chalk.cyan(symbol)}\n` + `${emoji.get('link')} URI: ${chalk.blue(uri)}\n` + `${emoji.get('deciduous_tree')} Tree: ${chalk.gray(treeId)}\n` + `${emoji.get('bust_in_silhouette')} Owner: ${chalk.gray(recipient)}\n` + `${emoji.get('link')} Signature: ${chalk.gray(confirmation.signature)}\n\n` + `${emoji.get('moneybag')} Cost: ~0.000001 SOL (99% savings vs regular NFT)`, { padding: 1, borderStyle: 'round', borderColor: 'green', title: ' NFT Minted ' })); outro(`${emoji.get('white_check_mark')} Compressed NFT successfully minted!`); } catch (error) { console.error(boxen(`${emoji.get('x')} Failed to mint NFT:\n\n${error.message}`, { padding: 1, borderStyle: 'round', borderColor: 'red', title: ' Error ' })); } }); // Transfer compressed NFT zk .command('transfer') .description('Transfer a compressed NFT') .option('--asset <assetId>', 'asset ID to transfer') .option('--to <address>', 'recipient address') .action(async (options) => { intro(`${emoji.get('outbox_tray')} Transferring Compressed NFT`); let assetId = options.asset; let recipient = options.to; if (!assetId) { assetId = await text({ message: 'Enter asset ID to transfer:', placeholder: 'asset_abc123...' }); } if (!recipient) { recipient = await text({ message: 'Enter recipient address:', placeholder: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM' }); } try { const wallet = loadWalletFromFile(); const { operations } = createSafeUmiOperations(wallet, 'https://api.devnet.solana.com'); // Get asset with proof const assetWithProof = await operations.getAssetWithProof(assetId); const transferConfig = { assetWithProof, leafOwner: wallet.address.toString(), newLeafOwner: recipient }; const result = await operations.transfer(transferConfig); const confirmation = await result.sendAndConfirm(); console.log(boxen(`${emoji.get('white_check_mark')} Transfer Complete!\n\n` + `${emoji.get('package')} Asset: ${chalk.cyan(assetId)}\n` + `${emoji.get('point_right')} To: ${chalk.cyan(recipient)}\n` + `${emoji.get('link')} Signature: ${chalk.gray(confirmation.signature)}\n\n` + `${emoji.get('moneybag')} Cost: ~0.0001 SOL (minimal gas fees)`, { padding: 1, borderStyle: 'round', borderColor: 'green', title: ' Transfer Complete ' })); outro(`${emoji.get('white_check_mark')} Compressed NFT transferred successfully!`); } catch (error) { console.error(boxen(`${emoji.get('x')} Failed to transfer NFT:\n\n${error.message}`, { padding: 1, borderStyle: 'round', borderColor: 'red', title: ' Error ' })); } }); // List compressed NFTs zk .command('list') .description('List compressed NFTs by owner') .option('--owner <address>', 'owner address') .option('--tree <treeId>', 'filter by specific tree') .option('--limit <number>', 'number of NFTs to show', '20') .action(async (options) => { intro(`${emoji.get('scroll')} Listing Compressed NFTs`); let owner = options.owner; if (!owner) { const wallet = loadWalletFromFile(); owner = wallet.address.toString(); } try { const dasAPI = createDASAPI('https://devnet.helius-rpc.com', true); const response = await dasAPI.getAssetsByOwner(owner, { limit: parseInt(options.limit) }); const compressedAssets = DASUtils.filterCompressedAssets(response.items); if (compressedAssets.length === 0) { console.log(boxen(`${emoji.get('empty_nest')} No compressed NFTs found for this owner.\n\n` + `${emoji.get('bulb')} Try minting some with: pod zk mint`, { padding: 1, borderStyle: 'round', borderColor: 'yellow', title: ' No NFTs Found ' })); return; } const groupedByTree = DASUtils.groupAssetsByTree(compressedAssets); const collections = DASUtils.extractCollections(compressedAssets); console.log(boxen(`${emoji.get('art')} Compressed NFTs for: ${chalk.cyan(owner)}\n\n` + `${emoji.get('1234')} Total NFTs: ${chalk.green(compressedAssets.length)}\n` + `${emoji.get('deciduous_tree')} Trees Used: ${chalk.blue(Object.keys(groupedByTree).length)}\n` + `${emoji.get('package')} Collections: ${chalk.magenta(collections.length)}\n\n` + `${emoji.get('scroll')} Recent NFTs:\n` + compressedAssets.slice(0, 5).map(asset => ` • ${chalk.cyan(asset.content.metadata.name)} (${asset.content.metadata.symbol})`).join('\n') + '\n\n' + `${emoji.get('information_source')} Use --limit to see more NFTs`, { padding: 1, borderStyle: 'round', borderColor: 'blue', title: ' Compressed NFTs ' })); outro(`${emoji.get('white_check_mark')} Found ${compressedAssets.length} compressed NFTs`); } catch (error) { console.error(boxen(`${emoji.get('x')} Failed to list NFTs:\n\n${error.message}`, { padding: 1, borderStyle: 'round', borderColor: 'red', title: ' Error ' })); } }); // Calculate savings zk .command('savings') .description('Calculate storage cost savings for compressed NFTs') .option('--count <number>', 'number of NFTs to calculate for', '10000') .action(async (options) => { const nftCount = parseInt(options.count); const savings = DASUtils.calculateStorageSavings(nftCount); console.log(boxen(`${emoji.get('money_with_wings')} ZK Compression Savings Analysis\n\n` + `${emoji.get('1234')} NFT Count: ${chalk.cyan(nftCount.toLocaleString())}\n\n` + `${emoji.get('moneybag')} Regular NFT Cost: ${chalk.red(savings.regular_cost)}\n` + `${emoji.get('sparkles')} Compressed Cost: ${chalk.green(savings.compressed_cost)}\n` + `${emoji.get('chart_with_downwards_trend')} Total Savings: ${chalk.green(savings.savings)}\n` + `${emoji.get('rocket')} Cost Multiplier: ${chalk.yellow(savings.multiplier)} cheaper\n\n` + `${emoji.get('earth_americas')} Environmental Impact: 99% less storage\n` + `${emoji.get('zap')} Network Efficiency: Massive throughput improvement`, { padding: 1, borderStyle: 'double', borderColor: 'green', title: ' Savings Calculator ' })); }); // Tree recommendations zk .command('recommend') .description('Get tree configuration recommendations') .option('--nfts <number>', 'expected number of NFTs') .action(async (options) => { intro(`${emoji.get('bulb')} ZK Compression Tree Recommendations`); let expectedNFTs = options.nfts; if (!expectedNFTs) { expectedNFTs = await select({ message: 'How many NFTs do you plan to mint?', options: [ { value: '1000', label: '1,000 NFTs (Small collection)' }, { value: '10000', label: '10,000 NFTs (Medium collection)' }, { value: '100000', label: '100,000 NFTs (Large collection)' }, { value: '1000000', label: '1,000,000+ NFTs (Massive collection)' } ] }); } const nftCount = parseInt(expectedNFTs); const suitableConfigs = ALL_DEPTH_SIZE_PAIRS.filter(pair => pair.capacity >= nftCount).slice(0, 3); console.log(boxen(`${emoji.get('deciduous_tree')} Recommended Tree Configurations for ${chalk.cyan(nftCount.toLocaleString())} NFTs:\n\n` + suitableConfigs.map((config, index) => `${emoji.get('white_check_mark')} Option ${index + 1}: ${config.description}\n` + ` Depth: ${config.maxDepth}, Buffer: ${config.maxBufferSize}\n` + ` Capacity: ${chalk.green(config.capacity.toLocaleString())} NFTs\n` + ` Cost: ~${chalk.yellow(config.estimatedCost)} SOL`).join('\n\n') + '\n\n' + `${emoji.get('bulb')} Recommendation: Start with Option 1 for most use cases\n` + `${emoji.get('information_source')} You can always create additional trees later`, { padding: 1, borderStyle: 'round', borderColor: 'blue', title: ' Tree Recommendations ' })); outro(`${emoji.get('white_check_mark')} Use these configurations with 'pod zk create-tree'`); }); return zk; } //# sourceMappingURL=zk-compression.js.map