Skip to content

Atomic Swaps ​

Complete reference for BTC ⇄ RGB atomic swap endpoints. All paths carry the /api/v1 prefix. Swaps run on the node's native swap engine and settle the on-chain asset and the Lightning HTLC atomically.

Offer and execution flow ​

A typical swap:

  1. Maker creates an offer — POST /swap/offers (single-hop) or POST /swap/offers/multihop (multi-hop), returning a swap_string and payment_hash.
  2. Taker decodes and accepts — POST /swap/decode validates the offer, POST /swap/accept submits the acceptance.
  3. Maker executes — POST /swap/execute; the swap moves to InFlight on return.
  4. Query status — poll GET /swap/{payment_hash} until Settled or Failed.

maker_gives_rgb indicates whether the maker gives RGB: true means the maker gives RGB and the taker pays BTC (buy direction); false reverses it (sell direction).

Create an offer ​

POST /api/v1/swap/offers ​

Create a single-hop offer: the RGB leg and the BTC leg land on the same channel.

Request:

json
{
  "maker_gives_rgb": true,
  "contract_id": "contract:...",
  "asset_amount": "100",
  "btc_amount_msat": "3000000",
  "btc_carrier_amount_msat": "3000000",
  "counterparty_node_id": "02def456...",
  "channel_scid": "144927727168913409",
  "expiry_secs": 3600
}

Parameter notes:

  • maker_gives_rgb (required): whether the maker gives RGB
  • contract_id (required): RGB contract ID being swapped
  • asset_amount (required): RGB asset amount (raw units)
  • btc_amount_msat (required): BTC counter-value (millisatoshis)
  • btc_carrier_amount_msat (required): BTC carrier amount (millisatoshis) carrying the RGB payment
  • counterparty_node_id (required): counterparty node pubkey
  • channel_scid (required): short channel ID the swap binds to
  • expiry_secs (required): offer validity in seconds

Response:

json
{
  "swap_string": "swap1...",
  "payment_hash": "abc123..."
}

POST /api/v1/swap/offers/multihop ​

Create a multi-hop offer: the RGB leg and the BTC leg may bind different channels, described by rgb_path / btc_path.

Request:

json
{
  "rgb_path": [
    { "node_id": "02def456...", "channel_scid": "144927727168913409" }
  ],
  "btc_path": [
    { "node_id": "03abc789...", "channel_scid": "144948000000000000" }
  ],
  "contract_id": "contract:...",
  "asset_amount": "100",
  "btc_amount_msat": "3000000",
  "btc_carrier_amount_msat": "3000000",
  "maker_gives_rgb": true,
  "expiry_secs": 3600
}

