Skip to main content

Documentation Index

Fetch the complete documentation index at: https://seilabs-docs-evm-reference-and-sei-js-examples.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Error Handling

Most transaction failures fall into three categories: contract reverts, wallet rejections, and RPC errors. This page shows how to decode each type and respond correctly.

Contract Reverts

When a transaction reverts, the error contains the revert reason if the contract provides one.
import { ContractFunctionRevertedError, BaseError } from 'viem';

try {
  await client.simulateContract({
    address: TOKEN,
    abi: ERC20_ABI,
    functionName: 'transfer',
    args: ['0xRecipient', 1_000_000n],
    account,
  });
} catch (err) {
  if (err instanceof BaseError) {
    const revert = err.walk((e) => e instanceof ContractFunctionRevertedError);
    if (revert instanceof ContractFunctionRevertedError) {
      console.error('Revert reason:', revert.data?.errorName);
      // e.g. 'ERC20InsufficientBalance'
    }
  }
}

Simulating Before Sending

Always simulate write calls before submitting them. Simulation runs the call against current chain state and surfaces reverts before you spend gas:
viem
import { ContractFunctionRevertedError, BaseError } from 'viem';

async function safeWrite(params: Parameters<typeof walletClient.writeContract>[0]) {
  // 1. Simulate — reverts here cost no gas
  const { request } = await client.simulateContract(params);
  // 2. Execute only if simulation passes
  return walletClient.writeContract(request);
}

Wallet Rejections

Users can reject signature and transaction requests. These rejections have a predictable error code:
import { UserRejectedRequestError } from 'viem';

try {
  await walletClient.sendTransaction({ to: '0xRecipient', value: 1n });
} catch (err) {
  if (err instanceof UserRejectedRequestError) {
    // User cancelled — don't show an error, just reset UI state
    console.log('User rejected the request');
  }
}

Insufficient Funds

import { InsufficientFundsError } from 'viem';

try {
  await walletClient.sendTransaction({ to: '0xRecipient', value: parseEther('9999') });
} catch (err) {
  if (err instanceof InsufficientFundsError) {
    console.error('Not enough SEI for this transaction');
  }
}

Classifying Any Error

A utility to handle the most common cases in one place:
import {
  BaseError,
  ContractFunctionRevertedError,
  UserRejectedRequestError,
  InsufficientFundsError,
} from 'viem';

function classifyError(err: unknown): string {
  if (err instanceof UserRejectedRequestError) return 'rejected';
  if (err instanceof InsufficientFundsError)  return 'insufficient_funds';

  if (err instanceof BaseError) {
    const revert = err.walk((e) => e instanceof ContractFunctionRevertedError);
    if (revert instanceof ContractFunctionRevertedError) {
      return `revert:${revert.data?.errorName ?? 'unknown'}`;
    }
  }

  return 'unknown';
}

RPC Errors and Retries

Transient RPC failures (network timeouts, rate limits) can be retried. Don’t retry on contract reverts or user rejections — those are deterministic.
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
  let lastError: unknown;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      // Don't retry user rejections or contract reverts
      if (
        err instanceof UserRejectedRequestError ||
        err?.code === 4001 ||
        err?.code === 'CALL_EXCEPTION'
      ) {
        throw err;
      }
      lastError = err;
      if (attempt < maxAttempts) {
        await new Promise((r) => setTimeout(r, 500 * attempt)); // backoff
      }
    }
  }
  throw lastError;
}

// Usage
const balance = await withRetry(() =>
  client.readContract({ address: TOKEN, abi: ERC20_ABI, functionName: 'balanceOf', args: [owner] })
);