> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Handle structured exceptions thrown by the Portal Android SDK using the PortalException hierarchy.

The Portal Android SDK uses a structured `PortalException` hierarchy so you can catch errors at the granularity you need — from individual error cases up to broad categories. Every exception thrown by the SDK is a subclass of `PortalException`, which itself extends `Exception`.

## Exception hierarchy

`PortalException` is a sealed class with eight category-level sealed subclasses:

| Category                      | Description                                                  |
| ----------------------------- | ------------------------------------------------------------ |
| `PortalException.Mpc`         | MPC key generation, backup, recovery, and signing operations |
| `PortalException.Storage`     | Google Drive, Passkey, and Keychain storage operations       |
| `PortalException.Wallet`      | Wallet state and availability                                |
| `PortalException.Transaction` | Transaction building and asset transfers                     |
| `PortalException.Api`         | HTTP requests, RPC calls, and API errors                     |
| `PortalException.Connect`     | PortalConnect (WalletConnect) operations                     |
| `PortalException.Provider`    | Provider request routing and validation                      |
| `PortalException.Blockchain`  | Chain ID parsing and namespace resolution                    |

## Catching exceptions

Because the hierarchy is sealed, you can catch at any level of specificity.

### Catch a specific exception

```kotlin theme={null}
try {
    portal.backupWallet(backupMethod = BackupMethods.Passkey)
} catch (e: PortalException.Mpc.BackupMethodNotConfigured) {
    println("Passkey backup is not configured: ${e.message}")
} catch (e: PortalException.Storage.PasskeyNotSupported) {
    println("This device does not support passkeys: ${e.message}")
}
```

### Catch an entire category

```kotlin theme={null}
try {
    portal.backupWallet()
} catch (e: PortalException.Mpc) {
    println("MPC operation failed: ${e.message}")
} catch (e: PortalException.Storage) {
    println("Storage operation failed: ${e.message}")
}
```

### Catch any Portal exception

```kotlin theme={null}
try {
    portal.createWallet().getOrThrow()
} catch (e: PortalException) {
    println("Portal error: ${e.message}")
}
```

### Handle errors from `Result`-returning methods

Some methods like `sendAsset` return a `Result` instead of throwing. Use `onFailure` or `exceptionOrNull()` to inspect the error:

```kotlin theme={null}
portal.sendAsset(
    chainId = "eip155:11155111",
    params = SendAssetParams(to = "0x...", amount = "0.01", token = "NATIVE")
).onSuccess { txHash ->
    println("Transaction sent: $txHash")
}.onFailure { e ->
    when (e) {
        is PortalException.Transaction.SendAssetArgumentError ->
            println("Invalid parameters: ${e.message}")
        is PortalException ->
            println("Portal error: ${e.message}")
        else ->
            println("Unexpected error: ${e.message}")
    }
}
```

### Branch on the HTTP status code

`PortalException.Api.HttpRequestFailed` exposes the status code that produced the failure, so you can separate a transient server-side error from a terminal one without matching on the message string.

| Property     | Type   | Description                                                                                     |
| ------------ | ------ | ----------------------------------------------------------------------------------------------- |
| `statusCode` | `Int?` | The HTTP status code that produced the failure, or `null` when the SDK captured no status code. |

`HttpRequestFailed` only covers status codes that aren't already routed to a more specific exception: 4xx codes of 402 and above, every 5xx, and unrecognized codes. A 400 throws `PortalException.Api.HttpBadRequest` and a 401 throws `PortalException.Api.HttpUnauthorized`, both of which are siblings of `HttpRequestFailed` rather than subclasses. `statusCode` is therefore never `400` or `401` — catch those two types directly.

`statusCode` is `null` in two situations, and neither one means "unknown error":

* **No HTTP response was received.** A socket timeout, a DNS failure, or a malformed status line never produces a status code. Codes outside the `100..599` range are reported as `null` for the same reason.
* **The request returned `200 OK` but the body carried an error envelope.** The failure is real, but the HTTP status was a success, so there is no error code to report.

