Transfers (BTC/RGB × L1/L2)
End-to-end examples for mutual transfers across the four combinations: BTC/RGB × L1/L2.
This page is a practical “copy/paste” guide for mutual transfers across the four combinations:
- BTC L1 (on-chain settlement via channel
--push-msat+ cooperative close): node-a → node-b, and node-b → node-a - BTC L2 (Lightning Bolt11): node-a → node-b, and node-b → node-a
- RGB L1 (RGB on-chain invoices + consignment import): node-a → node-b, and node-b → node-a
- RGB L2 (RGB Lightning invoices): node-a → node-b, and node-b → node-a
No jq, no shell tricks. You will manually copy values from command output and paste them into the next command.
Setup (two nodes)
Assume you have two running daemons:
node-a: http://127.0.0.1:8501
node-b: http://127.0.0.1:85021) Add contexts
rgbldk ctx add node-a --url http://127.0.0.1:8501 --use-now
rgbldk ctx add node-b --url http://127.0.0.1:85022) Collect values you will paste later
You need both values for each node:
node_id(Lightning node public key)p2p listen address(whatpeer connect/channel openneeds)
Node-a values
rgbldk ctx use node-a
rgbldk --output json --pretty node id
rgbldk node listenExample output:
{ "node_id": "03431a20a8ff...a4af6de0bd" }
127.0.0.1:61452Copy/paste:
- Copy the
node_idvalue (the long hex string). - Copy the listen address (looks like
127.0.0.1:61452).
Node-b values
rgbldk ctx use node-b
rgbldk --output json --pretty node id
rgbldk node listen3) Make sure the nodes are connected (recommended)
Run these once so opening channels and paying invoices is reliable:
rgbldk ctx use node-a
rgbldk peer connect <NODE_ID_B> <NODE_B_P2P> --persist
rgbldk ctx use node-b
rgbldk peer connect <NODE_ID_A> <NODE_A_P2P> --persistIf you see truncated IDs in tables, re-run the command with --no-truncate so you can copy the full value.
BTC L2 (Lightning Bolt11)
You need at least one usable Lightning channel between the nodes.
If you do not have a usable channel yet
Open a small private channel from node-a to node-b:
rgbldk ctx use node-a
rgbldk channel open \
--node-id <NODE_ID_B> \
--addr <NODE_B_P2P> \
--amount-sats 60000 \
--privateCopy the user_channel_id from the response:
{ "user_channel_id": "a2f103359c5bf4987caa028878118345" }Then mine blocks / wait for confirmations until the channel becomes usable, and check:
rgbldk channel lsnode-a → node-b
- Create an invoice on the receiver (node-b):
rgbldk ctx use node-b
rgbldk --output json --pretty pay invoice create --desc "demo" --amount-msat 10000-
Copy the
invoicestring from the output. -
Pay it from the sender (node-a):
rgbldk ctx use node-a
rgbldk --output json --pretty pay invoice pay --invoice '<INVOICE_FROM_NODE_B>'- Copy the
payment_idstring from the output, then wait:
rgbldk pay wait <PAYMENT_ID> --timeout-secs 60
rgbldk pay get <PAYMENT_ID>node-b → node-a
Same flow, reversed:
rgbldk ctx use node-a
rgbldk --output json --pretty pay invoice create --desc "demo-back" --amount-msat 12000Copy invoice, then:
rgbldk ctx use node-b
rgbldk --output json --pretty pay invoice pay --invoice '<INVOICE_FROM_NODE_A>'
rgbldk pay wait <PAYMENT_ID> --timeout-secs 60
rgbldk pay get <PAYMENT_ID>BTC L1 (on-chain settlement via push+close)
This is the simplest “node-to-node L1 transfer” workflow supported by the daemon:
- open a channel with
--push-msat(gives the receiver an initial Lightning balance) - confirm the funding transaction (mine blocks on regtest)
- cooperatively close the channel to settle on-chain
node-a → node-b
- Open the channel from node-a and push some sats to node-b:
rgbldk ctx use node-a
rgbldk channel open \
--node-id <NODE_ID_B> \
--addr <NODE_B_P2P> \
--amount-sats 60000 \
--push-msat 2000000 \
--private- Copy the
user_channel_idfrom the output:
{ "user_channel_id": "e71a34a34e13ace55677d1ba36dfc777" }- Mine blocks until the channel is usable, then close:
rgbldk channel close --user-channel-id <USER_CHANNEL_ID> --counterparty-node-id <NODE_ID_B>- Mine blocks again so the close confirms, then sync wallets:
rgbldk wallet sync
rgbldk ctx use node-b
rgbldk wallet syncRegtest mining (docker-compose example)
If you are using the CLI repo’s docker-compose regtest stack, this is what “mine 6 blocks” looks like:
docker compose -f crates/cli/docker-compose.yaml exec -T bitcoind \
bitcoin-cli -regtest -rpcuser=bitcoin -rpcpassword=bitcoin \
generatetoaddress 6 <MINER_ADDRESS>node-b → node-a
Same pattern, but open from node-b and push to node-a:
rgbldk ctx use node-b
rgbldk channel open \
--node-id <NODE_ID_A> \
--addr <NODE_A_P2P> \
--amount-sats 60000 \
--push-msat 2000000 \
--privateCopy user_channel_id, mine blocks, then:
rgbldk channel close --user-channel-id <USER_CHANNEL_ID> --counterparty-node-id <NODE_ID_A>RGB prerequisites (asset_id / contract_id)
You need an existing RGB asset. Find it like this:
rgbldk ctx use node-a
rgbldk --no-truncate rgb contracts lsFrom the table output, copy:
asset_id(hex)contract_id(starts withcontract:)
RGB L2 (RGB Lightning)
You need an RGB-enabled Lightning channel that has RGB liquidity for the chosen asset_id.
If you do not have an RGB-enabled channel yet
This command opens a private channel and also attaches RGB funding information.
rgbldk ctx use node-a
rgbldk channel open \
--node-id <NODE_ID_B> \
--addr <NODE_B_P2P> \
--amount-sats 100000 \
--push-msat 20000000 \
--private \
--rgb-asset-id <ASSET_ID_HEX> \
--rgb-asset-amount 10 \
--rgb-context 'http://<NODE_A_HOST>:8501/api/v1/rgb/consignments/{txid}?format=zip'Notes:
- Replace
<NODE_A_HOST>with an address that node-b can reach (it must be able to HTTP GET that URL). - If you do not know what this is, run on a single machine first (local dev), then revisit cross-host later.
node-a → node-b
- Create an RGB Lightning invoice on node-b:
rgbldk ctx use node-b
rgbldk --output json --pretty rgb ln invoice create \
--asset-id <ASSET_ID_HEX> \
--asset-amount 5 \
--desc "rgb ln demo" \
--btc-carrier-amount-msat 5000000- Copy the
invoicestring, then (optional) decode it:
rgbldk rgb ln invoice decode '<INVOICE>'- Pay the invoice from node-a:
rgbldk ctx use node-a
rgbldk --output json --pretty rgb ln pay --invoice '<INVOICE>'
rgbldk pay wait <PAYMENT_ID> --timeout-secs 60
rgbldk pay get <PAYMENT_ID>node-b → node-a
rgbldk ctx use node-a
rgbldk --output json --pretty rgb ln invoice create \
--asset-id <ASSET_ID_HEX> \
--asset-amount 3 \
--desc "rgb ln demo back" \
--btc-carrier-amount-msat 4000000Copy invoice, then:
rgbldk ctx use node-b
rgbldk --output json --pretty rgb ln pay --invoice '<INVOICE>'
rgbldk pay wait <PAYMENT_ID> --timeout-secs 60
rgbldk pay get <PAYMENT_ID>RGB L1 (on-chain)
The receiver creates an RGB on-chain invoice, the sender sends, then the receiver imports the consignment.
The cross-host friendly flow is:
- sender runs
rgb onchain send(gets aconsignment_key) - sender downloads a zip file with
rgb consignments download - receiver runs
rgb onchain receive --file ... --payment-id ...
node-a → node-b
- Create the invoice on node-b:
rgbldk ctx use node-b
rgbldk --output json --pretty rgb onchain invoice-create --contract-id <CONTRACT_ID> --amount 10Copy the invoice value (keep the quotes when you paste it into the next command).
Also derive the receiver-side payment_id as sha256(invoice_string). The daemon uses that value to correlate uploaded consignments with the invoice record.
- Send from node-a:
rgbldk ctx use node-a
rgbldk --output json --pretty rgb onchain send \
--invoice '<INVOICE_FROM_NODE_B>' \
--sats-for-fee-and-outputs 10000 \
--fee-rate-sats-per-vb 1.0Copy the consignment_key value, then download it to a file:
rgbldk rgb consignments download --key <CONSIGNMENT_KEY> --out ./a-to-b.zip --format zip- Import on node-b:
rgbldk ctx use node-b
rgbldk rgb onchain receive --file ./a-to-b.zip --format zip --payment-id <PAYMENT_ID_FROM_INVOICE>node-b → node-a
rgbldk ctx use node-a
rgbldk --output json --pretty rgb onchain invoice-create --contract-id <CONTRACT_ID> --amount 7Copy invoice, then:
- derive
payment_id=sha256(invoice_string)
rgbldk ctx use node-b
rgbldk --output json --pretty rgb onchain send \
--invoice '<INVOICE_FROM_NODE_A>' \
--sats-for-fee-and-outputs 10000 \
--fee-rate-sats-per-vb 1.0Copy consignment_key, then:
rgbldk rgb consignments download --key <CONSIGNMENT_KEY> --out ./b-to-a.zip --format zip
rgbldk ctx use node-a
rgbldk rgb onchain receive --file ./b-to-a.zip --format zip --payment-id <PAYMENT_ID_FROM_INVOICE>Useful helpers while debugging RGB L1:
rgbldk rgb onchain invoice-decode '<INVOICE>'
rgbldk rgb onchain payments --contract-id <CONTRACT_ID>