Skip to main content
This guide covers Node.js server-side usage. For mobile (iOS/Android) see the React Native guide, and for browser apps see the Web guide. For concepts shared across all guides, see the Quickstart Overview.

Prerequisites

  • Node.js v18 or laterDownload here
  • A testnet BTC balance for transaction fees (see Quickstart Overview for faucet links)
  • Basic familiarity with async/await JavaScript
This guide uses testnet. Never use mainnet keys or real funds while following this tutorial. Testnet assets have no monetary value.

Step 1: Install the SDK

npm install @utexo/rgb-sdk
The @utexo/rgb-sdk package is Node.js only and is not browser-compatible. It requires access to the file system and native crypto modules. For browser environments use @utexo/rgb-sdk-web; for iOS and Android use @utexo/rgb-sdk-rn.

Step 2: Generate Wallet Keys

Start by generating a BIP-39 mnemonic. This 12-word seed phrase is the only way to recover your wallet — store it securely before proceeding.
const { generateKeys } = require('@utexo/rgb-sdk');

const keys = await generateKeys('testnet');
console.log('Mnemonic:', keys.mnemonic); // Store this securely — never share it
console.log('Pubkey:', keys.xpub);
Back up your mnemonic immediately. If you lose it, your wallet and any assets it holds cannot be recovered. Never commit mnemonics to source control.

Step 3: Initialise the Wallet

Create and initialise a UTEXOWallet instance using your mnemonic. Initialisation connects to the default RGB transport and Bitcoin indexer endpoints for testnet.
const { UTEXOWallet } = require('@utexo/rgb-sdk');

const wallet = new UTEXOWallet(keys.mnemonic, {
  network: 'testnet'
});
await wallet.initialize();
console.log('Wallet initialised successfully');
initialize() performs the initial sync with the Bitcoin indexer and RGB transport layer. This may take a few seconds on first run.

Step 4: Get a Deposit Address

Get your wallet’s Bitcoin deposit address. You will use this to receive testnet BTC from a faucet — a small BTC balance is required to pay RGB transaction fees.
const address = await wallet.getAddress();
console.log('Deposit address:', address);
Send testnet BTC to this address using one of the faucets listed in the Quickstart Overview. Wait for at least 1 confirmation before continuing. A few thousand satoshis is enough to cover fees for this tutorial.

Step 5: Create UTXOs

RGB asset state must be anchored to Bitcoin UTXOs. Before you can receive any RGB asset, your wallet needs dedicated UTXOs created for this purpose.
// num: number of UTXOs to create
// size: size of each UTXO in satoshis
await wallet.createUtxos({ num: 5, size: 1000 });
await wallet.refreshWallet();
console.log('UTXOs created and wallet synced');
Parameter reference:
ParameterTypeDescription
numnumberNumber of UTXOs to create. 5 is recommended for a new wallet.
sizenumberSize of each UTXO in satoshis. 1000 satoshis per UTXO is the minimum recommended value.
refreshWallet() syncs the wallet’s local state with the Bitcoin chain. Always call it after any on-chain operation before querying balances or generating invoices.

Step 6: Get the USDT on Bitcoin Asset ID

Before generating an invoice, you need the asset ID of the USDT on Bitcoin token on testnet. This ID uniquely identifies the asset contract in the RGB protocol.
// List known assets to find the USDT asset ID
const assets = await wallet.listAssets();
console.log('Available assets:', JSON.stringify(assets, null, 2));

// The USDT asset ID will look like:
// "rgb:2dkSTbr-jFhznbPmo-TQafzswCN-av2UEe9m2-LnFCMMG9-M9vByXo"
const usdtAssetId = assets.find(a => a.ticker === 'USDT')?.id;
console.log('USDT Asset ID:', usdtAssetId);
If no assets appear, your wallet may not yet have been assigned any RGB assets. You can also obtain the canonical testnet USDT asset ID from the SDK Reference or by asking in the Utexo Discord.

Step 7: Generate an RGB Invoice (Receiver Side)

