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:
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);
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)")
}
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
}
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)
}
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}");
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)
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)
}
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:
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);
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)")
}
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
}
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)
}
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");
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)
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)
}
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.
let destination_address = String::from("bc1..");
sdk.pay_onchain(PayOnchainRequest {
recipient_address: destination_address,
prepare_res,
})
.await?;
let destinationAddress = "bc1.."
let response = try? sdk.payOnchain(req: PayOnchainRequest(recipientAddress: destinationAddress, prepareRes: prepareResponse))
val address = "bc1.."
try {
sdk.payOnchain(PayOnchainRequest(address, prepareRes))
} catch (e: Exception) {
// handle error
}
try {
const onchainRecipientAddress = 'bc1..'
const reverseSwapInfo = await payOnchain({
recipientAddress: onchainRecipientAddress,
prepareRes
})
} catch (err) {
console.error(err)
}
PayOnchainRequest req = PayOnchainRequest(
recipientAddress: onchainRecipientAddress,
prepareRes: prepareRes,
);
PayOnchainResponse res = await breezSDK.payOnchain(req: req);
destination_address = "bc1.."
try:
req = breez_sdk.PayOnchainRequest(destination_address, prepare_res)
sdk_services.pay_onchain(req)
destinationAddress := "bc1.."
payOnchainRequest := breez_sdk.PayOnchainRequest{
RecipientAddress: destinationAddress,
PrepareRes: prepareRes,
}
if reverseSwapInfo, err := sdk.PayOnchain(payOnchainRequest); err == nil {
log.Printf("%#v", reverseSwapInfo)
}
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:
for rs in sdk.in_progress_onchain_payments().await? {
info!(
"Onchain payment {} in progress, status is {:?}",
rs.id, rs.status
);
}
if let inProgressOnchainPayments = try? sdk.inProgressOnchainPayments() {
for rs in inProgressOnchainPayments {
print("Onchain payment \(rs.id) in progress, status is \(rs.status)")
}
}
for (rs in sdk.inProgressOnchainPayments()) {
// Log.v("Breez", "Onchain payment ${rs.id} in progress, status is ${rs.status}")
}
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)
}
List<ReverseSwapInfo> inProgOnchainPaymentList = await breezSDK.inProgressOnchainPayments();
for (var inProgOnchainPayment in inProgOnchainPaymentList) {
print("Onchain payment ${inProgOnchainPayment.id} in progress, status is ${inProgOnchainPayment.status.name}");
}
reverse_swaps = sdk_services.in_progress_onchain_payments()
for rs in reverse_swaps:
print("Onchain payment ",rs.id , " in progress, status is ", rs.status)
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)
}
}
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