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.
Action required: IBC assets on Sei will become inaccessible If you hold USDC.n (USDC via Noble), USDT.kava (Kava USDT), Wormhole-bridged tokens, or any other IBC asset on Sei, you must swap, migrate, or bridge out before the governance proposal to disable inbound/outbound IBC transfers passes and is activated to avoid permanent loss of access. After this, Sei will no longer support IBC bridging of assets from Cosmos-based chains to and from Sei Network. Consult the SIP-03 Migration Guide for the full list of affected assets, required actions, and supported routes. For USDC.n specifically, see: Holders of USDC.n Need to Swap or Migrate.
Decode contract reverts, handle wallet rejections, and recover from RPC errors in Sei apps
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.
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'
}
}
}
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);
}
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');
}
}
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');
}
}
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';
}
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] })
);