Skip to main content
All SDK operations return values directly and throw typed CofheError objects on failure. This replaces the Result wrapper pattern used by cofhejs.

Catching errors

import { isCofheError, CofheErrorCode, Encryptable } from '@cofhe/sdk';

try {
  const [encrypted] = await client
    .encryptInputs([Encryptable.uint32(42n)])
    .execute();
} catch (err) {
  if (isCofheError(err)) {
    console.error(err.code);    // CofheErrorCode enum value
    console.error(err.message); // human-readable description
  }
}

CofheError structure

Every CofheError has:
  • code — a CofheErrorCode enum value identifying the error type
  • message — a human-readable description of what went wrong
Use isCofheError(err) to check if a caught error is a CofheError.

Common error codes

Error codeWhen it occurs
ZkPackFailedencryptInputs exceeded the 2048-bit plaintext limit
PermitNotFoundNo permit found for the given chainId + account
PermitInvalidThe permit signature is invalid or expired
DecryptFailedDecryption request was rejected by the Threshold Network
NotConnectedAttempted an operation before calling client.connect(...)

Error handling patterns

Encryption errors

import { isCofheError, CofheErrorCode, Encryptable } from '@cofhe/sdk';

try {
  const encrypted = await client
    .encryptInputs([Encryptable.uint128(veryLargeValue)])
    .execute();
} catch (err) {
  if (isCofheError(err) && err.code === CofheErrorCode.ZkPackFailed) {
    console.error('Input too large — split into multiple calls');
  }
}

Decryption errors

import { isCofheError, CofheErrorCode, FheTypes } from '@cofhe/sdk';

try {
  const plaintext = await client
    .decryptForView(ctHash, FheTypes.Uint32)
    .execute();
} catch (err) {
  if (isCofheError(err) && err.code === CofheErrorCode.PermitNotFound) {
    // Create a permit and retry
    await client.permits.getOrCreateSelfPermit();
    const plaintext = await client
      .decryptForView(ctHash, FheTypes.Uint32)
      .execute();
  }
}

Distinguishing why a permit is invalid

Since @cofhe/sdk@0.5.0, the decrypt flows call PermitUtils.validate(permit) internally before talking to the Threshold Network. That helper enforces schema + signed + not-expired all at once, so when it fails the recovery path depends on which check tripped. Use the non-throwing ValidationUtils.isValid helper from @cofhe/sdk/permits to pre-flight the active permit and route based on the typed reason — this avoids the thrown error path entirely:
import { FheTypes } from '@cofhe/sdk';
import { ValidationUtils } from '@cofhe/sdk/permits';

const active = client.permits.getActivePermit();
const result = active
  ? ValidationUtils.isValid(active)
  : { valid: false, error: 'not-signed' as const };

if (!result.valid) {
  switch (result.error) {
    case 'expired':
      await client.permits.getOrCreateSelfPermit(); // create a fresh one
      break;
    case 'not-signed':
      await client.permits.getOrCreateSelfPermit(); // prompt the wallet to sign
      break;
    case 'invalid-schema':
      client.permits.removeActivePermit(); // stored payload is malformed
      break;
  }
}

const plaintext = await client
  .decryptForView(ctHash, FheTypes.Uint32)
  .execute();
ValidationResult.error is the typed union 'invalid-schema' | 'expired' | 'not-signed' | null — see Permits → Validating permits for the full helper surface.
If you prefer the throwing path: PermitUtils.validate(permit) raises plain Errors with messages Permit is expired / Permit is not signed (or a Zod schema error). These are not wrapped in CofheError, so use err.message rather than an error code to branch.