Structured errors for programmatic error handling #4281
-
|
Currently, viem's encoding errors only expose information through message strings. For example: // InvalidAddressError
"Address "0x..." is invalid."
// IntegerOutOfRangeError
"Number "123" is not in safe 256-bit unsigned integer range (0 to ...)"This makes programmatic error handling difficult — consumers have to parse strings to extract context like the invalid value, and there's no way to know which parameter caused the error when encoding multiple parameters. Use case: Form validation for transaction builders — need to map errors to specific input fields. A couple of questions:
Thoughts? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
Viem does have structured errors — they all extend Error structureEvery viem error is a
Catching and inspecting errorsimport {
BaseError,
ContractFunctionRevertedError,
ContractFunctionExecutionError,
EstimateGasExecutionError,
InsufficientFundsError,
} from 'viem';
try {
await client.simulateContract({ ... });
} catch (err) {
if (err instanceof BaseError) {
// Walk the error chain to find the root cause
const revertError = err.walk(
(e) => e instanceof ContractFunctionRevertedError
);
if (revertError instanceof ContractFunctionRevertedError) {
const errorName = revertError.data?.errorName;
// e.g., 'InsufficientBalance', 'Unauthorized'
console.log('Contract reverted with:', errorName);
console.log('Args:', revertError.data?.args);
}
// Or check for specific error types
const fundsError = err.walk(
(e) => e instanceof InsufficientFundsError
);
if (fundsError) {
console.log('Not enough ETH for gas');
}
}
}Mapping errors to form fieldsimport { BaseError, ContractFunctionRevertedError } from 'viem';
type FieldErrors = Record<string, string>;
function mapContractError(err: unknown): FieldErrors {
if (!(err instanceof BaseError)) return { _form: 'Unknown error' };
const revert = err.walk(
(e) => e instanceof ContractFunctionRevertedError
) as ContractFunctionRevertedError | null;
if (!revert?.data) return { _form: err.shortMessage };
// Map custom Solidity errors to form fields
switch (revert.data.errorName) {
case 'InsufficientBalance':
return { amount: `Insufficient balance. Have: ${revert.data.args?.[0]}` };
case 'InvalidRecipient':
return { recipient: 'Invalid recipient address' };
case 'SlippageExceeded':
return { slippage: 'Price moved beyond slippage tolerance' };
default:
return { _form: revert.data.errorName ?? err.shortMessage };
}
}Getting ABI-decoded error dataFor this to work, pass the contract ABI so viem can decode custom errors: try {
await client.simulateContract({
address: contractAddr,
abi: myContractAbi, // Include custom error definitions
functionName: 'swap',
args: [tokenA, tokenB, amount],
});
} catch (err) {
// With the ABI provided, revert.data.errorName and revert.data.args
// will be populated with decoded values
}The error hierarchy is well-structured — the key insight is that |
Beta Was this translation helpful? Give feedback.
Viem does have structured errors — they all extend
BaseErrorand carry machine-readable data. The trick is using the.walk()method to traverse the error chain.Error structure
Every viem error is a
BaseErrorwith:shortMessage— one-liner summarydetails— raw RPC error stringmetaMessages— array of contextual linesname— error class name (e.g.,ContractFunctionExecutionError).walk()— traverses the cause chain to find a specific error typeCatching and inspecting errors