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

What a CofheError carries

Two fields are always set:
  • code: CofheErrorCode — the enum value identifying the error type
  • message: string — a human-readable description of what went wrong
Four more are declared optional on the class. The property always exists; its value is undefined unless the code that threw supplied it, so narrow before using it:
  • hint?: string — an actionable suggestion for fixing the error. Set by most SDK throw sites, but not all
  • context?: Record<string, unknown> — the state that produced the error. For a ZkPackFailed from oversized inputs this holds totalBits, maxBits, and the offending items
  • cause?: Error — the inner error being wrapped. When set, its message is also appended to message as | Caused by: ...
  • apiErrorCode?: string — the raw backend error string. Set only on errors built from a decryption backend response
Because the four are optional, guard each one rather than assuming it is there:
Use isCofheError(err) to check if a caught error is a CofheError.

Common error codes

The first two codes above are raised locally by the SDK. The ACP* codes other than ACPNotFound are stable codes returned by the decryption backend and mapped onto the enum, so those errors also carry apiErrorCode.
Migrating from 0.6.x: the Permit* codes are gone. PermitNotFound is now ACPNotFound, InvalidPermitData is InvalidACPData, InvalidPermitDomain is InvalidACPDomain, and CannotRemoveLastPermit is CannotRemoveLastACP. The set also expanded: expiry, revocation, and scope denial each have their own code, so you no longer have to infer which one applied from the message.

Error handling patterns

Encryption errors

Decryption errors

Distinguishing why an ACP is invalid

The decrypt flows call ACPUtils.validate(acp) internally before submitting the request to the decryption backend. 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/acps to pre-flight the active ACP and route based on the typed reason, which avoids the thrown error path entirely:
ValidationResult.error is the typed union 'invalid-schema' | 'expired' | 'not-signed' | null. See validating ACPs for the full helper surface.
If you prefer the throwing path: ACPUtils.validate(acp) raises plain Errors with messages ACP is expired / ACP 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.