POC bridge API for RGB + Lightning LSP workflows.
- Overview
- Endpoints
- Request examples
- Cron jobs
- Method mapping and transfer status model
- Configuration
- Run locally
- Manual flow tests
- Automation script
- Troubleshooting
This service exposes API endpoints for two flows:
onchain_send: user provides RGB invoice -> service creates LN invoice -> once paid, service executessendrgblightning_receive: user provides LN invoice -> service creates RGB invoice -> once RGB transfer settles, service executessendpaymentlightning_address: user providesusername@domain-> service serves LNURL-pay discovery and callback for a DB-backed account, with haiku handles minted once and persisted perpeer_pubkey
GET /healthGET /get_infoGET /.well-known/lnurlp/{username}GET /pay/callback/{username}POST /onchain_sendPOST /lightning_receive
{
"rgb_invoice": "rgb1...",
"lninvoice": {
"amt_msat": 3000000,
"expiry_sec": 3600,
"asset_id": "...",
"asset_amount": 1000
}
}Validation rules:
- if
lninvoice.asset_idis provided, it must match decoded RGBasset_id - if
lninvoice.asset_amountis provided, it must match decoded fungible assignment amount - if either field is omitted, service auto-fills from decoded RGB invoice when available
lninvoice.expiry_secmust match decoded RGB remaining lifetime (within tolerance)- if
lninvoice.expiry_secis omitted/zero, service auto-fills from RGB remaining lifetime
{
"ln_invoice": "lnbc...",
"rgb_invoice": {
"asset_id": "...",
"assignment": "Value",
"duration_seconds": 3600,
"min_confirmations": 1,
"witness": false
}
}Validation and normalization:
rgb_invoice.asset_idis requiredrgb_invoice.min_confirmationsis backend-controlled viaMIN_CONFIRMATIONS; caller value is ignored- assignment default is
Any - input
"Value"is accepted and normalized toAny - unsupported assignment values are rejected
duration_secondsis validated against LN remaining lifetime; if missing/zero, auto-filled from decoded LN invoice
Returns LNURL-pay discovery metadata for a Lightning Address account stored in lnaddr_accounts.
Example:
curl -s http://127.0.0.1:8080/.well-known/lnurlp/txalkanReturns a BOLT11 invoice for the requested amount in millisatoshis.
The callback includes the LNURL metadata hash as description_hash in the underlying /lninvoice request, which is required by LUD-06 so the invoice h tag matches the metadata string.
It also sends min_final_cltv_expiry_delta from the configured inbound Lightning Address CLTV policy.
Example:
curl -s "http://127.0.0.1:8080/pay/callback/txalkan?amount=3000000"Runs every CRON_EVERY (default 30s):
listpeers+listchannels, and autoopenchannelif channel is missing.- UTXO maintenance: if count drops below
UTXO_MIN_COUNT, callcreateutxoswithUTXO_TARGET_COUNT - UTXO_MIN_COUNT. - Monitor LN invoices for
onchain_send; if paid, executesendrgb. - Monitor RGB transfers for
lightning_receive; if settled, executesendpayment. - Mark expired unpaid invoices as
expiredand optionally call cancel endpoint.
This POC maps rgb-lightning-node routes:
listconnections->listpeersopenconnection->connectpeer(or rely onopenchannelauto-connect)sendln->sendpaymentrgbinvoicestatus->refreshtransfers+listtransfers(matched bybatch_transfer_idx)
Why refreshtransfers + listtransfers:
POST /rgbinvoicereturnsbatch_transfer_idxandexpiration_timestamp.POST /refreshtransfersupdates wallet transfer states.POST /listtransfersreturns transfer states for anasset_id.- Transfer with
idx == batch_transfer_idxis used as tracked invoice state.
Relevant transfer states:
WaitingCounterpartyWaitingConfirmationsSettledFailed
For deterministic tracking of lightning_receive, persist:
- user LN invoice
- generated RGB invoice
batch_transfer_idxasset_idexpiration_timestamp(rgb_expires_at)
Core env vars:
SERVER_ADDRdefault:8080DATABASE_DRIVERsqlite(default) orpostgresDATABASE_URLdefaultutexo_lsp.dbLSP_BASE_URLdefaulthttp://127.0.0.1:3001LSP_TOKENoptional bearer token used by utexo-lsp for outbound calls to the node APIRGB_NODE_BASE_URLdefaultLSP_BASE_URLHTTP_TIMEOUTdefault15sCRON_EVERYdefault30sEXPIRY_MATCH_TOLERANCE_SECdefault5MIN_AMT_MSATdefault3000000MIN_CONFIRMATIONSdefault1DEFAULT_RGB_ASSIGNMENTdefaultAnySUPPORTED_ASSET_IDScomma-separated allowlist (example:assetA,assetB)DEFAULT_VIRTUAL_OPEN_MODEoptional
Lightning Address / Async Payments (APay) env vars:
LIGHTNING_ADDRESS_DOMAIN_URLdefaulthttp://127.0.0.1:8080(must be an http(s) origin only, with no path/query/fragment; host used forusername@domain)LIGHTNING_ADDRESS_SHORT_DESCRIPTIONdefaultPayment to utexo-lspLIGHTNING_ADDRESS_MIN_SENDABLE_MSATdefault3_000_000LIGHTNING_ADDRESS_MAX_SENDABLE_MSATdefault3_000_000APAY_INBOUND_INVOICE_EXPIRYdefault3600s(APayInboundInvoiceExpiryin config)APAY_OUTBOUND_INVOICE_EXPIRYdefault900s(APayOutboundInvoiceExpiryin config)APAY_INBOUND_MIN_FINAL_CLTV_EXPIRY_DELTAdefault144(APayInboundMinFinalCltvExpiryDeltain config)APAY_OUTBOUND_MIN_FINAL_CLTV_EXPIRY_DELTAdefault18(APayOutboundMinFinalCltvExpiryDeltain config)APAY_CLAIM_MARGIN_BLOCKSdefault12(APayClaimMarginBlocksin config)APAY_BEARER_TOKENbearer token required forPOST /internal/async_order/new(APayBearerTokenin config)APAY_REQUEST_OUTBOUND_INVOICE_PATHdefault/apay/outboundinvoice(APayRequestInvoicePathin config)
Lightning address accounts:
lnaddr_accounts.peer_pubkeyis the primary key- The
localpart(used asusername) is generated once usinggo-haikunatorand then stored persistently. reconcileChannelsseeds accounts automatically for peers discovered fromlistconnections
Route override env vars:
LSP_GET_INFO_PATH,LSP_OPENCONNECTION_PATH,LSP_LISTCONNECTIONS_PATH,LSP_LISTCHANNELS_PATH,LSP_OPENCHANNEL_PATHLSP_LNINVOICE_PATH,LSP_INVOICESTATUS_PATH,LSP_CANCELLNINVOICE_PATH,LSP_SENDRGB_PATH,LSP_SENDLN_PATHRGB_DECODE_LN_PATH,RGB_DECODE_RGB_PATH,RGB_INVOICE_PATH,RGB_REFRESH_TRANSFERS_PATH,RGB_LIST_TRANSFERS_PATH,RGB_LIST_UNSPENTS_PATH,RGB_CREATE_UTXOS_PATH
UTXO/channel tuning:
DEFAULT_CHANNEL_CAPACITY_SATdefault200000DEFAULT_CHANNEL_PUSH_MSATdefault0UTXO_MIN_COUNT,UTXO_TARGET_COUNT,UTXO_SIZE_SAT,UTXO_FEE_RATE,UTXO_SKIP_SYNC
SUPPORTED_ASSET_IDS behavior:
- BTC channels (empty
asset_id) are allowed - RGB channels are auto-opened only if
asset_idis in allowlist POST /lightning_receiveandPOST /onchain_sendreject asset IDs outside allowlist- if allowlist is empty, asset-bound flows are rejected
From project root:
export LSP_BASE_URL="http://127.0.0.1:3001"
export LSP_TOKEN=""
export RGB_NODE_BASE_URL="http://127.0.0.1:3001"
export LIGHTNING_ADDRESS_DOMAIN_URL="http://127.0.0.1:8080"
export APAY_BEARER_TOKEN=""
export CRON_EVERY="10s"
go run .Health check:
curl -s http://127.0.0.1:8080/healthcurl -s -X POST http://127.0.0.1:8080/lightning_receive \
-H 'content-type: application/json' \
-d '{
"ln_invoice":"<USER_LN_INVOICE>",
"rgb_invoice":{
"asset_id":"<ASSET_ID>",
"assignment":"Value",
"duration_seconds":3600,
"min_confirmations":1,
"witness":false
}
}'Then pay the returned RGB invoice and check status:
sqlite3 utexo_lsp.db "select id,status,rgb_asset_id,batch_transfer_idx,created_at from lightning_receive_mappings order by id desc limit 5;"Expected: pending_rgb -> completed (or failed / expired).
curl -s -X POST http://127.0.0.1:8080/onchain_send \
-H 'content-type: application/json' \
-d '{
"rgb_invoice":"<USER_RGB_INVOICE>",
"lninvoice":{
"amt_msat":3000000,
"expiry_sec":3600
}
}'Then pay the returned LN invoice and check status:
sqlite3 utexo_lsp.db "select id,status,created_at from onchain_send_mappings order by id desc limit 5;"Expected: pending_ln -> completed (or failed / expired).
Quick start:
# Optional one-time init
NODE_PASSWORD="password123" ./scripts/poc_flow.sh node-init
# Unlock node
NODE_PASSWORD="password123" \
BITCOIND_RPC_USERNAME="user" \
BITCOIND_RPC_PASSWORD="password" \
BITCOIND_RPC_HOST="localhost" \
BITCOIND_RPC_PORT=18443 \
INDEXER_URL="127.0.0.1:50001" \
PROXY_ENDPOINT="rpc://127.0.0.1:3000/json-rpc" \
./scripts/poc_flow.sh node-unlock
./scripts/poc_flow.sh preflight
./scripts/poc_flow.sh node-initialAuth check:
NODE_BASE_URL="http://127.0.0.1:3001" \
NODE_TOKEN="<YOUR_RLN_TOKEN>" \
AUTH_CHECK_PATH="/nodeinfo" \
./scripts/poc_flow.sh auth-checklightning_receive script flow:
ASSET_ID="<ASSET_ID>" \
USER_LN_INVOICE="<USER_LN_INVOICE>" \
AUTO_PAY_RGB=true \
./scripts/poc_flow.sh lightning-receive
./scripts/poc_flow.sh monitoronchain_send script flow:
USER_RGB_INVOICE="<USER_RGB_INVOICE>" \
LN_AMT_MSAT=3000000 \
LN_EXPIRY_SEC=3600 \
AUTO_PAY_LN=true \
./scripts/poc_flow.sh onchain-send
./scripts/poc_flow.sh monitorAll-in-one run:
NODE_PASSWORD="password123" \
BITCOIND_RPC_USERNAME="user" \
BITCOIND_RPC_PASSWORD="password" \
BITCOIND_RPC_HOST="localhost" \
BITCOIND_RPC_PORT=18443 \
INDEXER_URL="127.0.0.1:50001" \
PROXY_ENDPOINT="rpc://127.0.0.1:3000/json-rpc" \
ASSET_ID="<ASSET_ID>" \
USER_LN_INVOICE="<USER_LN_INVOICE>" \
USER_RGB_INVOICE="<USER_RGB_INVOICE>" \
AUTO_PAY_LN=true \
AUTO_PAY_RGB=true \
WAIT_SECONDS=20 \
./scripts/poc_flow.sh allTwo-node openchannel verification:
NODE_BASE_URL="http://127.0.0.1:3001" \
SECOND_NODE_BASE_URL="http://127.0.0.1:3002" \
SECOND_NODE_P2P_ADDR="127.0.0.1:9736" \
OPENCHANNEL_VERIFY_TIMEOUT=120 \
OPENCHANNEL_VERIFY_INTERVAL=5 \
./scripts/poc_flow.sh two-nodes-openchannel-verifySDK client smoke:
NODE_BASE_URL="http://127.0.0.1:3001" \
SECOND_NODE_BASE_URL="http://127.0.0.1:3002" \
SERVER_ASSET_ID="<ASSET_ID_ON_NODE_A>" \
CLIENT_ASSET_ID="<ASSET_ID_ON_NODE_B>" \
CLIENT_LN_AMT_MSAT=3000000 \
CLIENT_LN_EXPIRY_SEC=3600 \
LN_AMT_MSAT=3000000 \
LN_EXPIRY_SEC=3600 \
./scripts/poc_flow.sh sdk-client-smokelightning_receivenot completing:- inspect
POST /refreshtransfersandPOST /listtransfers - verify
asset_idmatches transfer records
- inspect
POST /lninvoiceEOF/empty reply:- verify bitcoind RPC port (regtest here uses
18443) - ensure node data dir has
.ldk/ - restart node after fixing
.ldk
- verify bitcoind RPC port (regtest here uses
- auto
openchannelfailing:- verify peers via
GET /listpeers - verify channel defaults are valid for node policy
- verify peers via