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

typescript
import { 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

typescript
import { 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

typescript
async 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

typescript
async 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

typescript
class 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

typescript
const 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

typescript
class 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

typescript
export 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

tsx
import 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

Error Classification
  • Categorize errors by type and severity
  • Use consistent error codes
  • Provide actionable error messages
  • Log errors with sufficient context
Recovery Strategies
  • Implement appropriate retry logic
  • Use circuit breakers for external services
  • Provide fallback mechanisms
  • Allow manual error recovery

Next Steps

Now that you have comprehensive CreateKit documentation, you can:

Start Building

Use the quickstart guide to create your first collection

Join Community

Connect with other developers in the B3 Discord

Ask a question... ⌘I