Sending an on-chain transaction (Swap-Out)

You can send funds from the Breez SDK wallet to an on-chain address as follows.

Checking the limits

First, fetch the current reverse swap limits:

Rust
let current_limits = sdk.onchain_payment_limits().await?;

info!("Minimum amount: {} sats", current_limits.min_sat);
info!("Maximum amount: {} sats", current_limits.max_sat);
Swift
let currentLimits = try? sdk.onchainPaymentLimits()
if let limits = currentLimits {
    print("Minimum amount, in sats: \(limits.minSat)")
    print("Maximum amount, in sats: \(limits.maxSat)")
}
Kotlin
try {
    val currentLimits = sdk.onchainPaymentLimits()
    // Log.v("Breez", "Minimum amount, in sats: ${currentLimits.minSat}")
    // Log.v("Breez", "Maximum amount, in sats: ${currentLimits.maxSat}")
} catch (e: Exception) {
    // handle error
}
React Native
try {
  const currentLimits = await onchainPaymentLimits()

  console.log(`Minimum amount, in sats: ${currentLimits.minSat}`)
  console.log(`Maximum amount, in sats: ${currentLimits.maxSat}`)
} catch (err) {
  console.error(err)
}
Dart
OnchainPaymentLimitsResponse currentLimits = await breezSDK.onchainPaymentLimits();
print("Minimum amount, in sats: ${currentLimits.minSat}");
print("Maximum amount, in sats: ${currentLimits.maxSat}");
Python
current_limits = sdk_services.onchain_payment_limits()
print("Minimum amount, in sats: ", current_limits.min_sat)
print("Maximum amount, in sats: ", current_limits.max_sat)
Go
if currentLimits, err := sdk.OnchainPaymentLimits(); err == nil {
    log.Printf("Minimum amount, in sats: %v", currentLimits.MinSat)
    log.Printf("Maximum amount, in sats: %v", currentLimits.MaxSat)
}
C#
try
{
    var currentLimits = sdk.OnchainPaymentLimits();
    Console.WriteLine($"Minimum amount, in sats: {currentLimits.minSat}");
    Console.WriteLine($"Maximum amount, in sats: {currentLimits.maxSat}");
}
catch (Exception)
{
    // Handle error
}

This represents the range of valid amounts that can be sent at this point in time. The range may change depending on the wallet's liquidity, swap service parameters or mempool feerate fluctuations.

Developer note

It is best to fetch these limits just before your app shows the Pay Onchain (reverse swap) UI. You can then use these limits to validate the user input.

Preparing to send, checking fees

The next step is to get an overview of the exact amount that will be sent, the amount that will be received, and the fees.

There are two ways to do this:

  • you can set the sender amount, then the recipient amount will be your input minus the fees
  • you can set the recipient amount, in which case the sender amount will be your input plus the fees

Assuming you'd like to specify the sender amount, the snippet is as follows:

Rust
let amount_sat = current_limits.min_sat;
let claim_tx_feerate = fee_rate;

let prepare_res = sdk
    .prepare_onchain_payment(PrepareOnchainPaymentRequest {
        amount_sat,
        amount_type: SwapAmountType::Send,
        claim_tx_feerate,
    })
    .await?;

info!("Sender amount: {} sats", prepare_res.sender_amount_sat);
info!(
    "Recipient amount: {} sats",
    prepare_res.recipient_amount_sat
);
info!("Total fees: {} sats", prepare_res.total_fees);
Swift
let amountSat = currentLimits.minSat
let satPerVbyte: UInt32 = 5

let prepareRequest = PrepareOnchainPaymentRequest(amountSat: amountSat, amountType: .send, claimTxFeerate: satPerVbyte);
let prepareResponse = try? sdk.prepareOnchainPayment(req: prepareRequest)

