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
Signature Error Handling
Copy
Ask AI
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
Storage Error Handling
Copy
Ask AI
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
Contract Error Handling
Copy
Ask AI
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
Retry with Exponential Backoff
Copy
Ask AI
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
Circuit Breaker Pattern
Copy
Ask AI
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
Error Message Translation
Copy
Ask AI
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
Error Tracking
Copy
Ask AI
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
Input Validation
Copy
Ask AI
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
Error Boundary Component
Copy
Ask AI
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