Skip to main content

Bitcoin → Smart Chain

Swap Bitcoin L1 (on-chain) to Starknet or EVM tokens. These swaps are based on the UTXO-controlled vault primitive and verified through the on-chain Bitcoin light client.

The user and LP cooperatively sign a single Bitcoin transaction that atomically sends BTC to the LP and authorizes the smart-chain payout to the user. Users do not need destination-chain gas upfront, and watchtowers or liquidity fronters can settle the swap automatically once the Bitcoin transaction is confirmed.

Looking for Solana?

Solana uses a different (legacy) swap protocol. See BTC to Solana.

Executing the Swap

Here is a full flow for creating and executing the swap in the Bitcoin → Smart chain direction via the execute() helper function, uses the SpvFromBTCSwap swap class:

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

// Create a quote
const swap = await swapper.swap(
Tokens.BITCOIN.BTC, // Bitcoin on-chain input
Tokens.STARKNET.STRK, // Destination smart-chain token, e.g. STRK on Starknet or cBTC on Citrea
"0.00003", // Amount (3000 sats to send)
SwapAmountType.EXACT_IN,
undefined, // Source address is not used for BTC source swaps
starknetSigner.getAddress(), // Destination smart-chain address
{
gasAmount: 0n // Optional: request native destination token as gas drop
}
); // Type gets inferred as SpvFromBTCSwap

// Execute the swap
const automaticallySettled = await swap.execute(
{
address: "bc1q...", // User's Bitcoin address
publicKey: "03...", // User's Bitcoin public key
signPsbt: (psbt, signInputs) => {
// Sign the PSBT with the Bitcoin wallet
// Return the signed PSBT in hex or base64 format
return "<signed PSBT>";
}
},
{
onSourceTransactionSent: (txId) => {
console.log(`Bitcoin tx sent: ${txId}`);
},
onSourceTransactionConfirmationStatus: (txId, confirmations, target, etaMs) => {
console.log(`Confirmations: ${confirmations}/${target}, ETA: ${etaMs / 1000}s`);
},
onSourceTransactionConfirmed: (txId) => {
console.log(`Bitcoin tx confirmed: ${txId}`);
},
onSwapSettled: (destinationTxId) => {
console.log(`Swap settled on destination chain: ${destinationTxId}`);
}
}
);

if (!automaticallySettled) {
// Handle the edge-case when automatic settlement does not happen in time
console.log("Automatic settlement timed out, claiming manually...");
await swap.claim(starknetSigner);
console.log("Claimed!");
} else {
console.log("Success! Output transaction ID: ", swap.getOutputTxId());
}
warning

execute() requires a single-address (non-BIP32) Bitcoin wallet capable of signing PSBTs. If your signing flow is handled by an external wallet UI, or you use a BIP-32 HD wallet, use the manual Funded PSBT or Raw PSBT flow below.

Manual Execution Flow

Choose the Bitcoin signing flow that matches your wallet integration:

  • use Bitcoin wallet flow if you have a wallet which already implements IBitcoinWallet (e.g. the built-in SingleAddressBitcoinWallet) or MinimalBitcoinWalletInterfaceWithSigner.
  • use Funded PSBT if you have a single address bitcoin wallet (non-HD) and want to get a PSBT already funded with wallet input UTXOs.
  • use Raw PSBT if you want full control of the PSBT construction, allowing you to add arbitrary inputs and outputs to the PSBT yourself.

Pass the bitcoinWallet argument which follows the IBitcoinWallet interface or MinimalBitcoinWalletInterfaceWithSigner type to the sendBitcoinTransaction() function.

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

// Create a quote
const swap = await swapper.swap(
Tokens.BITCOIN.BTC,
Tokens.STARKNET.STRK,
"0.00003",
SwapAmountType.EXACT_IN,
undefined,
starknetSigner.getAddress(),
{
gasAmount: 0n
}
);

// 1. Let the SDK build, sign, and submit the Bitcoin transaction through your IBitcoinWallet implementation
const bitcoinTxId = await swap.sendBitcoinTransaction(bitcoinWallet);

