# Smart Chain → BTC/Lightning

Swap smart chain tokens (Solana, Starknet & EVM tokens) to Bitcoin L1 (on-chain) or Lightning L2. These swaps are based on the [HTLC](https://docs.atomiq.exchange/overview/core-primitives/htlc.md) (for lightning) & [PrTLC](https://docs.atomiq.exchange/overview/core-primitives/prtlc.md) (for on-chain) primitives.

tip

For Smart chain → Bitcoin L1 (on-chain) swaps:

* [smartchain-to-btc/swapBasic.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btc/swapBasic.ts)
* [smartchain-to-btc/swapAdvancedSolana.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btc/swapAdvancedSolana.ts)
* [smartchain-to-btc/swapAdvancedStarknet.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btc/swapAdvancedStarknet.ts)
* [smartchain-to-btc/swapAdvancedEVM.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btc/swapAdvancedEVM.ts)

For Smart chain → Lightning L2 swaps:

* [smartchain-to-btcln/swapBasic.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btcln/swapBasic.ts)
* [smartchain-to-btcln/swapAdvancedSolana.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btcln/swapAdvancedSolana.ts)
* [smartchain-to-btcln/swapAdvancedStarknet.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btcln/swapAdvancedStarknet.ts)
* [smartchain-to-btcln/swapAdvancedEVM.ts](https://github.com/atomiqlabs/atomiq-sdk-demo/blob/main/src/smartchain-to-btcln/swapAdvancedEVM.ts)

## Executing the Swap

Here is a full flow for creating an executing the swap in the Smart Chain → BTC/Lightning direction via the `execute()` helper function. The flow is the same for all the cases, with the only differences being:

* Smart chain → Lightning swaps require `EXACT_OUT` mode and a BOLT11 invoice with explicit amount in sats.
* Smart chain → LNURL-pay swaps allow `EXACT_IN` mode, support passing additional comment & reading success actions after payment.

- Smart chain → BTC
- Smart chain → Lightning
- Smart chain → LNURL-pay

Uses the [ToBTCSwap](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ToBTCSwap) swap class.

```
import {SwapAmountType, ToBTCSwap} from "@atomiqlabs/sdk";



// Create a quote

const swap: ToBTCSwap = await swapper.swap(

  Tokens.STARKNET.STRK,           // Any smart chain asset, e.g. SOL on Solana, STRK on Starknet or cBTC on Citrea

  Tokens.BITCOIN.BTC,             // Or `Tokens.BITCOIN.BTCLN` when swapping into Lightning

  "0.00003",                      // Amount (3000 sats to receive)

  SwapAmountType.EXACT_OUT,       // NOTE: When swapping to BOLT11 lightning invoice, only EXACT_OUT mode is supported

  starknetSigner.getAddress(),    // Source signer address

  "bc1q..."                       // Bitcoin on-chain address, BOLT11 lightning invoice or LNURL-pay link

); // Type gets infered as ToBTCSwap (for Bitcoin on-chain)



// Execute the swap

const swapSuccessful = await swap.execute(

  starknetSigner, // Pass in the signer on the source chain to sign the transactions

  {

    onSourceTransactionSent: (txId) => {

      console.log(`Source tx sent: ${txId}`);

    },

    onSourceTransactionConfirmed: (txId) => {

      console.log(`Source tx confirmed: ${txId}`);

    },

    onSwapSettled: (btcTxId) => {

      console.log(`Bitcoin tx sent: ${btcTxId}`);

    }

  }

);



if (!swapSuccessful) {

  // Handle the edge-case when LP fails to process the swap

  console.log("Swap failed, refunding...");

  // Refund funds back to the 

  await swap.refund(starknetSigner);

  console.log("Refunded!");

} else {

  console.log("Success! Output transaction ID: ", swap.getOutputTxId());

  // For lightning network swaps you can get the payment preimage with swap.getSecret()

}
```

Uses the [ToBTCLNSwap](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ToBTCLNSwap) swap class.

```
import {SwapAmountType, ToBTCLNSwap} from "@atomiqlabs/sdk";



// Create a quote

const swap: ToBTCLNSwap = await swapper.swap(

  Tokens.STARKNET.STRK,           // Any smart chain asset, e.g. SOL on Solana, STRK on Starknet or cBTC on Citrea

  Tokens.BITCOIN.BTCLN,           // Swap into Lightning BTC

  undefined,                      // Amount is determined by the passed BOLT11 lightning invoice

  SwapAmountType.EXACT_OUT,       // When swapping to BOLT11 lightning invoice, only EXACT_OUT mode is supported

  starknetSigner.getAddress(),    // Source signer address

  "lnbc10u1p...",                 // BOLT11 lightning invoice, needs to have a fixed amount of sats!

  {

    // For additional swap options checks the `Swap Options` section

  }

); // Type gets infered as ToBTCLNSwap (for Lightning)



// Execute the swap

const swapSuccessful = await swap.execute(

  starknetSigner, // Pass in the signer on the source chain to sign the transactions

  {

    onSourceTransactionSent: (txId) => {

      console.log(`Source tx sent: ${txId}`);

    },

    onSourceTransactionConfirmed: (txId) => {

      console.log(`Source tx confirmed: ${txId}`);

    },

    onSwapSettled: (btcTxId) => {

      console.log(`Bitcoin tx sent: ${btcTxId}`);

    }

  }

);



// When swapping to lightning you can also additionally check:

// The swap will likely fail due to lightning network routing error

if (swap.willLikelyFail()) {

  console.log("Lightning network payment cannot be routed, ensure the recipient is online and has channels!");

}

// Destination wallet is probably a non-custodial wallet, which needs to be online to accept the payment

if (swap.isPayingToNonCustodialWallet()) {

  console.log("Destination lightning wallet is non-custodial, ensure it is online to accept the payment!");

}



if (!swapSuccessful) {

  // Handle the edge-case when LP fails to process the swap

  console.log("Swap failed, refunding...");

  // Refund funds back to the

  await swap.refund(starknetSigner);

  console.log("Refunded!");

} else {

  console.log("Success! Output transaction ID: ", swap.getOutputTxId());

  // For lightning network swaps you can get the payment preimage with swap.getSecret()

}
```

warning

When swapping to Bitcoin Lightning and using BOLT11 lightning network invoices ("lnbc10u1p...") the invoice needs to have a fixed amount and therefore only `EXACT_OUT` swap mode is supported.

For swapping variable amounts in `EXACT_IN` mode to Bitcoin Lightning check the **Smart chain → LNURL-pay** tab, or the [EXACT\_IN Lightning Swaps](#exact_in-lightning-swaps) section below if you have programatic API access to the destination lightning network wallet.

Uses the [ToBTCLNSwap](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ToBTCLNSwap) swap class.

```
import {SwapAmountType, ToBTCLNSwap} from "@atomiqlabs/sdk";



// Create swap with LNURL-pay or Lightning address

const swap: ToBTCLNSwap = await swapper.swap(

  Tokens.STARKNET.STRK,             // Swap STRK

  Tokens.BITCOIN.BTCLN,             // To Lightning BTC

  "100",                            // Now we can specify amount, and even an input amount!

  SwapAmountType.EXACT_IN,          // With LNURL-pay we can use EXACT_IN mode on Lightning!

  starknetSigner.getAddress(),      // Source signer address

  "lnurl1...",                      // LNURL-pay link or Lightning address ("user@example.com")

  {

    comment: "Payment for coffee"   // Optional comment

    // For additional swap options checks the `Swap Options` section

  }

); // Type gets infered as ToBTCLNSwap (for Lightning)



// When swapping to lightning you can also additionally check:

// The swap will likely fail due to lightning network routing error

if (swap.willLikelyFail()) {

  console.log("Lightning network payment cannot be routed, ensure the recipient is online and has channels!");

}

// Destination wallet is probably a non-custodial wallet, which needs to be online to accept the payment

if (swap.isPayingToNonCustodialWallet()) {

  console.log("Destination lightning wallet is non-custodial, ensure it is online to accept the payment!");

}



// Execute the swap

const swapSuccessful = await swap.execute(

  starknetSigner, // Pass in the signer on the source chain to sign the transactions

  {

    onSourceTransactionSent: (txId) => {

      console.log(`Source tx sent: ${txId}`);

    },

    onSourceTransactionConfirmed: (txId) => {

      console.log(`Source tx confirmed: ${txId}`);

    },

    onSwapSettled: (btcTxId) => {

      console.log(`Bitcoin tx sent: ${btcTxId}`);

    }

  }

);



if (!swapSuccessful) {

  // Handle the edge-case when LP fails to process the swap

  console.log("Swap failed, refunding...");

  // Refund funds back to the

  await swap.refund(starknetSigner);

  console.log("Refunded!");

  return;

}



// LNURL-pay link might also contain a success action, this can be displayed to the user

//  upon successful payment

if (swap.hasSuccessAction()) {

  const action = swap.getSuccessAction();

  console.log("Description:", action.description);

  console.log("Text:", action.text);        // May be null

  console.log("URL:", action.url);          // May be null

}
```

info

For more information about how to parse LNURLs, distinguish between LNURL-withdraw and LNURL-pay variants, check out the [LNURL-pay](#lnurl-pay) section.

## Manual Execution Flow

* Signer object
* Manually sign transactions

```
import {SwapAmountType} from "@atomiqlabs/sdk";



// Create a quote

const swap = await swapper.swap(

  Tokens.STARKNET.STRK,           // Any smart chain asset, e.g. SOL on Solana, STRK on Starknet or cBTC on Citrea

  Tokens.BITCOIN.BTC,             // Or `Tokens.BITCOIN.BTCLN` when swapping into Lightning

  "0.00003",                      // Amount (3000 sats to receive)

  SwapAmountType.EXACT_OUT,       // NOTE: When swapping to BOLT11 lightning invoice, only EXACT_OUT mode is supported

  starknetSigner.getAddress(),    // Source signer address

  "bc1q..."                       // Bitcoin on-chain address, BOLT11 lightning invoice or LNURL-pay link

); // Type gets infered as ToBTCSwap (for Bitcoin on-chain) or ToBTCLNSwap (for Lightning)



// 1. Initiate the swap on the source chain

await swap.commit(starknetSigner);



// 2. Wait for the swap to execute and for the destination tx to be sent

const swapSuccessful = await swap.waitForPayment();



// 3. Refund in case of failure

if(!swapSuccessful) {

  await swap.refund(starknetSigner);

}
```

info

Type of the signer object passed to the [`commit()`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/IToBTCSwap#commit) & [`refund()`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/IToBTCSwap#refund) functions is dependent on the source network:

* For **Solana** swaps: [SolanaSigner](https://docs.atomiq.exchange/sdk-reference/api/atomiq-chain-solana/src/classes/SolanaSigner), or native `@coral-xyz/anchor` [Wallet](https://solana-foundation.github.io/anchor/ts/classes/Wallet.html)
* For **Starknet** swaps: [StarknetSigner](https://docs.atomiq.exchange/sdk-reference/api/atomiq-chain-starknet/src/classes/StarknetSigner), [StarknetBrowserSigner](https://docs.atomiq.exchange/sdk-reference/api/atomiq-chain-starknet/src/classes/StarknetBrowserSigner), or native `starknet` [Account](https://starknetjs.com/docs/API/classes/Account)
* For **EVM** swaps: [EVMSigner](https://docs.atomiq.exchange/sdk-reference/api/atomiq-chain-evm/src/classes/EVMSigner), or native `ethers` [Signer](https://docs.ethers.org/v6/api/providers/#Signer)

```
import {SwapAmountType} from "@atomiqlabs/sdk";



// Create a quote

const swap = await swapper.swap(

  Tokens.STARKNET.STRK,           // Any smart chain asset, e.g. SOL on Solana, STRK on Starknet or cBTC on Citrea

  Tokens.BITCOIN.BTC,             // Or `Tokens.BITCOIN.BTCLN` when swapping into Lightning

  "0.00003",                      // Amount (3000 sats to receive)

  SwapAmountType.EXACT_OUT,       // NOTE: When swapping to BOLT11 lightning invoice, only EXACT_OUT mode is supported

  starknetSigner.getAddress(),    // Source signer address

  "bc1q..."                       // Bitcoin on-chain address, BOLT11 lightning invoice or LNURL-pay link

); // Type gets infered as ToBTCSwap (for Bitcoin on-chain) or ToBTCLNSwap (for Lightning)



// 1. Initiate the swap on the source chain

const txsCommit = await swap.txsCommit();

... // Sign and send transactions here

// Important to wait till SDK processes the swap initialization

await swap.waitTillCommited();



// 2. Wait for the swap to execute and for the destination tx to be sent

const swapSuccessful = await swap.waitForPayment();



// 3. Refund in case of failure

if(!swapSuccessful) {

  const txsRefund = await swap.txsRefund();

  ... // Sign and send refund transactions here

  // Important to wait till SDK processes the swap refund and updates the swap state

  await swap.waitTillRefunded();

}
```

info

The transactions returned by the `txs*` functions use the following types:

* For **Solana**, uses the [SolanaTx](https://docs.atomiq.exchange/sdk-reference/api/atomiq-chain-solana/src/type-aliases/SolanaTx) type
* For **Starknet**, uses the [StarknetTx](https://docs.atomiq.exchange/sdk-reference/api/atomiq-chain-starknet/src/type-aliases/StarknetTx) type
* For **EVM**, uses the `ethers` [TransactionRequest](https://docs.ethers.org/v6/api/providers/#TransactionRequest) type

For more information about how to sign and send these transactions manually refer to the [Manual Transactions](https://docs.atomiq.exchange/sdk-guide/advanced/manual-transactions.md) page.

## Refunding Past Failed Swaps

The swaps might only transition into the refundable state after several hours (in case of LP non-cooperativeness combined with Lightning swaps even multiple days). Therefore there are a few helper functions to allow you to determine whether the swap is already refundable & to list all refundable swaps.

Checking if a single swap is refundable and refunding it:

```
if (swap.isRefundable()) {

  await swap.refund(signer);

}
```

Getting all swaps that are refundable and refunding them:

```
const refundable = await swapper.getRefundableSwaps(

  "STARKNET",                   // Allows you to specify to only get refundable swaps on `STARKNET`

  starknetSigner.getAddress()   // Allows you to specify to only get refundable swpas for the signer address

);



for (const swap of refundable) {

  await swap.refund(starknetSigner);

}
```

info

It is a good practice to get the refundable swaps on your app's startup and either refunding them automatically or prompting the user to refund.

## LNURL-pay

LNURL-pay links ("lnurl1p...") and static lightning addresses ("<user@example.com>") are reusable and allow payer to set an amount. Hence, they can be used to swap into Lightning with `EXACT_IN` mode.

Additionally, they also support attaching an optional comment to the payment (which you can pass in the `swap()` function options), and also expose a success action, which can be displayed to the user after a successful payment.

You can parse LNURLs either with the generic [`SwapperUtils.parseAddress()`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/SwapperUtils#parseaddress) function exposed by the swapper as `swapper.Utils.parseAddress()` or with the specific [`SwapperUtils.getLNURLTypeAndData()`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/SwapperUtils#getlnurltypeanddata) function, which return the [LNURLPay](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/type-aliases/LNURLPay) or [LNURLWithdraw](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/type-aliases/LNURLWithdraw) object allowing you to read the LNURL's details.

```
// LNURL-pay link or Lightning address ("user@example.com")

const lnurlOrLightningAddress: string = "lnurl1...";

// Check if it has correct format

if(!swapper.Utils.isValidLNURL(lnurlOrLightningAddress))

  throw new Error("Invalid LNURL specified!");

// Fetch the details of the LNURL, it can be either LNURL-pay or LNURL-withdraw

const lnurlDetails: LNURLPay | LNURLWithdraw = await swapper.Utils.getLNURLTypeAndData(lnurlOrLightningAddress);

// Check if the LNURL is of the LNURL-pay type

if(!isLNURLPay(lnurlDetails))

  throw new Error("LNURL isn't of the LNURL-pay type!");



//You can check the details of the LNURL-pay link, for the full details refer to the `LNURLPay` typedoc page.

const swapMinimumSats: bigint = lnurlDetails.min;

const swapMaximumSats: bigint = lnurlDetails.max;

const shortDescription: string = lnurlDetails.shortDescription;



// Create swap with LNURL-pay or Lightning address

const swap = await swapper.swap(

  Tokens.STARKNET.STRK,             // Swap STRK

  Tokens.BITCOIN.BTCLN,             // To Lightning BTC

  "100",                            // Now we can specify amount, and even an input amount!

  SwapAmountType.EXACT_IN,          // With LNURL-pay we can use EXACT_IN mode on Lightning!

  starknetSigner.getAddress(),      // Source signer address

  lnurlDetails,                     // Fetched details or raw LNURL-pay link or Lightning address string

  {

    comment: "Payment for coffee"   // Optional comment

  }

);



... // Handle swap execution
```

## Swap Options

The overview of possible options you can pass to the swap via the `options` argument of `swapper.swap(...)`. For Bitcoin on-chain swaps there are currently no quote creation options, while Lightning swaps let you fine-tune Lightning routing and LNURL-pay behavior.

* Smart chain → BTC
* Smart chain → Lightning

> No swap creation options are currently exposed for this swap type.

| Name                      | Type     | Description                                                                                                                                                                                                                                        |
| ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `expirySeconds`           | `number` | HTLC expiration timeout, in seconds, offered to the LP. Larger values allow more Lightning routes, but also keep funds locked for longer if the LP does not cooperate.<br />Default: 5 days.                                                       |
| `maxExpirySeconds`        | `number` | Maximum HTLC expiration timeout, in seconds. Allows `expirySeconds` to be increased when paying invoices with `min_final_cltv_expiry` greater than 144, which is common for systems with longer potential settlement times.<br />Default: 10 days. |
| `maxRoutingFeePercentage` | `number` | Caps the percentage component of the maximum Lightning routing fee.<br />Default: `0.2` (0.2% of the swap value).                                                                                                                                  |
| `maxRoutingBaseFee`       | `bigint` | Caps the base component of the maximum Lightning routing fee, in sats.<br />Default: `10` sats.                                                                                                                                                    |
| `comment`                 | `string` | Optional LNURL-pay comment. Only applies when the destination is an LNURL-pay endpoint and must fit the service's `commentAllowed` limit.<br />Default: none.                                                                                      |

## Swap States

Read the current state of the swap with either in its [ToBTCSwapState](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/enumerations/ToBTCSwapState) enum form with [`getState()`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ISwap#getstate) or in human readable [SwapStateInfo](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/type-aliases/SwapStateInfo) form with description with [`getStateInfo()`](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ISwap#getstateinfo):

```
import {ToBTCSwapState, SwapStateInfo} from "@atomiqlabs/sdk";



const state: ToBTCSwapState = swap.getState();

console.log(`State state (numeric): ${state}`);



const richState: SwapStateInfo<ToBTCSwapState> = swap.getStateInfo();

console.log(`State name: ${richState.name}`);

console.log(`State description: ${richState.description}`);
```

Subscribe to swap state updates with:

```
swap.events.on("swapState", () => {

  const state: ToBTCSwapState = swap.getState();

});
```

### Table of States

| State                | Value | Description                                                     |
| -------------------- | ----- | --------------------------------------------------------------- |
| `REFUNDED`           | -3    | Swap failed and was refunded                                    |
| `QUOTE_EXPIRED`      | -2    | Quote expired before execution                                  |
| `QUOTE_SOFT_EXPIRED` | -1    | Quote probably expired (may still succeed if init tx in flight) |
| `CREATED`            | 0     | Quote created, waiting for execution                            |
| `COMMITED`           | 1     | Init transaction sent                                           |
| `SOFT_CLAIMED`       | 2     | LP processing (BTC tx sent, but not yet confirmed)              |
| `CLAIMED`            | 3     | Swap complete, BTC sent                                         |
| `REFUNDABLE`         | 4     | LP failed, can refund                                           |

## EXACT\_IN Lightning Swaps

info

This is an advanced feature, you should only use this if you have programmatic API to fetch invoices from a lightning network wallet and want to support `EXACT_IN` swaps.

The main limitation of regular lightning network swaps (Smart chains → Lightning), is the fact that exactIn swaps are not possible (as invoices need to have a fixed amount). LNURL-pay links solve this issue, but are not supported by all the wallets. Therefore, the SDK exposes a hook/callback that can be implemented by lightning wallets directly, which request fixed amount invoices on-demand. This then makes exact input amount swaps possible. The way it works:

1. SDK sends a request to the LP saying it wants to swap `x` USDC to BTC, with a dummy invoice (either 1 sat or as specified in the `minMsats` parameter - this is requested from the `getInvoice()` function) - this dummy invoice is used to estimate the routing fees by the LP (extra care must be taken for both invoices, dummy and the real one to have the same destination node public key & routing hints).
2. LP responds with the output amount of `y` BTC
3. SDK calls the provided `getInvoice()` callback to request the real invoice for the `y` amount of BTC (in satoshis)
4. SDK forwards the returned fixed amount (`y` BTC) lightning network invoice back to the LP to finish creating the quote

To get the `EXACT_IN` quote for Smart Chain → Lightning swap from the SDK:

```
const swap = await swapper.swap(

  Tokens.STARKNET.STRK,

  Tokens.BITCOIN.BTCLN,

  "100",                          // 100 STRK input

  SwapAmountType.EXACT_IN,

  starknetSigner.getAddress(),

  {

    getInvoice: async (amountSats, abortSignal?) => {

      // Generate invoice for the calculated output amount

      const invoice = await myLnWallet.createInvoice(amountSats);

      return invoice;

    },

    minMsats: 1_000_000n,        // Optional: 1000 sats minimum

    maxMsats: 1_000_000_000n     // Optional: 1M sats maximum

  }

);
```

## API Reference

* [ToBTCSwap](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ToBTCSwap) - Swap class for Smart Chain to BTC
* [ToBTCLNSwap](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/classes/ToBTCLNSwap) - Swap class for Smart Chain to Lightning
* [SwapAmountType](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/enumerations/SwapAmountType) - Amount type enum
* [ToBTCSwapState](https://docs.atomiq.exchange/sdk-reference/api/atomiq-sdk/src/enumerations/ToBTCSwapState) - Swap states