if let response = prepareResponse {
    print("Sender amount, in sats: \(response.senderAmountSat)")
    print("Recipient amount, in sats: \(response.recipientAmountSat)")
    print("Total fees, in sats: \(response.totalFees)")
}
Kotlin
val amountSat = currentLimits.minSat
val satPerVbyte = 10.toUInt()
try {
    val prepareRequest = PrepareOnchainPaymentRequest(amountSat, SwapAmountType.SEND, satPerVbyte)
    val prepareRes = sdk.prepareOnchainPayment(prepareRequest)
    // Log.v("Breez", "Sender amount: ${prepareRes.senderAmountSat} sats")
    // Log.v("Breez", "Recipient amount: ${prepareRes.recipientAmountSat} sats")
    // Log.v("Breez", "Total fees: ${prepareRes.totalFees} sats")
} catch (e: Exception) {
    // handle error
}
React Native
try {
  const amountSat = currentLimits.minSat
  const satPerVbyte = 10

  const prepareResponse = await prepareOnchainPayment({
    amountSat,
    amountType: SwapAmountType.SEND,
    claimTxFeerate: satPerVbyte
  })
  console.log(`Sender amount: ${prepareResponse.senderAmountSat} sats`)
  console.log(`Recipient amount: ${prepareResponse.recipientAmountSat} sats`)
  console.log(`Total fees: ${prepareResponse.totalFees} sats`)
} catch (err) {
  console.error(err)
}
Dart
PrepareOnchainPaymentRequest req = PrepareOnchainPaymentRequest(
  amountSat: amountSat,
  amountType: SwapAmountType.Send,
  claimTxFeerate: satPerVbyte,
);
PrepareOnchainPaymentResponse prepareRes = await breezSDK.prepareOnchainPayment(req: req);
print("Sender amount: ${prepareRes.senderAmountSat} sats");
print("Recipient amount: ${prepareRes.recipientAmountSat} sats");
print("Total fees: ${prepareRes.totalFees} sats");
Python
req = breez_sdk.PrepareOnchainPaymentRequest(amount_sat, breez_sdk.SwapAmountType.SEND, claim_tx_feerate)
prepare_res = sdk_services.prepare_onchain_payment(req)

print("Sender amount, in sats: ", prepare_res.sender_amount_sat)
print("Recipient amount, in sats: ", prepare_res.recipient_amount_sat)
print("Total fees, in sats: ", prepare_res.total_fees)
Go
sendAmountSat := currentLimits.MinSat
satPerVbyte := uint32(10)

req := breez_sdk.PrepareOnchainPaymentRequest{
    AmountSat:      sendAmountSat,
    AmountType:     breez_sdk.SwapAmountTypeSend,
    ClaimTxFeerate: satPerVbyte,
}

if prepareRes, err := sdk.PrepareOnchainPayment(req); err == nil {
    log.Printf("Sender amount, in sats: %v", prepareRes.SenderAmountSat)
    log.Printf("Recipient amount, in sats: %v", prepareRes.RecipientAmountSat)
    log.Printf("Total fees, in sats: %v", prepareRes.TotalFees)
}
C#
var amountSat = currentLimits.minSat;
var claimTxFeerate = feeRate;

try
{
    var prepareRes = sdk.PrepareOnchainPayment(
        new PrepareOnchainPaymentRequest(
            amountSat,
            SwapAmountType.SEND,
            claimTxFeerate));

    Console.WriteLine($"Sender amount, in sats: {prepareRes.senderAmountSat}");
    Console.WriteLine($"Recipient amount, in sats: {prepareRes.recipientAmountSat}");
    Console.WriteLine($"Total fees, in sats: {prepareRes.totalFees}");
}
catch (Exception)
{
    // Handle error
}

If instead you'd like to specify the recipient amount, simply change the SwapAmountType from Send to Receive.

In case you want to drain your channels and send the maximum amount possible, you can use the above snippet with amount_sat set to current_limits.max_sat and amount_type as Send.

Executing the Swap

Once you checked the amounts and the fees are acceptable, you can continue with sending the payment.

Note that one of the arguments will be the result from the prepare call above.

Rust
let destination_address = String::from("bc1..");

sdk.pay_onchain(PayOnchainRequest {
    recipient_address: destination_address,
    prepare_res,
})
.await?;
Swift
let destinationAddress = "bc1.."

