Skip to main content
Skip to main content

Error Handling

web3agent has two error surfaces: the MCP tool layer (used by AI agents) and the SDK layer (used by TypeScript/JavaScript code). Both follow consistent patterns, but the shape of the error differs.

MCP Tool Errors

When a tool call fails, the MCP server returns a structured error response instead of throwing:

// Success
{ ok: true, data: { ... } }

// Failure
{
  ok: false,
  error: {
    code: "SWAP_QUOTE_FAILED",
    message: "Insufficient liquidity for this pair on Base"
  }
}

AI agents receive this response and can decide how to handle it (retry, ask the user, try a different approach). The code field is a stable string identifier; message is human-readable.

SDK Errors

SDK functions throw Web3AgentError when something goes wrong. This is a subclass of Error with two additional fields:

class Web3AgentError extends Error {
  code: string;    // stable error code
  message: string; // human-readable description
}

Import and use it like this:

import { getSwapQuote, Web3AgentError } from "web3agent";

try {
  const quote = await getSwapQuote({
    fromToken: "0x833589...",
    toToken: "0x4200000...",
    fromAmount: "100000000",
    chainId: 8453,
  });
  console.log(quote);
} catch (e: unknown) {
  if (e instanceof Web3AgentError) {
    // Structured error from web3agent
    console.error(`[${e.code}] ${e.message}`);
  } else {
    // Unexpected error (network, runtime, etc.)
    throw e;
  }
}

Common Error Codes

CodeCauseResolution
CHAIN_NOT_SUPPORTEDChain ID not in the supported listCheck supported chains
WALLET_NOT_CONFIGUREDWrite operation attempted with no walletSet PRIVATE_KEY or MNEMONIC
WALLET_NOT_ACTIVENo wallet is currently activeCall wallet_activate first
POLICY_REJECTEDTransaction exceeds spend limitsIncrease POLICY_MAX_* or reduce trade size
CONFIRMATION_REQUIREDWrite queued, awaiting confirmationCall transaction_confirm with the returned ID
CONFIRMATION_EXPIREDConfirmation TTL elapsedRe-initiate the operation
SWAP_QUOTE_FAILEDOrbs Liquidity Hub returned no quoteTry a different pair, amount, or chain
INSUFFICIENT_LIQUIDITYNot enough liquidity for the swapReduce amount or try a different route
LOW_DOLLAR_VALUETrade amount below minimum (~$6-10)Increase the trade size
TOKEN_NOT_FOUNDSymbol or address not in registryUse the full token address instead of symbol
BLOCKSCOUT_NOT_SUPPORTEDExplorer tool called on unsupported chainUse an EVM tool instead, or switch to a supported chain
RPC_ERRORNode RPC call failedCheck RPC_URL, try a different endpoint
INVALID_INPUTZod schema validation failedCheck the parameter types and required fields
BRIDGE_QUOTE_FAILEDLI.FI returned no routeTry different tokens, chains, or amounts
OPERATION_NOT_FOUNDoperation_resume called with unknown IDRe-prepare the operation
TOOL_NOT_FOUNDTool name doesn't existCheck the tool catalog

Tool Error Utilities

When building custom tools or handlers, use these utilities from src/utils/:

formatToolError(code, message)

Creates a standardized { ok: false, error: { code, message } } response.

import { formatToolError } from "web3agent";

return formatToolError("MY_ERROR_CODE", "Something went wrong");

formatToolErrorFromUnknown(code, error)

Converts an unknown caught value into a tool error response. Handles Error instances, strings, and arbitrary objects.

import { formatToolErrorFromUnknown } from "web3agent";

try {
  // ... some operation
} catch (e: unknown) {
  return formatToolErrorFromUnknown("OPERATION_FAILED", e);
}

Best Practices

Always type the catch variable as unknown

TypeScript's catch (e: unknown) is the correct pattern. Never use a bare catch (e) or catch (e: any).

// Correct
try {
  await someOperation();
} catch (e: unknown) {
  if (e instanceof Web3AgentError) {
    // handle structured error
  } else if (e instanceof Error) {
    // handle generic error
  } else {
    // handle unknown
  }
}

// Wrong — don't do this
try {
  await someOperation();
} catch (e) {
  console.error(e.message); // TypeScript error: 'e' is of type 'unknown'
}

Don't swallow errors silently

If you catch an error and can't handle it meaningfully, re-throw it. Silent catches hide bugs.

// Wrong
try {
  await riskyOperation();
} catch {
  // nothing
}

// Better
try {
  await riskyOperation();
} catch (e: unknown) {
  console.error("riskyOperation failed:", e);
  throw e;
}

Check ok before accessing data

When calling tools directly via runtime.invokeTool(), always check the ok field before accessing data:

const result = await runtime.invokeTool("resolve_token", { symbol: "USDC", chainId: 8453 });

if (!result.ok) {
  throw new Error(`resolve_token failed: [${result.error.code}] ${result.error.message}`);
}

const token = result.data;

The SDK functions (getSwapQuote, prepareSwapIntent, etc.) do this check internally and throw Web3AgentError for you, so you only need to handle the thrown error.

Error Handling in CLI Context

When running as an MCP server via npx web3agent, errors are written to stderr with a [module-name] prefix. The MCP client (Claude Code, Cursor, etc.) receives the structured error response and decides how to surface it to the user.

[orbs] swap quote failed: insufficient liquidity for USDC→WETH on Base
[policy] transaction rejected: $450 exceeds single-tx limit of $100
[wallet] no active wallet — set PRIVATE_KEY or call wallet_activate

stdout is reserved exclusively for the MCP protocol. Never write to stdout in custom integrations.