Hosting runtime assets

The web transport loads its WebAssembly runtime from a set of files that you host yourself and point runtimeBaseUrl at. You always provide the assets. The worker entry (wavewalletdk-worker.js) ships inside @lightninglabs/wavelength-web and is emitted by your bundler; only the daemon binaries below need to be hosted.

client.ts
import { createWebClient } from '@lightninglabs/wavelength-web';
const client = createWebClient({
runtimeBaseUrl: 'https://your-host/wavewalletdk/<version>/',
});

If runtimeBaseUrl is unset, assets resolve relative to the page URL in both worker and main-thread mode, so you can also serve the runtime files alongside your app.

The asset set

The runtime is a fixed set of files built from wavelength. Import RUNTIME_ASSET_FILES to see every filename that must be served together at one base URL. RUNTIME_MANIFEST_VERSION identifies the daemon build the SDK is paired with:

assets.ts
import {
RUNTIME_ASSET_FILES,
RUNTIME_MANIFEST_VERSION,
} from '@lightninglabs/wavelength-web';
console.log(RUNTIME_ASSET_FILES);
// wavewalletdk.wasm, wavewalletdk.wasm.gz, wasm_exec.js, sqlite-bridge.js,
// sqlite-worker.js, sqlite3.js, sqlite3.wasm, sqlite3-opfs-async-proxy.js

Get the asset set

There are two ways to obtain the files, both producing the set for the pinned RUNTIME_MANIFEST_VERSION:

Download from the wavelength release. The matching asset set is attached to the paired wavelength release as Wavewalletdk.wasm.tar.gz. It holds every file above, flat with no enclosing directory, so it unpacks straight into the directory you serve them from:

Terminal
BASE=https://github.com/lightninglabs/wavelength/releases/download
curl -fsSLO "$BASE/<version>/Wavewalletdk.wasm.tar.gz"
mkdir -p "./public/wavewalletdk/<version>"
tar -xzf Wavewalletdk.wasm.tar.gz -C "./public/wavewalletdk/<version>"

Build from a wavelength checkout. From a checkout of wavelength, build the WASM wallet target and copy the output into your app’s static folder:

Terminal
# Build the WASM runtime into wavelength/bin/wasm/
make -C /path/to/wavelength wasm-wallet
# Copy every runtime binary into a directory named after
# RUNTIME_MANIFEST_VERSION (example: Vite public/)
mkdir -p "./public/wavewalletdk/<version>"
for f in wavewalletdk.wasm wavewalletdk.wasm.gz wasm_exec.js sqlite-bridge.js \
sqlite-worker.js sqlite3.js sqlite3.wasm sqlite3-opfs-async-proxy.js; do
cp "/path/to/wavelength/bin/wasm/$f" "./public/wavewalletdk/<version>/"
done

The monorepo demo wraps the same build in pnpm --filter web-wallet-demo run wasm:local, which builds from a sibling wavelength checkout and stages files into apps/web-wallet-demo/public/runtime/<version>/, the versioned path the demo’s runtimeBaseUrl points at. After vite build, those files land in dist/ and are served from the app origin.

A runtime built this way will not match the pinned digests described in Integrity verification below; point createWebClient() at it with runtimeIntegrity: false as covered there.

Host the assets

Host all of them under a single path and set runtimeBaseUrl to that directory. Prefer a path that includes RUNTIME_MANIFEST_VERSION (for example /wavewalletdk/<version>/): every asset set then gets a unique URL, so browsers pick up new assets on an SDK upgrade instead of serving stale cached copies, and you can cache the files aggressively. Trailing slashes are optional; the client normalizes the base URL.

Version lock. Runtime assets are version-locked to the embedded daemon inside @lightninglabs/wavelength-web. When you upgrade the SDK, obtain and redeploy the matching WASM bundle. Mismatched versions can fail at load time or produce subtle runtime errors.

Compression. The client fetches wavewalletdk.wasm.gz first and falls back to the uncompressed wavewalletdk.wasm if it cannot be used (for example when the browser has no DecompressionStream to inflate it). Host both files so every supported browser can load the runtime.

No particular compression headers are required. The client reads the file’s first bytes and branches on the magic number rather than on Content-Type or Content-Encoding, so a body your host serves compressed, one it has already inflated, and one labelled application/gzip all load the same way. Compress it however your host makes easiest.

Other headers still matter: a cross-origin runtimeBaseUrl needs Cross-Origin-Resource-Policy: cross-origin on these assets, covered in cross-origin isolation.

Do still set long-lived cache headers, but note that the browser will not keep a module this large in its HTTP cache regardless of what you send. The SDK therefore keeps its own copy in Cache Storage so returning visitors do not re-download it; see Data & persistence for what that stores and when it is pruned. Cached bytes are re-verified against the pinned digest on every read, exactly like a fresh fetch, so a cache entry can never become a bypass for the checks below.

Integrity verification

Before executing the wasm binary and the two bootstrap scripts the transport executes (wasm_exec.js and sqlite-bridge.js), the web transport verifies their fetched bytes against SHA-256 digests pinned in the npm package for the paired RUNTIME_MANIFEST_VERSION. Hosting a mismatched version’s assets fails loudly with asset_integrity_failed instead of silently running stale or tampered code. wavewalletdk.wasm.gz is covered too: the client verifies its decompressed bytes against the same digest entry as wavewalletdk.wasm, since decompressing it yields identical content.

The remaining files in the asset set (the sqlite worker chain: sqlite-worker.js, sqlite3.js, sqlite3.wasm, sqlite3-opfs-async-proxy.js) are not verified at runtime. Cover them by checking your deployed set at deploy time instead: RUNTIME_ASSET_DIGESTS exports the digest for every file in the set, keyed by filename in the same SRI format (sha256-<base64>) the client checks against.

Terminal
f=./public/wavewalletdk/<version>/wavewalletdk.wasm
echo "sha256-$(openssl dgst -sha256 -binary "$f" | openssl base64 -A)"
# compare the output against RUNTIME_ASSET_DIGESTS['wavewalletdk.wasm']

Escape hatch for source-built runtimes. A runtime you build yourself from a wavelength checkout will not match the pinned digests, since those pin a specific published release. Set runtimeIntegrity: false on createWebClient() or createWebWalletEngine() to skip verification; doing so logs a one-time console warning so the switch is never silently left off in production.

Hosting requirements. Serve the runtime assets same-origin where possible. If your app sets a Content-Security-Policy, script-src must include blob: alongside 'self': the SDK executes its verified bootstrap scripts from blob URLs, and a policy without blob: breaks the SDK outright. Hosting the assets cross-origin additionally requires CORS headers on the asset host, since the client fetches them before verifying their bytes.