let response = try? sdk.payOnchain(req: PayOnchainRequest(recipientAddress: destinationAddress, prepareRes: prepareResponse))
Kotlin
val address = "bc1.."
try {
    sdk.payOnchain(PayOnchainRequest(address, prepareRes))
} catch (e: Exception) {
    // handle error
}
React Native
try {
  const onchainRecipientAddress = 'bc1..'

  const reverseSwapInfo = await payOnchain({
    recipientAddress: onchainRecipientAddress,
    prepareRes
  })
} catch (err) {
  console.error(err)
}
Dart
PayOnchainRequest req = PayOnchainRequest(
  recipientAddress: onchainRecipientAddress,
  prepareRes: prepareRes,
);
PayOnchainResponse res = await breezSDK.payOnchain(req: req);
Python
destination_address = "bc1.."
try:
    req = breez_sdk.PayOnchainRequest(destination_address, prepare_res)
    sdk_services.pay_onchain(req)
Go
destinationAddress := "bc1.."

payOnchainRequest := breez_sdk.PayOnchainRequest{
    RecipientAddress: destinationAddress,
    PrepareRes:       prepareRes,
}

if reverseSwapInfo, err := sdk.PayOnchain(payOnchainRequest); err == nil {
    log.Printf("%#v", reverseSwapInfo)
}
C#
var destinationAddress = "bc1..";
try
{
    var reverseSwapInfo = sdk.PayOnchain(
        new PayOnchainRequest(destinationAddress, prepareRes));
}
catch (Exception)
{
    // Handle error
}

Starting the onchain payment (reverse swap) will trigger a HODL invoice payment, which will only be settled if the entire swap completes. This means you will see an outgoing pending payment in your list of payments, which locks those funds until the invoice is either settled or cancelled. This will happen automatically at the end of the reverse swap.

List in-progress Swaps

You can check the ongoing onchain payments (reverse swaps) and their status with:

Rust
for rs in sdk.in_progress_onchain_payments().await? {
    info!(
        "Onchain payment {} in progress, status is {:?}",
        rs.id, rs.status
    );
}
Swift
if let inProgressOnchainPayments = try? sdk.inProgressOnchainPayments() {
    for rs in inProgressOnchainPayments {
        print("Onchain payment \(rs.id) in progress, status is \(rs.status)")
    }
}
Kotlin
for (rs in sdk.inProgressOnchainPayments()) {
    // Log.v("Breez", "Onchain payment ${rs.id} in progress, status is ${rs.status}")
}
React Native
try {
  const swaps = await inProgressOnchainPayments()
  for (const swap of swaps) {
    console.log(
      `Onchain payment ${swap.id} in progress, status is ${swap.status}`
    )
  }
} catch (err) {
  console.error(err)
}
Dart
List<ReverseSwapInfo> inProgOnchainPaymentList = await breezSDK.inProgressOnchainPayments();
for (var inProgOnchainPayment in inProgOnchainPaymentList) {
  print("Onchain payment ${inProgOnchainPayment.id} in progress, status is ${inProgOnchainPayment.status.name}");
}
Python
reverse_swaps = sdk_services.in_progress_onchain_payments()
for rs in reverse_swaps:
    print("Onchain payment ",rs.id , " in progress, status is ", rs.status)
Go
if swaps, err := sdk.InProgressOnchainPayments(); err == nil {
    for _, swap := range swaps {
        log.Printf("Onchain payment %v in progress, status is %v", swap.Id, swap.Status)
    }
}
C#
try
{
    var swaps = sdk.InProgressOnchainPayments();
    foreach (var swap in swaps)
    {
        Console.WriteLine(
            $"Onchain payment {swap.id} in progress, " +
            $"status is {swap.status}`");
    }
}
catch (Exception)
{
    // Handle error
}

If the reverse swap is successful, you'll get the on-chain payment on your destination address and the HODL invoice will change from pending to settled.

If however something goes wrong at any point in the process, the initial HODL payment will be cancelled and the funds in your Breez SDK wallet will be unlocked.