Back to Fhevm

Error handling

sdk/js-sdk/docs/error-handling.md

0.14.0-26.3 KB
Original Source

Error handling

The SDK throws typed error classes. Each one sets a distinct name and carries structured context — there are no numeric error codes. Identify an error by its name (or instanceof), not by parsing its message.

ts
try {
  await client.decryptValue({ transportKeyPair, encryptedValue, contractAddress, signedPermit });
} catch (err) {
  if (err instanceof Error && err.name === 'AclUserDecryptionError') {
    // the user isn't allowed to decrypt this value
  }
}

Every SDK error extends the built-in Error, so err.message, err.name, and err.cause are always available. Domain errors add extra getters (a contract address, a list of handles, an HTTP status) depending on the error.

Categories

Errors fall into three families:

  • Validation errors — you passed something malformed (a bad address, an invalid handle). Thrown synchronously, before any network call.
  • FHEVM domain errors — the operation is well-formed but not permitted or not possible (Access Control List (ACL) denies decryption, an input proof is invalid).
  • Relayer errors — the request to the Relayer failed, timed out, was aborted, or came back rejected.

Validation errors

Thrown when an argument doesn't parse. Catch these to give users immediate feedback without a round-trip.

nameMeaning
AddressErrorA value isn't a valid Ethereum address.
ChecksummedAddressErrorAn address failed EIP-55 checksum validation.
FheTypeErrorAn unknown or invalid FHE type name/id.
InvalidTypeErrorA value didn't match the expected type.
InvalidPropertyErrorAn object was missing or had an invalid property.
InvalidUrlErrorA URL isn't valid.
Sha256VerificationErrorA downloaded WASM/worker asset failed its SHA-256 check.

FHEVM domain errors

Thrown by encrypt, decrypt, and configuration operations.

nameThrown when…
AclUserDecryptionErrorThe user isn't allowed to privately decrypt this handle.
AclPublicDecryptionErrorOne or more handles aren't allowed for public decryption. Exposes handles.
EncryptionErrorAn encryption operation failed.
ZkProofErrorThe zero-knowledge proof is invalid.
InputProofErrorThe input proof is invalid.
FhevmHandleErrorA handle (encrypted value) couldn't be parsed.
TooManyHandlesErrorMore than 256 variables packed into one input ciphertext.
FhevmConfigErrorThe FHEVM configuration is invalid.
UnknownSignerErrorA signer address isn't in the coprocessor/KMS signer set.
ThresholdSignerErrorThe signer quorum threshold wasn't reached.
DuplicateSignerErrorA duplicate signer address was found.
TfheErrorA failure inside the TFHE WASM layer.

Relayer errors

Thrown when talking to the Relayer. Response errors carry an HTTP status; fetch errors carry the url, operation, and retry metadata.

nameThrown when…
RelayerAbortErrorThe request was aborted via an AbortSignal.
RelayerTimeoutErrorThe request exceeded its timeout.
RelayerMaxRetryErrorPolling exceeded the maximum retry count.
RelayerFetchErrorA network error or JSON parse failure.
RelayerStateErrorThe request can't run in its current state.
RelayerResponseApiErrorThe Relayer returned an API error. Exposes relayerApiError.
RelayerResponseStatusErrorAn unexpected HTTP status (e.g. a 403 from a proxy). Exposes status.
RelayerResponseInvalidBodyErrorThe response body didn't match the expected schema.
RelayerResponseInputProofRejectedErrorThe Relayer rejected the submitted input proof.

Handling patterns

Abort a slow request. Pass an AbortSignal and catch RelayerAbortError:

ts
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 30_000);

try {
  await client.encryptValues({
    contractAddress,
    userAddress,
    values,
    options: { signal: controller.signal },
  });
} catch (err) {
  if (err instanceof Error && err.name === 'RelayerAbortError') {
    // handle cancellation
  }
} finally {
  clearTimeout(timer);
}

Prefer canDecrypt* over catching ACL errors. To decide whether to offer decryption, check permission up front instead of catching AclUserDecryptionError. canDecryptValue is a standalone action, not a client method, so import it and pass the client as the first argument:

ts
import { canDecryptValue } from '@fhevm/sdk/actions/decrypt';

const { allowed } = await canDecryptValue(client, { encryptedValue, contractAddress, signedPermit });
if (allowed) {
  // safe to call decryptValue
}

Inspect the cause. Errors chain via the standard cause property, so you can reach the underlying failure:

ts
catch (err) {
  console.error(err.name, err.message, err.cause);
}