> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kaleidoswap.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Node-to-Node Atomic Swaps with the Node SDK

> Create and execute Bitcoin atomic swaps with the Node SDK in a step-by-step guide that runs from quote to settlement

This guide walks through an atomic swap (direct node-to-node) end to end with the Node SDK, in both TypeScript and Python.

Atomic swaps are trustless: the maker never takes custody, because your own RGB Lightning Node holds the keys and has to whitelist the swap before it can settle. That also means you need an RLN node running alongside the client — see [Node Hosting](https://docs.kaleidoswap.com/desktop-app/getting-started/node-hosting.md) for the options.

## Setup

Both a `baseUrl` and a `nodeUrl` are required: the first reaches the maker, the second reaches your node.

```typescript TypeScript theme={null}
import { KaleidoClient, Layer } from 'kaleido-sdk';

const client = KaleidoClient.create({
  baseUrl: 'https://api.signet.kaleidoswap.com',
  nodeUrl: 'http://localhost:3001',
});
```

```python Python theme={null}
from kaleido_sdk import (
    ConfirmSwapRequest,
    KaleidoClient,
    Layer,
    PairQuoteRequest,
    SwapLegInput,
    SwapRequest,
    SwapStatusRequest,
)
from kaleido_sdk.rln import TakerRequest

client = KaleidoClient.create(
    base_url="https://api.signet.kaleidoswap.com",
    node_url="http://localhost:3001",
)
```

## Step 1: Get a Quote

Request a quote for the pair you want to swap. Amounts are always **raw integer units** — see [Utilities](https://docs.kaleidoswap.com/cli/node-sdk/utilities.md) for conversion helpers.

```typescript TypeScript theme={null}
const quote = await client.maker.getQuote({
  from_asset: {
    asset_id: 'BTC',
    layer: Layer.BTC_LN,
    amount: 1000000,
  },
  to_asset: {
    asset_id: 'rgb:...:...', // Your RGB asset ID
    layer: Layer.RGB_LN,
  },
});
```

```python Python theme={null}
quote = await client.maker.get_quote(PairQuoteRequest(
    from_asset=SwapLegInput(
        asset_id="BTC",
        layer=Layer.BTC_LN,
        amount=1000000,
    ),
    to_asset=SwapLegInput(
        asset_id="rgb:...:...",  # Your RGB asset ID
        layer=Layer.RGB_LN,
    ),
))
```

The `rfq_id` on the response is what ties the next step to this price. Quotes expire — do not hold an `rfq_id` across user think-time, or `initSwap` will raise `QuoteExpiredError`.

## Step 2: Initialize the Swap

Lock the swap with the maker against that quote. This returns the `swapstring` and the `payment_hash` you need for every remaining step, plus the `access_token` that authorizes status polling — it is only returned here, so store it alongside the payment hash.

```typescript TypeScript theme={null}
const swap = await client.maker.initSwap({
  rfq_id: quote.rfq_id,
  from_asset: quote.from_asset.asset_id,
  from_amount: quote.from_asset.amount,
  to_asset: quote.to_asset.asset_id,
  to_amount: quote.to_asset.amount,
});

// swap.swapstring, swap.payment_hash, swap.access_token
```

```python Python theme={null}
swap = await client.maker.init_swap(SwapRequest(
    rfq_id=quote.rfq_id,
    from_asset=quote.from_asset.asset_id,
    from_amount=quote.from_asset.amount,
    to_asset=quote.to_asset.asset_id,
    to_amount=quote.to_asset.amount,
))

# swap.swapstring, swap.payment_hash, swap.access_token
```

## Step 3: Whitelist on Your Taker Node

Your node has to authorize the `swapstring` before it will honour the incoming HTLC. This is the only step that runs against `client.rln`.

```typescript TypeScript theme={null}
const takerPubkey = await client.rln.getTakerPubkey();

await client.rln.whitelistSwap({ swapstring: swap.swapstring });
```

```python Python theme={null}
taker_pubkey = await client.rln.get_taker_pubkey()

await client.rln.whitelist_swap(TakerRequest(
    swapstring=swap.swapstring,
))
```

> [!WARNING]
>
> Skipping this step is the most common cause of a `SwapError` on execute: init succeeds, then execution fails because the taker side never agreed to the swap.

## Step 4: Execute the Swap

Hand the maker your node's details so it can settle.

```typescript TypeScript theme={null}
const execution = await client.maker.executeSwap({
  swapstring: swap.swapstring,
  payment_hash: swap.payment_hash,
  taker_pubkey: takerPubkey,
});

console.log(`Swap executed. Status: ${execution.status}`);
```

```python Python theme={null}
execution = await client.maker.execute_swap(ConfirmSwapRequest(
    swapstring=swap.swapstring,
    payment_hash=swap.payment_hash,
    taker_pubkey=taker_pubkey,
))

print(f"Swap executed. Status: {execution.status}")
```

## Step 5: Track Settlement

`executeSwap` returning is not the same as the swap having settled. Poll the status by `payment_hash`, passing the `access_token` from `initSwap`, until it reaches a terminal state.

```typescript TypeScript theme={null}
const status = await client.maker.getAtomicSwapStatus({
  payment_hash: swap.payment_hash,
  access_token: swap.access_token,
});

console.log(status.swap?.status);
```

```python Python theme={null}
status = await client.maker.get_atomic_swap_status(SwapStatusRequest(
    payment_hash=swap.payment_hash,
    access_token=swap.access_token,
))

print(status.swap.status if status.swap else None)
```

If a transfer sits pending longer than expected, `client.rln.refreshTransfers()` / `refresh_transfers()` advances pending RGB transfers, and `client.rln.listSwaps()` gives you the node's own view. See [Troubleshooting](https://docs.kaleidoswap.com/cli/node-sdk/troubleshooting.md#swap-issues) for the failure modes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.