Skip to content

Implement prepare_refund_failed_swap() #712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion examples/node/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,21 @@ fn refund_failed_swap(node: &LightningNode, words: &mut dyn Iterator<Item = &str

let fee_rate = node.query_onchain_fee_rate()?;

let txid = node.resolve_failed_swap(swap_address.into(), to_address.into(), fee_rate)?;
let failed_swaps = node
.get_unresolved_failed_swaps()
.map_err(|e| anyhow!("Failed to fetch currently unresolved failed swaps: {e}"))?;
let failed_swap = failed_swaps
.into_iter()
.find(|s| s.address.eq(swap_address))
.ok_or(anyhow!(
"No unresolved failed swap with provided swap address was found"
))?;
let resolve_failed_swap_info = node
.prepare_resolve_failed_swap(failed_swap, to_address.into(), fee_rate)
.map_err(|e| anyhow!("Failed to prepare the resolution of the failed swap: {e}"))?;
let txid = node
.resolve_failed_swap(resolve_failed_swap_info)
.map_err(|e| anyhow!("Failed to resolve failed swap: {e}"))?;
println!("Successfully broadcasted refund transaction - txid: {txid}");

Ok(())
Expand Down
85 changes: 70 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ use breez_sdk_core::{
parse, parse_invoice, BreezEvent, BreezServices, EventListener, GreenlightCredentials,
GreenlightNodeConfig, InputType, ListPaymentsRequest, LnUrlPayRequest, LnUrlPayRequestData,
LnUrlWithdrawRequest, LnUrlWithdrawResult, NodeConfig, OpenChannelFeeRequest, OpeningFeeParams,
PaymentDetails, PaymentStatus, PaymentTypeFilter, ReceiveOnchainRequest, RefundRequest,
SendPaymentRequest, SweepRequest,
PaymentDetails, PaymentStatus, PaymentTypeFilter, PrepareRefundRequest, ReceiveOnchainRequest,
RefundRequest, SendPaymentRequest, SweepRequest,
};
use cipher::generic_array::typenum::U32;
use crow::{
Expand Down Expand Up @@ -284,12 +284,26 @@ pub struct SwapAddressInfo {
/// Information about a failed swap
pub struct FailedSwapInfo {
pub address: String,
/// The amount that is available to be refunded. The refund will involve paying some
/// The amount that is available to be recovered. The recovery will involve paying some
/// on-chain fees so it isn't possible to recover the entire amount.
pub amount: Amount,
pub created_at: SystemTime,
}

/// Information the resolution of a failed swap.
pub struct ResolveFailedSwapInfo {
/// The address of the failed swap.
pub swap_address: String,
/// The amount that will be sent (swap amount - onchain fee).
pub recovered_amount: Amount,
/// The amount that will be paid in onchain fees.
pub onchain_fee: Amount,
/// The address to which recovered funds will be sent.
pub to_address: String,
/// The onchain fee rate that will be applied. This fee rate results in the `onchain_fee`.
pub onchain_fee_rate: u32,
}

#[derive(Clone, PartialEq, Debug)]
pub(crate) struct UserPreferences {
fiat_currency: String,
Expand Down Expand Up @@ -1395,7 +1409,7 @@ impl LightningNode {
})
}

/// Lists all unresolved failed swaps. Each individual failed swap can be refunded
/// Lists all unresolved failed swaps. Each individual failed swap can be recovered
/// using [`LightningNode::resolve_failed_swap`].
pub fn get_unresolved_failed_swaps(&self) -> Result<Vec<FailedSwapInfo>> {
Ok(self
Expand All @@ -1418,29 +1432,70 @@ impl LightningNode {
.collect())
}

/// Creates and broadcasts a refund transaction to recover funds from a failed swap. Existing
/// failed swaps can be listed using [`LightningNode::get_unresolved_failed_swaps`].
/// Prepares the resolution of a failed swap in order to know how much will be recovered and how much
/// will be paid in onchain fees.
///
/// Parameters:
/// * `failed_swap_address` - the address of the failed swap (can be obtained from [`FailedSwapInfo`])
/// * `failed_swap_info` - the failed swap that will be prepared
/// * `to_address` - the destination address to which funds will be sent
/// * `onchain_fee_rate` - the fee rate that will be applied. The recommeded one can be fetched
/// * `onchain_fee_rate` - the fee rate that will be applied. The recommended one can be fetched
/// using [`LightningNode::query_onchain_fee_rate`]
///
/// Returns the txid of the refund transaction.
pub fn resolve_failed_swap(
pub fn prepare_resolve_failed_swap(
&self,
failed_swap_address: String,
failed_swap_info: FailedSwapInfo,
to_address: String,
onchain_fee_rate: u32,
) -> Result<ResolveFailedSwapInfo> {
let response = self
.rt
.handle()
.block_on(self.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.get_exchange_rate();
let onchain_fee = response.refund_tx_fee_sat.as_sats().to_amount_up(&rate);
let recovered_amount = (failed_swap_info.amount.sats - onchain_fee.sats)
.as_sats()
.to_amount_down(&rate);

Ok(ResolveFailedSwapInfo {
swap_address: failed_swap_info.address,
recovered_amount,
onchain_fee,
to_address,
onchain_fee_rate,
})
}

/// Creates and broadcasts a resolving transaction to recover funds from a failed swap. Existing
/// failed swaps can be listed using [`LightningNode::get_unresolved_failed_swaps`] and preparing
/// the resolution of a failed swap can be done using [`LightningNode::prepare_resolve_failed_swap`].
///
/// Parameters:
/// * `resolve_failed_swap_info` - Information needed to resolve the failed swap. Can be obtained
/// using [`LightningNode::prepare_resolve_failed_swap`].
///
/// Returns the txid of the resolving transaction.
///
/// Paid on-chain fees can be known in advance using [`LightningNode::prepare_resolve_failed_swap`].
pub fn resolve_failed_swap(
&self,
resolve_failed_swap_info: ResolveFailedSwapInfo,
) -> Result<String> {
Ok(self
.rt
.handle()
.block_on(self.sdk.refund(RefundRequest {
swap_address: failed_swap_address,
to_address,
sat_per_vbyte: onchain_fee_rate,
swap_address: resolve_failed_swap_info.swap_address,
to_address: resolve_failed_swap_info.to_address,
sat_per_vbyte: resolve_failed_swap_info.onchain_fee_rate,
}))
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
Expand Down
13 changes: 12 additions & 1 deletion src/lipalightninglib.udl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ interface LightningNode {
sequence<FailedSwapInfo> get_unresolved_failed_swaps();

[Throws=LnError]
string resolve_failed_swap(string failed_swap_address, string to_address, u32 onchain_fee_rate);
ResolveFailedSwapInfo prepare_resolve_failed_swap(FailedSwapInfo failed_swap_info, string to_address, u32 onchain_fee_rate);

[Throws=LnError]
string resolve_failed_swap(ResolveFailedSwapInfo resolve_failed_swap_info);

[Throws=LnError]
void hide_topup(string id);
Expand Down Expand Up @@ -346,6 +349,14 @@ dictionary SwapAddressInfo {
Amount max_deposit;
};

dictionary ResolveFailedSwapInfo {
string swap_address;
Amount recovered_amount;
Amount onchain_fee;
string to_address;
u32 onchain_fee_rate;
};

dictionary FailedSwapInfo {
string address;
Amount amount;
Expand Down