A local-first developer toolkit for Web3 errors -- structured classification, severity, actionable suggestions, and 790+ local patterns. Optional AI fallback.
When a DEX swap fails, your users see this:
execution reverted: INSUFFICIENT_OUTPUT_AMOUNT
Error: Pancake: K
ContractFunctionRevertedError: UniswapV2: LOCKED
With this library, they see this instead:
Price moved too much. Try increasing your slippage tolerance.
Low liquidity for this pair. Try a smaller swap amount.
This pair is currently locked. Try again shortly.
One import. Zero config. No API key required.
import { humanizeError } from "web3-error-humanizer";
const message = humanizeError(error);
// "Price moved too much. Try increasing your slippage tolerance."EVM-only apps can import a smaller dictionary (generic + wallets + EVM + bridges). The default import still includes every chain.
import { humanizeErrorDetailed } from "web3-error-humanizer/evm";Or get full structured output for building smart UIs:
import {
humanizeErrorDetailed,
isRecoverable,
classifyError,
} from "web3-error-humanizer";
const result = humanizeErrorDetailed(error);
// {
// message: "Price moved too much. Try increasing your slippage tolerance.",
// category: "slippage",
// severity: "warning",
// suggestion: "Increase your slippage tolerance or try a smaller amount.",
// recoverable: true,
// source: "local",
// matchedKey: "INSUFFICIENT_OUTPUT_AMOUNT",
// rawMessage: "INSUFFICIENT_OUTPUT_AMOUNT"
// }
if (result.category === "insufficient_allowance") showApproveButton();
if (result.category === "chain_mismatch") showSwitchNetworkButton();
if (result.recoverable) showRetryButton();Apps with i18n should keep the English message as a fallback and map result.category or result.matchedKey through their own translator.
- Useful without AI -- the default import is local-only, fast, and has zero runtime dependencies
- Structured, not just pretty strings -- categories, severity, suggestions, and recoverability let you build real product UX
- Optional AI instead of forced AI -- unknown errors can use the
/aientry point, but the core package never requires an API key - Safe to bundle -- ESM + CommonJS exports and TypeScript types. Use
/evmor/solanato skip unused chain dictionaries.
- 790+ local error patterns -- exact, token, and scored substring matches, no API calls needed
- Structured error output -- category, severity, suggestion, recoverability, and
matchedKeyfor every error - 16 error categories --
user_rejection,insufficient_funds,slippage,gas,network,bridge, and more - Zero dependencies -- the main entry point has no runtime dependencies
- Isolated instances --
createHumanizer({ chain, patterns })for Next.js, tests, and multi-chain apps - AI fallback -- unknown errors optionally analyzed by GPT-4o-mini (separate import, not for swap confirm)
- viem-compatible -- deep error extraction for nested blockchain errors (viem is optional)
- Extensible -- add your own patterns with
createHumanizer({ patterns })or process-wideaddPattern() - Dual module -- ESM and CommonJS, full TypeScript types included
| Protocol | Errors Covered |
|---|---|
| Uniswap V2/V3/V4 | K, INSUFFICIENT_OUTPUT_AMOUNT, EXPIRED, LOCKED, SPL, Hook errors, and more |
| PancakeSwap | K, INSUFFICIENT_LIQUIDITY, TRANSFER_FAILED, etc. |
| SushiSwap | K, INSUFFICIENT_OUTPUT_AMOUNT, EXPIRED |
| Curve Finance | Insufficient output/input, slippage, math errors |
| Balancer | Insufficient liquidity, paused pools, swap disabled |
| 1inch | minReturn, ReturnAmountIsNotEnough, insufficient liquidity |
| DODO | Insufficient output/input, liquidity errors |
| KyberSwap | Insufficient output/input, liquidity errors |
| Aave V3 | VL_* errors (borrowing, supply caps, health factors) |
| Account Abstraction (ERC-4337) | AA10-AA51 (EntryPoint errors, paymaster, validation) |
| Solana / Jupiter | Program errors, slippage, compute budget, blockhash |
| LayerZero | Bridge errors, token unavailability, message blocking |
| Li.Fi / Stargate | Route errors, slippage, amount limits |
| Arbitrum / Optimism | Retryable tickets, L2 execution, fee errors |
| MetaMask/EIP-1193 | 4001, 4100, 4900, -32603, and all standard codes |
| WalletConnect/Reown | USER_REJECTED, SESSION_EXPIRED, APKT001-APKT010 |
| Gnosis Safe | GS000-GS031 (initialization, signatures, owners) |
| ERC20/721/1155 | ERC-6093 standard errors, allowance, balance, transfer |
| Gas/Network | Underpriced, out of gas, timeout, replacement errors |
| Hardware Wallets | Ledger, Trezor connection and signing errors |
| Multi-chain Wallets | Phantom, TronLink, Sui, Aptos, TON, Bitcoin wallets |
npm install web3-error-humanizerAlso works with
pnpm addandyarn add. Zero dependencies for the main entry point. Installopenaiseparately if you want AI fallback. The published package runs on Node.js >= 20. Publishing a release withsemantic-releasecurrently needs Node.js >= 22.14.
import { humanizeError } from "web3-error-humanizer";
try {
await contract.write.swap([...]);
} catch (error) {
const message = humanizeError(error);
console.log(message);
// "Price moved too much. Try increasing your slippage tolerance."
}import { Web3ErrorHumanizer } from "web3-error-humanizer/ai";
const humanizer = new Web3ErrorHumanizer({
openaiApiKey: process.env.OPENAI_API_KEY!,
});
try {
await contract.write.swap([...]);
} catch (error) {
const message = await humanizer.humanize(error);
// Local match -> instant response
// Unknown error -> AI generates response
}Requires
openaias a peer dependency. The/aientry point only sends sanitized error text to OpenAI when you configure an API key. The default import never makes network requests.
Every matched error is classified into one of 16 categories:
| Category | Description | Severity | Recoverable |
|---|---|---|---|
user_rejection |
User cancelled/rejected in wallet | info |
Yes |
insufficient_funds |
Not enough balance or gas | error |
Yes |
insufficient_allowance |
Token needs approval first | warning |
Yes |
slippage |
Price moved beyond tolerance | warning |
Yes |
liquidity |
Pool has no/low liquidity | error |
Yes |
gas |
Gas estimation or pricing failed | error |
Yes |
nonce |
Transaction ordering issue | warning |
Yes |
network |
RPC / connection problems | error |
Yes |
contract_error |
Smart contract reverted | error |
No |
timeout |
Transaction/request timed out | warning |
Yes |
wallet_connection |
Wallet not connected/locked | error |
Yes |
chain_mismatch |
Wrong network selected | warning |
Yes |
protocol_limit |
Supply/borrow caps, paused state | error |
Yes |
signature |
Signing failed | error |
Yes |
bridge |
Cross-chain bridge errors | error |
Yes |
unknown |
Unrecognized error | error |
No |
import {
humanizeErrorDetailed,
classifyError,
isRecoverable,
getSuggestion,
} from "web3-error-humanizer";
try {
await sendTransaction();
} catch (err) {
const result = humanizeErrorDetailed(err);
showToast(result.message);
if (result.category === "insufficient_allowance") {
showApproveButton();
} else if (result.category === "chain_mismatch") {
showSwitchNetworkButton();
} else if (result.category === "insufficient_funds") {
showAddFundsLink();
} else if (result.recoverable) {
showRetryButton();
}
analytics.track("tx_error", {
category: result.category,
severity: result.severity,
recoverable: result.recoverable,
raw: result.rawMessage,
});
}import {
classifyError,
isRecoverable,
getSuggestion,
getErrorSeverity,
} from "web3-error-humanizer";
const category = classifyError(error); // "slippage"
const canRetry = isRecoverable(error); // true
const nextStep = getSuggestion(error); // "Increase your slippage tolerance or try a smaller amount."
const severity = getErrorSeverity(error); // "warning"addPattern() mutates a process-wide registry. Prefer createHumanizer() when you need custom patterns, a chain hint, or test isolation:
import { createHumanizer } from "web3-error-humanizer";
const humanizer = createHumanizer({
chain: "evm",
fallbackMessage: "Swap failed. Please try again.",
patterns: {
MY_DEX_ERROR: { message: "This pool is paused.", category: "protocol_limit" },
},
});
const result = humanizer.humanizeDetailed(error);chain only narrows protocol-specific codes (for example Aave 26 vs Jupiter 0x1771). Shared wallet and rejection patterns still match. Omit chain to use the full dictionary.
import { humanizeErrorDetailed } from "web3-error-humanizer";
try {
await walletClient.writeContract({ /* ... */ });
} catch (error) {
const result = humanizeErrorDetailed(error);
toast.error(result.message);
if (result.category === "insufficient_allowance") openApprove();
else if (result.category === "chain_mismatch") openSwitchNetwork();
else if (result.recoverable) showRetry();
}Provide swap context for smarter AI responses. Do not call /ai on the swap confirm path -- keep unmatched errors on the generic fallback:
const message = await humanizer.humanize(error, {
fromToken: "USDC",
toToken: "PEPE",
amount: "1000",
slippage: "0.5%",
network: "Ethereum",
});
// "PEPE's price is changing rapidly. Increase slippage to 1-2% or try a smaller amount."| Function | Returns | Description |
|---|---|---|
humanizeError(error, fallback?) |
string |
Human-friendly message, or fallback |
humanizeErrorLocal(error) |
string | null |
Human-friendly message, or null if no match |
humanizeErrorDetailed(error, fallback?) |
HumanizedResult |
Rich object with category, severity, suggestion |
classifyError(error) |
ErrorCategory |
Error category without humanizing |
isRecoverable(error) |
boolean |
Whether the user can fix this |
getSuggestion(error) |
string |
Actionable next step |
getErrorSeverity(error) |
ErrorSeverity |
"error" / "warning" / "info" |
extractRawMessage(error) |
string |
Raw message from any error shape |
createHumanizer(options?) |
LocalHumanizer |
Isolated registry with optional chain and patterns |
addPattern(key, msg, category?) |
void |
Add a custom pattern at runtime (process-wide) |
addPatterns(map) |
void |
Batch add patterns (process-wide) |
resetCustomPatterns() |
void |
Restore built-ins and rebuild the lookup index |
getLocalErrorCount() |
number |
Total patterns in registry |
hasLocalPattern(key) |
boolean |
Check if a pattern exists |
getLocalPatterns() |
string[] |
List all pattern keys |
Class-based (AI fallback): Import Web3ErrorHumanizer from web3-error-humanizer/ai. See API.md for full documentation with examples.
flowchart TD
A["Caught Error"] --> B["Extract Message"]
B --> C{"Local Dictionary\n790+ patterns"}
C -->|match found| D["Instant Response\nfree, less than 1ms"]
C -->|no match| E{"AI configured?"}
E -->|yes| F["OpenAI API\npaid, ~500ms"]
E -->|no| G["Fallback Message"]
- Extract -- Pulls the raw error message from viem
BaseError, ethers error objects, EIP-1193 codes, or plain strings - Match locally -- exact phrase/code, then embedded RPC/hex tokens, then scored substring matches (category priority, then length)
- AI fallback -- Optional
/aiimport only. Unknown errors can stay on the generic fallback. - Fallback -- Returns a configurable default message if nothing else matches
- Local matching is only as good as the current dictionary. Unknown protocol-specific errors still need new patterns. Leaving them on the fallback is fine.
- Generic single-word keys such as
TIMEOUTare exact-only so they do not hijack unrelated messages. addPattern()andaddPatterns()mutate a shared in-memory registry for the current process. PrefercreateHumanizer({ patterns })in apps and tests.resetCustomPatterns()restores built-ins and rebuilds the index.- If you use
web3-error-humanizer/ai, do not pass secrets in error context that you would not want sent to your model provider. - For high-volume AI usage, consider caching responses for repeated errors.
If the app already has chain-aware helpers and t() keys, do not replace that layer in one shot:
- Call
humanizeErrorDetailed()orcreateHumanizer({ chain }).humanizeDetailed()beside the existing parser. - If the app already has a translation key, keep it.
- Otherwise use
category/matchedKeyto drivet()and CTAs. - Keep
/aioff the swap confirm path.
- Full API Reference -- every function documented with examples and types
- Framework Examples -- Next.js, Node.js, CommonJS integration patterns
- Contributing Guide -- how to add error patterns and submit PRs
MIT © halilatilla