Parameter notes:

  • rgb_path / btc_path: the two route halves, each an array of hops with node_id (that leg's endpoint node) and channel_scid. The two names are historical and do not denote channel type: the channel holding the RGB asset and the channel carrying the BTC counter-value may live in different halves
  • The remaining fields match a single-hop offer (no counterparty_node_id / channel_scid; the paths decide them)

Response: same as a single-hop offer (swap_string, payment_hash).

Fill must match direction

The node_id / channel_scid of the two multi-hop halves must match the swap direction (maker_gives_rgb). A wrong fill is rejected with errors such as SwapInvalidRoute or InvalidNodeId, or leaves the swap stuck in Offered. Validate the offer with POST /swap/decode before execution.

Decode, accept and execute ​

POST /api/v1/swap/decode ​

Decode a swap_string and return its direction, amounts, channels and status.

Request:

json
{
  "swap_string": "swap1..."
}

Response: a swap info object; see swap info below.

POST /api/v1/swap/accept ​

Accept an offer (called by the taker). The maker can execute only after acceptance.

Request:

json
{
  "swap_string": "swap1..."
}

Response:

json
{ "ok": true }

POST /api/v1/swap/execute ​

Execute the swap (called by the maker).

Request:

json
{
  "swap_string": "swap1...",
  "force": false
}

force is optional; true skips the strict peer-accepted check, for cases where the peer has accepted but this node has not observed it yet.

Response:

json
{
  "ok": true,
  "payment_hash": "abc123...",
  "status": "InFlight"
}

200 means the swap was initiated only; settlement requires polling GET /swap/{payment_hash}.

Swap status ​

GET /api/v1/swap/ ​

Query swap info by payment hash.

Response:

json
{
  "payment_hash": "abc123...",
  "role": "Maker",
  "status": "Settled",
  "counterparty_node_id": "02def456...",
  "channel_scid": "144927727168913409",
  "contract_id": "contract:...",
  "asset_amount": "100",
  "btc_amount_msat": "3000000",
  "btc_carrier_amount_msat": "3000000",
  "maker_gives_rgb": true,
  "expiry_secs": 3600,
  "created_at_unix_secs": "1704067200",
  "is_multihop": false,
  "last_error": null
}

GET /api/v1/swap/list ​

List this node's current swaps. The response is a list of swap info objects with the same fields.

Swap info ​

FieldDescription
payment_hashThe swap's payment hash, also the status query key
roleThis node's role, Maker or Taker
statusSwap status, see below
counterparty_node_idCounterparty node pubkey
channel_scidBound short channel ID
contract_idRGB contract ID
asset_amountRGB asset amount
btc_amount_msatBTC counter-value (millisatoshis)
btc_carrier_amount_msatBTC carrier amount (millisatoshis)
maker_gives_rgbWhether the maker gives RGB
expiry_secsOffer validity in seconds
created_at_unix_secsCreation time (unix seconds)
is_multihopWhether this is a multi-hop swap
last_errorFailure reason (present when status=Failed)

Status values:

  • Offered: offer created, waiting for the peer to accept
  • Accepted: peer accepted, waiting for the maker to execute
  • InFlight: swap in progress
  • Settled: swap completed atomically
  • Failed: swap failed, see last_error

Multi-hop swaps ​

Multi-hop swaps are for cases where both legs cannot land on the same route, or where intermediate nodes/channels are needed:

  • Create them with POST /swap/offers/multihop, describing the RGB and BTC legs through rgb_path and btc_path
  • Each hop settles atomically through a Lightning HTLC
  • The final result: RGB or BTC is deducted per direction and the counter-value is received

Multi-hop swaps are more sensitive to channel capacity and routing: insufficient outbound capacity, a channel's per-HTLC maximum or reserves on any half can fail execution (for example HTLCMaximum, RetriesExhausted). Check capacity with the network graph and channel list before creating an offer.

Error handling ​

  • 400: invalid swap parameters, invalid route, or peer has not accepted
  • 404: offer not found or expired
  • 409: swap balance or state conflict
  • 410: offer already accepted
  • 422: swap validation failed
  • 500: swap execution error

Swaps are atomic: channel balances do not change when execution fails. After a timeout or dropped connection, query the real state with GET /swap/{payment_hash} instead of retrying the execution blindly.

Examples ​

Create a single-hop offer (maker gives RGB) ​

bash
curl -X POST https://<your-node-domain>/api/v1/swap/offers \
  -H "Authorization: Bearer <node-credential>" \
  -H "Content-Type: application/json" \
  -d '{
    "maker_gives_rgb": true,
    "contract_id": "contract:...",
    "asset_amount": "100",
    "btc_amount_msat": "3000000",
    "btc_carrier_amount_msat": "3000000",
    "counterparty_node_id": "02def456...",
    "channel_scid": "144927727168913409",
    "expiry_secs": 3600
  }'

Accept and execute a swap ​

bash
# Taker accepts
curl -X POST https://<your-node-domain>/api/v1/swap/accept \
  -H "Authorization: Bearer <node-credential>" \
  -H "Content-Type: application/json" \
  -d '{ "swap_string": "swap1..." }'

# Maker executes
curl -X POST https://<your-node-domain>/api/v1/swap/execute \
  -H "Authorization: Bearer <node-credential>" \
  -H "Content-Type: application/json" \
  -d '{ "swap_string": "swap1...", "force": false }'

Query swap status ​

bash
curl -X GET https://<your-node-domain>/api/v1/swap/abc123... \
  -H "Authorization: Bearer <node-credential>"

Trade and secure your RGB assets | Unlock the full potential of the RGB protocol