npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@utexo/wdk-rgb-lightning

v0.1.0-beta.14

Published

WDK module for RGB Lightning (rgb-lightning-node) — channels, invoices, payments, hodl, RGB-over-LN.

Downloads

836

Readme

@utexo/wdk-rgb-lightning

Built with WDK

WDK module for RGB-over-Lightning, built on rgb-lightning-node (RLN). It runs a full LDK + rgb-lib Lightning node behind WDK's wallet-manager/account contract and adds channels, BOLT11 + RGB invoices, payments, HODL invoices, atomic swaps, async payments (APay), optional VSS cloud backup, and a complete LSP client for the UTEXO Lightning Service Provider.

The node runs in external-signer mode: the BIP-39 mnemonic stays in the WDK secret manager, and all channel-state cryptography happens in-process through a VLS signer. RLN's on-disk state holds only public identifying material.

Complements @utexo/wdk-wallet-rgb, the on-chain RGB wallet module. The two are independent: each owns its own rgb-lib SQLite state, keyed by its own wallet fingerprint, and they do not share asset records. Give each module a separate dataDirrgb-lib takes an exclusive lock on a wallet directory, and the Lightning node derives a different wallet fingerprint (it signs in-process via VLS from a 32-byte entropy) than the on-chain module (standard BIP-32 from the full seed), so even a shared dataDir resolves to different <fingerprint>/ subfolders. RLN holds and transfers RGB assets for its channels and invoices. For RGB asset issuance, use @utexo/wdk-wallet-rgb, the on-chain RGB wallet module — issuance is not part of this module's API.

Status: pre-1.0 beta (0.1.0-beta line).

Contents

Architecture

@utexo/wdk-rgb-lightning                    ← this package (WDK module)
       │                                       manager + account + LSP client
       ▼
[Bare runtime]                  [Node runtime]
@utexo/rgb-lightning-node-bare  @utexo/rgb-lightning-node-nodejs
       │                                │
       └──────────────┬─────────────────┘
                      ▼
   rgb-lightning-node/bindings/c-ffi      ← Rust C FFI (librlncffi.a)
                      │
                      ▼
              LDK + tokio + rgb-lib

The package exposes one entry per runtime via conditional exports:

Both paths re-export the same WalletManagerRgbLightning, account class, error types, and LSP surface; only the native binding selected at module-load differs.

Installation

npm install @utexo/wdk-rgb-lightning

# Plus the native binding matching your runtime (optional peer deps):
npm install @utexo/rgb-lightning-node-nodejs   # Node host
# or
npm install @utexo/rgb-lightning-node-bare     # Bare / React Native host

Both bindings are declared as optional peer dependencies — install only the one for your runtime. Each binding's postinstall downloads the platform-specific prebuilt native artifact from its GitHub Release (no Rust toolchain required on the consumer machine). See the binding READMEs for the supported platform matrix.

Quick start

import WalletManagerRgbLightning from '@utexo/wdk-rgb-lightning'

const manager = new WalletManagerRgbLightning(seedPhrase, {
  network: 'regtest',
  dataDir: '/path/to/persistent/dir',
  // Optional VSS cloud backup — omit to disable.
  vssUrl: 'https://vss.example.com',
  vssAllowHttp: false,
  // Optional LSP wiring for async payments.
  lspBaseUrl: 'https://lsp.example.com',
  lspBearerToken: '<token>'
})

const account = await manager.getAccount(0) // RGB Lightning is single-account

await account.unlock({
  bitcoind_rpc_username: 'user',
  bitcoind_rpc_password: 'pass',
  bitcoind_rpc_host: '127.0.0.1',
  bitcoind_rpc_port: 18443,
  indexer_url: 'tcp://localhost:50001',
  proxy_endpoint: 'rpc://localhost:3000/json-rpc',
  announce_addresses: [],
  announce_alias: 'my-node'
})

const info = await account.getNodeInfo()
console.log(info.pubkey)

await account.connectPeer('<pubkey>@<host>:<port>')

const channel = await account.openChannel({
  peer_pubkey_and_opt_addr: '<pubkey>@<host>:<port>',
  capacity_sat: 1_000_000,
  push_msat: 0,
  public: true,
  with_anchors: true
})