The receiver generates a blinded invoice to share with the sender. The blinded invoice hides the receiver’s UTXO from the sender while still allowing the transfer to be verified client-side.
// Run this on the RECEIVER's wallet
const receiveData = await wallet.blindReceive({
  assetId: usdtAssetId,   // RGB asset ID from Step 6
  amount: 100,             // Amount in asset base units (1 USDT = 1 unit)
  minConfirmations: 1,     // Minimum Bitcoin confirmations before the transfer is accepted
  durationSeconds: 3600    // Invoice expiry: 3600 seconds = 1 hour
});
console.log('RGB Invoice:', receiveData.invoice);
// Share receiveData.invoice with the sender
Parameter reference:
ParameterTypeDescription
assetIdstringThe RGB asset contract ID. See Step 6.
amountnumberAmount to receive in asset base units. For USDT on Bitcoin, 1 unit = 1 USDT.
minConfirmationsnumberMinimum Bitcoin confirmations required before accepting the transfer.
durationSecondsnumberInvoice validity window in seconds. Invoice is rejected by the receiver after expiry.
Invoices expire after durationSeconds. The sender must complete the transfer before the invoice expires, otherwise a new invoice is required.

Step 8: Send an RGB Asset (Sender Side)

The sender uses the invoice from Step 7 to transfer the asset. The sender must already hold a balance of the RGB asset being transferred.
// Run this on the SENDER's wallet (must hold USDT on Bitcoin balance)
const sendResult = await wallet.send({
  invoice: receiveData.invoice, // Invoice string from Step 7
  assetId: usdtAssetId,         // Must match the asset ID in the invoice
  amount: 100                   // Must match the amount in the invoice
});
console.log('Transfer TXID:', sendResult.txid);
console.log('Transfer status:', sendResult.status);

// Sync both wallets after the transfer
await wallet.refreshWallet();        // Sender
await receiverWallet.refreshWallet(); // Receiver (run on receiver's side)
The send() call constructs the RGB state transition, attaches it to a Bitcoin transaction, and broadcasts it. The RGB transport layer (rpcs://) is used to communicate the state transition to the receiver. The amount and assetId in the send() call must exactly match those in the invoice. Mismatches will cause the transfer to be rejected by the receiver’s client-side validator.

Step 9: Verify the Transfer

After both wallets have called refreshWallet(), check the balances to confirm the transfer completed successfully.
// Check receiver's balance
const receiverAssets = await receiverWallet.listAssets();
console.log('Receiver assets:', JSON.stringify(receiverAssets, null, 2));

// Check sender's remaining BTC balance
const senderBtcBalance = await wallet.getBtcBalance();
console.log('Sender BTC balance (satoshis):', senderBtcBalance);
Expected result: the receiver’s asset list shows a balance of 100 for the USDT asset. If the transfer is still pending confirmation, call refreshWallet() again after the next Bitcoin block. RGB transfers require Bitcoin confirmation to finalise. On testnet, a new block typically arrives every 1–10 minutes.

Troubleshooting

IssueLikely causeFix
initialize() hangs or times outNo network connection to indexerCheck your internet connection and firewall settings
createUtxos() failsInsufficient BTC balanceFund your wallet with more testnet BTC from a faucet
Invoice rejectedInvoice has expiredGenerate a new invoice and retry
send() fails with validation erroramount or assetId mismatchConfirm the send parameters exactly match the invoice
Balance not updated after sendWallet not syncedCall refreshWallet() on both sender and receiver wallets
Asset ID not foundAsset not yet in walletCheck listAssets() or obtain the canonical asset ID from the SDK reference
If you encounter an issue not listed here, join the Utexo Discord for community support.

Next Steps

  • SDK Reference — Full method reference for UTEXOWallet, including Lightning channel management and backup
  • Architecture — Understand how Bitcoin, Lightning, and RGB fit together
  • Glossary — Definitions for RGB, PSBT, blinded invoice, UTXO, and other key terms