Concept · Errors

Typed errors + recovery

The SDK throws three kinds of errors. Each is a typed class you can catch by reference — no string sniffing, no opaque viem ContractFunctionRevertedError swallowing the signal.

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#

components/ClaimButton.tsx
ts
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 to err.context.unlocksAt
  • VestingNotFoundError → 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.

See also