Treat `null` as "there is no status code to branch on" and fall through to your default handling:

```kotlin theme={null}
portal.api.getAssets(chainId)
    .onSuccess { assets ->
        println("Assets: $assets")
    }
    .onFailure { error ->
        when (error) {
            is PortalException.Api.HttpBadRequest ->
                println("Invalid request: ${error.rawBody}")
            is PortalException.Api.HttpUnauthorized ->
                println("Unauthorized — check your Portal API key")
            is PortalException.Api.HttpRequestFailed -> when {
                error.statusCode == null ->
                    println("No status code: network failure or error envelope")
                error.statusCode in 500..599 ->
                    println("Transient server error ${error.statusCode} — retry with backoff")
                else ->
                    println("Request failed with HTTP ${error.statusCode}")
            }
            else ->
                println("Unexpected error: ${error.message}")
        }
    }
```

<Note>
  `statusCode` was added in `9.1.0`. On earlier versions the only way to identify the status code was to parse it out of `message`, which is formatted as `"<statusCode> - <responseBody>"`.
</Note>

## Absent shares versus unreadable shares

As of `9.1.0`, having no share on the device is no longer an error. `getShares()` on a `Keychain` instance returns `null` in that case; before `9.1.0` it threw `IllegalArgumentException("No user share found on device storage")`.

`getShares()` reads through a fallback chain: the current `shares` key first, then three legacy key formats kept for backward compatibility. An entry that can't be parsed doesn't fail the call — it moves on to the next format in the chain. So an unreadable entry under the current `shares` key resolves to `null` too, as long as no legacy entry is readable either.

| Situation                                                                 | Before `9.1.0`                    | `9.1.0` and later                                             |
| ------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------- |
| No share on the device                                                    | throws `IllegalArgumentException` | returns `null`                                                |
| Unreadable entry under the current `shares` key, no readable legacy entry | throws `IllegalArgumentException` | returns `null`                                                |
| Unreadable entry under the deprecated `PortalMpc.DgkResult` key           | throws `IllegalArgumentException` | throws, message `"Unable to parse share from device storage"` |

<Warning>
  Treat a `null` result as "this device has no usable share" rather than strictly "no share exists". Code that relied on the throw to detect "no wallet on this device" will now take the success path with a `null` value instead of entering its `catch` block, so check the result for `null` explicitly.
</Warning>

The `"Unable to parse share from device storage"` message is reserved for the last link in that chain — the misspelled deprecated key from an older SDK version — so in practice you'll only see it on a device mid-migration. A parse failure elsewhere in the chain surfaces as whatever the JSON parser threw, not as an `IllegalArgumentException`.

This is what makes `portal.isWalletOnDeviceOrThrow()` return `false` rather than throw when the user has no usable wallet on the current device. See [manage wallet lifecycle states](./manage-wallet-lifecycle-states) for the migration.

## Exception reference

### MPC (`PortalException.Mpc`)

