frontier field notes · no. 06 ↩ back to the index
A source read of @utexo/rgb-sdk-web

The node in your tab.

Utexo's Browser SDK makes two claims: a full Lightning + RGB node inside a browser tab, and payments that arrive while you're offline. Both are real - and both come with architecture worth reading before you trust it. This note answers the two questions the announcement raised: who holds the HTLC, and where the stash lives.

package: @utexo/rgb-sdk-web 1.0.0 engine: rln-wasm 0.9.0-beta.3 read at: commit 67972ee
§ 00

What a tab can and cannot do.

Before reading the SDK, fix the physics. A browser tab is a strange place to run financial infrastructure - powerful in two ways, constrained in one that shapes everything else.

CAN · WASM

Run the real node

rgb-lightning-node compiles to WebAssembly and runs in-tab as @utexo/rln-wasm - the same Rust core read in No. 04, not a thin client. Two WASM bundles, deliberately separate memory spaces.

CANNOT · TCP

Open a socket

Browsers cannot dial raw TCP, and Lightning peers speak TCP. So every P2P byte tunnels through a WebSocket gateway - wss://ln-gateway-*.utexo.com by default, with production relays requiring an auth token.

SORT-OF · DISK

Persist state

The wallet DB path is an in-memory path inside WASM; a nodeRuntimeId keeps runtime state across reloads, and a VSS cloud backup - encrypted client-side - is configured automatically at init.

§ 01

Anatomy of a tab-node.

Five parts make the claim true. Tap each one for what the source actually says - including the parts that live outside your tab.

01
The WASM node
in your tab
02
Keys and signer
in your tab
03
The RGB stash
in your tab
04
The wire
their gateway
05
The backup
their vss · ciphertext
01 · in your tab

The WASM node

@utexo/rln-wasm is the rgb-lightning-node core compiled to WebAssembly - LDK for channels, rgb-lib for assets, running entirely inside the tab. Only one RLN wallet can exist per tab; creating a second requires removing the first and reloading.

Source: RlnWasmBinding.create() - initRlnWasm() → sdk.initValue(password, mnemonic); "Only one RLN wallet can exist per tab."
§ 02

Receive while offline - the six beats.

The announcement's boldest line, decoded from docs/async-payments.md. You pre-register a signed pool of payment hashes with an always-online Host node. Then close the tab. Here is exactly what happens when someone pays you.

recipient: offline
Register the hash pool

While online, your tab signs and uploads a batch of payment hashes - enableLightningAddress() - and the LSP mints you a stable username@domain address. The attestation signature guards against hash substitution.

money withnobody yet
A payer resolves your address

LNURL discovery returns a fresh HODL BOLT11 from the Host for one of your pre-registered hashes. You are offline. Nothing wakes your tab.

money withthe payer
The payer pays

The HTLC locks at the Host RLN - and stops there. Held, not settled. The Host cannot claim it: settling requires a preimage only your side can produce.

money locked atthe host · htlc
You come back online

Your tab opens, lsp.connect() runs, and the LSP outbox reaches your node over P2P to request an outbound invoice for the held amount.

money locked atthe host · htlc
The outbox delivers

The Host pays your invoice; your node auto-claims - no manual claimHodlInvoice. The asset lands in your channel, validated client-side like any RGB transfer.

money withyou
The Host settles the payer

Only now - holding the preimage your claim revealed - does the Host settle the original HTLC. The payer's payment completes end-to-end.

everyonesettled
So who holds it? The Host does - locked. It can see every payment to your address, and it can delay. What it cannot do is spend: the HTLC settles only with a preimage produced by your claim. Custody of the money, no. Custody of your availability, yes.
§ 03

The tab, by the numbers.

Read off the repo, not the announcement.

1
wallet per tab - a second needs a removal and a reload
0
raw TCP sockets - every peer byte rides the WS gateway
6
beats in an offline receive - three yours, three the outbox's
2
WASM bundles, separate memory - node and on-chain wallet
true
default for virtual channels v0 - on unless you turn it off
beta.14
the RLN core underneath - same stack No. 04 read at beta.8
§ 04

The sovereignty gradient.

