Error Handling
Comprehensive error handling patterns and recovery strategies for CreateKit
Overview
Robust error handling is crucial for production applications using CreateKit. This guide covers common error scenarios, best practices, and recovery strategies.
Common Error Types
Signature Errors
typescriptimport { CollectionManager } from '@b3dotfun/basemint' async function handleSignatureErrors(walletClient: any, metadata: any) { try { const signature = await collectionManager.generateCreatorSignature( walletClient, metadata ) return signature } catch (error: any) { if (error.message.includes('User rejected')) { throw new Error('SIGNATURE_REJECTED: User rejected the signature request') } else if (error.message.includes('Insufficient funds')) { throw new Error('INSUFFICIENT_FUNDS: Not enough funds for gas') } else if (error.message.includes('Network error')) { throw new Error('NETWORK_ERROR: Unable to connect to network') } else { throw new Error(`SIGNATURE_FAILED: ${error.message}`) } } }
Storage Errors
typescriptimport { BaseMintStorage } from '@b3dotfun/basemint' async function handleStorageErrors(storage: BaseMintStorage, metadata: any, signature: string) { try { return await storage.submitCollection(metadata, signature) } catch (error: any) { if (error.message.includes('Invalid signature')) { throw new Error('INVALID_SIGNATURE: Signature verification failed') } else if (error.message.includes('Collection exists')) { throw new Error('DUPLICATE_COLLECTION: Collection already exists') } else if (error.message.includes('Rate limit')) { throw new Error('RATE_LIMITED: Too many requests, please try again later') } else if (error.status === 503) { throw new Error('SERVICE_UNAVAILABLE: Storage service temporarily unavailable') } else { throw new Error(`STORAGE_ERROR: ${error.message}`) } } }
Contract Interaction Errors
typescriptasync function handleMintingErrors(collection: any, walletClient: any, params: any) { try { return await collection.mint(walletClient, ...params) } catch (error: any) { if (error.message.includes('Invalid merkle proof')) { throw new Error('NOT_WHITELISTED: Address not in whitelist') } else if (error.message.includes('Insufficient payment')) { throw new Error('INSUFFICIENT_PAYMENT: Incorrect mint price') } else if (error.message.includes('Max per wallet exceeded')) { throw new Error('WALLET_LIMIT_EXCEEDED: Wallet minting limit reached') } else if (error.message.includes('Max supply exceeded')) { throw new Error('SUPPLY_EXHAUSTED: Collection fully minted') } else if (error.message.includes('Minting not active')) { throw new Error('MINTING_INACTIVE: Minting period not active') } else { throw new Error(`MINT_FAILED: ${error.message}`) } } }
Error Recovery Patterns
Retry Logic
typescriptasync function retryWithBackoff<T>( operation: () => Promise<T>, maxRetries: number = 3, baseDelayMs: number = 1000 ): Promise<T> { let lastError: Error for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation() } catch (error: any) { lastError = error // Don't retry certain errors if (error.message.includes('SIGNATURE_REJECTED') || error.message.includes('INVALID_SIGNATURE') || error.message.includes('DUPLICATE_COLLECTION')) { throw error } if (attempt < maxRetries) { const delay = baseDelayMs * Math.pow(2, attempt - 1) console.warn(`Attempt ${attempt} failed, retrying in ${delay}ms...`) await new Promise(resolve => setTimeout(resolve, delay)) } } } throw lastError } // Usage const result = await retryWithBackoff(async () => { return await storage.submitCollection(metadata, signature) })
Circuit Breaker
typescriptclass CircuitBreaker { private failures = 0 private lastFailTime = 0 private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED' constructor( private maxFailures: number = 5, private timeoutMs: number = 60000 ) {} async call<T>(operation: () => Promise<T>): Promise<T> { if (this.state === 'OPEN') { if (Date.now() - this.lastFailTime > this.timeoutMs) { this.state = 'HALF_OPEN' } else { throw new Error('CIRCUIT_OPEN: Service temporarily unavailable') } } try { const result = await operation() this.reset() return result } catch (error) { this.recordFailure() throw error } } private recordFailure() { this.failures++ this.lastFailTime = Date.now() if (this.failures >= this.maxFailures) { this.state = 'OPEN' } } private reset() { this.failures = 0 this.state = 'CLOSED' } } // Usage const circuitBreaker = new CircuitBreaker() const result = await circuitBreaker.call(() => storage.submitCollection(metadata, signature))
User-Friendly Error Messages
typescriptconst ERROR_MESSAGES = { SIGNATURE_REJECTED: "Please approve the signature in your wallet to continue.", INSUFFICIENT_FUNDS: "You don't have enough funds to cover gas fees.", NOT_WHITELISTED: "Your address is not eligible for whitelist minting.", WALLET_LIMIT_EXCEEDED: "You've reached the maximum number of tokens per wallet.", SUPPLY_EXHAUSTED: "This collection is fully minted out.", MINTING_INACTIVE: "Minting is not currently active for this collection.", NETWORK_ERROR: "Network connection issue. Please check your internet and try again.", SERVICE_UNAVAILABLE: "Service is temporarily unavailable. Please try again in a few minutes.", RATE_LIMITED: "Too many requests. Please wait a moment before trying again." } function getUserFriendlyError(error: Error): string { const errorCode = error.message.split(':')[0] return ERROR_MESSAGES[errorCode] || "An unexpected error occurred. Please try again." } // Usage in React export function ErrorDisplay({ error }: { error: Error | null }) { if (!error) return null return ( <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded"> <p>{getUserFriendlyError(error)}</p> </div> ) }
Error Monitoring
typescriptclass ErrorTracker { private errors: Array<{ timestamp: Date; error: Error; context: any }> = [] track(error: Error, context: any = {}) { this.errors.push({ timestamp: new Date(), error, context }) // Send to monitoring service this.sendToMonitoring(error, context) } private sendToMonitoring(error: Error, context: any) { // Integration with error monitoring services console.error('Error tracked:', { message: error.message, stack: error.stack, context, timestamp: new Date().toISOString() }) } getErrorStats() { const last24h = this.errors.filter( e => Date.now() - e.timestamp.getTime() < 24 * 60 * 60 * 1000 ) return { total: this.errors.length, last24h: last24h.length, mostCommon: this.getMostCommonErrors() } } private getMostCommonErrors() { const errorCounts = new Map<string, number>() this.errors.forEach(({ error }) => { const errorType = error.message.split(':')[0] errorCounts.set(errorType, (errorCounts.get(errorType) || 0) + 1) }) return Array.from(errorCounts.entries()) .sort(([,a], [,b]) => b - a) .slice(0, 5) } } // Global error tracker export const errorTracker = new ErrorTracker()
Validation Helpers
typescriptexport class ValidationError extends Error { constructor(field: string, message: string) { super(`${field}: ${message}`) this.name = 'ValidationError' } } export function validateCollectionMetadata(metadata: any): void { if (!metadata.name || metadata.name.length < 1) { throw new ValidationError('name', 'Collection name is required') } if (!metadata.symbol || metadata.symbol.length < 1) { throw new ValidationError('symbol', 'Collection symbol is required') } if (!metadata.creator || !isValidAddress(metadata.creator)) { throw new ValidationError('creator', 'Valid creator address is required') } if (metadata.maxSupply && metadata.maxSupply <= 0n) { throw new ValidationError('maxSupply', 'Max supply must be greater than 0') } if (metadata.mintPrice && metadata.mintPrice < 0n) { throw new ValidationError('mintPrice', 'Mint price cannot be negative') } } function isValidAddress(address: string): boolean { return /^0x[a-fA-F0-9]{40}$/.test(address) }
React Error Boundaries
tsximport React, { Component, ErrorInfo, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error?: Error } export class CreateKitErrorBoundary extends Component<Props, State> { constructor(props: Props) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('CreateKit Error Boundary caught an error:', error, errorInfo) // Track error errorTracker.track(error, { errorInfo }) } render() { if (this.state.hasError) { return this.props.fallback || ( <div className="bg-red-50 border border-red-200 rounded-lg p-6"> <h2 className="text-red-800 text-lg font-semibold mb-2"> Something went wrong </h2> <p className="text-red-600"> {getUserFriendlyError(this.state.error!)} </p> <button onClick={() => this.setState({ hasError: false })} className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700" > Try Again </button> </div> ) } return this.props.children } }
Best Practices
Next Steps
Now that you have comprehensive CreateKit documentation, you can: