English
Implement exclusive minting with Merkle tree-based whitelists in CreateKit
import { WhitelistManager } from '@b3dotfun/basemint' // Define whitelisted addresses const whitelistedAddresses = [ { address: "0x1234567890123456789012345678901234567890" }, { address: "0x2345678901234567890123456789012345678901" }, { address: "0x3456789012345678901234567890123456789012" }, { address: "0x4567890123456789012345678901234567890123" }, { address: "0x5678901234567890123456789012345678901234" } ] // Create whitelist manager const whitelist = new WhitelistManager(whitelistedAddresses) // Get Merkle root for contract deployment const merkleRoot = whitelist.getRoot() console.log(`Merkle root: ${merkleRoot}`) // Verify the whitelist is constructed correctly console.log(`Whitelist contains ${whitelistedAddresses.length} addresses`)
// Whitelist entries can include additional metadata const advancedWhitelist = [ { address: "0x1234567890123456789012345678901234567890", allocation: 5, // Max 5 tokens for this address tier: "gold" }, { address: "0x2345678901234567890123456789012345678901", allocation: 3, tier: "silver" }, { address: "0x3456789012345678901234567890123456789012", allocation: 1, tier: "bronze" } ] // Create whitelist (only addresses are used for Merkle tree) const whitelist = new WhitelistManager( advancedWhitelist.map(entry => ({ address: entry.address })) ) // Store metadata separately for application logic const allocationMap = new Map( advancedWhitelist.map(entry => [entry.address, entry.allocation]) )
import { CollectionManager } from '@b3dotfun/basemint' // Create collection with whitelist enabled const whitelistCollection = { name: "Exclusive Collection", symbol: "EXCL", creator: account.address, gameOwner: account.address, // Whitelist configuration isWhitelistEnabled: true, whitelistMerkleRoot: merkleRoot, // Collection settings maxSupply: 1000n, mintPrice: parseEther("0.01"), maxPerWallet: 3n, // Optional: Combine with time-based access startTime: BigInt(Math.floor(Date.now() / 1000) + 3600), // Whitelist starts in 1 hour endTime: BigInt(Math.floor(Date.now() / 1000) + 86400 * 7), // Ends in 7 days } // Generate signatures and deploy const creatorSignature = await collectionManager.generateCreatorSignature( walletClient, whitelistCollection ) const predictedAddress = collectionManager.predictCollectionAddress( whitelistCollection, creatorSignature ) console.log(`Whitelist collection will deploy at: ${predictedAddress}`)
// Create collections with different access phases const phasedCollection = { name: "Phased Access Collection", symbol: "PAC", creator: account.address, gameOwner: account.address, // Phase 1: Whitelist only (first 24 hours) isWhitelistEnabled: true, whitelistMerkleRoot: merkleRoot, startTime: BigInt(Math.floor(Date.now() / 1000)), // Note: For phase 2 (public access), you'll need additional logic // to disable whitelist checking after a certain time } // In your application, implement phase logic async function checkMintingPhase(collection: any): Promise<'whitelist' | 'public' | 'ended'> { const now = BigInt(Math.floor(Date.now() / 1000)) const startTime = await collection.startTime() const endTime = await collection.endTime() const whitelistPhaseEnd = startTime + 86400n // 24 hours if (now < startTime) { throw new Error("Minting hasn't started yet") } else if (now < whitelistPhaseEnd) { return 'whitelist' } else if (now < endTime) { return 'public' } else { return 'ended' } }
// Generate proof for a specific address function generateProofForAddress(whitelist: WhitelistManager, address: string): string[] { try { const proof = whitelist.getProof(address) console.log(`Generated proof for ${address}:`, proof) return proof } catch (error) { console.error(`Failed to generate proof for ${address}:`, error) throw new Error(`Address ${address} not in whitelist`) } } // Verify proof locally (optional check) function verifyWhitelistProof( whitelist: WhitelistManager, address: string, proof: string[] ): boolean { const isValid = whitelist.verify(address, proof) console.log(`Proof verification for ${address}: ${isValid ? '✅ Valid' : '❌ Invalid'}`) return isValid } // Example usage const userAddress = "0x1234567890123456789012345678901234567890" const proof = generateProofForAddress(whitelist, userAddress) const isValid = verifyWhitelistProof(whitelist, userAddress, proof)
// Generate proofs for multiple addresses function generateBatchProofs( whitelist: WhitelistManager, addresses: string[] ): Map<string, string[]> { const proofMap = new Map<string, string[]>() for (const address of addresses) { try { const proof = whitelist.getProof(address) proofMap.set(address, proof) console.log(`✅ Generated proof for ${address}`) } catch (error) { console.error(`❌ Failed to generate proof for ${address}:`, error) } } return proofMap } // Generate proofs for all whitelisted addresses const allAddresses = whitelistedAddresses.map(entry => entry.address) const allProofs = generateBatchProofs(whitelist, allAddresses) // Save proofs to a file or database for frontend use const proofData = { merkleRoot, proofs: Object.fromEntries(allProofs) } // Example: Save to JSON file import { writeFileSync } from 'fs' writeFileSync('whitelist-proofs.json', JSON.stringify(proofData, null, 2))
async function mintWithWhitelist( collection: any, walletClient: any, whitelist: WhitelistManager, quantity: bigint = 1n ) { const userAddress = walletClient.account.address try { // Generate proof for the user const proof = whitelist.getProof(userAddress) // Verify proof locally (optional) const isValid = whitelist.verify(userAddress, proof) if (!isValid) { throw new Error("Invalid whitelist proof") } // Get mint price const mintPrice = await collection.mintPrice() const totalPrice = mintPrice * quantity // Mint with whitelist proof const tx = await collection.mint( walletClient, quantity, undefined, // metadata URI totalPrice, proof // Whitelist proof ) console.log(`✅ Whitelist mint successful: ${tx}`) return tx } catch (error: any) { if (error.message.includes('Invalid merkle proof')) { console.error('❌ Address not in whitelist or invalid proof') } else { console.error('❌ Whitelist mint failed:', error) } throw error } } // Usage await mintWithWhitelist(collection, walletClient, whitelist, 2n)
async function advancedWhitelistMint( collection: any, walletClient: any, whitelist: WhitelistManager, allocationMap: Map<string, number>, quantity: bigint ) { const userAddress = walletClient.account.address // Check user's allocation const maxAllocation = allocationMap.get(userAddress) || 0 if (maxAllocation === 0) { throw new Error("Address not in whitelist") } // Check current balance against allocation const currentBalance = await collection.balanceOf(userAddress) const newBalance = currentBalance + quantity if (newBalance > BigInt(maxAllocation)) { throw new Error(`Would exceed allocation. Max: ${maxAllocation}, Current: ${currentBalance}`) } // Generate proof and mint const proof = whitelist.getProof(userAddress) const mintPrice = await collection.mintPrice() const tx = await collection.mint( walletClient, quantity, undefined, mintPrice * quantity, proof ) console.log(`✅ Advanced whitelist mint successful: ${tx}`) return tx }
import { useState, useEffect } from 'react' import { useAccount } from 'wagmi' interface WhitelistStatus { isWhitelisted: boolean proof: string[] | null allocation: number loading: boolean error: string | null } export function useWhitelistStatus( whitelist: WhitelistManager, allocationMap?: Map<string, number> ): WhitelistStatus { const { address } = useAccount() const [status, setStatus] = useState<WhitelistStatus>({ isWhitelisted: false, proof: null, allocation: 0, loading: true, error: null }) useEffect(() => { async function checkWhitelistStatus() { if (!address) { setStatus({ isWhitelisted: false, proof: null, allocation: 0, loading: false, error: null }) return } try { setStatus(prev => ({ ...prev, loading: true, error: null })) // Generate proof const proof = whitelist.getProof(address) const isValid = whitelist.verify(address, proof) const allocation = allocationMap?.get(address) || 0 setStatus({ isWhitelisted: isValid, proof: isValid ? proof : null, allocation, loading: false, error: null }) } catch (error: any) { setStatus({ isWhitelisted: false, proof: null, allocation: 0, loading: false, error: error.message }) } } checkWhitelistStatus() }, [address, whitelist, allocationMap]) return status }
import React from 'react' import { useWhitelistStatus } from './useWhitelistStatus' interface WhitelistStatusProps { whitelist: WhitelistManager allocationMap?: Map<string, number> } export function WhitelistStatus({ whitelist, allocationMap }: WhitelistStatusProps) { const { isWhitelisted, allocation, loading, error } = useWhitelistStatus( whitelist, allocationMap ) if (loading) { return <div className="animate-pulse">Checking whitelist status...</div> } if (error) { return <div className="text-red-500">Error: {error}</div> } return ( <div className={`p-4 rounded-lg ${isWhitelisted ? 'bg-green-100' : 'bg-gray-100'}`}> {isWhitelisted ? ( <div className="text-green-800"> <h3 className="font-bold">✅ Whitelisted</h3> <p>You can mint up to {allocation} tokens</p> </div> ) : ( <div className="text-gray-800"> <h3 className="font-bold">❌ Not Whitelisted</h3> <p>Your address is not eligible for whitelist minting</p> </div> )} </div> ) }
// Create different tiers with different benefits const goldTierAddresses = [ { address: "0x1111..." }, { address: "0x2222..." } ] const silverTierAddresses = [ { address: "0x3333..." }, { address: "0x4444..." } ] const bronzeTierAddresses = [ { address: "0x5555..." }, { address: "0x6666..." } ] // Create separate whitelists for each tier const goldWhitelist = new WhitelistManager(goldTierAddresses) const silverWhitelist = new WhitelistManager(silverTierAddresses) const bronzeWhitelist = new WhitelistManager(bronzeTierAddresses) // For smart contract, you might combine all tiers const allTierAddresses = [ ...goldTierAddresses, ...silverTierAddresses, ...bronzeTierAddresses ] const combinedWhitelist = new WhitelistManager(allTierAddresses) // Application logic for tier benefits const tierBenefits = { gold: { maxMint: 10, discount: 0.2 }, silver: { maxMint: 5, discount: 0.1 }, bronze: { maxMint: 2, discount: 0.05 } } function getTierForAddress(address: string): 'gold' | 'silver' | 'bronze' | null { if (goldTierAddresses.some(entry => entry.address === address)) return 'gold' if (silverTierAddresses.some(entry => entry.address === address)) return 'silver' if (bronzeTierAddresses.some(entry => entry.address === address)) return 'bronze' return null }
async function getActiveTierForTime(timestamp: number): Promise<'gold' | 'silver' | 'bronze' | 'public' | null> { const phaseStartTime = 1640995200 // Example timestamp const hoursSinceStart = (timestamp - phaseStartTime) / 3600 if (hoursSinceStart < 0) return null // Not started if (hoursSinceStart < 1) return 'gold' // First hour: gold tier only if (hoursSinceStart < 4) return 'silver' // Next 3 hours: gold + silver if (hoursSinceStart < 24) return 'bronze' // Next 20 hours: all tiers return 'public' // After 24 hours: public access } async function mintWithTierAccess( collection: any, walletClient: any, address: string, quantity: bigint ) { const now = Math.floor(Date.now() / 1000) const activeTier = await getActiveTierForTime(now) const userTier = getTierForAddress(address) // Check if user can mint in current phase if (activeTier === 'public') { // Public minting - no whitelist needed await collection.mint(walletClient, quantity, undefined, mintPrice * quantity, []) } else if (userTier && shouldTierHaveAccess(userTier, activeTier)) { // Whitelist minting with tier access const proof = combinedWhitelist.getProof(address) await collection.mint(walletClient, quantity, undefined, mintPrice * quantity, proof) } else { throw new Error(`Access denied. Current phase: ${activeTier}, User tier: ${userTier}`) } } function shouldTierHaveAccess(userTier: string, activeTier: string): boolean { const tierHierarchy = { gold: 3, silver: 2, bronze: 1 } return tierHierarchy[userTier] >= tierHierarchy[activeTier] }
class WhitelistAnalytics { private whitelist: WhitelistManager private addresses: { address: string }[] constructor(whitelist: WhitelistManager, addresses: { address: string }[]) { this.whitelist = whitelist this.addresses = addresses } getWhitelistSize(): number { return this.addresses.length } getMerkleTreeDepth(): number { return Math.ceil(Math.log2(this.addresses.length)) } getProofSize(address: string): number { try { const proof = this.whitelist.getProof(address) return proof.length } catch { return 0 } } getAverageProofSize(): number { const proofSizes = this.addresses.map(entry => this.getProofSize(entry.address)) return proofSizes.reduce((a, b) => a + b, 0) / proofSizes.length } estimateGasCost(): { verificationGas: number; totalGasPerMint: number } { const avgProofSize = this.getAverageProofSize() const verificationGas = 21000 + (avgProofSize * 3000) // Rough estimate const totalGasPerMint = verificationGas + 50000 // Add base mint cost return { verificationGas, totalGasPerMint } } generateReport(): any { const gasCosts = this.estimateGasCost() return { size: this.getWhitelistSize(), merkleTreeDepth: this.getMerkleTreeDepth(), averageProofSize: this.getAverageProofSize(), estimatedGasCosts: gasCosts, merkleRoot: this.whitelist.getRoot() } } } // Usage const analytics = new WhitelistAnalytics(whitelist, whitelistedAddresses) const report = analytics.generateReport() console.log("Whitelist Report:", report)
function validateWhitelistAddresses(addresses: { address: string }[]): { valid: { address: string }[] invalid: { address: string; reason: string }[] } { const valid: { address: string }[] = [] const invalid: { address: string; reason: string }[] = [] for (const entry of addresses) { if (!entry.address) { invalid.push({ address: entry.address, reason: "Missing address" }) continue } if (!entry.address.startsWith('0x')) { invalid.push({ address: entry.address, reason: "Missing 0x prefix" }) continue } if (entry.address.length !== 42) { invalid.push({ address: entry.address, reason: "Invalid length" }) continue } if (!/^0x[a-fA-F0-9]{40}$/.test(entry.address)) { invalid.push({ address: entry.address, reason: "Invalid hex characters" }) continue } valid.push(entry) } return { valid, invalid } } // Check for duplicates function removeDuplicateAddresses(addresses: { address: string }[]): { address: string }[] { const seen = new Set<string>() const unique: { address: string }[] = [] for (const entry of addresses) { const normalizedAddress = entry.address.toLowerCase() if (!seen.has(normalizedAddress)) { seen.add(normalizedAddress) unique.push({ address: entry.address }) } } return unique }
Invalid Merkle proof error
Proof generation fails
Whitelist not working as expected