Exception Handling
Overview
Errors raised during embedded-wallet operations come from three layers:
- Server-side, RFC 9457 Problem Details — JSON responses returned by the Keyban API or by the cryptographic signers behind it. Identified by a
typeURI and a stabletitle. - SDK-side, runtime errors — JavaScript exceptions thrown by
@keyban/sdk-baseand@keyban/sdk-reactbefore or after the network call. Identified by a stringcodefrom theSdkErrorCodeenum. - Generic HTTP errors — standard 4xx/5xx responses without a Keyban-specific type (validation, authentication, rate limiting at the edge). Use the HTTP status code only.
RFC 9457 obsoletes RFC 7807 but is wire-compatible — clients written against 7807 do not need to change.
Problem Details envelope
| Field | Required | Description |
|---|---|---|
type | yes | URI reference identifying the problem type. Dereferenceable to this page (see anchors below). |
title | yes | Short, human-readable summary, stable across occurrences (modulo localization). |
status | yes | HTTP status code. |
detail | yes | Human-readable explanation specific to this occurrence. |
instance | optional | URI reference for the specific occurrence (e.g. the request path with the relevant id). |
Extension members may be present (e.g. keyid) to convey context. RFC 9457 §3.2 defines how extension members are namespaced; consumers should ignore unknown keys.
SdkError envelope
SdkError extends KeybanError, both exported from @keyban/sdk-base. Each instance carries:
- a
name("SdkError"), - a
codefrom theSdkErrorCodeenum (string discriminator), - a
message(the default text shown in the catalogue, overridable), - an optional
cause(the underlying error, for example a ViemBaseErroror a fetch failure).
Match on error instanceof SdkError && error.code === SdkErrorCode.X rather than on the message string.
Client strategy
Recommended client behaviour by category. Apply the row that matches the actual response.
| Source | Symptom | Recommended client behaviour |
|---|---|---|
HTTP 400 Bad Request | Malformed payload (JSON, base64). | Fail-fast. Log the request, fix the client encoding bug, do not retry — the next attempt will fail identically. |
HTTP 401 Unauthorized / 403 Forbidden | Missing or invalid API key, expired session, organization permission denied. | Fail-fast. Re-authenticate or surface the permission error to the user. |
HTTP 404 Not Found (key/round) | Key share or DKG round-1 state missing on the server. | Treat as a state-reset signal. Recreate the share for a missing key, restart the DKG protocol from round 0 for a missing round. Do not retry the same call. |
HTTP 409 Conflict | Resource already exists or current state forbids the operation (e.g. wallet still deploying). | Fail-fast. Reconcile client state by re-reading the resource. |
HTTP 422 Unprocessable Entity | Semantic validation failure (e.g. billing item misconfigured upstream). | Fail-fast. The fix is operational, not retry-able client-side. |
HTTP 429 Too Many Requests | Quota or rate limit reached. | Surface the quota error to the user; retry only after the operator has raised the quota or the period has rolled over. Use exponential backoff if the limit is a sliding rate. |
HTTP 500 (FROST internal) | Threshold-signing protocol failure. | Fail-fast. Capture keyid and instance and escalate to support — the protocol cannot continue with the current state. |
HTTP 500 (infrastructure) | Transient backend (Redis, serialization, base64). | Retry with exponential backoff (e.g. 3 attempts, 250 ms → 1 s → 4 s). Surface to the user only if all attempts fail. |
SdkError (validation code) | Address/amount/nft-standard rejected before the request leaves the SDK. | Fail-fast. Show the input back to the user with the reason. |
SdkError (RelayFailed) | Relay endpoint returned non-2xx; cause carries the underlying message (often an on-chain revert). | Inspect cause. Revert messages are usually deterministic — fail-fast. Network/RPC timeouts may be retried once. |
SdkError (Unexpected) | Runtime invariant violated (backend payload inconsistent with schema). | Fail-fast. Report to support with the request context. |
Server-side errors (Problem Details)
FROST signer errors
Returned by the threshold-signing services that back DKG and signing operations.
| # | Title | Slug | HTTP | Emitted by |
|---|---|---|---|---|
| 1 | Base64 decode error | base64-decode-error | 400 Bad Request | ECDSA-Schnorr, EdDSA |
| 2 | Frost request json invalid | frost-request-json-invalid | 400 Bad Request | ECDSA-Schnorr, EdDSA |
| 3 | No DKG round found | no-dkg-round-found | 404 Not Found | ECDSA-Schnorr, EdDSA |
| 4 | No key found | no-key-found | 404 Not Found | ECDSA-Schnorr, EdDSA |
| 5 | Frost internal error | frost-internal-error | 500 Internal Server Error | ECDSA-Schnorr, EdDSA |
| 6 | Frost Poseidon internal error | frost-poseidon-internal-error | 500 Internal Server Error | ECDSA-Schnorr |
| 7 | Redis error | redis-error | 500 Internal Server Error | ECDSA-Schnorr, EdDSA |
| 8 | Serialize error | serialize-error | 500 Internal Server Error | ECDSA-Schnorr, EdDSA |
Base64 decode error
- HTTP status:
400 Bad Request - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#base64-decode-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#base64-decode-error",
"title": "Base64 decode error",
"status": 400,
"detail": "<error description>"
}
Frost request json invalid
- HTTP status:
400 Bad Request - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#frost-request-json-invalid - Extension members:
url
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#frost-request-json-invalid",
"title": "Frost request json invalid",
"status": 400,
"detail": "<error description>",
"url": "/signers/ecdsa-schnorr/dkg/round1"
}
No DKG round found
- HTTP status:
404 Not Found - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#no-dkg-round-found - Extension members:
keyid
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#no-dkg-round-found",
"title": "No DKG round found",
"status": 404,
"detail": "<error description>",
"instance": "/signers/ecdsa-schnorr/dkg/round1/0x4f8b…",
"keyid": "0x4f8b…"
}
No key found
- HTTP status:
404 Not Found - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#no-key-found - Extension members:
keyid
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#no-key-found",
"title": "No key found",
"status": 404,
"detail": "Cannot find an ecdsa key with id: <keyid>",
"keyid": "0x4f8b…"
}
Frost internal error
- HTTP status:
500 Internal Server Error - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#frost-internal-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#frost-internal-error",
"title": "Frost internal error",
"status": 500,
"detail": "<error description>",
"instance": "/signers/ecdsa-schnorr/dkg/round1/0x4f8b…"
}
Frost Poseidon internal error
- HTTP status:
500 Internal Server Error - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#frost-poseidon-internal-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#frost-poseidon-internal-error",
"title": "Frost Poseidon internal error",
"status": 500,
"detail": "<error description>",
"instance": "/signers/ecdsa-schnorr/dkg/round1/0x4f8b…"
}
Redis error
- HTTP status:
500 Internal Server Error - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#redis-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#redis-error",
"title": "Redis error",
"status": 500,
"detail": "An error occurred while accessing Redis"
}
Serialize error
- HTTP status:
500 Internal Server Error - Type URI:
https://docs.keyban.io/products/embedded-wallet/exceptions#serialize-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#serialize-error",
"title": "Serialize error",
"status": 500,
"detail": "An error occurred while serializing"
}
Backend service errors
Returned by the public Keyban API when org-level constraints (quota, plan features, network allowlist) reject the operation. The current implementations do not assign a type URI — clients should match on title and status.
| # | Title | Slug | HTTP |
|---|---|---|---|
| 1 | Feature not allowed | feature-not-allowed | 403 Forbidden |
| 2 | Network not allowed | network-not-allowed | 403 Forbidden |
| 3 | Quota active users limit exceeded | quota-active-users-limit-exceeded | 429 Too Many Requests |
| 4 | Quota application limit exceeded | quota-application-limit-exceeded | 429 Too Many Requests |
| 5 | Quota DPP limit exceeded | quota-dpp-limit-exceeded | 429 Too Many Requests |
Feature not allowed
- HTTP status:
403 Forbidden
{
"type": "about:blank",
"title": "Feature not allowed",
"status": 403,
"detail": "One or more requested features are not allowed for your organization. Contact your administrator."
}
Network not allowed
- HTTP status:
403 Forbidden
{
"type": "about:blank",
"title": "Network not allowed",
"status": 403,
"detail": "The requested network is not allowed for your organization. Contact your administrator."
}
Quota active users limit exceeded
- HTTP status:
429 Too Many Requests
{
"type": "about:blank",
"title": "Quota active users limit exceeded",
"status": 429,
"detail": "You have reached your monthly active users quota. Contact your administrator to increase your limit."
}
Quota application limit exceeded
- HTTP status:
429 Too Many Requests
{
"type": "about:blank",
"title": "Quota application limit exceeded",
"status": 429,
"detail": "You have reached your application limit. Contact your administrator to increase your quota."
}
Quota DPP limit exceeded
- HTTP status:
429 Too Many Requests
{
"type": "about:blank",
"title": "Quota DPP limit exceeded",
"status": 429,
"detail": "You have reached your monthly DPP quota (products and passports). Contact your administrator to increase your limit."
}
In addition to the catalog above, the API can return generic HTTP errors that are not Keyban-specific:
400 Bad Request— Zod validation failure on a request body or query parameter. Thedetailfield carries the validation message.401 Unauthorized— missing or invalidAuthorization/x-api-key.403 Forbidden— authenticated request lacks the required organization permission.404 Not Found— referenced resource (passport, certification key, agent wallet, …) does not exist or is not visible to the current organization.409 Conflict— resource state forbids the operation (e.g. wallet still deploying when adding a signer, passport already exists for the given identifier).
These responses still follow the Problem Details envelope but use type: "about:blank" and a generic title.
Client-side errors (SdkErrorCode)
Thrown by the SDK as instances of SdkError. The code field carries the discriminator value.
| # | SdkErrorCode | code value | Default message |
|---|---|---|---|
| 1 | AddressInvalid | ADDRESS_INVALID | Address is invalid |
| 2 | AmountInvalid | AMOUNT_INVALID | The specified amount is invalid |
| 3 | AmountIrrelevant | AMOUNT_IRRELEVANT | The amount provided is irrelevant and should not be included |
| 4 | AmountRequired | AMOUNT_REQUIRED | An amount is required for this operation |
| 5 | EstimateGasExecution | ESTIMATE_GAS_EXECUTION | Gas estimation failed |
| 6 | InsufficientFunds | INSUFFICIENT_FUNDS | Insufficient funds to complete the transaction |
| 7 | InvalidNftStandard | INVALID_NFT_STANDARD | Invalid NFT standard. Supported standards are ERC721 and ERC1155 |
| 8 | NftNotFound | NFT_NOT_FOUND | NFT not found with the provided contract address and token ID |
| 9 | RecipientAddressEqualsSender | RECIPIENT_ADDRESS_EQUALS_SENDER | Recipient address cannot be the same as the sender address |
| 10 | RelayFailed | RelayFailed | Relay transaction failed |
| 11 | TokenBalanceNotFound | TOKEN_BALANCE_NOT_FOUND | Token balance not found with the provided contract address |
| 12 | Unexpected | UNEXPECTED | An unexpected runtime invariant was violated |
| 13 | UnknownIframeApiError | UNKNOWN_IFRAME_API_ERROR | An unknown error occured with iframe API call |
| 14 | UnknownTransactionError | UNKNOWN_TRANSACTION_ERROR | An unknown error occurred with the transaction |
SdkErrorCode.AddressInvalid
codevalue:ADDRESS_INVALID- Default message: Address is invalid
- When raised: The Ethereum address does not conform to the expected format.
- Possible cause: Typographical errors in the address or incorrect formatting.
SdkErrorCode.AmountInvalid
codevalue:AMOUNT_INVALID- Default message: The specified amount is invalid
- When raised: The amount specified for a transaction is not acceptable.
- Possible cause: Amount is zero, negative, or exceeds allowed limits.
SdkErrorCode.AmountIrrelevant
codevalue:AMOUNT_IRRELEVANT- Default message: The amount provided is irrelevant and should not be included
- When raised: An operation does not require an amount, but one was supplied.
- Possible cause: Misuse of the API by providing unnecessary parameters.
SdkErrorCode.AmountRequired
codevalue:AMOUNT_REQUIRED- Default message: An amount is required for this operation
- When raised: An operation that requires a monetary amount did not receive one.
- Possible cause: Missing amount parameter in the function call.
SdkErrorCode.EstimateGasExecution
codevalue:ESTIMATE_GAS_EXECUTION- Default message: Gas estimation failed
- When raised: The SDK was unable to estimate the gas required for a transaction.
- Possible cause: Network issues, incorrect transaction parameters, or contract errors.
SdkErrorCode.InsufficientFunds
codevalue:INSUFFICIENT_FUNDS- Default message: Insufficient funds to complete the transaction
- When raised: The user's account balance is too low to cover the transaction amount and associated fees.
- Possible cause: Attempting a transaction with an amount exceeding the available balance.
SdkErrorCode.InvalidNftStandard
codevalue:INVALID_NFT_STANDARD- Default message: Invalid NFT standard. Supported standards are ERC721 and ERC1155
- When raised: An unsupported NFT standard was specified.
- Possible cause: Using an NFT standard other than ERC721 or ERC1155.
SdkErrorCode.NftNotFound
codevalue:NFT_NOT_FOUND- Default message: NFT not found with the provided contract address and token ID
- When raised: The NFT identified by the provided contract address and token ID does not exist.
- Possible cause: Incorrect token ID, wrong contract address, or the NFT has been burned.
SdkErrorCode.RecipientAddressEqualsSender
codevalue:RECIPIENT_ADDRESS_EQUALS_SENDER- Default message: Recipient address cannot be the same as the sender address
- When raised: A transaction was attempted where the sender and recipient are identical.
- Possible cause: User error in specifying addresses or attempting redundant transactions.
SdkErrorCode.RelayFailed
codevalue:RelayFailed- Default message: Relay transaction failed
- When raised: The backend
/v1/signers/ecdsa-schnorr/relayendpoint returned a non-2xx status. TherootErrorcarries the message returned by the API, which usually includes the on-chain revert reason (e.g. an OZ custom error selector or anexecuteRelayedrevert message). - Possible cause: Operator pool exhausted, RPC error, on-chain revert (insufficient funds, invalid relay signature, account not deployed, etc.), or a request timeout.
SdkErrorCode.TokenBalanceNotFound
codevalue:TOKEN_BALANCE_NOT_FOUND- Default message: Token balance not found with the provided contract address
- When raised: The token balance identified by the provided contract address does not exist.
- Possible cause: Incorrect wallet address, wrong contract address.
SdkErrorCode.Unexpected
codevalue:UNEXPECTED- Default message: An unexpected runtime invariant was violated
- When raised: A value expected to be non-null was null or undefined.
- Possible cause: Backend returned a payload inconsistent with its schema, or an assumption about data shape no longer holds.
SdkErrorCode.UnknownIframeApiError
codevalue:UNKNOWN_IFRAME_API_ERROR- Default message: An unknown error occured with iframe API call
- When raised: An unknown error during iframe API call.
SdkErrorCode.UnknownTransactionError
codevalue:UNKNOWN_TRANSACTION_ERROR- Default message: An unknown error occurred with the transaction
- When raised: An unknown transaction error.