Errors
All SDK error classes, codes, and the matchZamaError utility.
All SDK errors extend ZamaError and carry a .code string you can match on. Catch them with instanceof or use matchZamaError for exhaustive handling.
Import
import {
ZamaError,
matchZamaError,
SigningRejectedError,
SigningFailedError,
EncryptionFailedError,
DecryptionFailedError,
TransactionRevertedError,
InvalidTransportKeyPairError,
TransportKeyPairExpiredError,
NoCiphertextError,
KeyWrappingError,
TransportKeyPairChangedError,
PreparedPermitChainMismatchError,
PreparedPermitExpiredError,
RelayerRequestFailedError,
NotEntitledError,
RpcRateLimitError,
ConfigurationError,
InsufficientConfidentialBalanceError,
InsufficientERC20BalanceError,
InsufficientAllowanceError,
BalanceCheckUnavailableError,
ERC20ReadFailedError,
DelegationSelfNotAllowedError,
DelegationDelegateEqualsContractError,
DelegationExpiryUnchangedError,
DelegationNotFoundError,
DelegationExpiredError,
DelegationCooldownError,
DelegationContractIsSelfError,
DelegationExpirationTooSoonError,
DelegationNotPropagatedError,
SignerRequiredError,
SignerNotConfiguredError,
WalletNotConnectedError,
WalletAccountNotReadyError,
ChainMismatchError,
AclPausedError,
} from "@zama-fhe/sdk";matchZamaError
Pattern-match on error codes instead of chaining instanceof checks. Returns the handler's return value, or undefined if the error is not a ZamaError and no _ wildcard is provided.
error
unknown
The caught error
handlers
{ [K in ErrorCode]?: (e: ErrorForCode[K]) => T } & { _?: (e: unknown) => T }
Map of error codes to handler functions
The _ wildcard catches any ZamaError not explicitly handled. Each handler receives the error class for its code, so subclass fields like InsufficientConfidentialBalanceError.available or RelayerRequestFailedError.statusCode are available without a cast.
Error summary
SigningRejectedError
SIGNING_REJECTED
User rejected the wallet signature
SigningFailedError
SIGNING_FAILED
Wallet signature failed (connectivity, firmware)
EncryptionFailedError
ENCRYPTION_FAILED
FHE encryption failed in the WASM runtime
EncryptOffloadUnavailableError
ENCRYPT_OFFLOAD_UNAVAILABLE
offloadEncrypt: true required the encrypt worker, which is unavailable
DecryptionFailedError
DECRYPTION_FAILED
FHE decryption failed
TransactionRevertedError
TRANSACTION_REVERTED
On-chain transaction reverted (includes failed ERC-20 approvals during shield)
UnshieldAlreadyFinalizedError
UNSHIELD_ALREADY_FINALIZED
The unwrap request behind a resumed unshield was already finalized — funds delivered, nothing to resume
InvalidTransportKeyPairError
INVALID_KEYPAIR
Relayer rejected transport key pair (stale or malformed)
TransportKeyPairExpiredError
KEYPAIR_EXPIRED
Transport key pair expired — user must re-sign
RevokedKmsContextError
REVOKED_KMS_CONTEXT
Permit's KMS context revoked on-chain; the automatic recovery could not restore a usable permit
NoCiphertextError
NO_CIPHERTEXT
No encrypted balance for this account
KeyWrappingError
KEY_WRAPPING_FAILED
At-rest encryption or decryption of the transport private key failed (transportKeyPairDerivationSecret)
TransportKeyPairChangedError
TRANSPORT_KEY_PAIR_CHANGED
Transport key pair changed between preparePermit and registerPermit
PreparedPermitChainMismatchError
PREPARED_PERMIT_CHAIN_MISMATCH
The chain embedded in prepared.eip712 doesn't match the chain registerPermit is running against
PreparedPermitExpiredError
PREPARED_PERMIT_EXPIRED
A prepared permit's validity window elapsed before its signature was registered
RelayerRequestFailedError
RELAYER_REQUEST_FAILED
Relayer HTTP request failed
NotEntitledError
NOT_ENTITLED
Direct signer lacks ACL permission to decrypt this encrypted value (don't retry; delegated path → DelegationNotPropagatedError)
RpcRateLimitError
RPC_RATE_LIMITED
Consumer's RPC provider rate-limited an on-chain read (HTTP 429 / -32005; retry)
ConfigurationError
CONFIGURATION
Invalid SDK configuration or FHE runtime failed to initialize
InsufficientConfidentialBalanceError
INSUFFICIENT_CONFIDENTIAL_BALANCE
Confidential balance too low for transfer or unshield
InsufficientERC20BalanceError
INSUFFICIENT_ERC20_BALANCE
ERC-20 balance too low for shield
InsufficientAllowanceError
INSUFFICIENT_ALLOWANCE
ERC-20 allowance too low for a manual wrap (approve first)
BalanceCheckUnavailableError
BALANCE_CHECK_UNAVAILABLE
Balance validation impossible (no stored permits)
ERC20ReadFailedError
ERC20_READ_FAILED
Public ERC-20 read failed (network or contract error)
DelegationSelfNotAllowedError
DELEGATION_SELF_NOT_ALLOWED
Delegate equals connected wallet
DelegationDelegateEqualsContractError
DELEGATION_DELEGATE_EQUALS_CONTRACT
Delegate equals contract address
DelegationExpiryUnchangedError
DELEGATION_EXPIRY_UNCHANGED
New expiry matches the current value
DelegationNotFoundError
DELEGATION_NOT_FOUND
No active delegation exists
DelegationExpiredError
DELEGATION_EXPIRED
Delegation has expired
DelegationCooldownError
DELEGATION_COOLDOWN
Same-block delegate/revoke not allowed
DelegationContractIsSelfError
DELEGATION_CONTRACT_IS_SELF
Contract address equals caller
DelegationExpirationTooSoonError
DELEGATION_EXPIRATION_TOO_SOON
Expiration date less than 1 hour in the future
DelegationNotPropagatedError
DELEGATION_NOT_PROPAGATED
Delegated decrypt failed transiently (gateway not synced yet, or delegator ACL read stale) — retry
SignerNotConfiguredError
SIGNER_NOT_CONFIGURED
SDK operation needs a signer but none is configured
WalletNotConnectedError
WALLET_NOT_CONNECTED
Signer exists but has no connected wallet account
WalletAccountNotReadyError
WALLET_ACCOUNT_NOT_READY
Async signer adapter has not resolved its account yet
ChainMismatchError
CHAIN_MISMATCH
Signer and provider are on different chains
AclPausedError
ACL_PAUSED
ACL contract is paused
Error details
SignerNotConfiguredError
Code: SIGNER_NOT_CONFIGURED
Thrown when a write, sign, or decrypt operation is called on an SDK instance configured without a signer. The error carries the operation name that was attempted.
How to handle: Reconfigure the SDK with a signer.
WalletNotConnectedError
Code: WALLET_NOT_CONNECTED
Thrown when a signer adapter is configured but does not currently have a connected wallet account.
How to handle: Prompt the user to connect or unlock their wallet.
ChainMismatchError
Code: CHAIN_MISMATCH
Thrown when the signer and provider resolve to different chains during an operation. The error carries operation, signerChainId, and providerChainId.
How to handle: Prompt the user to switch their wallet to the chain the operation targets, then retry.
SigningRejectedError
Code: SIGNING_REJECTED
Thrown when the user clicks "Reject" in their wallet popup during an EIP-712 signature request (transport key pair generation or session signing). The error carries operation (the SDK method that was signing, e.g. "grantPermit"), and, when the wallet/provider's raw error exposes them, rpcCode and walletErrorName.
How to handle: Re-prompt the user. The operation can be retried immediately.
SigningFailedError
Code: SIGNING_FAILED
The wallet attempted to sign but failed for a reason other than user rejection — network issues, hardware wallet firmware problems, or RPC timeouts. Like SigningRejectedError, it carries operation, and, when recoverable from the wallet/provider's raw error, rpcCode (its JSON-RPC / EIP-1193 numeric error code) and walletErrorName (the error class name the wallet/provider library threw, e.g. viem's InvalidParamsRpcError) — useful for grouping and alerting on structured fields instead of parsing message. grantPermit, grantDelegationPermit, and registerPermit failures also emit a ZamaSDKEvents.PermitError event carrying the same error before throwing; see onEvent.
How to handle: Check wallet connectivity and firmware version. Retry after the underlying issue is resolved.
EncryptionFailedError
Code: ENCRYPTION_FAILED
FHE encryption failed inside the WASM runtime. Usually caused by missing WASM support or restrictive CSP headers.
How to handle: Verify your Content Security Policy includes wasm-unsafe-eval. Check that the browser supports WebAssembly.
EncryptOffloadUnavailableError
Code: ENCRYPT_OFFLOAD_UNAVAILABLE
Thrown only under web({ offloadEncrypt: true }): encryption was required to run in a Web Worker, but the worker could not spawn, missed a lifecycle deadline, or crashed. The strict mode rejects rather than finishing the work on the main thread. The .cause carries the underlying failure.
How to handle: Fix the deployment, not the call: see the CSP requirement and offloadWorker for the usual causes. Switch to offloadEncrypt: "auto" to fall back to main-thread encryption instead.
DecryptionFailedError
Code: DECRYPTION_FAILED
FHE decryption failed. Can occur after an interrupted unshield or when the transport key pair state is corrupted.
How to handle: If this happens after a page reload during unshield, use getPendingUnshield() and resumeUnshield() to recover. Otherwise, calling sdk.permits.clear() and retrying forces a fresh transport key pair.
TransactionRevertedError
Code: TRANSACTION_REVERTED
An on-chain transaction reverted. The error .message includes the revert reason when available.
How to handle: Inspect the revert reason. Common causes: insufficient balance or an expired operator approval. Finalizing an already-finalized unwrap through resumeUnshield() or unshield() throws the more specific UnshieldAlreadyFinalizedError instead.
UnshieldAlreadyFinalizedError
Code: UNSHIELD_ALREADY_FINALIZED
Thrown by resumeUnshield() when the unwrap request no longer exists on-chain: it was already finalized, so the underlying ERC-20 tokens were delivered. unshield() and unshieldAll() throw it too when a concurrent finalize wins the race. The SDK clears the persisted pending-unshield state before throwing, so getPendingUnshield() returns null afterwards. The error carries unwrapTxHash and unwrapRequestId.
How to handle: Treat it as completion, not a failure: dismiss the "resume unshield" prompt and refresh balances. useResumeUnshield invalidates the affected queries automatically. Do not retry; nothing is left to finalize.
InvalidTransportKeyPairError
Code: INVALID_KEYPAIR
The relayer rejected the transport key pair. This happens when the key pair is malformed or was generated for a different chain.
How to handle: Clear credentials and prompt the user to re-sign. The SDK generates a fresh transport key pair on the next operation.
RevokedKmsContextError
Code: REVOKED_KMS_CONTEXT
The KMS context the permit was signed under has been revoked on-chain, so the permit is permanently unusable. The SDK recovers automatically: it evicts the dead permit, re-grants under the current context (one wallet prompt), and retries the decrypt once. This error surfaces in two cases: the retry failed the same way (typically because the on-chain validity check is cached for up to 15 minutes, so a just-revoked context can keep failing across that window), or the re-grant itself failed because the configured signer cannot sign (the signing failure is attached as cause).
How to handle: cause is always present, check its type to tell the two cases apart. If cause is a SigningFailedError, the re-grant failed: establish a new permit before retrying; the other permits of the scope were kept. In a session whose signer cannot sign, that means re-running the offline permit flow: preparePermit, sign out-of-process, registerPermit. Otherwise the retry failed against the cached validity window: wait ~15 minutes and trigger the decrypt again, the SDK re-runs the recovery on the next call. No manual credential cleanup is needed, the dead permit was already evicted.
TransportKeyPairExpiredError
Code: KEYPAIR_EXPIRED
The transport key pair exceeded its TTL (default: 30 days). The user needs to sign again to generate a new one.
How to handle: Prompt the user to re-sign. Adjust transportKeyPairTTL in the SDK constructor if the default TTL of 30 days is not appropriate.
NoCiphertextError
Code: NO_CIPHERTEXT
The account has no encrypted balance on-chain — it has never shielded tokens for this contract. This is different from a zero balance.
How to handle: Show an empty state in your UI prompting the user to shield tokens. Do not display "0" — there is no balance to show.
KeyWrappingError
Code: KEY_WRAPPING_FAILED
At-rest encryption or decryption of the transport private key failed. This is the key-protection feature behind transportKeyPairDerivationSecret, unrelated to token wrapping (shield/unshield).
Thrown by grantPermit, grantDelegationPermit, warmTransportKeyPair, and warmTransportKeyPairScope, and by any operation that resolves credentials under the hood (decryptValues, token.balanceOf). hasPermit and hasDelegationPermit never throw it: they return false, so a permit check stays a safe read (see Permit Model).
How to handle: A wrong secret without a scope never throws: the SDK treats it as a cache miss and regenerates. So without a transportKeyPairScope, the cause is one of:
An encrypted entry was read with no
transportKeyPairDerivationSecretconfigured. Restore the secret, or callsdk.permits.clear()to downgrade to plaintext deliberately.The entry was written by a newer wrapping scheme version. Upgrade the SDK, or call
sdk.permits.clear()to discard the entry deliberately.crypto.subtleis unavailable in the environment.
With a transportKeyPairScope, no mismatch self-heals: regenerating would clobber the entry every other signer in the scope reads. Verify that every instance sharing the scope is configured with the same transportKeyPairDerivationSecret, and that none is missing it. See Security Model for the mechanism and migration steps.
TransportKeyPairChangedError
Code: TRANSPORT_KEY_PAIR_CHANGED
Thrown by registerPermit when the transport key pair changed between preparePermit and registerPermit — a TTL expiry or eviction happened in between. The prepared EIP-712 payload is signed against the old key pair's public key; registering it under a different one would bind the permit to the wrong key.
How to handle: Call preparePermit again to rebind the request to the current key pair, then repeat the external signing step.
PreparedPermitChainMismatchError
Code: PREPARED_PERMIT_CHAIN_MISMATCH
Thrown by registerPermit when the chain embedded in prepared.eip712 doesn't match the chain the SDK is currently configured for. The error carries preparedChainId and activeChainId.
How to handle: Register the permit while the SDK is configured for the chain it was prepared for, or call preparePermit again against the currently active chain.
PreparedPermitExpiredError
Code: PREPARED_PERMIT_EXPIRED
A prepared permit's validity window (startTimestamp + durationDays) elapsed before its signature was registered — the out-of-process signing ceremony took longer than the permit's lifetime.
How to handle: Call preparePermit again for a fresh validity window. Consider a longer durationDays (up to 365) if approval routinely takes this long.
RelayerRequestFailedError
Code: RELAYER_REQUEST_FAILED
The HTTP request to the relayer failed. The error exposes .statusCode for further diagnosis. On rate-limited responses (HTTP 429) it also surfaces the relayer's back-pressure: .retryable is true, and .retryAfter carries the server's suggested delay in seconds when the response included a Retry-After header (otherwise undefined).
How to handle: For a 429, wait .retryAfter seconds (when present) before retrying instead of inventing a backoff. Otherwise, check relayerUrl in your transport config, verify the auth option if using API key authentication, and check relayer service health.
Browser note: back-pressure is reliable server-side. In the browser, the relayer's 429 is served cross-origin without CORS headers, so
.retryAfter(and sometimes.statusCode) may be unavailable — fall back to your own backoff.
NotEntitledError
Code: NOT_ENTITLED
The configured signer is not entitled to decrypt the encrypted value: the relayer's ACL check (persistAllowed) denied it. This is a terminal, non-retryable condition — the account needs an on-chain ACL grant (FHE.allow) before it can decrypt. It is distinct from a transient infrastructure failure, so entitlement-aware consumers (e.g. server-side indexers) can branch deterministically instead of pre-checking on-chain out of band or string-matching messages.
The SDK derives this typed error from the relayer's own authoritative ACL check — it adds no extra on-chain reads.
Scope:
NotEntitledErrorcovers the direct signer (user-decrypt path) not being entitled. The rarer "the dapp contract itself is not authorized for this encrypted value" case is a dapp misconfiguration and currently surfaces asDecryptionFailedError, so retry-aware consumers should not treat everyDecryptionFailedErroras transient.
Delegated path: on a delegated decrypt, a "not entitled" verdict comes from the delegator's
persistAllowedL1 read, which returnsfalsetransiently while a just-granted delegation propagates or when the RPC serves a stale block. That case is not terminal — it surfaces as the retryableDelegationNotPropagatedErrorinstead, mirroring the delegated-500 handling.
The error carries encryptedValue, contractAddress, and account.
How to handle: Do not retry the same request. Wait until the encrypted value is granted to the account on-chain (e.g. a later block / backfill), then decrypt again.
RpcRateLimitError
Code: RPC_RATE_LIMITED
The consumer's RPC provider rate-limited an on-chain read the SDK performs during decryption (e.g. the ACL check) — surfaced as HTTP 429 or the JSON-RPC -32005 ("limit exceeded") code. This is an RPC-endpoint problem, not a decryption or entitlement failure, and the operation is safe to retry (ideally with backoff). It is separate from the relayer's own back-pressure, which remains a RelayerRequestFailedError. The error exposes retryAfter (seconds) when the provider supplies a hint — a numeric value or a Retry-After header (e.g. viem's HttpRequestError).
How to handle: Back off and retry. If it persists, raise your RPC provider's rate limit or switch to a higher-throughput endpoint.
"No balance" vs "zero balance"
These are distinct states:
NoCiphertextError— the account has never shielded tokens. There is no encrypted balance to decrypt. Show an empty state like "No confidential balance".Balance of
0n— the account has shielded before but currently holds zero. Show "Balance: 0".
ConfigurationError
Code: CONFIGURATION
Thrown when the SDK configuration is invalid (e.g. forbidden chain ID, unsupported signer type) or when the FHE runtime fails to initialize (e.g. missing WASM support, terminated relayer).
How to handle: Check your transport config, CSP headers, and that the relayer has not been terminated. If the error mentions runtime initialization, verify WASM support and wasm-unsafe-eval in your CSP.
InsufficientConfidentialBalanceError
Code: INSUFFICIENT_CONFIDENTIAL_BALANCE
The decrypted confidential balance is less than the requested amount. Thrown by confidentialTransfer() and unshield() before submitting the transaction. Exposes structured details for UI display.
requested
bigint
Amount the caller requested
available
bigint
Decrypted balance at the time of the check
token
Address
Token contract address
How to handle: Show the user their current balance and the shortfall. No retry will help until the balance increases (via shielding or receiving a transfer).
InsufficientERC20BalanceError
Code: INSUFFICIENT_ERC20_BALANCE
The public ERC-20 balance is less than the requested shield amount. Thrown by shield() before submitting the transaction. This is a public read with no signing requirement, so it works for all wallet types.
requested
bigint
Amount the caller requested to shield
available
bigint
ERC-20 balance at the time of the check
token
Address
Underlying ERC-20 token contract address
How to handle: Show the user their public token balance and the shortfall. They need to acquire more tokens before shielding.
InsufficientAllowanceError
Code: INSUFFICIENT_ALLOWANCE
The ERC-20 allowance granted to the wrapper is less than the requested wrap amount. Thrown by wrap() before submitting the transaction, when you drive the manual approve + wrap flow yourself. shield() never throws this — it manages approval internally.
requested
bigint
Amount the caller requested to wrap
available
bigint
Allowance approved to the wrapper at check time
token
Address
Underlying ERC-20 token contract address
How to handle: Call approveUnderlying() (or useApproveUnderlying) for at least the wrap amount before calling wrap(). Most apps avoid this entirely by using shield(), which approves and wraps in one call.
BalanceCheckUnavailableError
Code: BALANCE_CHECK_UNAVAILABLE
Balance validation could not be performed. For confidential operations (confidentialTransfer, unshield), this means no stored permits exist and the SDK cannot decrypt the balance without prompting a wallet signature. For shield, this means the ERC-20 balance read failed.
How to handle: Either call sdk.permits.grantPermit([token.address]) first to sign permits, or pass skipBalanceCheck: true to bypass validation (useful for smart wallets that cannot produce EIP-712 signatures).
ERC20ReadFailedError
Code: ERC20_READ_FAILED
A public ERC-20 read (e.g. balanceOf) failed due to a network or contract error. Thrown by shield() when the pre-flight balance check cannot read the underlying token balance. This is distinct from BalanceCheckUnavailableError, which indicates missing credentials for confidential balance decryption.
How to handle: Check network connectivity and RPC endpoint health. The underlying ERC-20 contract may also be paused or unreachable. Retry the shield operation.
DelegationSelfNotAllowedError
Code: DELEGATION_SELF_NOT_ALLOWED
Thrown when attempting to delegate decryption to your own address. The ACL contract rejects delegate === msg.sender.
How to handle: Use a different delegate address.
DelegationCooldownError
Code: DELEGATION_COOLDOWN
Only one delegate or revoke operation is allowed per (delegator, delegate, contract) tuple per block.
How to handle: Wait for the next block before retrying the operation.
DelegationNotFoundError
Code: DELEGATION_NOT_FOUND
No active delegation exists for the given (delegator, delegate, contract) tuple. Thrown when attempting to revoke a non-existent delegation, and by decryptBalanceAs / batchDecryptBalancesAs (including on cache hits) when the delegation is missing or has been revoked.
How to handle: Verify the delegator, delegate, and contract addresses are correct.
DelegationExpiredError
Code: DELEGATION_EXPIRED
The delegation has expired and can no longer be used for decryption.
How to handle: Create a new delegation.
DelegationExpirationTooSoonError
Code: DELEGATION_EXPIRATION_TOO_SOON
Thrown client-side before submitting a delegateDecryption transaction when the expiration date is less than 1 hour in the future. This mirrors the on-chain ExpirationDateBeforeOneHour revert in the ACL contract.
How to handle: Choose a later expiration date (at least 1 hour from now) or omit it for a permanent delegation.
DelegationDelegateEqualsContractError
Code: DELEGATION_DELEGATE_EQUALS_CONTRACT
Thrown client-side before submitting a delegateDecryption transaction when the delegate address equals the token contract address.
How to handle: Use a different delegate address.
DelegationExpiryUnchangedError
Code: DELEGATION_EXPIRY_UNCHANGED
Thrown client-side (after an RPC read) when the new expiration date matches the current on-chain value. Saves gas by skipping a no-op transaction.
How to handle: No action needed — the delegation is already configured as requested.
DelegationContractIsSelfError
Code: DELEGATION_CONTRACT_IS_SELF
Caught from the on-chain SenderCannotBeContractAddress revert. The contract address passed to the delegation call equals the caller address.
How to handle: Verify the contract address parameter is the token contract, not the caller's address.
DelegationNotPropagatedError
Code: DELEGATION_NOT_PROPAGATED
Thrown on a delegated decrypt when either (a) the relayer returns an HTTP 500, or (b) the delegator fails the on-chain ACL check (persistAllowed returns false). The most likely cause in both cases is that the delegation was recently granted on L1 but hasn't propagated to the gateway (on Arbitrum) yet — cross-chain sync usually completes within ~10 blocks (a few seconds) — or the consumer's RPC is serving a stale block. Because it is a timing window rather than a permanent denial, it is retryable (unlike the terminal NotEntitledError on the direct user-decrypt path).
The delegated-decrypt path rides out this window with a bounded internal retry (~30s), so you rarely see this error — it surfaces only when propagation outlasts the retry budget, or when you opt out with waitForPropagation: false.
How to handle: Retry shortly — propagation normally completes within seconds. If the error persists, the gateway or relayer may be experiencing an unrelated issue.
AclPausedError
Code: ACL_PAUSED
Caught from the on-chain EnforcedPause revert. The ACL contract is paused, temporarily disabling all delegation operations.
How to handle: Wait for the ACL contract to be unpaused. This is an operator-level action — contact the protocol team if this persists.
Common problems
SigningRejectedError on every decrypt
Wallet rejects EIP-712 signature
Verify wallet supports eth_signTypedData_v4. Hardware wallets may need firmware updates.
Balance always undefined
Encrypted value is zero (never shielded)
Catch NoCiphertextError and show an empty state.
ConfigurationError on first operation
FHE runtime failed to initialize
Check CSP headers (wasm-unsafe-eval), transport config, and WASM support.
EncryptionFailedError
FHE encryption failed during an operation
Add wasm-unsafe-eval to your CSP headers.
DecryptionFailedError after page reload
Unshield was interrupted mid-flow
Call getPendingUnshield() on mount, then resumeUnshield() to complete.
TransactionRevertedError on finalize
Unwrap already finalized or invalid tx hash
Check unwrap state. If already finalized, the unshield is complete -- stop prompting to resume.
RelayerRequestFailedError
Wrong relayer URL or missing auth
Verify relayerUrl in transport config. Check the auth option if using API key auth.
NotEntitledError on decrypt
Account lacks ACL grant for the value
Don't retry. Wait for an on-chain FHE.allow grant / backfill, then decrypt again.
RpcRateLimitError on decrypt
Consumer RPC provider throttled (429/-32005)
Back off and retry. Raise your RPC rate limit or use a higher-throughput endpoint.
InsufficientConfidentialBalanceError
Confidential balance < requested amount
Show the user their balance and the shortfall. Wait for incoming transfers or shield more.
InsufficientERC20BalanceError
ERC-20 balance < requested shield amount
Show the user their public token balance. They need to acquire more tokens.
BalanceCheckUnavailableError
No stored permits for balance check
Call sdk.permits.grantPermit([token.address]) first, or pass skipBalanceCheck: true.
ERC20ReadFailedError
ERC-20 balanceOf read failed
Check network connectivity and RPC endpoint. Retry the shield.
Related
Error handling guide — practical patterns for catching and displaying errors
ZamaSDK — SDK constructor and permit management
Last updated