Validating RGB Assets on Lightning Network
A note before reading:
This demo is designed to step-by-step verify the integration of rgbldkd with rgbdex.net in a local Regtest environment, without modifying any production code.
The Stash Labs team has successfully verified a complete end-to-end demo of RGB assets running natively on the Lightning Network, powered by Bitlight Labs' rgbldkd node implementation. This post documents what we validated, how the system is architected, and the key protocol insights we gained along the way.
What We Validated
In 13 sequential steps, we demonstrated the full lifecycle of an RGB20 token — from issuance on a regtest Bitcoin chain, through a Lightning channel opening that simultaneously carries BTC and RGB assets, to an atomic swap between BTC and RGB tokens using Hold Invoices. Each step was driven by a React frontend calling a Node.js proxy server that relayed commands to two rgbldkd nodes (Alice and Bob) running in Docker.
All 13 steps passed validation:
| # | Step | Status | Verification Notes |
|---|---|---|---|
| 01 | Check Nodes | ✅ | Both Alice and Bob rgbldkd nodes running; Node IDs (secp256k1 pubkeys) retrieved successfully |
| 02 | Mine 101 Blocks | ✅ | Regtest coinbase maturity confirmed; 101 blocks mined so block 1's reward is spendable |
| 03 | Fund Alice BTC | ✅ | 1 BTC sent to Alice's LN wallet; balance confirmed after wallet/sync |
| 04 | Fund Alice RGB UTXO | ✅ | 0.01 BTC UTXO prepared as Genesis Seal for RGB contract issuance |
| 05 | Import Issuer Template | ✅ | RGB20-Simplest-v0 AluVM bytecode imported into Alice's node via binary upload |
| 06 | Issue RGB20 Contract | ✅ | 1,000,000 TTK issued; contract_id and asset_id returned; commitment anchored on-chain |
| 07 | Share Contract → Bob | ✅ | Consignment exported from Alice, verified and imported by Bob — fully off-chain |
| 08 | Fund Bob BTC | ✅ | 0.05 BTC sent to Bob's wallet; available for Hold Invoice payment leg |
| 09 | Connect Peers | ✅ | Alice → Bob Noise Protocol TCP connection established over Docker internal network |
| 10 | Open RGB LN Channel | ✅ | Dual-asset Funding TX created: 2,000,000 sats + 500,000 TTK in one output |
| 11 | Confirm Channel | ✅ | 6-block confirmation reached; ChannelReady event received on both sides |
| 12 | RGB LN Payment | ✅ | Alice sent Bob 100 TTK instantly via Lightning; no block confirmation required |
| 13 | Hold Invoice Swap | ✅ | Bob locked 10,000 sats via HTLC; Alice delivered 200 TTK; preimage released to settle |
System Architecture
The demo environment runs entirely on a single host:
┌────────────────────────────────────────────────────────────┐
│ Host (macOS) │
│ │
│ ┌──────────────┐ ┌───────────────────────────────┐ │
│ │ Browser │ │ Docker Network 172.18.0.0/24│ │
│ │ :5173 Vite │ │ │ │
│ └──────┬───────┘ │ bitcoind 172.18.0.2 │ │
│ │ HTTP │ esplora 172.18.0.3 │ │
│ ┌──────▼───────┐ │ rgb-node-alice 172.18.0.4 │ │
│ │ server.mjs │◄────► rgb-node-bob 172.18.0.5 │ │
│ │ :3000 Proxy │ │ │ │
│ └──────────────┘ └───────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘| Component | Technology | Role |
|---|---|---|
bitcoind | Bitcoin Core 24.0.1, Regtest | Local test chain, BTC transfers, block mining |
esplora | Bitlight custom Esplora | Blockchain indexer, serves Electrum protocol to rgbldkd for UTXO sync |
rgb-node-alice/bob | bitlightlabs/rln-ldk-node | RGB+LDK fusion node managing BTC wallet, RGB assets, and Lightning channels |
server.mjs | Node.js HTTP (no framework) | Proxy: CORS handling, binary file relay, composite operations |
| Vite + React | React 18 + Tailwind CSS | Wizard-style UI, one step per group of API calls |
The proxy server (server.mjs) solves two fundamental browser limitations: rgbldkd does not set Access-Control-Allow-Origin headers, and binary consignment data cannot be streamed directly from the browser. The proxy handles both, presenting a clean REST surface to the frontend.
Step-by-Step Walkthrough
Steps 1–4: Foundation
Step 1 — Check Nodes queries both nodes for their node_id (secp256k1 public keys), which doubles as the Lightning Network P2P identity for the Noise Protocol handshake later.
Step 2 — Mine 101 Blocks creates the initial Bitcoin coinbase maturity. Bitcoin requires 100 confirmations before coinbase outputs can be spent; mining 101 blocks makes the first block's reward available. The Docker bitcoind-init container handles this automatically on startup.
Step 3 — Fund Alice BTC sends 1 BTC to Alice's rgbldkd wallet. The node uses BDK (Bitcoin Dev Kit) internally and syncs via Esplora — wallet/sync must be called explicitly since the node does not listen reactively.
Step 4 — Fund Alice RGB UTXO is a critical preparation step. RGB protocol uses Single-Use Seals: asset ownership is committed to a specific UTXO. When that UTXO is spent, the RGB state transition occurs. This requires RGB and BTC wallets to manage UTXOs independently, hence Alice needs a dedicated RGB address funded with 0.01 BTC before any token issuance.
Steps 5–7: RGB20 Token Issuance & Distribution
Step 5 — Import Issuer Template loads a .issuer binary file — compiled AluVM bytecode — into Alice's node. AluVM is RGB's virtual machine; contract logic executes locally on each recipient's machine, never on-chain. The RGB20-Simplest-v0 template is a stripped-down RGB20 implementation ideal for demos.
Step 6 — Issue RGB20 Contract creates 1,000,000 TTK tokens:
{
"issuer_name": "RGB20-Simplest-v0-rLosfg",
"contract_name": "TestToken",
"ticker": "TTK",
"precision": 0,
"issued_supply": "1000000"
}Internally, the node consumes the Step 4 UTXO as the Genesis Seal, constructs the Genesis State (Alice owns all 1M TTK), generates a globally unique contract_id, and embeds a cryptographic commitment into a Bitcoin transaction using Tapret or OP_RETURN. No new on-chain data structure broadcasts the token — only a commitment hash appears on-chain.
Step 7 — Share Contract → Bob distributes the contract to Bob's node entirely off-chain via the Consignment mechanism. The proxy server orchestrates three calls atomically:
- Alice exports a
consignment_key - The proxy downloads the binary consignment zip from Alice
- The proxy uploads it to Bob
Bob independently verifies the full contract history against the Bitcoin blockchain. If every anchored commitment is found in a confirmed transaction, Bob accepts the contract. Alice cannot forge this history — the verification is entirely on Bob's side.
Steps 8–11: Lightning Channel with RGB Assets
Step 8 — Fund Bob BTC gives Bob 0.05 BTC for later use in the atomic swap payment leg.
Step 9 — Connect Peers establishes the Lightning P2P TCP connection between Alice and Bob using the Noise Protocol (BOLT #8). The connection uses Docker internal IPs (172.18.0.5:9735) since P2P happens container-to-container.
Step 10 — Open RGB LN Channel is where RGB meets Lightning in a single Funding Transaction:
{
"channel_amount_sats": "2000000",
"push_to_counterparty_msat": "50000000",
"rgb": {
"asset_id": "<asset_id>",
"asset_amount": "500000",
"color_context_data": "http://172.18.0.4:8500/api/v1/rgb/consignments/{txid}?format=zip"
}
}The BTC side follows standard LDK: a 2-of-2 multisig Funding Output with 2M sats, pushing 50K sats to Bob for initial outbound liquidity. The RGB side binds 500,000 TTK ownership to the same Funding Transaction output. The color_context_data URL tells Bob where to download the RGB Consignment to verify Alice's token ownership independently before accepting the channel.
Step 11 — Confirm Channel mines enough blocks to reach the 6-confirmation threshold required by BOLT #2. The demo uses a dual-confirmation strategy: first it waits for a ChannelReady event from the node's event queue, and falls back to polling the /channels API if the event was already consumed (e.g., after a page refresh).
Step 12: RGB Lightning Payment
With the channel open, Alice pays Bob 100 TTK instantly with no block confirmation:
{
"asset_id": "<asset_id>",
"asset_amount": "100",
"btc_carrier_amount_msat": "3000000"
}The key design insight here: RGB tokens cannot be routed in the Lightning Network independently — they have no millisatoshi denomination. Every RGB LN payment must piggyback on a small BTC HTLC (the "carrier"). The 3,000 sat carrier activates the HTLC routing path; the RGB state update travels alongside it in the off-chain protocol layer. Payment completes in milliseconds with no on-chain footprint.
Step 13: Hold Invoice Atomic Swap
This is the demo's most sophisticated feature — a BTC ↔ RGB token swap implemented using Hold Invoices. The swap: Bob pays 10,000 sats, Alice delivers 200 TTK.
How it works
① Alice creates a Hold Invoice
Alice generates a random 32-byte preimage locally and computes paymentHash = SHA256(preimage). She creates a Hold Invoice for 10,000 sats locked to paymentHash, but does not reveal the preimage yet.
preimage = random_bytes(32) // kept secret by Alice
paymentHash = SHA256(preimage) // published in the invoice② Bob pays the invoice (HTLC locked)
Bob sends the payment. His 10,000 sats are now locked in an HTLC:
HTLC condition: reveal x where SHA256(x) == paymentHash
→ funds locked, Bob cannot unilaterally cancel before expiryThis is the "commitment" phase. Bob's funds are locked but Alice hasn't received them yet.
③ Alice sends 200 TTK to Bob
Seeing the HTLC is locked, Alice now sends the RGB payment to Bob via the same Lightning channel. This is a standard RGB LN payment and settles immediately.
④ Alice claims BTC by revealing preimage
{
"payment_hash": "<paymentHash>",
"preimage": "<preimage>",
"claimable_amount_msat": "10000000"
}Alice calls claim_for_hash, broadcasts the preimage, and the HTLC settles — she receives 10,000 sats.
Final state:
Alice: +10,000 sats, -200 TTK
Bob: -10,000 sats, +200 TTKThe production rgb-lightning-node protocol achieves true cryptographic atomicity without DLC or cross-chain coordination. It does so through a circular Lightning route: Maker is both the start and end of the route, Taker sits in the middle as the conversion point, and a single payment_hash locks both legs simultaneously. When Maker reveals the preimage at the destination, both the inbound and outbound legs settle at once — atomicity is guaranteed by the routing topology itself, not by any additional protocol layer.
Key Protocol Insights
RGB's Client-Side Validation Model
| Traditional On-Chain Tokens | RGB |
|---|---|
| All transfers publicly visible | Only counterparties know transfer history |
| Contract state lives on-chain | Contract state lives with the recipient |
| Validation by full nodes | Validation by the recipient locally |
| All data permanently on-chain | Only commitments (hashes) on-chain |
This design gives RGB three important properties: privacy (transfer history is not globally visible), scalability (no per-transfer on-chain data), and sovereignty (you validate your own funds independently).
Single-Use Seals
Every RGB state transition is bound to a UTXO spend. When the UTXO is spent, the seal is "broken" — the RGB transition is executed irreversibly. This prevents double-spending at the protocol level without requiring a global ledger of RGB transactions.
Consignments as Proof Packages
When you receive RGB assets, you receive a Consignment: a complete audit trail from the Genesis transaction to the current state. You verify this trail locally against Bitcoin transactions. The sender cannot manipulate this history — all RGB commitments are anchored in confirmed Bitcoin blocks that anyone can verify independently.
BTC as RGB's Lightning Carrier
RGB tokens cannot exist in Lightning's HTLC routing natively (no msat value). The btc_carrier_amount_msat field in every RGB LN payment is the minimum BTC required to "carry" the RGB state update through the channel. The BTC carrier and RGB payload travel together; if the BTC HTLC fails, the RGB transfer fails too.
Open Source Reference
The full verification demo code is available at rgbstashlabs/rgb-ldk-verify-demo. It includes the complete Docker infrastructure, proxy server, and React frontend. Developers can run the full 13-step flow locally on any macOS or Linux machine with Docker.
RGB assets on Lightning represent one of the most significant capability expansions for Bitcoin's programmability layer. The ability to issue, transfer, and swap custom assets inside off-chain channels — with Bitcoin's security guarantees and client-side privacy — opens the door to a new class of Lightning-native DeFi applications that don't require a separate blockchain.