Use when implementing error handling in Midnight DApps, displaying errors to users, handling proof generation failures, managing transaction rejections, or dealing with network disconnections.
Handle errors gracefully in Midnight DApps with user-friendly messaging, proper error classification, and recovery strategies.
Midnight DApps encounter several categories of errors:
| Category | Source | Examples |
|---|---|---|
| Proof Errors | ZK proof generation | Timeout, constraint violations, witness failures |
| 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 |
Technical errors should be translated to actionable messages:
// Technical error
"Circuit constraint failed at gate 1042: input > max_value"
// User-friendly message
"The amount exceeds the maximum allowed. Please enter a smaller value."
Different errors require different recovery strategies:
| Document | Description |
|---|---|
| proof-errors.md | ZK proof failures and diagnosis |
| transaction-errors.md | Contract execution errors |
| network-errors.md | Connection and timeout handling |
| user-messaging.md | UX guidelines for error display |
| Example | Description |
|---|---|
| error-boundary/ | React error boundary for Midnight |
| error-toast/ | Toast notification component |
| typed-errors/ | Custom error classes and catalog |
import { MidnightError, ErrorCode } from './MidnightErrors';
// Throw typed errors in your code
throw new MidnightError(
ErrorCode.PROOF_TIMEOUT,
'Proof generation timed out',
{ timeoutMs: 60000, operation: 'transfer' }
);
import { classifyError } from './errorUtils';
try {
await contract.callTx.transfer(recipient, amount, witnesses);
} catch (error) {
const classified = classifyError(error);
// { code, message, userMessage, suggestion, retryable, category }
}
import { useErrorToast } from './useErrorToast';
function TransferButton() {
const { showError } = useErrorToast();
const handleTransfer = async () => {
try {
await performTransfer();
} catch (error) {
showError(error); // Shows user-friendly toast
}
};
}
import { MidnightErrorBoundary } from './MidnightErrorBoundary';
function App() {
return (
<MidnightErrorBoundary
onReset={() => window.location.reload()}
fallback={<ErrorPage />}
>
<MyDApp />
</MidnightErrorBoundary>
);
}
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,
};
}
// Classify by error message patterns
const message = error instanceof Error ? error.message : String(error);
return classifyByMessage(message);
}
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;
}
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]);
// Auto-dismiss non-critical errors
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>
);
}
wallet-integration - Wallet-specific error handlingproof-handling - Proof generation error patternstransaction-flows - Transaction error recoverystate-management - State read error handling/dapp-check - Validates error handling patterns/dapp-debug errors - Diagnose error handling issues