This repository is no longer actively maintained. Please migrate to the Rust implementation:
The Rust node is the supported target for new integrations and receives all ongoing development, bug fixes, and rgb-lib upgrades. This codebase remains available for reference only.
RGB Node is infrastructure software and is NOT intended to be used as a public shared service.
Running RGB Node requires explicit trust in the operator, because the node:
- Receives wallet identifiers (xpubs, master fingerprint)
- Maintains wallet state and UTXO sets
- Constructs PSBTs for signing
- Observes all wallet activity and transaction graph metadata
While private keys are never held by the RGB Node, wallet privacy and transaction integrity depend on the honesty and security of the server operator.
If you use an RGB Node operated by a third party:
- That operator can observe all wallet activity
- Extended public keys must be assumed disclosed
- A malicious or compromised server could construct malicious PSBTs
- Privacy exposure is permanent for any xpub ever used
RGB Node MUST be deployed inside infrastructure you control, such as:
- Exchange backend
- Wallet backend
- Internal settlement system
- Enterprise custody environment
RGB Node is a drop‑in HTTP service for integrating RGB asset transfers on Bitcoin L1. It exposes a developer‑friendly REST API for wallets, exchanges, and apps to issue, receive, and transfer RGB assets without embedding the full RGB protocol logic in the client.
- Responsibilities: RGB state handling, invoice creation/decoding, PSBT building, UTXO maintenance, and transfer lifecycle management
- Non‑custodial: signing happens externally by a signer service or the client itself (via PSBT)
- Multi‑wallet: manage multiple RGB wallets concurrently via the API (separate xpubs/state)
- Built on
rgb-libmaintained by Bitfinex - Full rgb-lib coverage: expose rgb-lib functionality through HTTP endpoints
To simplify integration with the RGB Node from JavaScript/TypeScript backends, you can use the client SDK:
rgb-sdk: a Node.js SDK that wraps the RGB Node API and common flows (invoice, UTXOs, PSBT build/sign/finalize, balances, transfers), making server integrations faster and more consistent. See the repository for usage examples and flow helpers:RGB-OS/rgb-sdk.
This SDK mirrors the API surface and patterns described here, and can be adapted to your signing setup (local mnemonic etc) and orchestration needs. It is well‑suited for building your own wallet backend or exchange integration. Repository link.
- Issue RGB20 assets
- Create blinded and witness invoices
- Decode invoices
- Begin/send transfers (PSBT build), end transfers (broadcast + finalize)
- List assets, balances, UTXOs, transactions, and transfers
- Backup/restore wallet state
- Work with multiple wallets in parallel (e.g., per user/account/xpub)
- Provide a simple, intuitive interface for managing RGB assets and on‑chain transactions
- Client wallets interact with the RGB Node over a simple REST API. This keeps wallet apps lightweight while enabling full RGB functionality.
- The node encapsulates RGB state and PSBT construction using
rgb-lib. Private keys remain with the client or an external signer. - Wallets can be “online” via the node: a wallet can be created/registered with the node and then use all RGB features (invoice creation, transfers, state refresh) through the API.
- Invoices embed transport endpoints (from
PROXY_ENDPOINT) and can be paid by any RGB‑compatible wallet.
Typical flow for an online wallet:
- Create/register wallet on the node → node derives addresses/maintains UTXOs.
- Generate invoices (blinded or witness) and receive payments.
- Build PSBTs for outgoing transfers; sign client‑side or by a dedicated signer; submit to finalize.
Most wallet endpoints require headers to identify which wallet instance (state) to use. These headers are mandatory for endpoints that depend on a wallet (e.g., list assets, balances, create invoices, send, refresh):
xpub-van: the vanilla (BTC) xpub for the walletxpub-col: the colored (RGB) xpub for the walletmaster-fingerprint: BIP32 master key fingerprint (hex)
Notes:
- Header names are case‑insensitive; dashes are required (
xpub-van). - Registration (
/wallet/register) also uses these headers to initialize state for this wallet in the node.
Example:
curl -X POST :8000/wallet/listassets \
-H 'xpub-van: xpub6...van' \
-H 'xpub-col: xpub6...col' \
-H 'master-fingerprint: ffffffff'- Python 3.11+ (Docker image uses 3.11 for the bundled
rgb-libwheel compatibility) - FastAPI
rgb-libPython bindings (PSBT + RGB protocol integration)
Self‑host
- Use the Python or Docker instructions below
- Configure env vars like
NETWORKandPROXY_ENDPOINT - Pair with a signer if you want server‑side signing
- Python 3.11+ (match
requirements.txt/ Docker if using the bundledrgb-libwheel) - Or Docker/Docker Compose
Create an .env (or export env vars) if needed:
# Network: 0=Mainnet, 1=Testnet, 2=Signet, 3=Regtest (default)
export NETWORK=3
# Transport endpoint used in invoices (proxy or transport URL)
export PROXY_ENDPOINT=http://127.0.0.1:9090The service reads:
NETWORK→ selectsrgb_lib.BitcoinNetworkPROXY_ENDPOINT→ used as transport endpoint for invoicesREUSE_ADDRESSES→ optional default for rgb-libWalletData.reuse_addresseswhen registration does not set it (see Reuse addresses)
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reloadService will start on http://127.0.0.1:8000 by default.
docker build -t rgb-node .
docker run -p 8000:8000 \
-e NETWORK=3 \
-e PROXY_ENDPOINT=http://127.0.0.1:9090 \
rgb-nodeOr via Compose:
docker compose up --buildBelow is a practical summary of key endpoints implemented in src/routes.py. Payload shapes are defined in src/rgb_model.py. All endpoints are POST unless specified.
Base URL examples:
- Local dev:
http://127.0.0.1:8000
POST /wallet/generate_keys→ generate network‑specific keys (xpubs/mnemonic material as applicable)POST /wallet/register→ derive address and return on‑chain BTC balance snapshot; optional JSON body controls address reuse (see below)POST /wallet/address→ returns BTC deposit address stringPOST /wallet/rotatevanillaaddress→ rotate vanilla (BTC) receive slot; returns new address stringPOST /wallet/rotatecoloredaddress→ rotate colored (RGB) receive slot; returns new address string
Include headers for wallet selection:
curl -X POST :8000/wallet/register \
-H 'xpub-van: xpub6...van' \
-H 'xpub-col: xpub6...col' \
-H 'master-fingerprint: ffffffff' \
-H 'Content-Type: application/json' \
-d '{"reuse_addresses": true}'Register body (all fields optional):
| Field | Meaning |
|---|---|
reuse_addresses |
If true or false, saved to per-wallet config and passed to rgb-lib WalletData.reuse_addresses. If omitted, the server uses existing wallet.json or the REUSE_ADDRESSES env default. |
Response includes reuse_addresses — the effective value after resolution.
rgb-lib can reuse derivation slots for receive flows when reuse_addresses is enabled. That affects whether consecutive calls to POST /wallet/address return the same deposit string (until you rotate), which matters for testing and for UX patterns that expect stable addresses.
How to enable (pick one):
- Per registration —
POST /wallet/registerwith JSON{"reuse_addresses": true}(see example above). - Deploy default — set
REUSE_ADDRESSES=1(ortrue/yes/on) so new wallets get reuse unless the register body overrides it. - Persisted — once set, the value is stored under the wallet’s data directory (
wallet.json); later registrations can omit the body to keep the saved preference.
/wallet/address vs rotation
POST /wallet/address— returns the wallet’s current deposit address string (same headers as other wallet routes). With reuse on, two calls in a row typically return the identical string.POST /wallet/rotatevanillaaddress— advances the vanilla (BTC) receive derivation; response body is the new vanilla address string.POST /wallet/rotatecoloredaddress— advances the colored (RGB) receive derivation; response body is the new colored address string.
Use rotation when you want a new receive slot after reuse (e.g. privacy or a new invoice period). After you rotate, POST /wallet/address reflects the new deposit address; with reuse still enabled, consecutive /wallet/address calls should again be identical until the next rotation.
POST /wallet/listunspents→ list UTXOs known to the nodePOST /wallet/createutxosbegin→ build PSBT to create N UTXOsPOST /wallet/createutxosend→ finalize UTXO creation using a signed PSBT
Headers required (example):
curl -X POST :8000/wallet/listunspents \
-H 'xpub-van: xpub6...van' \
-H 'xpub-col: xpub6...col' \
-H 'master-fingerprint: ffffffff'POST /wallet/listassets→ list RGB assetsPOST /wallet/assetbalance→ get balance forasset_id(JSON body)POST /wallet/btcbalance→ get BTC balance (vanilla + colored)
POST /wallet/blindreceive→ create blinded invoicePOST /wallet/witnessreceive→ create witness invoice (wvout)POST /wallet/decodergbinvoice→ decode invoice (InvoiceData: assignment, network, transport endpoints, etc.)
Request model for blind/witness receive (RgbInvoiceRequestModel):
amount— fungible amount (required when creating a fungible invoice)asset_id— optional; omit when the wallet does not know the asset yet (e.g. first receive of an asset from another party). If set, the asset must exist in that wallet’s catalog or rgb-lib may return an error.duration_seconds,min_confirmations— optional; see OpenAPI/docs
Example:
{
"asset_id": "<rgb20 asset id or null>",
"amount": 12345,
"duration_seconds": 86400,
"min_confirmations": 1
}- Build PSBT →
POST /wallet/sendbegin
Headers required:
-H 'xpub-van: xpub6...van' \
-H 'xpub-col: xpub6...col' \
-H 'master-fingerprint: ffffffff'Request model:
{
"invoice": "<rgb invoice>",
"asset_id": "<optional explicit asset id>",
"amount": 12345,
"witness_data": {
"amount_sat": 1000,
"blinding": null
},
"fee_rate": 5,
"min_confirmations": 3
}Rules:
recipient_idis derived from the invoice; if it containswvout:it’s a witness send- For witness sends,
witness_datais required and must include positiveamount_sat(and optionalblinding) - For blind / non‑witness sends, omit
witness_data(do not send the field, or it is treated as absent) - Optional
fee_rateandmin_confirmationsdepend on network (e.g. signet vs mainnet defaults inroutes.py)
Signing PSBTs — use POST /wallet/sign with a JSON body containing the mnemonic and account xpubs (xpub_van, xpub_col, master_fingerprint) plus psbt. This endpoint does not use the wallet headers above; it uses an offline signer path. Then finalize with POST /wallet/sendend using the usual wallet headers.
Response:
"<psbt base64>"-
Sign PSBT on client
-
Finalize →
POST /wallet/sendend
{
"signed_psbt": "<base64>"
}Response:
{
"txid": "<txid>",
"batch_transfer_idx": 0
}POST /wallet/listtransactions→ list on‑chain / RGB‑related transactions (typed schema in OpenAPI)POST /wallet/listtransfers→ list RGB transfers (optional JSON{"asset_id": "..."}; omitasset_idto list all supported by rgb-lib)POST /wallet/refresh→ refresh wallet state (returns a map of refresh results per transfer index)POST /wallet/sync→ sync wallet with networkGET /wallet/refresh/status/{job_id}/GET /wallet/refresh/watcher/...— refresh queue status (when PostgreSQL worker is enabled)
Interactive API docs: /docs (Swagger UI) and /openapi.json — schemas reflect the current FastAPI response_model definitions; restart the process after code changes to refresh them.
POST /wallet/backup→ create encrypted backupGET /wallet/backup/{id}→ download backupPOST /wallet/restore(multipart form) → restore from backup
- The RGB Node never needs application private keys. It constructs PSBTs; signing is performed by a separate signer service or client app, then submitted back.
- For production deployments, place the node behind your own API gateway and auth
NETWORKcontrols Bitcoin network selection forrgb_libPROXY_ENDPOINTis propagated into transport endpoints for invoices and witness invoices
- Wallet state: Stored on the file system (
./data/) due to currentrgb-libconstraints - Refresh queue & watchers: Stored in PostgreSQL for durability and recovery
- Automatic recovery: Active watchers are automatically recovered on startup
For production, pair the RGB Node with a dedicated signer service that holds keys in your environment and validates and signs PSBTs via secure messaging. See:
- Signer repository (TypeScript service):
RGB-OS/thunderlink-signer
Typical use:
- RGB Node builds an unsigned PSBT via
/wallet/sendbegin - Signer receives a sign request over RabbitMQ, signs using your mnemonic, returns signed PSBT
- RGB Node finalizes via
/wallet/sendend
This model keeps private keys off the RGB Node.
- Authentication/Authorization:
- For self‑hosted deployments, customers should add their own JWT/auth middleware and gateway
- Pluggable storage for wallet state (PostgreSQL)
- Multi‑tenant admin endpoints and quota/rate‑limit hooks
- Observability: metrics endpoints and structured logs
- Extended rgb-lib surface area as new features land
The RGB Node includes an automatic refresh worker that syncs wallet state when invoices are created or assets are sent. The worker runs as a separate service and automatically refreshes wallets until transfers are settled or failed.
The refresh worker is included in docker-compose.yml and starts automatically:
docker compose upThis starts:
postgres- PostgreSQL database (port 5432)thunderlink-python- FastAPI service (port 8000)refresh-worker- Background process (no port, connects to PostgreSQL and FastAPI)
Scale workers:
docker compose up --scale refresh-worker=3- Start PostgreSQL:
# Using Docker
docker run -d --name postgres-rgb \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=rgb_node \
-p 5432:5432 \
postgres:15-alpine
# Or using local PostgreSQL
createdb rgb_node
psql rgb_node < migrations/001_initial_schema.sql- Start FastAPI:
uvicorn main:app --reload- Start Worker (in separate terminal):
python -m workers.refresh_workerAdd to your .env file:
POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/rgb_node
REFRESH_INTERVAL=100
MAX_REFRESH_RETRIES=10
ENABLE_RECOVERY=trueThe worker automatically:
- Watches invoices until
SETTLEDorFAILED - Refreshes wallet state after asset sends
- Retries with exponential backoff on failures
- Recovers active watchers on startup (if
ENABLE_RECOVERY=true)
For more details, see REFRESH_FLOW.md.