The three tiers#
SDK-level errors. Network, RPC, encryptor setup, ABI mismatch. Throw types like MissingEncryptorError, DeploymentAddressUnavailableError, UnsupportedChainError. Thrown before the contract is touched.
Product-level errors. The contract reverted with a known reason and the SDK decoded it into a typed class. Vesting examples: VestingNotFoundError, ClaimLockedError. Each class carries the offending values as fields so your UI can render a specific message.
SDK generic fallback. The contract reverted but the SDK has no specific typed class for that reason. You get a ContractRevertError (from @tokenops/sdk) — a cross-product fallback shared by vesting, airdrop, and disperse, not a vesting-specific class. Its context.revertName and context.revertArgscarry the decoded selector and args. The SDK never lets viem's ContractFunctionRevertedError escape unwrapped.
The catch ladder#
import {
VestingNotFoundError,
ClaimLockedError,
} from "@tokenops/sdk/fhe-vesting";
import { ContractRevertError } from "@tokenops/sdk";
try {
await manager.claim(args);
} catch (err) {
if (err instanceof VestingNotFoundError) {
// Product-level: the vesting id doesn't exist on this manager.
// err.context.vestingId is the offending value.
showInline("That vesting doesn't exist. Did you switch managers?");
} else if (err instanceof ClaimLockedError) {
// Product-level: timelock period has not yet elapsed.
showInline(`Locked until ${new Date(err.context.unlocksAt * 1000)}.`);
} else if (err instanceof ContractRevertError) {
// SDK generic fallback: an on-chain revert with no typed class. The SDK
// never surfaces viem's ContractFunctionRevertedError here — it wraps it.
// err.context.revertName / revertArgs carry the decoded selector + args.
showInline(`Contract reverted: ${err.context.revertName ?? err.message}`);
} else {
// SDK-level: network, RPC, encryptor failure, etc.
showInline("Network error. Try again.");
}
}Recovery patterns by tier#
SDK-level: retry with backoff (network), prompt the user (encryptor / wallet), or fix the deployment surface (chain config).
Product-level: recoverable per error. Examples:
ClaimLockedError→ show countdown toerr.context.unlocksAtVestingNotFoundError→ user is on the wrong manager; offer the correct address
SDK generic fallback: branch on err.context.revertName from a ContractRevertError to message specific revert reasons without importing the contract ABI — and file an issue so the SDK can add a typed class.