| Exception                             | Description                                                                                                                                                                                                                   |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BackupShareNotFound`                 | No backup share found for the specified backup method.                                                                                                                                                                        |
| `OrganizationBackupShareNotFound`     | No organization backup share found for the specified backup method.                                                                                                                                                           |
| `WalletNotFound`                      | No wallet found for the specified backup method.                                                                                                                                                                              |
| `MpcVersionNoLongerSupported`         | The MPC operation is not supported on the current MPC version. Upgrade to `v6`.                                                                                                                                               |
| `WalletModificationAlreadyInProgress` | Another wallet modification operation (generate, backup, recover) is already running.                                                                                                                                         |
| `MpcResultError`                      | The MPC server returned an error. Exposes `id` (the error identifier string) and `error` (a `PortalError` with `id` and `message`). See [MPC error codes](/resources/error-codes) for the full list of server-side error IDs. |
| `UnableToDeriveShareId`               | Could not derive a share ID from the generated share.                                                                                                                                                                         |
| `UnableToReadShares`                  | Failed to read shares from storage.                                                                                                                                                                                           |
| `BackendStorageFailed`                | Failed to store data on Portal's backend.                                                                                                                                                                                     |
| `ClientCipherTextNotFound`            | No client cipherText found for the specified backup method during recovery.                                                                                                                                                   |
| `UnableToReadFromBackupStorage`       | Failed to read data from backup storage.                                                                                                                                                                                      |
| `UnableToReadFromKeychain`            | Failed to read data from keychain storage.                                                                                                                                                                                    |
| `UnableToNotifyEjection`              | Could not notify Portal of the ejection request.                                                                                                                                                                              |
| `NoPrivateKeysEjected`                | No private keys could be ejected.                                                                                                                                                                                             |
| `ShareFormattingError`                | Error formatting shares during ejection.                                                                                                                                                                                      |
| `NoFormattedSharesData`               | No data found in the formatted shares response.                                                                                                                                                                               |
| `InstanceNotAvailable`                | The MPC instance is not available.                                                                                                                                                                                            |
| `BackupMethodNotConfigured`           | The specified backup method has not been configured on the Portal instance. See [back up a wallet](./back-up-a-wallet) for configuration steps.                                                                               |
| `UnsupportedCurve`                    | The specified cryptographic curve is not supported.                                                                                                                                                                           |
| `Eip155WalletNotFound`                | No EIP-155 wallet found. Call `createWallet()` first.                                                                                                                                                                         |

### Storage (`PortalException.Storage`)

| Exception                          | Description                                                     |
| ---------------------------------- | --------------------------------------------------------------- |
| `GoogleDriveWriteFailed`           | Failed to write to Google Drive.                                |
| `GoogleDriveVerificationFailed`    | Could not verify the file was saved to Google Drive.            |
| `GoogleDriveFilenameError`         | Cannot determine filename without a configured API.             |
| `GoogleDriveClientDataUnavailable` | Client data is not available for Google Drive operations.       |
| `GoogleDriveHashesError`           | Error retrieving hashes from Google Drive.                      |
| `PasskeySessionIdNotFound`         | No session ID found during passkey operations.                  |
| `PasskeyOperationError`            | A passkey operation failed.                                     |
| `KeychainValidationError`          | Keychain validation failed.                                     |
| `UserRecoverableGDriveException`   | A user-recoverable Google Drive authentication error.           |
| `StorageAuthError`                 | Storage authentication failed.                                  |
| `PasskeyNotSupported`              | Passkeys are not supported on this device.                      |
| `PasskeyStorageNotConfigured`      | Passkey storage has not been configured on the Portal instance. |

### Wallet (`PortalException.Wallet`)

| Exception            | Description                                                                                                                                                                                                  |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `WalletNotOnDevice`  | No wallet share is available on device storage. The user needs to [recover their wallet](./recover-a-wallet). See [wallet lifecycle states](./manage-wallet-lifecycle-states) for handling this proactively. |
| `InvalidWalletState` | The wallet is in an invalid state for the requested operation.                                                                                                                                               |

### Transaction (`PortalException.Transaction`)

| Exception                    | Description                                                                                                      |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `InvalidTransactionResponse` | The response from the transaction was an unexpected type.                                                        |
| `SendAssetArgumentError`     | Invalid arguments passed to `sendAsset` (missing parameters, invalid chain ID format, or unsupported namespace). |

### API (`PortalException.Api`)

| Exception            | Description                                                                                                                                                                |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HttpBadRequest`     | An HTTP request returned 400 Bad Request. Exposes `rawBody`, the unparsed response body.                                                                                   |
| `YieldApiBadRequest` | The Yield API returned a 400 Bad Request error. Extends `HttpBadRequest`.                                                                                                  |
| `HttpRequestFailed`  | An HTTP request failed with a non-success status code other than 400 or 401. Exposes `statusCode` — see [branch on the HTTP status code](#branch-on-the-http-status-code). |
| `RpcError`           | An RPC request failed. Contains `code` and `message` fields.                                                                                                               |
| `HttpUnauthorized`   | An HTTP request returned 401 Unauthorized. Check your API key.                                                                                                             |

### Connect (`PortalException.Connect`)

| Exception              | Description                                       |
| ---------------------- | ------------------------------------------------- |
| `InvalidRequestMethod` | The WalletConnect request method is invalid.      |
| `InvalidRequestParams` | The WalletConnect request parameters are invalid. |

### Provider (`PortalException.Provider`)

| Exception                          | Description                                                |
| ---------------------------------- | ---------------------------------------------------------- |
| `InvalidRequestParams`             | The provider request parameters are invalid.               |
| `NoAddress`                        | No address was found for the requested chain.              |
| `NoRpcUrlFoundForChainId`          | No RPC URL is configured for the given chain ID.           |
| `NoSignatureFoundInSignResult`     | The signing operation completed but returned no signature. |
| `UnsupportedRequestMethod`         | The request method is not supported on this chain.         |
| `NoBindingForSigningApprovalFound` | No signing approval callback is registered.                |
| `UserDeclinedApproval`             | The user declined the transaction approval prompt.         |

### Blockchain (`PortalException.Blockchain`)

| Exception                        | Description                                                                                     |
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
| `InvalidChainId`                 | The chain ID string is malformed (missing `:` separator). Contains the invalid `chainId` value. |
| `NoSupportedCurveForChainId`     | No supported cryptographic curve found for the given chain ID.                                  |
| `NoSupportedNamespaceForChainId` | No supported namespace found for the given chain ID.                                            |

## Migration from previous versions

In previous versions of the Android SDK, errors were thrown as generic `Error`, `Exception`, or flat `PortalException` subclasses like `SendAssetArgumentError` and `PortalHttpUnauthorizedException`. These legacy classes are now deprecated and will be removed in a future major version.

### What changed

* All generic `Error(...)` and `Exception(...)` throws have been replaced with specific `PortalException` subclasses.
* Flat exception classes (`SendAssetArgumentError`, `PortalHttpUnauthorizedException`, `PasskeyNotSupportedException`, `PasskeyStorageNotConfiguredException`, `MpcError`, `InvalidWalletStateError`, `StorageAuthError`) now extend their corresponding `PortalException` subcategory. Your existing `catch` blocks will still work, but you should migrate to the new types.

### Before and after

```kotlin theme={null}
// Before — catching a flat legacy class
try {
    portal.backupWallet()
} catch (e: PortalHttpUnauthorizedException) {
    println("Unauthorized: ${e.message}")
} catch (e: Exception) {
    println("Something went wrong: ${e.message}")
}

// After — catching the structured exception
try {
    portal.backupWallet()
} catch (e: PortalException.Api.HttpUnauthorized) {
    println("Unauthorized: ${e.message}")
} catch (e: PortalException.Mpc) {
    println("MPC error: ${e.message}")
} catch (e: PortalException.Storage) {
    println("Storage error: ${e.message}")
}
```

### Deprecated classes mapping

| Deprecated class                       | Replace with                                          |
| -------------------------------------- | ----------------------------------------------------- |
| `SendAssetArgumentError`               | `PortalException.Transaction.SendAssetArgumentError`  |
| `PortalHttpUnauthorizedException`      | `PortalException.Api.HttpUnauthorized`                |
| `PasskeyNotSupportedException`         | `PortalException.Storage.PasskeyNotSupported`         |
| `PasskeyStorageNotConfiguredException` | `PortalException.Storage.PasskeyStorageNotConfigured` |
| `MpcError`                             | `PortalException.Mpc.MpcResultError`                  |
| `InvalidWalletStateError`              | `PortalException.Wallet.InvalidWalletState`           |
| `StorageAuthError`                     | `PortalException.Storage.StorageAuthError`            |
