| name | midnight-dapp:error-handling |
| description | Use when implementing error handling in Midnight DApps, displaying errors to users, handling proof generation failures, managing transaction rejections, or dealing with network disconnections. |
Error Handling
Handle errors gracefully in Midnight DApps with user-friendly messaging, proper error classification, and recovery strategies.
When to Use
- Displaying errors to users in a helpful way
- Implementing error boundaries for React components
- Handling proof generation failures
- Managing transaction rejections and contract errors
- Dealing with network disconnections (indexer, proof server)
- Creating consistent error messaging across your DApp
Key Concepts
Error Taxonomy
Midnight DApps encounter several categories of errors:
| Category | Source | Examples |
|---|
| Proof Errors | ZK proof generation | Timeout, constraint violations, witness failures |
| Transaction Errors | Contract execution | Insufficient balance, state conflicts, rejections |
| Network Errors | Infrastructure | Indexer down, proof server unavailable, WebSocket disconnect |
| Wallet Errors | User interaction | Not installed, rejected, wrong network |
User-Friendly Messaging
Technical errors should be translated to actionable messages:
"Circuit constraint failed at gate 1042: input > max_value"
"The amount exceeds the maximum allowed. Please enter a smaller value."
Recovery Patterns
Different errors require different recovery strategies:
- Retryable: Timeouts, temporary network issues - offer retry button
- User Action Required: Wrong network, insufficient balance - guide user
- Fatal: Contract bugs, invalid state - show support contact
References
Examples
Quick Start
1. Create Typed Errors
import { MidnightError, ErrorCode } from './MidnightErrors';
throw new MidnightError(
ErrorCode.PROOF_TIMEOUT,
'Proof generation timed out',
{ timeoutMs: 60000, operation: 'transfer' }
);
2. Classify Unknown Errors
import { classifyError } from './errorUtils';
try {
await contract.callTx.transfer(recipient, amount, witnesses);
} catch (error) {
const classified = classifyError(error);
}
3. Display to Users
import { useErrorToast } from './useErrorToast';
function TransferButton() {
const { showError } = useErrorToast();
const handleTransfer = async () => {
try {
await performTransfer();
} catch (error) {
showError(error);
}
};
}
4. Wrap Components
import { MidnightErrorBoundary } from './MidnightErrorBoundary';
function App() {
return (
<MidnightErrorBoundary
onReset={() => window.location.reload()}
fallback={<ErrorPage />}
>
<MyDApp />
</MidnightErrorBoundary>
);
}
Common Patterns
Error Classification Utility
interface ClassifiedError {
code: string;
message: string;
userMessage: string;
suggestion: string;
retryable: boolean;
category: 'proof' | 'transaction' | 'network' | 'wallet' | 'unknown';
}
function classifyError(error: unknown): ClassifiedError {
if (error instanceof MidnightError) {
return {
code: error.code,
message: error.message,
userMessage: ERROR_CATALOG[error.code].userMessage,
suggestion: ERROR_CATALOG[error.code].suggestion,
retryable: ERROR_CATALOG[error.code].retryable,
category: ERROR_CATALOG[error.code].category,
};
}
const message = error instanceof Error ? error.message : String(error);
return classifyByMessage(message);
}
Retry with Backoff
async function withRetry<T>(
operation: () => Promise<T>,
options: {
maxRetries: number;
baseDelayMs: number;
shouldRetry: (error: unknown) => boolean;
}
): Promise<T> {
let lastError: unknown;
for (let attempt = 1; attempt <= options.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (!options.shouldRetry(error) || attempt === options.maxRetries) {
throw error;
}
const delay = options.baseDelayMs * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
Error Context Provider
const ErrorContext = createContext<ErrorContextValue | null>(null);
function ErrorProvider({ children }: { children: ReactNode }) {
const [errors, setErrors] = useState<ClassifiedError[]>([]);
const addError = useCallback((error: unknown) => {
const classified = classifyError(error);
setErrors(prev => [...prev, classified]);
if (classified.category !== 'wallet') {
setTimeout(() => dismissError(classified.code), 5000);
}
}, []);
const dismissError = useCallback((code: string) => {
setErrors(prev => prev.filter(e => e.code !== code));
}, []);
return (
<ErrorContext.Provider value={{ errors, addError, dismissError }}>
{children}
</ErrorContext.Provider>
);
}
Error Handling Best Practices
- Never swallow errors silently - Always log or display
- Provide actionable messages - Tell users what they can do
- Log technical details - Keep full error info for debugging
- Use error boundaries - Prevent cascading failures in UI
- Categorize appropriately - Different errors need different handling
- Test error paths - Verify error UI works correctly
- Consider accessibility - Error messages should work with screen readers
Related Skills
wallet-integration - Wallet-specific error handling
proof-handling - Proof generation error patterns
transaction-flows - Transaction error recovery
state-management - State read error handling
Related Commands
/dapp-check - Validates error handling patterns
/dapp-debug errors - Diagnose error handling issues