Error Handling
The Veridex SDK ships a unified error system that normalizes chain-specific errors
from EVM, Solana, Starknet, and Stacks into a single VeridexError class. Catch
one type everywhere — no more guessing which chain threw what.
VeridexError Class
import { VeridexError, VeridexErrorCode } from '@veridex/sdk';
try {
await sdk.transferViaRelayer({
targetChain: 10004,
token: USDC,
recipient: '0x...',
amount: 1_000_000n,
});
} catch (err) {
if (err instanceof VeridexError) {
console.log(err.code); // VeridexErrorCode.INSUFFICIENT_FUNDS
console.log(err.message); // "Insufficient funds in vault."
console.log(err.chain); // "base"
console.log(err.retryable); // false
console.log(err.cause); // original ethers / RPC error
}
}Properties
| Property | Type | Description |
|---|---|---|
code | VeridexErrorCode | Unified error code (see table below) |
message | string | Human-readable description |
chain | string | undefined | Chain that produced the error ('base', 'solana', 'starknet', etc.) |
cause | unknown | Original chain-specific error object |
retryable | boolean | Whether the operation could succeed if retried |
Error Codes
Wallet & Identity
| Code | Default Message | Retryable |
|---|---|---|
NO_CREDENTIAL | No credential set. Call passkey.register() or passkey.setCredential() first. | No |
UNAUTHORIZED | Unauthorized: the signer is not an owner of this vault. | No |
INVALID_SIGNATURE | Signature verification failed. | No |
Vault State
| Code | Default Message | Retryable |
|---|---|---|
VAULT_NOT_FOUND | Vault does not exist. Call ensureVault() first. | No |
VAULT_PAUSED | Vault is paused. Unpause before continuing. | No |
PROTOCOL_PAUSED | Protocol is paused. Try again later. | No |
Balance & Limits
| Code | Default Message | Retryable |
|---|---|---|
INSUFFICIENT_FUNDS | Insufficient funds in vault. | No |
DAILY_LIMIT_EXCEEDED | Daily spending limit exceeded. Try a smaller amount or wait for reset. | No |
Payload & Dispatch
| Code | Default Message | Retryable |
|---|---|---|
INVALID_PAYLOAD | Invalid action payload. | No |
INVALID_ACTION | Unknown or invalid action type. | No |
EXPIRED | Prepared transaction has expired. Please prepare again. | No |
Cross-Chain (Wormhole)
| Code | Default Message | Retryable |
|---|---|---|
VAA_ALREADY_PROCESSED | This cross-chain message has already been processed (replay protection). | No |
INVALID_VAA | Invalid VAA: verification failed. | No |
INVALID_EMITTER | Invalid emitter: message source is not trusted. | No |
BRIDGE_ERROR | Cross-chain bridge error. | No |
Network & Infrastructure
| Code | Default Message | Retryable |
|---|---|---|
RPC_ERROR | RPC call failed. The node may be unavailable. | Yes |
TIMEOUT | Operation timed out. | Yes |
RELAYER_ERROR | Relayer submission failed. | Yes |
Sessions
| Code | Default Message | Retryable |
|---|---|---|
SESSION_EXPIRED | Session key has expired. Create a new session. | No |
SESSION_INVALID | Session key is invalid or revoked. | No |
Capability
| Code | Default Message | Retryable |
|---|---|---|
UNSUPPORTED_FEATURE | This feature is not supported on the current chain. | No |
Catch-All
| Code | Default Message | Retryable |
|---|---|---|
UNKNOWN | An unknown error occurred. | No |
normalizeError()
The normalizeError function converts any chain-specific error into a VeridexError.
The SDK calls this internally at every boundary, but you can use it directly when
working with chain clients.
import { normalizeError } from '@veridex/sdk';
try {
await evmClient.dispatch(signature, pubX, pubY, payload, signer);
} catch (err) {
const veridexErr = normalizeError(err, 'base');
// veridexErr.code === 'INSUFFICIENT_FUNDS' (if ethers threw "insufficient funds")
}Chain-Specific Mappings
The normalizer recognizes errors from each chain's native error surface:
EVM regex patterns match against the error message:
| Pattern | Maps To |
|---|---|
insufficient funds | INSUFFICIENT_FUNDS |
execution reverted.*paused | VAULT_PAUSED |
execution reverted.*unauthorized or not owner | UNAUTHORIZED |
daily.*limit | DAILY_LIMIT_EXCEEDED |
nonce.*expired or nonce.*too low | EXPIRED |
already.*processed or already known | VAA_ALREADY_PROCESSED |
invalid.*signature or ECDSA | INVALID_SIGNATURE |
timeout or ETIMEDOUT or ECONNREFUSED | TIMEOUT |
could not detect network or failed to fetch | RPC_ERROR |
ethers error codes are also detected:
| ethers Code | Maps To |
|---|---|
INSUFFICIENT_FUNDS | INSUFFICIENT_FUNDS |
CALL_EXCEPTION | RPC_ERROR |
NETWORK_ERROR / SERVER_ERROR | RPC_ERROR (retryable) |
TIMEOUT | TIMEOUT (retryable) |
Handling Patterns
Switch on Error Code
import { VeridexError, VeridexErrorCode } from '@veridex/sdk';
try {
await sdk.transferViaRelayer(params);
} catch (err) {
if (!(err instanceof VeridexError)) throw err;
switch (err.code) {
case VeridexErrorCode.INSUFFICIENT_FUNDS:
showToast('Not enough tokens in your vault');
break;
case VeridexErrorCode.DAILY_LIMIT_EXCEEDED:
showToast('Daily limit reached — try again after reset');
break;
case VeridexErrorCode.SESSION_EXPIRED:
await sdk.sessions.create({ duration: 3600 });
break; // retry
case VeridexErrorCode.NO_CREDENTIAL:
router.push('/login');
break;
default:
if (err.retryable) {
await sleep(2000);
// retry the operation
} else {
showToast(err.message);
}
}
}Retry Retryable Errors
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err instanceof VeridexError && err.retryable && attempt < maxRetries - 1) {
await new Promise(r => setTimeout(r, 1000 * 2 ** attempt));
continue;
}
throw err;
}
}
throw new Error('unreachable');
}
const result = await withRetry(() => sdk.transferViaRelayer(params));React Error Boundary
import { VeridexError, VeridexErrorCode } from '@veridex/sdk';
import { Component, type ReactNode } from 'react';
class VeridexErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean; error?: VeridexError }
> {
state = { hasError: false, error: undefined as VeridexError | undefined };
static getDerivedStateFromError(error: Error) {
if (error instanceof VeridexError) {
return { hasError: true, error };
}
return { hasError: true };
}
render() {
if (this.state.hasError) {
if (this.state.error?.code === VeridexErrorCode.NO_CREDENTIAL) {
return <div>Please log in with your passkey.</div>;
}
return this.props.fallback;
}
return this.props.children;
}
}Agent SDK Errors
The Agent SDK (@veridex/agentic-payments) has its own error class:
import { AgentPaymentError, AgentPaymentErrorCode } from '@veridex/agentic-payments';| Code | Description | Retryable |
|---|---|---|
LIMIT_EXCEEDED | Transaction exceeds daily or per-tx spending limit | No |
INSUFFICIENT_BALANCE | Not enough tokens in session wallet | No |
SESSION_EXPIRED | Session key has expired | No |
CHAIN_NOT_SUPPORTED | Chain ID not in allowedChains | No |
TOKEN_NOT_SUPPORTED | Token not available on target chain | No |
NETWORK_ERROR | Network or RPC failure | Yes |