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 signetimport { 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
- Your app calls
deposit()and gets a boarding address (a standard Bitcoin receive address). - The user sends on-chain BTC to that address.
- Esplora lets the daemon see the UTXO arrive.
- 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, seeprepareSend()/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().
For deeper detail on each rail, see Balances & VTXOs, Lightning payments = swaps, and Leaving Ark.