---
title: "Handle phases & errors"
description: "How to respond to Wavelength wallet phase transitions and interpret structured error codes in your application."
canonical: https://wavelength.lightning.engineering/guides/handle-phases-and-errors/
---

> Docs index: https://wavelength.lightning.engineering/llms.txt

[Wavelength](/)›Guides›Handle phases & errors

# Handle phases & errors

3 min read

Guides

Web SDK

## React to wallet phases

`phase` from [`useWallet()`](/reference/wavelength-react/#useWallet) describes where the wallet is in its lifecycle. Use a switch or a series of conditionals at the root of your app to render the right screen for each phase. The value updates reactively, so the UI transitions automatically as the wallet moves through phases.

| Phase          | Meaning                                                                                                                                                                                                                                              |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `loading`      | The runtime assets have not finished loading.                                                                                                                                                                                                        |
| `runtimeReady` | The runtime is usable, but the daemon has not started.                                                                                                                                                                                               |
| `starting`     | `start()` has been called and has not yet resolved.                                                                                                                                                                                                  |
| `needsWallet`  | No wallet exists yet; create or restore one.                                                                                                                                                                                                         |
| `locked`       | A wallet exists but is locked; unlock it.                                                                                                                                                                                                            |
| `syncing`      | The wallet is unlocked and catching up with the chain.                                                                                                                                                                                               |
| `restoring`    | A background restore ([`useWalletRestore()`](/reference/wavelength-react/#useWalletRestore)) is bringing a freshly restored wallet up; setting `recoverState: true` on the request additionally tracks the recovery scan through the recovery state. |
| `ready`        | The wallet is unlocked and ready to use.                                                                                                                                                                                                             |
| `stopping`     | `stop()` has been called and has not yet resolved.                                                                                                                                                                                                   |
| `stopped`      | The daemon has stopped.                                                                                                                                                                                                                              |
| `error`        | The runtime failed to start, `start()`/`stop()` failed, or a background process (sync poll, activity stream, refresh) exhausted its failure budget.                                                                                                  |

`info.walletState` (from [`useWalletInfo()`](/reference/wavelength-react/#useWalletInfo)) carries the underlying [`WalletState`](/reference/wavelength-core/#WalletState) string (`none`, `locked`, `ready`, or `syncing`) when you need the daemon-level detail instead of the broader [`RuntimePhase`](/reference/wavelength-core/#RuntimePhase).

```tsx
import { useWallet } from '@lightninglabs/wavelength-react';

function WalletGate({ children }: { children: React.ReactNode }) {
  const { phase } = useWallet();

  switch (phase) {
    case 'loading':
    case 'runtimeReady':
    case 'starting':
      return <p>Starting wallet runtime...</p>;
    case 'needsWallet':
      return <CreateWalletScreen />;
    case 'locked':
      return <UnlockScreen />;
    case 'syncing':
    case 'restoring':
      return <p>Syncing with the wallet server...</p>;
    case 'ready':
      return <>{children}</>;
    case 'stopping':
    case 'stopped':
      return <p>Wallet stopped.</p>;
    case 'error':
      return <p>Runtime failed to start.</p>;
    default:
      return null;
  }
}
```

A failed `start()` or `stop()` moves `phase` straight to `'error'`; there is no separate `'startFailed'`/`'stopFailed'` phase to branch on. Read `error` from [`useWallet()`](/reference/wavelength-react/#useWallet) for the reason.

## The Error | null convention

Every mutation hook ([`useWalletCreate`](/reference/wavelength-react/#useWalletCreate), [`useWalletRestore`](/reference/wavelength-react/#useWalletRestore), [`useWalletUnlock`](/reference/wavelength-react/#useWalletUnlock), [`useWalletDeposit`](/reference/wavelength-react/#useWalletDeposit), [`useWalletReceive`](/reference/wavelength-react/#useWalletReceive), [`useWalletPrepareSend`](/reference/wavelength-react/#useWalletPrepareSend), [`useWalletSend`](/reference/wavelength-react/#useWalletSend), [`useWalletRefresh`](/reference/wavelength-react/#useWalletRefresh), and the `create`/`open` pair on [`useWalletPasskey`](/reference/wavelength-react/#useWalletPasskey)) exposes the same throw-and-capture shape: an action function, a verb-prefixed `<verb>Pending: boolean`, a verb-prefixed `<verb>Error: Error | null`, and (except [`useWalletRefresh`](/reference/wavelength-react/#useWalletRefresh) and [`useWalletPasskey`](/reference/wavelength-react/#useWalletPasskey), neither of which exposes a `<verb>Data` field) a verb-prefixed `<verb>Data`, the last successful result. The error field is always `Error | null`, never a string, so `.message` is always safe to read. Calling the action clears the previous error/data, awaits the underlying engine call, and on completion both updates the hook’s state and settles the returned promise the normal way: resolves with the result, or rejects with the same `Error` instance that lands in the error field. So you can read the error field for a declarative render, or `await`/`try`/`catch` the action for imperative flow, whichever fits the call site. `reset<Verb>()` clears the pending/error/data trio back to idle without calling anything.

`error` at the top level, from [`useWallet()`](/reference/wavelength-react/#useWallet), is the same `Error | null` shape but tracks fatal runtime-level failures instead of a single mutation: it is set when the initial runtime load fails, when `start()`/`stop()` fails, or after a background process exhausts its failure budget, and it clears on the next successful `start()`.

```tsx
import { useWalletSend } from '@lightninglabs/wavelength-react';

function PayButton({ bolt11 }: { bolt11: string }) {
  const { send, sendError, sendPending, resetSend } = useWalletSend();

  const pay = async () => {
    resetSend();
    try {
      await send({ invoice: bolt11 });
    } catch (err) {
      // sendError is already set for a declarative render; err is the same
      // Error instance for imperative handling right here.
      console.error('Payment failed:', err);
    }
  };

  return (
    <>
      <button onClick={pay} disabled={sendPending}>Pay</button>
      {sendError && <p role="alert">{sendError.message}</p>}
    </>
  );
}
```

## Interpret error codes

Every error the Wavelength SDK originates extends [`WavelengthError`](/reference/wavelength-core/#WavelengthError) and carries a `code` string that identifies the failure reason at the machine level. Match on `err.code` to show a specific message rather than a generic fallback. Named SDK codes include `runtime_not_ready`, `asset_load_failed`, and `worker_error`; daemon-originated errors currently use `wavelength_error`.

```tsx
import { useWalletSend, WavelengthError } from '@lightninglabs/wavelength-react';

function PayButton({ bolt11 }: { bolt11: string }) {
  const { send, sendError, sendPending, resetSend } = useWalletSend();

  const pay = async () => {
    resetSend();
    try {
      await send({ invoice: bolt11 });
    } catch (err) {
      if (err instanceof WavelengthError) {
        switch (err.code) {
          case 'runtime_not_ready':
            console.error('Runtime still loading.');
            break;
          case 'asset_load_failed':
            console.error('Check runtimeBaseUrl and hosted assets.');
            break;
          default:
            console.error('Payment failed:', err.message);
        }
      }
    }
  };

  return (
    <>
      <button onClick={pay} disabled={sendPending}>Pay</button>
      {sendError && <p role="alert">{sendError.message}</p>}
    </>
  );
}
```
