Transfer Stablecoins
Learn how to transfer stablecoins between nodes
TL;DR
Set up three nodes, open UDT (stablecoin) channels, and send a stablecoin payment through a multi-hop path. Builds on the Basic Transfer example.
Overview
This guide demonstrates UDT transfers using stablecoins (RUSD) across 3 nodes via multi-hop routing.
Prerequisites
- Completed Basic Transfer
- curl for RPC calls
- ckb-cli for key management
Setting Up Your Environment
1. Prepare Fiber Binary
Download from GitHub Releases or build from source:
git clone https://github.com/nervosnetwork/fiber.git
cd fiber
cargo build --releasemacOS Security
xattr -d com.apple.quarantine fnn fnn-cli2. Configure Three Nodes
for node in node1 node2 node3; do
mkdir $node
cp target/release/fnn $node/
cp target/release/fnn-cli $node/
cp config/testnet/config.yml $node/
doneCreate a CKB account for each node and export the keys:
ckb-cli account new # repeat 3 times, save each lock_arg
# For each node directory:
mkdir ckb
ckb-cli account export --lock-arg <node_lock_arg> --extended-privkey-path ./ckb/exported-key
head -n 1 ./ckb/exported-key > ./ckb/keyKey File Format
ckb/key must contain only the 64-character hex private key, no 0x prefix.
Get Testnet funds:
- CKB: https://faucet.nervos.org
- RUSD: https://testnet0815.stablepp.xyz/stablecoin (claim via JoyID wallet at testnet.joyid.dev, then transfer to node address)
3. Configure Ports
- Node 1: RPC
8227, P2P8228 - Node 2: RPC
8237, P2P8238 - Node 3: RPC
8247, P2P8248
View complete config.yml example
fiber:
listening_addr: "/ip4/127.0.0.1/tcp/8228"
bootnode_addrs:
- "/ip4/54.179.226.154/tcp/8228/p2p/Qmes1EBD4yNo9Ywkfe6eRw9tG1nVNGLDmMud1xJMsoYFKy"
announce_listening_addr: true
chain: testnet
scripts:
- name: FundingLock
script:
code_hash: 0x6c67887fe201ee0c7853f1682c0b77c0e6214044c156c7558269390a8afa6d7c
hash_type: type
args: 0x
cell_deps:
- type_id:
code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944
hash_type: type
args: 0x3cb7c0304fe53f75bb5727e2484d0beae4bd99d979813c6fc97c3cca569f10f6
- cell_dep:
out_point:
tx_hash: 0x12c569a258dd9c5bd99f632bb8314b1263b90921ba31496467580d6b79dd14a7
index: 0x0
dep_type: code
- name: CommitmentLock
script:
code_hash: 0x740dee83f87c6f309824d8fd3fbdd3c8380ee6fc9acc90b1a748438afcdf81d8
hash_type: type
args: 0x
cell_deps:
- type_id:
code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944
hash_type: type
args: 0xf7e458887495cf70dd30d1543cad47dc1dfe9d874177bf19291e4db478d5751b
- cell_dep:
out_point:
tx_hash: 0x12c569a258dd9c5bd99f632bb8314b1263b90921ba31496467580d6b79dd14a7
index: 0x0
dep_type: code
rpc:
listening_addr: "127.0.0.1:8227"
ckb:
rpc_url: "https://testnet.ckbapp.dev/"
udt_whitelist:
- name: RUSD
script:
code_hash: 0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a
hash_type: type
args: 0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b
cell_deps:
- type_id:
code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944
hash_type: type
args: 0x97d30b723c0b2c66e9cb8d4d0df4ab5d7222cbb00d4a9a2055ce2e5d7f0d8b0f
auto_accept_amount: 10
services:
- fiber
- rpc
- ckb4. Start All Nodes
# Terminal 1
FIBER_SECRET_KEY_PASSWORD='password1' RUST_LOG=info ./fnn -c node1/config.yml -d node1
# Terminal 2
FIBER_SECRET_KEY_PASSWORD='password2' RUST_LOG=info ./fnn -c node2/config.yml -d node2
# Terminal 3
FIBER_SECRET_KEY_PASSWORD='password3' RUST_LOG=info ./fnn -c node3/config.yml -d node3Creating Stablecoin Payment Channels
1. Connect Node 1 and Node 2
cd node1 && ./fnn-cli peer connect_peer --address "/ip4/127.0.0.1/tcp/8238"curl --location 'http://127.0.0.1:8227' \
--header 'Content-Type: application/json' \
--data '{
"id": 42, "jsonrpc": "2.0", "method": "connect_peer",
"params": [{"pubkey": "<node2_pubkey>", "address": "/ip4/127.0.0.1/tcp/8238"}]
}'Get Node 2's pubkey: cd node2 && ./fnn-cli --url http://127.0.0.1:8237 info | grep pubkey
2. Open a Stablecoin Channel (Node 1 → Node 2)
The funding_udt_type_script identifies the RUSD token:
cd node1 && ./fnn-cli channel open_channel \
--pubkey <node2_pubkey> \
--funding-amount 10 \
--public true \
--funding-udt-type-script '{"code_hash":"0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a","hash_type":"type","args":"0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b"}'curl --location 'http://127.0.0.1:8227' \
--header 'Content-Type: application/json' \
--data '{
"id": 42, "jsonrpc": "2.0", "method": "open_channel",
"params": [{
"pubkey": "<node2_pubkey>",
"funding_amount": "0xa",
"public": true,
"funding_udt_type_script": {
"code_hash": "0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a",
"hash_type": "type",
"args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b"
}
}]
}'3. Monitor Channel Status
Wait until state_name becomes ChannelReady:
./fnn-cli channel list_channelscurl --location 'http://127.0.0.1:8227' \
--header 'Content-Type: application/json' \
--data '{"id": 42, "jsonrpc": "2.0", "method": "list_channels", "params": [{}]}'Repeat steps 1–3 to connect Node 2 and Node 3 (using ports 8247/8248).
Generating Invoices and Making Payments
1. Generate a Stablecoin Invoice on Node 2
cd node2 && ./fnn-cli --url http://127.0.0.1:8237 invoice new_invoice \
--amount 10 \
--currency Fibt \
--description "test stablecoin invoice" \
--expiry 3600 \
--udt-type-script '{"code_hash":"0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a","hash_type":"type","args":"0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b"}'curl --location 'http://127.0.0.1:8237' \
--header 'Content-Type: application/json' \
--data '{
"id": 42, "jsonrpc": "2.0", "method": "new_invoice",
"params": [{
"amount": "0xa",
"currency": "Fibt",
"description": "test stablecoin invoice",
"expiry": "0xe10",
"udt_type_script": {
"code_hash": "0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a",
"hash_type": "type",
"args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b"
}
}]
}'Generate a unique payment_preimage with: echo "0x$(openssl rand -hex 32)"
Note: CLI auto-generates preimage; for RPC add "payment_preimage": "<your_preimage>" to params.
2. Send the Stablecoin Payment from Node 1
cd node1 && ./fnn-cli payment send_payment --invoice "fibt10000000001p..."curl --location 'http://127.0.0.1:8227' \
--header 'Content-Type: application/json' \
--data '{
"id": 42, "jsonrpc": "2.0", "method": "send_payment",
"params": [{"invoice": "fibt10000000001p..."}]
}'3. Check Channel Balance
# Node 1
./fnn-cli channel list_channels
# Node 2
./fnn-cli --url http://127.0.0.1:8237 channel list_channelscurl --location 'http://127.0.0.1:8227' \
--header 'Content-Type: application/json' \
--data '{"id": 42, "jsonrpc": "2.0", "method": "list_channels", "params": [{}]}'Closing the Channel
cd node1 && ./fnn-cli channel shutdown_channel \
--channel-id <channel_id> \
--close-script '{"code_hash":"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8","hash_type":"type","args":"<your_lock_arg>"}'curl --location 'http://127.0.0.1:8227' \
--header 'Content-Type: application/json' \
--data '{
"id": 42, "jsonrpc": "2.0", "method": "shutdown_channel",
"params": [{
"channel_id": "<channel_id>",
"close_script": {
"code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
"hash_type": "type",
"args": "<your_lock_arg>"
}
}]
}'close_script
Get args from fnn-cli info under default_funding_lock_script.args.
Important Notes
- RUSD amounts: CLI uses base units (e.g.
10= 10 RUSD); RPC uses hex strings (e.g."0xa"= 10) - Invoice currency: Use
"Fibt"for testnet, not"RUSD" - auto_accept_amount: Channels with funding below this value require manual acceptance