// 2. Wait for the Bitcoin transaction to reach the required confirmation count
await swap.waitForBitcoinTransaction((txId, confirmations, targetConfirmations, txEtaMs) => {
console.log(`${confirmations}/${targetConfirmations} confirmations, ETA ${txEtaMs / 1000}s`);
});

// 3. Wait for automatic settlement or fronting on the destination chain
const automaticallySettled = await swap.waitTillClaimedOrFronted(60);

// 4. If automatic settlement does not happen in time, claim manually
if (!automaticallySettled) {
await swap.claim(starknetSigner);
}
warning

Legacy non-SegWit Bitcoin inputs are not allowed in the submitted PSBT. Use witness-type inputs such as P2WPKH, P2WSH or P2TR.

Claiming Past Unsettled Swaps

If the app was offline and the automatic settlement did not happen, the swap can be manually claimed on the destination chain.

Checking if a single swap is claimable and claiming it:

if (swap.isClaimable()) {
await swap.claim(starknetSigner);
}

Getting all swaps that are claimable and claiming them:

const claimable = await swapper.getClaimableSwaps(
"STARKNET", // Only get claimable swaps on STARKNET
starknetSigner.getAddress() // Only get claimable swaps for this destination address
); // This returns all claimable swaps, which could be either Bitcoin → Smart chain or Lightning → Smart chain

for (const swap of claimable) {
// All the claimable swap types have the same `claim()` function signature
await swap.claim(starknetSigner);
}
info

It is a good practice to query claimable swaps on your app's startup and either claim them automatically or prompt the user to claim them.

Bitcoin → Smart chain swaps can be claimed at any time after the swap Bitcoin transaction gets enough confirmations. After the swap is settled on Bitcoin, the swap becomes claimable forever - there is no timelock.

Gas Drop

You can request native tokens on the destination chain along with the main swap output. This helps cover gas fees for the first transactions on the destination chain.

const swap = await swapper.swap(
Tokens.BITCOIN.BTC,
Tokens.STARKNET.WBTC,
"0.0001",
SwapAmountType.EXACT_IN,
undefined,
starknetSigner.getAddress(),
{
gasAmount: 1_000_000_000_000_000_000n // Request 1 STRK as gas drop
}
);

console.log("Gas drop:", swap.getGasDropOutput().toString());
warning

When already swapping to the native token of the respective destination chain (i.e. STRK on Starknet, cBTC on Citrea, etc.) don't specify the gasAmount, as LPs usually don't hold the necessary gas drop liquidity for those tokens! This will lead to errors during quoting and make it unable for you to request the quote.

Swap States

Read the current state of the swap in its SpvFromBTCSwapState enum form with getState() or in human readable SwapStateInfo form with description with getStateInfo():

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

const state: SpvFromBTCSwapState = swap.getState();
console.log(`State (numeric): ${state}`);

const richState: SwapStateInfo<SpvFromBTCSwapState> = 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: SpvFromBTCSwapState = swap.getState();
});

Table of States

StateValueDescription
CLOSED-5Catastrophic failure has occurred when processing the swap on the smart-chain side.
FAILED-4Some of the bitcoin swap transaction inputs were double-spent, so the swap failed and no BTC was sent.
DECLINED-3The intermediary (LP) declined to co-sign the submitted PSBT.
QUOTE_EXPIRED-2Swap has expired for good and there is no way it can be executed anymore.
QUOTE_SOFT_EXPIRED-1Swap is almost expired and should be presented as expired, though it still might be processed.
CREATED0Swap was created, waiting for the user to sign and submit the Bitcoin swap PSBT.
SIGNED1Swap Bitcoin PSBT was submitted by the client to the SDK.
POSTED2Swap PSBT was sent to the intermediary (LP), waiting for co-sign and broadcast.
BROADCASTED3The intermediary co-signed and broadcasted the Bitcoin transaction.
FRONTED4Settlement on the destination smart chain was fronted and funds were received before final settlement.
BTC_TX_CONFIRMED5Bitcoin transaction has the required confirmations, waiting for automatic settlement or manual claim.
CLAIMED6Swap settled on the smart chain and funds were received.

API Reference