uniffi_lipalightninglib/onchain/
swap.rsuse crate::amount::{AsSats, Sats, ToAmount};
use crate::errors::Result;
use crate::locker::Locker;
use crate::onchain::{get_onchain_resolving_fees, query_onchain_fee_rate};
use crate::support::Support;
use crate::util::unix_timestamp_to_system_time;
use crate::{
Amount, CalculateLspFeeResponseV2, FailedSwapInfo, LspFee, OnchainResolvingFees,
ResolveFailedSwapInfo, RuntimeErrorCode, SwapAddressInfo,
};
use breez_sdk_core::error::ReceiveOnchainError;
use breez_sdk_core::{
BitcoinAddressData, Network, OpeningFeeParams, PrepareRefundRequest, ReceiveOnchainRequest,
RefundRequest,
};
use perro::{ensure, permanent_failure, runtime_error, MapToError};
use std::sync::Arc;
pub struct Swap {
support: Arc<Support>,
}
impl Swap {
pub(crate) fn new(support: Arc<Support>) -> Self {
Self { support }
}
pub fn create(&self) -> std::result::Result<SwapAddressInfo, ReceiveOnchainError> {
let lsp_fee_params =
self.get_lsp_fee_params()
.map_err(|_e| ReceiveOnchainError::ServiceConnectivity {
err: "Could not retrieve lsp fee params".to_string(),
})?;
let swap_info = self
.support
.rt
.handle()
.block_on(self.support.sdk.receive_onchain(ReceiveOnchainRequest {
opening_fee_params: Some(lsp_fee_params),
}))?;
let rate = self.support.get_exchange_rate();
Ok(SwapAddressInfo {
address: swap_info.bitcoin_address,
min_deposit: (swap_info.min_allowed_deposit as u64)
.as_sats()
.to_amount_up(&rate),
max_deposit: (swap_info.max_allowed_deposit as u64)
.as_sats()
.to_amount_down(&rate),
swap_fee: 0_u64.as_sats().to_amount_up(&rate),
})
}
pub fn determine_resolving_fees(
&self,
failed_swap_info: FailedSwapInfo,
) -> Result<Option<OnchainResolvingFees>> {
let failed_swap_closure = failed_swap_info.clone();
let prepare_onchain_tx = move |address: String| -> Result<(Sats, Sats, u32)> {
let sweep_info = self.prepare_sweep(
failed_swap_closure,
BitcoinAddressData {
address,
network: Network::Bitcoin,
amount_sat: None,
label: None,
message: None,
},
)?;
Ok((
sweep_info.recovered_amount.sats.as_sats(),
sweep_info.onchain_fee.sats.as_sats(),
sweep_info.onchain_fee_rate,
))
};
get_onchain_resolving_fees(
&self.support,
self,
failed_swap_info.amount.to_msats().as_msats(),
prepare_onchain_tx,
)
}
pub fn prepare_sweep(
&self,
failed_swap_info: FailedSwapInfo,
destination: BitcoinAddressData,
) -> Result<SweepFailedSwapInfo> {
let to_address = destination.address;
let onchain_fee_rate = query_onchain_fee_rate(&self.support)?;
let response = self
.support
.rt
.handle()
.block_on(self.support.sdk.prepare_refund(PrepareRefundRequest {
swap_address: failed_swap_info.address.clone(),
to_address: to_address.clone(),
sat_per_vbyte: onchain_fee_rate,
}))
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to prepare a failed swap refund transaction",
)?;
let rate = self.support.get_exchange_rate();
let onchain_fee = response.refund_tx_fee_sat.as_sats().to_amount_up(&rate);
ensure!(
failed_swap_info.amount.sats > onchain_fee.sats,
permanent_failure("Swap amount is below fees")
);
let recovered_amount = (failed_swap_info.amount.sats - onchain_fee.sats)
.as_sats()
.to_amount_down(&rate);
Ok(SweepFailedSwapInfo {
swap_address: failed_swap_info.address,
recovered_amount,
onchain_fee,
to_address,
onchain_fee_rate,
})
}
pub fn sweep(&self, sweep_failed_swap_info: SweepFailedSwapInfo) -> Result<String> {
Ok(self
.support
.rt
.handle()
.block_on(self.support.sdk.refund(RefundRequest {
swap_address: sweep_failed_swap_info.swap_address,
to_address: sweep_failed_swap_info.to_address,
sat_per_vbyte: sweep_failed_swap_info.onchain_fee_rate,
}))
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to create and broadcast failed swap refund transaction",
)?
.refund_tx_id)
}
pub fn swap(&self, failed_swap_info: FailedSwapInfo, sats_per_vbyte: u32) -> Result<String> {
let swap_address_info = self.create().map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Couldn't generate swap address",
)?;
let prepare_response = self
.support
.rt
.handle()
.block_on(self.support.sdk.prepare_refund(PrepareRefundRequest {
swap_address: failed_swap_info.address.clone(),
to_address: swap_address_info.address.clone(),
sat_per_vbyte: sats_per_vbyte,
}))
.map_to_runtime_error(RuntimeErrorCode::NodeUnavailable, "Coudln't prepare refund")?;
let send_amount_sats = failed_swap_info.amount.sats - prepare_response.refund_tx_fee_sat;
ensure!(
swap_address_info.min_deposit.sats <= send_amount_sats,
runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed swap amount isn't enough for creating new swap"
)
);
ensure!(
swap_address_info.max_deposit.sats >= send_amount_sats,
runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed swap amount is too big for creating new swap"
)
);
let lsp_fees = self
.support
.calculate_lsp_fee_for_amount(send_amount_sats, self.get_lsp_fee_params()?)?
.lsp_fee
.sats;
ensure!(
lsp_fees < send_amount_sats,
runtime_error(
RuntimeErrorCode::NodeUnavailable,
"A new channel is needed and the failed swap amount is not enough to pay for fees"
)
);
let refund_response = self
.support
.rt
.handle()
.block_on(self.support.sdk.refund(RefundRequest {
swap_address: failed_swap_info.address,
to_address: swap_address_info.address,
sat_per_vbyte: sats_per_vbyte,
}))
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Couldn't broadcast swap refund transaction",
)?;
Ok(refund_response.refund_tx_id)
}
pub(crate) fn list_failed_unresolved(&self) -> Result<Vec<FailedSwapInfo>> {
Ok(self
.support
.rt
.handle()
.block_on(self.support.sdk.list_refundables())
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to list refundable failed swaps",
)?
.into_iter()
.filter(|s| s.refund_tx_ids.is_empty())
.map(|s| FailedSwapInfo {
address: s.bitcoin_address,
amount: s
.confirmed_sats
.as_sats()
.to_amount_down(&self.support.get_exchange_rate()),
created_at: unix_timestamp_to_system_time(s.created_at as u64),
})
.collect())
}
pub fn calculate_lsp_fee_for_amount(
&self,
amount_sat: u64,
) -> Result<CalculateLspFeeResponseV2> {
self.support
.calculate_lsp_fee_for_amount(amount_sat, self.get_lsp_fee_params()?)
}
pub fn get_lsp_fee(&self) -> Result<LspFee> {
let exchange_rate = self.support.get_exchange_rate();
let lsp_fee = self.get_lsp_fee_params()?;
Ok(LspFee {
channel_minimum_fee: lsp_fee.min_msat.as_msats().to_amount_up(&exchange_rate),
channel_fee_permyriad: lsp_fee.proportional as u64 / 100,
})
}
pub(crate) fn get_lsp_fee_params(&self) -> Result<OpeningFeeParams> {
self.support
.task_manager
.lock_unwrap()
.get_longer_valid_lsp_fee()
}
}
pub struct SweepFailedSwapInfo {
pub swap_address: String,
pub recovered_amount: Amount,
pub onchain_fee: Amount,
pub to_address: String,
pub onchain_fee_rate: u32,
}
impl From<ResolveFailedSwapInfo> for SweepFailedSwapInfo {
fn from(value: ResolveFailedSwapInfo) -> Self {
Self {
swap_address: value.swap_address,
recovered_amount: value.recovered_amount,
onchain_fee: value.onchain_fee,
to_address: value.to_address,
onchain_fee_rate: value.onchain_fee_rate,
}
}
}
impl From<SweepFailedSwapInfo> for ResolveFailedSwapInfo {
fn from(value: SweepFailedSwapInfo) -> Self {
Self {
swap_address: value.swap_address,
recovered_amount: value.recovered_amount,
onchain_fee: value.onchain_fee,
to_address: value.to_address,
onchain_fee_rate: value.onchain_fee_rate,
}
}
}