---
title: "System architecture"
description: "How Wavelength's embedded wallet daemon connects to the Ark, Lightning, and Bitcoin backend gateways to enable payments, boarding, swaps, and exits."
canonical: https://wavelength.lightning.engineering/introduction/system-architecture/
---

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

# System architecture

## The embedded daemon

The Wavelength SDK embeds [wavelength](https://github.com/lightninglabs/wavelength), the same wallet daemon on every platform, and runs it wherever your app runs. On the web it is compiled to WebAssembly and runs inside the browser; you boot it with `createWebClient()` and `client.start()`. On React Native it is compiled into your app binary and runs on device; you boot it with `createNativeClient()` and `client.start()`. Either way it stays running for the session, and you drive it through one typed `WavelengthClient`.

**`WavelengthClient` is the app-facing API.** It is defined in `@lightninglabs/wavelength-core` and implemented by each transport (`@lightninglabs/wavelength-web` on the web, `@lightninglabs/wavelength-react-native` on React Native). Your code never imports Go or protobuf types. You call typed methods (`deposit`, `receive`, `send`, `balance`, and so on) and receive plain JavaScript objects.

The wire protocol between the client and the embedded daemon differs by transport, and from your app’s perspective it is an implementation detail. The React Native transport uses the daemon’s native gRPC. Browsers cannot open arbitrary gRPC connections the way a native host can, so the web transport uses **REST** against the embedded daemon’s HTTP facade instead. You still hold one `WavelengthClient` and call the same methods on either.

The daemon also owns local persistence (wallet database, swap state) and signing. Your app configures *where* the daemon connects on the network, not *how* it stores keys.

## The three backend gateways

Once started, the daemon reaches three external backend services. Configure them in `RuntimeConfig` (or use your transport package’s `defaultConfig(network)` for the public signet and testnet presets):

```ts
import { createWebClient, defaultConfig } from '@lightninglabs/wavelength-web';

const client = createWebClient();
await client.ready();
await client.start(defaultConfig('signet'));
// arkServerAddress, walletEsploraUrl, swapServerAddress pre-filled for signet
```

```ts
import { createNativeClient, defaultConfig } from '@lightninglabs/wavelength-react-native';

const client = createNativeClient();
await client.ready();
await client.start(defaultConfig('signet'));
// arkServerAddress, walletEsploraUrl, swapServerAddress pre-filled for signet
```

| Field               | Service                       | What the daemon uses it for                                                                                                                      |
| ------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `arkServerAddress`  | Ark operator and mailbox edge | Ark **rounds**, VTXO lifecycle, and **mailbox relay** for out-of-round transfers. A REST URL on web; a `host:port` gRPC address on React Native. |
| `swapServerAddress` | Swap server                   | **Lightning↔Ark atomic swaps** (Lightning receive and send). A REST URL on web; a `host:port` gRPC address on React Native.                      |
| `walletEsploraUrl`  | Esplora-compatible indexer    | **Chain and UTXO queries**; an HTTP endpoint on both platforms that implements the Esplora `/address/:addr/utxo` API                             |

**Mailbox and operator share one edge.** VTXO mailbox traffic goes through the same Ark server URL. There is no separate `mailboxUrl` in `RuntimeConfig`; the redundant mailbox fields were removed so you configure a single Ark gateway.

Lightning is not a fourth gateway. Lightning send and receive are **swap operations** routed through `swapServerAddress`. On-chain visibility (boarding deposits, cooperative leaves, unilateral exits) goes through Esplora and the Ark operator as appropriate.

Set `disableSwaps: true` if you only need Ark and on-chain flows without Lightning. It suppresses the preset and any override swap fields.

## How boarding, swaps, and exits connect

Wavelength unifies three payment rails behind one balance and one activity stream. Here is how each rail uses the gateways above.

### On-chain in: boarding

1. Your app calls `deposit()` and gets a **boarding address** (a standard Bitcoin receive address).
2. The user sends on-chain BTC to that address.
3. Esplora lets the daemon see the UTXO arrive.
4. At the **next Ark round** (interval is operator-configured, often well under a minute), the deposit **boards** into Ark and becomes a **VTXO** you can spend instantly off-chain.

Until boarding completes, funds show as pending inbound balance.

### Lightning: swaps

Wavelength does not open Lightning channels in the browser. Lightning payments are **atomic swaps** between Lightning and Ark:

- **Receive Lightning** (`receive()`) starts a **receive swap**: the swap server holds a Lightning invoice; when it is paid, you receive Ark balance (a VTXO).
- **Send Lightning** (`send({ invoice })`) runs a **send swap**: you spend Ark balance; the swap server pays the BOLT-11 invoice on Lightning. For a quote-then-confirm flow instead of a single call, see `prepareSend()`/`sendPrepared()`.

Both directions go through `swapServerAddress`. Swaps can sit in a pending state while liquidity or routing resolves; the daemon tracks them in your activity stream and can refund if a swap expires.

### On-chain out: leave and exit

Spending to a normal Bitcoin address uses a **cooperative leave**: the Ark operator helps convert your VTXO into an on-chain transaction. This is the default path for `send({ onchainAddress })`. It is faster and cheaper than exiting alone.

**Unilateral exit** is the emergency fallback. If the operator is unavailable or you need to recover funds without cooperation, the daemon can execute a unilateral exit from a VTXO. It is slower, more expensive, and intended for edge cases. Use `exit()` and `exitStatus()` on `WavelengthClient` for this path; see [Unilateral exit](/guides/unilateral-exit/) for integration details. Call `getExitPlan()` first to preview readiness and backing-wallet funding requirements before triggering `exit()`.

On-chain in

Deposit

Boarding

Round

VTXO

Lightning receive

Receive swap

Swap server

VTXO

Lightning send

Send swap

Swap server

Invoice paid

On-chain send

Cooperative leave

Operator + Esplora

Emergency

Unilateral exit

On-chain

For deeper detail on each rail, see [Balances & VTXOs](/concepts/balances-and-vtxos/), [Lightning payments = swaps](/concepts/lightning-payments-are-swaps/), and [Leaving Ark](/concepts/leaving-ark/).
