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 class. Vesting examples: VestingNotFoundError, ClaimLockedError, ContractRevertError. Each class carries the offending values as fields so your UI can render a specific message.

viem passthrough. Contract reverted with a reason the SDK doesn't have a specific class for. You get a viem ContractFunctionRevertedError with the raw reason string. Catch this as a last resort.

The catch ladder#

components/ClaimButton.tsx
ts
import {
  VestingNotFoundError,
  ClaimLockedError,
  ClaimNotStartedError,
} from "@tokenops/sdk/fhe-vesting";
import { ContractFunctionRevertedError } from "viem";

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: cliff not reached.
    showInline(`Locked until ${new Date(err.context.unlocksAt * 1000)}.`);
  } else if (err instanceof ContractFunctionRevertedError) {
    // viem-passthrough: contract reverted, no SDK class wrapping yet.
    showInline(`Contract reverted: ${err.shortMessage}`);
  } 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
  • ContractRevertError → refresh fee via useManagerFeeInfo, retry with the right branch
  • VestingNotFoundError → user is on the wrong manager; offer the correct address

viem passthrough: show shortMessage inline. Don't blame the user — the SDK should have a class for this; treat it as "we'll be better next release."

See also