> ## 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.

# Pay Lightning from On-chain

> Pay a Lightning invoice or Lightning address with on-chain BTC, L-BTC, or L-USDT using a Swap SDK submarine swap, and refund it if the maker never pays

Your user has funds on chain and wants to pay a Lightning invoice. This is a **submarine swap**.

## How it works

Your app locks the funds on chain. The maker pays the invoice, which reveals its preimage, and only then can it claim your lockup. If the maker never pays, your app refunds the lockup.

In the normal case your app only funds the lockup: it builds no transaction.

## Setup

The code on this page assumes this setup. It connects to the signet maker and loads your swap mnemonic. See [Installation](https://docs.kaleidoswap.com/sdk/installation.md) and [Keys and recovery](https://docs.kaleidoswap.com/sdk/keys-and-recovery.md) if you have neither yet.

```typescript TypeScript theme={null}
import { init, SwapClient, SwapMasterKey, SwapScript, toJson } from "@kaleidorg/swap-sdk";

await init();

const network = "signet";
const makerUrl = "https://maker.signet.kaleidoswap.com/v2";
const bitcoinEsploraUrl = "https://esplora.signet.kaleidoswap.com";
const liquidEsploraUrl = "https://blockstream.info/liquidtestnet/api";

const client = new SwapClient(makerUrl);
const master = SwapMasterKey.fromSwapMnemonic(process.env.SWAP_MNEMONIC!, network);

// Two things your app provides:
//   nextSwapIndex(): Promise<bigint>  a persisted counter, never reused
//   store                             durable storage for swap records
```

```python Python theme={null}
import asyncio, os
import kaleidorg_swap_sdk as sdk

network = sdk.Network.SIGNET
client = sdk.SwapClient("https://maker.signet.kaleidoswap.com/v2", None)
master = sdk.SwapMasterKey.from_swap_mnemonic(os.environ["SWAP_MNEMONIC"], network)
```

## Pay an invoice

**1. Get a fixed-amount invoice**

Use a BOLT11 invoice with an amount. For a Lightning address or an LNURL-pay link, resolve it first:

```typescript theme={null}
import { resolveLightningAddress } from "@kaleidorg/swap-sdk";

const invoice = await resolveLightningAddress("alice@example.com", 25_000n); // sats
```

**2. Create the swap**

```typescript theme={null}
const index = await nextSwapIndex();
const refundKey = master.deriveSwapKey(index);

const card = (await client.submarinePairs())?.BTC?.BTC; // on-chain BTC -> Lightning
if (!card) throw new Error("Route not offered right now");

const swap = await client.createSubmarineSwap(network, {
  from: "BTC",
  to: "BTC",
  invoice,
  refundPublicKey: refundKey.publicKey,
  pairHash: card.hash,
});

SwapScript.fromSubmarine("bitcoin", network, swap, refundKey.publicKey); // throws if you could not refund it
await store.put(swap.id, toJson({ index, makerUrl, swap }));
```

The SDK checks the maker's answer against your refund key before it returns. Save the record before you fund.

**3. Fund the lockup**

Send exactly `swap.expectedAmount` to `swap.address` in **one transaction** from your wallet.

**4. Wait for the payment**

The swap reaches `invoice.paid`, then `transaction.claimed`. You are done.

> **Python**
>
> ```python theme={null}
> refund_key = master.derive_swap_key(index)
> btc = sdk.btc_chain_from_network(network)
> card = (await client.get_submarine_pairs()).pairs["BTC"]["BTC"]
>
> # Python names the submarine create `create_swap`
> swap = await client.create_swap(
>     sdk.CreateSubmarineRequest(
>         _from=btc, to=btc, invoice=invoice,
>         refund_public_key=refund_key.public(), pair_hash=card.hash,
>     )
> )
> print(f"Fund exactly {swap.expected_amount} sat to {swap.address}")
> ```

## If something goes wrong

If the swap reaches `invoice.failedToPay`, `transaction.lockupFailed`, or `swap.expired`, refund the lockup:

```typescript theme={null}
const { index, swap } = JSON.parse(await store.get(swapId));
const refundKey = master.deriveSwapKey(BigInt(index));
const script = SwapScript.fromSubmarine("bitcoin", network, swap, refundKey.publicKey);

const refund = await script.constructRefund({
  outputAddress: myBitcoinAddress,
  swapId: swap.id,
  keysSecretHex: refundKey.secretKey,
  swapAuth: swap.swapAuth,
  makerBaseUrl: makerUrl,
  network,
  bitcoinEsploraUrl,
  feeSatPerVb: 2,
});
// Save refund.hex() first, then:
await refund.broadcast(network, bitcoinEsploraUrl);
```

If the maker co-signs, the refund goes through immediately. If it does not, the refund is valid once the chain reaches the swap's `timeoutBlockHeight`. See [Claims and refunds](https://docs.kaleidoswap.com/sdk/claims-and-refunds.md).

## Paying from other assets

| Asset you lock | What changes |
| - | - |
| L-BTC | `from: "L-BTC"`, `SwapScript.fromSubmarine("liquid", ...)`, a Liquid Esplora URL for refunds |
| L-USDT | `from: "L-USDT"`. Refunds use a caller-funded PSET, because the Liquid fee is paid in L-BTC |
| USDT-RGB | Pin the RGB contract at creation, and your rgb-lib wallet funds the lockup. See [Swap BTC and USDT on RGB](https://docs.kaleidoswap.com/sdk/rgb-swaps.md) |


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