const invoice = await account.createInvoice({
  amt_msat: 5000,
  expiry_sec: 3600
})

await account.sendPayment({ invoice: '<bolt11>' })

await manager.dispose()

A complete end-to-end example — LSP wiring, RGB-over-Lightning transfers, and a regtest stack via Docker Compose — lives in utexo-rgb-wdk-demo.

Manager configuration

network and dataDir are required. Notable optional fields:

| Field | Default | Purpose | |-------|---------|---------| | daemonListeningPort / ldkPeerListeningPort | 0 | RLN listening ports; 0 = ephemeral. | | maxMediaUploadSizeMb | 5 | Cap on RGB media uploads. | | enableVirtualChannelsV0 | false | Enable virtual-channels-v0 (required for APay against a production LSP). | | virtualPeerPubkeys | — | Trust list of peer node_ids allowed to open trusted_no_broadcast virtual channels (the LSP's node_id for APay). | | permissiveSignerPolicy | true | Loosen the VLS policy filter for in-process single-user use. | | vssUrl / vssAllowHttp / vssAllowEmptyRestore | — | VSS cloud backup; see below. | | lspBaseUrl / lspBearerToken | — | LSP wiring for APay and the LSP client; see below. |

Account API

getAccount(0) returns a WalletAccountRgbLightning. RGB Lightning is single-account (index 0); RLN owns one LDK node per dataDir. All methods are async and forward to the active binding.

| Group | Methods | |-------|---------| | Lifecycle | unlock(request), getBootstrap(), shutdown(), dispose() | | Node info | getNodeInfo(), getNetworkInfo(), sync(), getAddress() | | Peers | connectPeer(pubkey@host:port), disconnectPeer(request), listPeers() | | Channels | openChannel(request), closeChannel(request), listChannels(), getChannelId(tempIdHex) | | Invoices | createInvoice(request), createLightningInvoice(request), decodeInvoice(invoice), getInvoiceStatus(invoice) | | HODL invoices | createHodlInvoice({ paymentHash, ... }), cancelHodlInvoice(request), claimHodlInvoice(request) | | Payments | sendPayment(request), keysend(request), listPayments(), getPayment(hash, type) | | RGB assets | listAssets(filter?), getAssetBalance(id), getAssetMetadata(id), listTransfers(id?), refreshTransfers(req), failTransfers(req) | | RGB invoices/transfers | createRgbInvoice(request), decodeRgbInvoice(invoice), sendRgbAsset(request), getAssetMedia(digest), postAssetMedia(request) | | BTC | getBalance(skipSync?), getBalanceDetails(skipSync?), getAddress(), sendTransaction(request), getTransactions(skipSync?), listUnspents(skipSync?), createUtxos(request), estimateFee(blocks) | | WDK-standard | transfer(options), quoteTransfer(options), getTransactionReceipt(hash), getKeyPair(), toReadOnlyAccount() | | Diagnostics | sign(message), sendOnionMessage(request), checkIndexerUrl(url), checkProxyEndpoint(endpoint) | | VSS | vssStatus(), vssBackup(), clearVssFence(password) | | APay / LSP | apayNew(hostNodeId), bootstrapLsp({ peerPubkeyAndAddr, hostNodeId? }), getLspConfig(), createLsp(peer?) |

Notes:

  • createInvoice / createLightningInvoice accept either RLN's native snake_case request or a camelCase convenience shape ({ amountMsat?, expirySec, assetId?, assetAmount?, paymentHash?, descriptionHash?, minFinalCltvExpiryDelta? }).
  • transfer(options) is a generic router: it classifies options.recipient (BOLT11 invoice, LN pubkey, BTC address, or RGB invoice) and dispatches to the right primitive. options.token is an RGB asset_id when present. Amounts are msats for LN flows and sats for on-chain flows.
  • RGB asset issuance is not part of this module. To issue RGB assets, use @utexo/wdk-wallet-rgb, the on-chain RGB wallet module. This module holds and transfers assets that already exist — in channels, invoices, and on-chain — and keeps its own separate rgb-lib wallet (give each module its own dataDir).
  • Atomic swaps (makerInit / taker / ...) are reachable on the binding but intentionally not surfaced on the WDK account.
  • verify and signTransaction throw NotImplementedError — the C-FFI doesn't expose them.

Error handling

Most of the surface forwards to RLN, which reports failures as Rln(<Variant>): <message> strings. This package wraps the boundaries that matter in a typed hierarchy so callers can branch on err.name / err.code instead of substring-matching. The original RLN message is preserved verbatim and the underlying error is attached as cause; each error has a toJSON() for structured logging.

RgbLightningError            code: RGB_LIGHTNING_ERROR
├── UnlockError              code: UNLOCK_FAILED
├── VssError                 code: VSS_ERROR
│   └── VssNotConfiguredError code: VSS_NOT_CONFIGURED
├── ApayError                code: APAY_ERROR (e.g. APAY_PEER_NOT_VISIBLE)
└── NotImplementedError      code: NOT_IMPLEMENTED

All are exported from the package root:

import {
  RgbLightningError, UnlockError, VssError,
  VssNotConfiguredError, ApayError, NotImplementedError
} from '@utexo/wdk-rgb-lightning'

try {
  await account.unlock(rpcArgs)
} catch (err) {
  if (err instanceof UnlockError) {
    console.error(err.code, err.message, err.cause)
  }
}

LSP integration

The package ships a pure-fetch LSP client and a composed high-level flow object, both exported from the root. They work unchanged in Bare (via the bare-fetch global installed by bare.js) and Node >= 18 (native fetch).

LspClient

A thin, retrying HTTP client over the UTEXO LSP REST API:

import { LspClient, LspError } from '@utexo/wdk-rgb-lightning'

const lsp = new LspClient({ baseUrl: 'https://lsp.example.com' })
const info = await lsp.getInfo()

Methods include health(), getInfo(), lnurlDiscovery(username), lnurlCallback(username, amountMsat), resolveAddress(username, amountMsat), getLightningAddressByPubkey(pubkey), onchainSend({ rgbInvoice, ln }), and lightningReceive({ lnInvoice, rgb }). Failures throw LspError (carrying endpoint, status, body).

UtexoLsp (composed flows)

account.createLsp(peer?) returns a UtexoLsp that orchestrates the multi-step LSP interactions — connect, wait for channel readiness, receive/send RGB over Lightning, pay a Lightning Address, enable a Lightning Address, and claim pending payments. The no-arg form auto-discovers the peer from the wallet's lspBaseUrl (GET /get_info).

const lsp = await account.createLsp()
await lsp.connect()
const { lnInvoice, rgbInvoice } = await lsp.receiveAsset({ assetId, amountRgb: 100 })
await lsp.awaitReceiveSettlement(lnInvoice)

Key methods: connect(), waitForChannel(assetId, opts?), receiveAsset(opts), awaitReceiveSettlement(lnInvoice, opts?), waitForOutboundLiquidity(minMsat, opts?), sendAsset(opts), payAddress(opts), enableLightningAddress(), claimPendingPayments(). Timeouts throw LspChannelTimeoutError / LspSettlementError.

LNURL / Lightning Address helpers

parseLightningAddress, fetchDiscovery, resolveAddressToInvoice (LNURL-pay), and the account-bound helpers payLightningAddress, requestLspRgbDeposit, payRgbViaLsp are also exported from the root.

VSS cloud backup

Set vssUrl at construction to mirror LDK channel state and RGB wallet data to a remote VSS key-value store in near-real-time. Payloads are client-side encrypted (XChaCha20-Poly1305, keyed via HKDF of a signing key derived from the BIP-39 mnemonic at BIP-32 path m/535'/1'); the server sees only ciphertext, and recovery requires the original seed. Plain http:// is rejected for non-loopback hosts unless vssAllowHttp: true.

  • account.vssStatus() — local view: whether VSS is configured, the URL + allow-http flag, and the snapshot version from the most recent vssBackup() this session.
  • account.vssBackup() — force an immediate flush, returning { version }. Useful for app-controlled checkpoints (e.g. before app suspend).
  • account.clearVssFence(password) — forcibly take over a stale VSS ownership fence after a previous node died holding it (restarts otherwise fail with Rln(VssFenceHeld)). Only call this when certain the previous owner is gone — pointing two live nodes at one VSS store corrupts state.

VSS operations on a wallet constructed without vssUrl throw VssNotConfiguredError.

Async payments (APay)

APay lets the wallet receive over Lightning while offline: it uploads a batch of pre-allocated payment hashes to an LSP, which accepts payments on the wallet's behalf. Against a production LSP this requires enableVirtualChannelsV0: true and the LSP's node_id in virtualPeerPubkeys.

  • account.apayNew(hostNodeId) — register with the LSP as an APay recipient (hostNodeId is the LSP node_id, hex). Requires lspBaseUrl (and lspBearerToken if the LSP enforces auth).
  • account.bootstrapLsp({ peerPubkeyAndAddr, hostNodeId? }) — connect to the LSP peer, wait until it appears in listPeers, then (if hostNodeId is given) call apayNew. Refuses to register before the peer is visible to avoid RLN's host-response timeout (throws ApayError with code APAY_PEER_NOT_VISIBLE).

Security model

  • Seed never leaves the host. The mnemonic is owned by the WDK secret manager. The binding derives a 32-byte BIP-32 entropy, passes it once to NativeExternalSigner.create, and RLN persists only public identifying material (xpubs, node id, master fingerprint). Re-deriving from the same mnemonic reproduces the same entropy, matches the on-disk key-source, and keeps the LDK node identity stable across restarts.
  • All channel-state crypto runs in-process through vls-protocol-signer. The signer's lifecycle is tied to the binding and is destroyed on manager.dispose().
  • VSS payloads are client-side encrypted (see above); the server only ever holds ciphertext.
  • Plain http:// is rejected by default for VSS and LSP endpoints; opt in (vssAllowHttp / LspClient({ allowHttp: true })) only for loopback or development.

Testing and local development

Unit tests

The host-side logic — recipient routing, fee estimation, typed-error wrapping, and binding config mapping — is covered by a jest suite that runs with no live node and no native binding (the addon is mocked):

npm install
npm test            # jest, host-side units
npm run test:coverage

Integration / end-to-end

End-to-end coverage (a real LDK node, RGB assets, channels, payments, and a regtest stack via Docker Compose) lives in utexo-rgb-wdk-demo. To exercise a local build of this package instead of a published tag, check the demo out next to this repo so the relative paths resolve:

parent/
  wdk-rgb-lightning/        # this repo
  utexo-rgb-wdk-demo/

The Node E2E harness already links this repo by path — utexo-rgb-wdk-demo/node-demo/package.json declares "@utexo/wdk-rgb-lightning": "file:../../wdk-rgb-lightning" (and the Node binding via file:../../packages/rgb-lightning-node-nodejs). Installing the harness symlinks your working tree in; the dockerised LSP + regtest stack then runs the suite:

cd utexo-rgb-wdk-demo/node-demo
npm install                                          # links file:../../wdk-rgb-lightning
./lsp/up.sh                                           # docker compose up --build; waits on :8080/health
LSP_BASE_URL=http://127.0.0.1:8080 npm run test:e2e  # tsx test-runner/run.ts
./lsp/down.sh

The React Native app at the demo root instead pins a published tag (github:UTEXO-Protocol/wdk-rgb-lightning#v<tag>). To test local changes on device, repoint that dependency to file:../wdk-rgb-lightning, re-run npm install, and rebuild the worklet bundle.

Troubleshooting

  • Rln(Conflict) on a re-launch — expected on every launch after the initial wallet create. The binding swallows it internally; if it bubbles up you're calling the lower-level binding directly instead of going through the WDK account.
  • UnlockError / Rln(FailedVssInit) — bad bitcoind/indexer/proxy credentials or an unreachable backend; for VSS, an unreachable URL or a rejected auth challenge. Check vssUrl (https vs http) and reachability; for fence takeover see clearVssFence.
  • ApayError (APAY_PEER_NOT_VISIBLE)apayNew was attempted before the LSP peer reached listPeers. Use bootstrapLsp or retry once the peer is visible.
  • Channels fail to open with a fresh node — confirm bitcoind RPC is reachable from inside the worklet's network sandbox; on regtest set rpcallowip=127.0.0.1/32 so loopback connections are accepted.
  • Port conflict on Metro / Expo — a local VSS test server may squat on port 8081 (Metro's default). Stop it before starting Metro, or move VSS to another port.

License

Apache-2.0. See LICENSE.