System architecture

The embedded daemon

The Wavelength SDK embeds 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):

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
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 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, Lightning payments = swaps, and Leaving Ark.