Error Handling Patterns
The Veridex SDK normalizes errors from EVM, Solana, Starknet, and Stacks into a single
VeridexError class. This guide covers practical patterns for handling errors in your app.
Basic Error Handling
import { VeridexError, VeridexErrorCode } from '@veridex/sdk';
try {
await sdk.transferViaRelayer(params);
} catch (err) {
if (err instanceof VeridexError) {
console.log(err.code); // 'INSUFFICIENT_FUNDS'
console.log(err.message); // Human-readable message
console.log(err.chain); // 'base', 'solana', etc.
console.log(err.retryable); // true for RPC_ERROR, TIMEOUT, RELAYER_ERROR
console.log(err.cause); // Original chain error
}
}💡
The retryable flag is true for RPC_ERROR, TIMEOUT, and RELAYER_ERROR.
All other codes are non-retryable by default.
Common Patterns
User-Facing Error Messages
Map error codes to friendly messages:
function getErrorMessage(err: VeridexError): string {
switch (err.code) {
case VeridexErrorCode.NO_CREDENTIAL:
return 'Please log in with your passkey first.';
case VeridexErrorCode.INSUFFICIENT_FUNDS:
return 'Not enough tokens in your vault.';
case VeridexErrorCode.DAILY_LIMIT_EXCEEDED:
return 'Daily spending limit reached. Try again after reset.';
case VeridexErrorCode.VAULT_NOT_FOUND:
return 'Your vault hasn\'t been created yet.';
case VeridexErrorCode.SESSION_EXPIRED:
return 'Your session has expired. Please sign in again.';
case VeridexErrorCode.UNSUPPORTED_FEATURE:
return 'This feature is not available on the current chain.';
default:
return err.retryable
? 'Something went wrong. Please try again.'
: err.message;
}
}Retry with Exponential Backoff
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
const isRetryable = err instanceof VeridexError && err.retryable;
const isLastAttempt = attempt === maxRetries - 1;
if (!isRetryable || isLastAttempt) throw err;
const delay = 1000 * 2 ** attempt; // 1s, 2s, 4s
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('unreachable');
}
// Usage
const result = await withRetry(() => sdk.transferViaRelayer(params));Chain-Aware Error Handling
Use err.chain to provide chain-specific guidance:
try {
await sdk.transferViaRelayer(params);
} catch (err) {
if (!(err instanceof VeridexError)) throw err;
if (err.code === VeridexErrorCode.INSUFFICIENT_FUNDS) {
switch (err.chain) {
case 'solana':
showToast('Fund your vault with SOL for rent + tokens');
break;
case 'stacks':
showToast('Fund your vault with STX + tokens');
break;
default:
showToast('Not enough tokens in your vault');
}
}
}normalizeError for Chain Clients
If you work directly with chain clients, use normalizeError to convert
chain-specific errors:
import { normalizeError } from '@veridex/sdk';
try {
await evmClient.dispatch(signature, pubX, pubY, payload, signer);
} catch (err) {
const error = normalizeError(err, 'base');
// error.code is now a VeridexErrorCode
}The normalizer detects:
- EVM: ethers error codes, revert messages (
insufficient funds,execution reverted, etc.) - Solana: Anchor program error codes (
6000–6013) - Starknet: Cairo/felt error patterns
- Stacks: Clarity error codes (
(err u100)through(err u202))
See the Error Code Reference for the complete mapping tables.
React Integration
Error Boundary
import { VeridexError, VeridexErrorCode } from '@veridex/sdk';
import { Component, type ReactNode } from 'react';
class VeridexErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean; code?: VeridexErrorCode }
> {
state: { hasError: boolean; code?: VeridexErrorCode } = { hasError: false };
static getDerivedStateFromError(error: Error) {
if (error instanceof VeridexError) {
return { hasError: true, code: error.code };
}
return { hasError: true };
}
render() {
if (!this.state.hasError) return this.props.children;
if (this.state.code === VeridexErrorCode.NO_CREDENTIAL) {
return <LoginPrompt />;
}
return this.props.fallback;
}
}Hook Error State
With @veridex/react:
import { useTransfer } from '@veridex/react';
function SendButton() {
const { prepare, execute, error, step } = useTransfer();
if (error) {
return <p className="text-red-500">{getErrorMessage(error)}</p>;
}
return <button onClick={() => prepare(params)}>Send</button>;
}