Node.js (napi-rs) bindings for rgb-lightning-node
(RLN) — the Lightning + RGB daemon built on LDK and rgb-lib.
This package is the Node counterpart to
@utexo/rgb-lightning-node-bare. Both wrap the
same Rust C-FFI (librlncffi.a) and expose the identical SdkNode +
NativeExternalSigner JavaScript surface, so the WDK layer
(@utexo/wdk-rgb-lightning) runs one code path across
runtimes — this package on Node.js (server, CLI, desktop), the bare addon
inside a Bare worklet on mobile.
Status: pre-1.0 beta. The API surface is stable across the 0.1.0-beta line; native artifacts are distributed per release tag.
- Why a separate package
- Platform support
- Installation
- Usage
- API surface
- Seed handling
- Architecture
- Build and release (maintainers)
- License
Node.js can dlopen shared libraries; the Bare runtime cannot. The Rust
layer is shared, but the JS-binding layer is forked:
- This package builds with napi-rs into one platform-specific
.nodeaddon per host, loaded dynamically atrequiretime. - The bare sibling builds with
cmake-bareinto one self-contained.bareaddon with the Rust statically linked in (required for iOS, where dynamic linking is disallowed).
Both expose the same JS API, so consumers — chiefly
@utexo/wdk-rgb-lightning — write a single
runtime-agnostic code path.
Prebuilt .node binaries are attached to this repo's GitHub Releases. The
postinstall script selects the artifact matching the host platform/arch
and the installed package version.
| Platform | Resolver suffix | Build host |
|---|---|---|
| macOS arm64 | darwin-arm64 |
native |
| macOS x64 | darwin-x64 |
native |
| Linux x64 glibc | linux-x64-gnu |
native |
| Linux x64 musl | linux-x64-musl |
Alpine container |
| Linux arm64 | linux-arm64-gnu |
native |
The musl variant is auto-selected on Alpine (/etc/alpine-release
present); all other Linux x64 hosts get the glibc build. Unsupported
platform/arch combinations throw a clear error at module load.
npm install @utexo/rgb-lightning-node-nodejsThe package's postinstall (scripts/download-libs.sh) downloads the
matching .node prebuild from the GitHub Release for the installed
version. No Rust toolchain or cross-compiler is required on the consumer
machine.
If postinstall was skipped (npm install --ignore-scripts), fetch the
prebuild manually:
bash node_modules/@utexo/rgb-lightning-node-nodejs/scripts/download-libs.shYou don't normally depend on this package directly — it's an optional peer
dependency of @utexo/wdk-rgb-lightning, installed
alongside it for Node-side hosts. Install it explicitly only when calling
RLN from Node without the WDK layer.
Requires Node.js >= 18.
The package exposes two classes (SdkNode, NativeExternalSigner) plus
module-level helpers. Requests and responses are plain JavaScript objects;
the JSON marshalling to/from the C-FFI is handled internally.
The example below uses the external-signer lifecycle — the mode the WDK ships with, where the host owns the seed.
const rln = require('@utexo/rgb-lightning-node-nodejs')
// Create the node handle (does not open the network yet).
const node = rln.SdkNode.create({
storage_dir_path: '/path/to/persistent/dir',
daemon_listening_port: 0,
ldk_peer_listening_port: 0,
network: 'regtest',
max_media_upload_size_mb: 5,
enable_virtual_channels_v0: false
})
// Build the in-process VLS signer from a host-owned 32-byte seed
// (64-char hex). permissiveSignerPolicy defaults to true.
const signer = rln.NativeExternalSigner.create(seedHex, 'regtest')
// First-launch init writes the key-source file to storage_dir_path.
// On every subsequent launch RLN throws Rln(Conflict) — swallow it.
try {
node.initWithNativeExternalSigner(signer)
} catch (e) {
if (!String(e.message).includes('Conflict')) throw e
}
// Bring the node online.
node.unlockWithNativeExternalSigner(signer, {
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'
})
console.log(node.nodeInfo().pubkey)
// ...later — deterministic teardown (signer is dropped too).
node.shutdown()
signer.destroy()Request bodies follow the JSON schemas in rgb-lightning-node's
openapi.yaml. All methods are synchronous and return parsed objects (or
throw napi::Error on the C-FFI Err branch).
NativeExternalSigner
| Method | Description |
|---|---|
create(seedHex, network, permissiveSignerPolicy = true) |
In-process VLS signer from a 64-char-hex 32-byte entropy. |
bootstrap() |
Returns { node_id, account xpubs, master_fingerprint, ... }. |
destroy() |
Eager drop; idempotent. |
SdkNode — lifecycle
create(request), initWithNativeExternalSigner(signer),
attachNativeExternalSigner(signer),
unlockWithNativeExternalSigner(signer, request),
initWithExternalSigner(bootstrap), unlockWithAttachedExternalSigner(request),
detachExternalSigner(), shutdown().
SdkNode — operations
| Group | Methods |
|---|---|
| Info / sync | nodeInfo, networkInfo, sync, address / getAddress |
| Peers | connectPeer, disconnectPeer, listPeers |
| Channels | openChannel, closeChannel, listChannels, getChannelId |
| Invoices | lnInvoice, decodeLnInvoice, invoiceStatus, rgbInvoice, decodeRgbInvoice, cancelHodlInvoice, claimHodlInvoice |
| Payments | sendPayment, keysend, listPayments, getPayment |
| Swaps | makerInit, makerExecute, taker, listSwaps, getSwap |
| RGB issuance | issueAssetNia, issueAssetUda, issueAssetCfa, issueAssetIfa |
| RGB assets | listAssets, assetBalance, assetMetadata, sendRgb, inflate, listTransfers, refreshTransfers, failTransfers, getAssetMedia, postAssetMedia |
| BTC | btcBalance, sendBtc, listTransactions, listUnspents, createUtxos, estimateFee |
| VSS | vssClearFence, vssBackup |
| APay | apayNew |
| Signing / onion / diagnostics | signMessage, sendOnionMessage, checkIndexerUrl, checkProxyEndpoint |
Module-level helpers — uniffiHealthcheck(), uniffiIsInitialized(),
sdkInitialize(), sdkShutdown(). These exist for surface parity with the
bare addon (which uses them to manage a process-global tokio runtime). In
the Node binding the runtime is per-SdkNode, so these are stubs/no-ops;
they're safe to call.
See index.js for the authoritative method list and
index.d.ts for type signatures.
RLN never sees the BIP-39 mnemonic. The host derives a 32-byte BIP-32
entropy and passes it as seedHex to NativeExternalSigner.create. The
key-source file written by initWithNativeExternalSigner records only
public identifying data (xpubs, node id, master fingerprint). Re-deriving
from the same mnemonic on a later launch reproduces the same seedHex,
which matches the on-disk key-source — so the LDK node identity stays
stable across restarts. All channel-state cryptography runs in-process via
the VLS signer.
JS façade (index.js) Rust crate "rln-node" (cdylib) Static lib
──────────────────── ────────────────────────────── ──────────────
SdkNode ──► #[napi] impl SdkNode { ... } ──► librlncffi.a
NativeExternalSigner ─► #[napi] impl NativeExternalSigner ► (rln-c-ffi, the
same .a the bare
addon links)
Each #[napi] method serialises its request to a C string, calls the
matching rln_* C-FFI function, and parses the returned CResultString,
throwing napi::Error on the error branch. index.js adds the JSON
stringify/parse layer so the JS surface matches the bare addon 1:1.
Releases are cut by the Build and Release (Node) GitHub Actions
workflow (.github/workflows/release.yml),
triggered either by a repository_dispatch (rln-release) from
rgb-lightning-node or manually via workflow_dispatch with an
rln_version input (e.g. v0.6.0-beta.1). The workflow:
- Clones
rgb-lightning-nodeat the pinned tag and applies the C-FFI patch series atpatches/(a no-op when the tag already carries the C-FFI surface upstream). - Builds the napi addon for every target in the matrix
(
index-<suffix>.node). - Attaches the
.nodeartifacts to a GitHub Release and runsnpm publish.
For a local host build:
# In the rgb-lightning-node tree (sibling checkout), build the static lib:
bash bindings/c-ffi/build-for-host.sh # writes target/release/librlncffi.a
# Here:
npm install --ignore-scripts
npm run build # release build → index-<platform>.node
npm run build:debug # faster compile, slower runtimeApache-2.0. See LICENSE.