Receiving an on-chain transaction (Swap-In)
There are cases when you have funds in some bitcoin address and you would like to send those to your lightning node.
In such cases, the SDK might have to open a new channel, for which case you can specify an optional user-selected channel opening fee1. In order to receive funds you first have to be connected to an LSP.
let swap_info = sdk
.receive_onchain(ReceiveOnchainRequest::default())
.await?;
// Send your funds to the below bitcoin address
let address = swap_info.bitcoin_address;
info!(
"Minimum amount allowed to deposit in sats: {}",
swap_info.min_allowed_deposit
);
info!(
"Maximum amount allowed to deposit in sats: {}",
swap_info.max_allowed_deposit
);
let swapInfo = try? sdk.receiveOnchain(req: ReceiveOnchainRequest())
// Send your funds to the bellow bitcoin address
let address = swapInfo?.bitcoinAddress
print("Minimum amount allowed to deposit in sats: \(swapInfo!.minAllowedDeposit)")
print("Maximum amount allowed to deposit in sats: \(swapInfo!.maxAllowedDeposit)")
try {
val swapInfo = sdk.receiveOnchain(ReceiveOnchainRequest())
// Send your funds to the bellow bitcoin address
val address = swapInfo.bitcoinAddress
// Log.v("Breez", "Minimum amount allowed to deposit in sats: ${swapInfo.minAllowedDeposit}")
// Log.v("Breez", "Maximum amount allowed to deposit in sats: ${swapInfo.maxAllowedDeposit}")
} catch (e: Exception) {
// handle error
}
try {
const swapInfo = await receiveOnchain({})
// Send your funds to the below bitcoin address
const address = swapInfo.bitcoinAddress
console.log(`Minimum amount allowed to deposit in sats: ${swapInfo.minAllowedDeposit}`)
console.log(`Maximum amount allowed to deposit in sats: ${swapInfo.maxAllowedDeposit}`)
} catch (err) {
console.error(err)
}
ReceiveOnchainRequest req = const ReceiveOnchainRequest();
SwapInfo swapInfo = await breezSDK.receiveOnchain(req: req);
// Send your funds to the below bitcoin address
String address = swapInfo.bitcoinAddress;
print(address);
print("Minimum amount allowed to deposit in sats: ${swapInfo.minAllowedDeposit}");
print("Maximum amount allowed to deposit in sats: ${swapInfo.maxAllowedDeposit}");
return swapInfo;
swap_info = sdk_services.receive_onchain(breez_sdk.ReceiveOnchainRequest())
# Send your funds to the below bitcoin address
address = swap_info.bitcoin_address
print("Minimum amount allowed to deposit in sats:", swap_info.min_allowed_deposit)
print("Maximum amount allowed to deposit in sats:", swap_info.max_allowed_deposit)
if swapInfo, err := sdk.ReceiveOnchain(breez_sdk.ReceiveOnchainRequest{}); err != nil {
// Send your funds to the below bitcoin address
address := swapInfo.BitcoinAddress
log.Printf("%v", address)
log.Printf("Minimum amount allowed to deposit in sats: %v", swapInfo.MinAllowedDeposit)
log.Printf("Maximum amount allowed to deposit in sats: %v", swapInfo.MaxAllowedDeposit)
}
try
{
var swapInfo = sdk.ReceiveOnchain(new ReceiveOnchainRequest());
// Send your funds to the below bitcoin address
var address = swapInfo.bitcoinAddress;
Console.WriteLine($"Minimum amount allowed to deposit in sats: {swapInfo.minAllowedDeposit}");
Console.WriteLine($"Maximum amount allowed to deposit in sats: {swapInfo.maxAllowedDeposit}");
}
catch (Exception)
{
// Handle error
}
Developer note
The swap_info
above includes maximum and minimum limits. Your application's users must be informed of these limits because if the amount transferred to the swap address falls outside this valid range, the funds will not be successfully received via lightning. In such cases, a refund will be necessary.
Get the in-progress Swap
Once you've sent the funds to the above address, the SDK will monitor this address for unspent confirmed outputs and use a trustless submarine swap to receive these into your Lightning node. You can always monitor the status of the current in-progress swap using the following code:
let swap_info = sdk.in_progress_swap().await?;
let swapInfo = try? sdk.inProgressSwap()
try {
val swapInfo = sdk.inProgressSwap()
} catch (e: Exception) {
// handle error
}
try {
const swapInfo = await inProgressSwap()
} catch (err) {
console.error(err)
}
SwapInfo? swapInfo = await breezSDK.inProgressSwap();
print(swapInfo);
swap_info = sdk_services.in_progress_swap()
if swapInfo, err := sdk.InProgressSwap(); err == nil {
log.Printf("%#v", swapInfo)
}
try
{
var swapInfo = sdk.InProgressSwap();
}
catch (Exception)
{
// Handle error
}
The process of receiving funds via an on-chain address is trustless and uses a submarine swap. This means there are two ways to spend the sent funds:
- Either by a preimage that is exposed when the Lightning payment is completed - this is the positive case where the swap was successful.
- Or by your node when the swap didn't complete within a certain timeout (216 blocks) - this is the negative case where your node will execute a refund (funds become refundable after 288 blocks). Refund will also be available in case the amount sent wasn't within the limits.
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 redeem 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-in (receive onchain), the app will
- automatically complete 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
Refund a Swap
In order to execute a refund, you need to supply an on-chain address to where the refunded amount will be sent. The following code will retrieve the refundable swaps:
let refundables = sdk.list_refundables().await?;
let refundables = try? sdk.listRefundables()
try {
val refundables = sdk.listRefundables()
} catch (e: Exception) {
// handle error
}
try {
const refundables = await listRefundables()
} catch (err) {
console.error(err)
}
List<SwapInfo> refundables = await breezSDK.listRefundables();
for (var refundable in refundables) {
print(refundable.bitcoinAddress);
}
refundables = sdk_services.list_refundables()
if refundables, err := sdk.ListRefundables(); err == nil {
log.Printf("%#v", refundables)
}
try
{
var refundables = sdk.ListRefundables();
}
catch (Exception)
{
// Handle error
}
Once you have a refundable swap in hand, use the following code to execute a refund:
let destination_address = "...".into();
let sat_per_vbyte = refund_tx_fee_rate;
sdk.refund(RefundRequest {
to_address: destination_address,
sat_per_vbyte,
swap_address: refundable.bitcoin_address,
})
.await?;
let destinationAddress = "..."
let response = try? sdk.refund(req: RefundRequest(swapAddress: refundables.bitcoinAddress, toAddress: destinationAddress, satPerVbyte: satPerVbyte))
val swapAddress = "..."
val toAddress = "..."
val satPerVbyte = 1.toUInt()
try {
sdk.refund(RefundRequest(swapAddress, toAddress, satPerVbyte))
} catch (e: Exception) {
// handle error
}
try {
const refundables = await listRefundables()
const toAddress = '...'
const satPerVbyte = 5
const refundResponse = await refund({
swapAddress: refundables[0].bitcoinAddress,
toAddress,
satPerVbyte
})
} catch (err) {
console.error(err)
}
RefundRequest req = RefundRequest(
swapAddress: swapAddress,
toAddress: toAddress,
satPerVbyte: satPerVbyte,
);
RefundResponse resp = await breezSDK.refund(req: req);
print(resp.refundTxId);
destination_address = "..."
sat_per_vbyte = 5
try:
result = sdk_services.refund(
swap_address=refundable.bitcoin_address,
to_address=destination_address,
sat_per_vbyte=sat_per_vbyte)
if refundables, err := sdk.ListRefundables(); err == nil {
destinationAddress := "..."
satPerVbyte := uint32(5)
refundRequest := breez_sdk.RefundRequest{
SwapAddress: refundables[0].BitcoinAddress,
ToAddress: destinationAddress,
SatPerVbyte: satPerVbyte,
}
if result, err := sdk.Refund(refundRequest); err == nil {
log.Printf("%v", result)
}
}
var destinationAddress = "...";
var satPerVbyte = refundTxFeeRate;
try
{
var result = sdk.Refund(
new RefundRequest(
refundable.bitcoinAddress,
destinationAddress,
satPerVbyte));
}
catch (Exception)
{
// Handle error
}
Developer note
A refund can be attempted several times. A common scenario where this is useful is if the initial refund transaction takes too long to mine, your application's users can be offered the ability to re-trigger the refund with a higher feerate.
Rescanning swaps
The SDK continuously monitors any ongoing swap transactions until they are either completed or refunded. Once one of these outcomes occurs, the SDK ceases its monitoring activities, and users are advised against sending additional funds to the swap address. However, if users inadvertently send additional funds to a swap address that was already used, the SDK won't automatically recognize it. In such cases, the SDK provides an option to manually scan the used swap addressed to identify additional transactions. This action allows the address to be included in the list eligible for refunds, enabling the initiation of a refund process. For the purpose of rescanning all historical swap addresses and updating their on-chain status, the following code can be used:
sdk.rescan_swaps().await?;
try? sdk.rescanSwaps()
try {
sdk.rescanSwaps()
} catch (e: Exception) {
// handle error
}
try {
await rescanSwaps()
} catch (err) {
console.error(err)
}
await breezSDK.rescanSwaps();
sdk_services.rescan_swaps()
if err := sdk.RescanSwaps(); err == nil {
log.Println("Rescan finished")
}
try
{
sdk.RescanSwaps();
}
catch (Exception)
{
// Handle error
}
Calculating fees
When the amount to be received exceeds the inbound liquidity of the node, a new channel will be opened by the LSP in order for the node to receive it. This can checked by retrieving the NodeState from the SDK and comparing the inbound liquidity to the amount to be received. If the amount is greater or equal to the inbound liquidity, a new channel opening is required.
To calculate the fees for a channel being opened by the LSP:
let channel_fees = sdk
.open_channel_fee(OpenChannelFeeRequest {
amount_msat,
expiry: None,
})
.await?;
let response = try? sdk.openChannelFee(req: OpenChannelFeeRequest(amountMsat: amountMsat))
try {
val amountMsat = 5_000.toULong();
val channelFees = sdk.openChannelFee(OpenChannelFeeRequest(amountMsat))
} catch (e: Exception) {
// handle error
}
try {
const amountMsat = 10000
const openChannelFeeResponse = await openChannelFee({
amountMsat
})
} catch (err) {
console.error(err)
}
OpenChannelFeeRequest req = OpenChannelFeeRequest(amountMsat: amountMsat, expiry: expiry);
OpenChannelFeeResponse resp = await breezSDK.openChannelFee(req: req);
print(resp.feeMsat);
req = breez_sdk.OpenChannelFeeRequest(amount_msat)
channel_fees = sdk_services.open_channel_fee(req)
if channelFees, err := sdk.OpenChannelFee(breez_sdk.OpenChannelFeeRequest{AmountMsat: amountMsat}); err == nil {
log.Printf("%#v", channelFees)
}
try
{
var channelFees = sdk.OpenChannelFee(
new OpenChannelFeeRequest(amountMsat));
}
catch (Exception)
{
// Handle error
}
For more details on these fees, see Channel Opening Fees