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);
info!("Maximum payable: {} sats", current_limits.max_payable_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)")
    print("Maximum payable, in sats: \(limits.maxPayableSat)")
}
Kotlin
try {
    val currentLimits = sdk.onchainPaymentLimits()
    // Log.v("Breez", "Minimum amount, in sats: ${currentLimits.minSat}")
    // Log.v("Breez", "Maximum amount, in sats: ${currentLimits.maxSat}")
    // Log.v("Breez", "Maximum payable, in sats: ${currentLimits.maxPayableSat}")
} 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}`)
  console.log(`Maximum payable, in sats: ${currentLimits.maxPayableSat}`)
} 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}");
print("Maximum payable, in sats: ${currentLimits.maxPayableSat}");
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)
print("Maximum payable, in sats: ", current_limits.max_payable_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)
    log.Printf("Maximum payable, in sats: %v", currentLimits.MaxPayableSat)
}
C#
try
{
    var currentLimits = sdk.OnchainPaymentLimits();
    Console.WriteLine($"Minimum amount, in sats: {currentLimits.minSat}");
    Console.WriteLine($"Maximum amount, in sats: {currentLimits.maxSat}");
    Console.WriteLine($"Maximum payable, in sats: {currentLimits.maxPayableSat}");
}
catch (Exception)
{
    // Handle error
}

This represents the Pay Onchain limits at this point in time.

The min-max range may change depending on the swap service parameters or mempool feerate fluctuations. The caller should make sure the Pay Onchain amount is within this range.

The max_payable_sat field shows the maximum amount the node can send, given its available channels and local balance. The caller should also ensure the Pay Onchain amount is lower or equal to this amount.

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.

Developer note

Consider implementing the Notification Plugin when using the Breez SDK in a mobile application. By registering a webhook, the application can receive a transaction confirmation notification to claim the swap in the background.

Notifications

Enabling mobile notifications will register your app for swap notifications.

This means that, when the user performs a swap-out (send onchain), the app will

  • automatically claim the swap in the background when the onchain transaction is confirmed, even if the app is closed
  • display an OS notification, informing the user of the received funds