"Full node in a tab" is true - and incomplete. The node is yours; by default, the wires are theirs. Five axes, each one swappable via constructor params. Tap for the source.

← in your tabthrough their infra →
Keys and validation

The mnemonic enters sdk.initValue(password, mnemonic) and never leaves the tab; signing is in-WASM. RGB validation is client-side, exactly as in No. 04 - your node replays state history itself. This is the part that makes "full node" honest.

Networking

No TCP in browsers, so proxyUrl defaults to wss://ln-gateway-signet.utexo.com and production gateways reject unauthenticated relays with 401 (relayAuthToken). The gateway sees your traffic patterns and can drop you - it cannot read channel crypto. Swappable: run your own gateway.

Chain view

The tab has no chain. indexerUrl defaults to Utexo's indexer; goOnline() runs against it at unlock, and if it's unreachable the wallet comes up OFFLINE with a warning. Your view of Bitcoin is whatever the indexer serves - point it elsewhere if that matters to you.

Backup

VSS cloud backup is configured automatically at init with an identity derived from the mnemonic (storeId = wallet_<fingerprint>), against DEFAULT_VSS_SERVER_URL. The server holds ciphertext only - the No. 04 security model - but the default is on, and restore is never automatic. Pass null to opt out.

Availability

Offline receive means the Host RLN fronts for you: it holds HTLCs, mints your Lightning Address, and its outbox decides when delivery runs. APay routes need a lspBearerToken. It can't spend your money; it is your availability. Virtual channels v0 - on by default - deepen the same dependency.

The honest read: this is not a custodial wallet wearing a mask - keys, validation and claims are genuinely yours. It is a sovereign node with default-routed plumbing. Every axis above has a constructor param to move the dot left. Defaults are destiny for most users; that is the trade the browser buys.

§ 05

Before you ship on it.

Four things I would test in anger before putting user money behind a tab - the kind of checks that turn into good bug reports.

CHECK 01

The reload gauntlet

Kill the tab mid-payment, mid-claim, mid-channel-open. nodeRuntimeId promises state across reloads; the interesting cases are the ones between beats ④ and ⑥.

CHECK 02

Eviction economics

Browsers evict storage under pressure. An evicted stash plus a VSS restore that is "never automatic" is a recovery flow real users will hit - walk it before they do.

CHECK 03

The signet gap

Every default URL - gateway, indexer, LSP - points at *-signet.utexo.com. "Live" today means live against signet infrastructure. The mainnet flip is a config change with production consequences.

CHECK 04

Hash pool hygiene

refillHashPool() when unusedHashes runs low, and prefer apayNewWithAddress - the attested variant - over bare apayNew, which skips the signature that guards against hash substitution.

§ 06

What the footnotes say.

The two questions from the announcement, answered - plus what turned up on the way.

FN·1

"Offline receive" decoded: held, not custodied

The Host RLN locks the payer's HTLC and cannot settle it without a preimage your claim produces. It sees every payment to your address and controls delivery timing - availability custody, not money custody. That is a defensible design, and it deserves to be stated as precisely as the marketing is bold.

question one · answered
FN·2

The stash lives in-memory, with a default-on cloud shadow

The wallet DB path is an in-memory path inside WASM; persistence rides nodeRuntimeId plus a VSS backup configured automatically at init, encrypted client-side, keyed from the mnemonic. Ciphertext-only - but a browser tab's stash is one eviction away from being a restore flow. No. 05 called stash custody RGB's sharp edge; the tab makes that edge a product surface.

question two · answered
FN·3

"Live" is signet-live

Every production default in RlnDefaults.ts - the WS gateway, the indexer, the LSP - resolves to *-signet.utexo.com. Nothing wrong with that for a launch; worth knowing before you read "full node in a tab" as a mainnet claim today.

read the defaults, not the tweet
FN·4

The series compounds

The engine in this tab is the exact core No. 04 read at beta.8 - now beta.14, with the version footnotes resolved upstream. The availability trade is the one No. 05 named as RGB's sharp edge. Field notes are starting to cite each other; that was the plan.

continuity · nos. 04 → 05 → 06