Skip to main content

The base error

Every typed SDK error extends TrustodianBaseError, an abstract subclass of Error.
name is set to the concrete class name, and the prototype chain is fixed so instanceof works across bundlers and transpilation targets. toString() renders [CODE] message, appending (chain: …) and (operation: …) when present. toJSON() returns { code, message, chain, operation, details, cause }; subclasses that carry extra fields override it to add them.
toJSON() serializes details wholesale, and most structured loggers call it. Never put key material in details: no wallet password, device share, private key or recovery phrase. Redact details.responseBody before logging if you cannot vouch for what the server put there.

Type guard

isTrustodianError(error) narrows to TrustodianBaseError, so error.code is typed afterwards. Anything that is not a TrustodianBaseError is either a plain Error from the session layer (see Session-layer errors below) or something outside the SDK.

Error classes

Importable marks the classes you can import and use with instanceof. The others are real errors the SDK throws but does not export, so match those on error.code.
SDKErrorCode also defines UNKNOWN, which no error class currently uses. Treat an unrecognised code as unhandled rather than assuming the set is closed.

ApiError extras

ApiError is the catch-all for HTTP failures, so statusCode is how you tell them apart. Its toJSON() adds statusCode (but not responseBody). responseBody is the parsed JSON when the response had a JSON content type, otherwise the raw text, otherwise null.

SeedScopeConflictError codes

SeedScopeConflictError carries one of these codes, also exported as the type SeedScopeConflictCode:

KeyMismatchError extras

Both are added by its toJSON(). Semantically it means the reconstruction produced a key deriving derivedAddress rather than the wallet’s expectedAddress.
The current release never throws this class. Every address-match failure arrives instead as a plain error carrying code: 'ADDRESS_MISMATCH', for which isTrustodianError returns false and instanceof KeyMismatchError does not match. Detect it with isDeviceShareRecoveryError(error) or by checking error.code === 'ADDRESS_MISMATCH'.
The address check runs before every signature, so a mismatch never reaches a signer. For a seed wallet it means the derivation is wrong, and retrying will not fix it. Where a device share is involved, an imported wallet or a vault activation secret, it usually means that share is stale, and the SDK discards it before rebuilding from the server and recovery shares.

HTTP status mapping

Only these statuses get a dedicated class; everything else becomes ApiError. The message is extracted from the body in this order: a string body as-is, then message (string, or the first element of an array, expanding constraints for class-validator payloads), then error, then the first entry of errors, and finally response.statusText.

Organization policy codes

OrgPolicyRejectedError is discriminated by platformCode, one of four strings also exported at runtime as ORG_POLICY_CODES. The mapping that throws this error is not switched on yet, so read Organization policies before you branch on it.
The class also carries statusCode and responseBody, and its toJSON() adds platformCode and statusCode. Policy checks apply to non-Master organization members only: Master accounts are exempt server-side, and individuals never have policies attached. See Organization policies.
Typed mapping of policy rejections is currently disabled in the SDK while the response shape is being finalised. Until it is enabled, a policy rejection arrives as an ordinary ApiError and the code is in responseBody. Check both paths when you catch it.

Hardware wallet errors

These come from accounts whose root is a hardware wallet. The classes are in the table above; see Hardware Wallets for the flow and what to do with each.

HardwareAccountRefusedError codes

SDKErrorCode is not exported from the package, so compare error.code as a string, for example error.code === 'HARDWARE_WALLET_DISABLED'.

Intent mismatch

SignatureIntentMismatchError is raised before any private key is rebuilt, so a tampered or misdescribed payload never reaches a signer. It fires when the payload about to be signed does not reproduce the digest the server handed over, or does not pay what the caller declared. The recipient is always compared. The amount is compared only when the asset’s scale is known; an unknown scale skips that half rather than guessing and failing an honest order. With no intent of your own, the SDK falls back to the order’s own declared recipient and value, the same figures you showed the user. Every chain raises the same error from its own payload check.
Treat this error as a security event, not a transient failure. It means the payload and the declared intent disagree. Do not retry, and do not fall back to signing without the check. Surface it, log it and investigate. Running the same check ahead of time is what preflight is for.

Session-layer errors

The session and share layer predates the typed hierarchy and throws plain errors carrying well-known message strings. They are matched by message, by error.name, or with the exported helper below.

isDeviceShareRecoveryError

Returns true for exactly the four signals the wallet password can repair: device share not on device, device share requires password, SHARE_DECRYPT_FAILED, and ADDRESS_MISMATCH. Use it to decide whether to run recovery and retry:
You usually do not need this. Passing walletPassword on the crypto call lets the gate recover silently before signing. Reach for the guard when the password is not available at the call site.

Putting it together

Ordering matters: check the specific, non-retryable classes first, then the recoverable device-share cases, then generic HTTP retries, and log whatever is left.

Key management

Sessions, shares, and what the crypto gate does before it throws

Recovery phrase

The three recovery-phrase errors in their lifecycle context

Preflight

Catch intent mismatches before you reach a signer

Organization policies

What each policy code means and how policies are configured
Last modified on October 27, 2026