Skip to content

Commit

Permalink
Migrate to the new pay on-chain API (#1102)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrei-21 authored Jun 17, 2024
1 parent da6326e commit 0140506
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 61 deletions.
12 changes: 6 additions & 6 deletions examples/node/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use uniffi_lipalightninglib::{
ExchangeRate, FailedSwapInfo, FiatValue, IncomingPaymentInfo, InvoiceCreationMetadata,
InvoiceDetails, LightningNode, LiquidityLimit, LnUrlPayDetails, LnUrlWithdrawDetails,
MaxRoutingFeeMode, OfferInfo, OfferKind, OutgoingPaymentInfo, PaymentInfo, PaymentMetadata,
Recipient, TzConfig,
RangeHit, Recipient, TzConfig,
};

pub(crate) fn poll_for_user_input(node: &LightningNode, log_file_path: &str) {
Expand Down Expand Up @@ -1346,11 +1346,11 @@ fn sweep(node: &LightningNode, address: String) -> Result<String> {
}

fn clear_wallet_info(node: &LightningNode) -> Result<()> {
ensure!(
node.is_clear_wallet_feasible()?,
"Clearing the wallet isn't feasible at the moment due to the available funds being \
either too low or too high"
);
match node.check_clear_wallet_feasibility()? {
RangeHit::Below { min } => bail!("Balance is below min: {}", amount_to_string(&min)),
RangeHit::In => (),
RangeHit::Above { max } => bail!("Balance is above max: {}", amount_to_string(&max)),
};

let clear_wallet_info = node.prepare_clear_wallet()?;

Expand Down
120 changes: 67 additions & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ use breez_sdk_core::{
ConnectRequest, EventListener, GreenlightCredentials, GreenlightNodeConfig, InputType,
ListPaymentsRequest, LnUrlPayRequest, LnUrlPayRequestData, LnUrlWithdrawRequest,
LnUrlWithdrawRequestData, Network, NodeConfig, OpenChannelFeeRequest, OpeningFeeParams,
PaymentDetails, PaymentStatus, PaymentTypeFilter, PrepareRedeemOnchainFundsRequest,
PayOnchainRequest, PaymentDetails, PaymentStatus, PaymentTypeFilter,
PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse, PrepareRedeemOnchainFundsRequest,
PrepareRefundRequest, ReceiveOnchainRequest, RedeemOnchainFundsRequest, RefundRequest,
ReportIssueRequest, ReportPaymentFailureDetails, ReverseSwapFeesRequest, SendOnchainRequest,
SendPaymentRequest, SignMessageRequest, UnspentTransactionOutput,
ReportIssueRequest, ReportPaymentFailureDetails, ReverseSwapFeesRequest, SendPaymentRequest,
SignMessageRequest, UnspentTransactionOutput,
};
use crow::{CountryCode, LanguageCode, OfferManager, TopupError, TopupInfo};
pub use crow::{PermanentFailureCode, TemporaryFailureCode};
Expand Down Expand Up @@ -137,6 +138,16 @@ const CLN_DUST_LIMIT_SAT: u64 = 546;

pub(crate) const DB_FILENAME: &str = "db2.db3";

/// Represent the result of comparision of a value with a given range.
pub enum RangeHit {
/// The value is below the left side of the range.
Below { min: Amount },
/// The value is whithin the range.
In,
/// The value is above the right side of the range.
Above { max: Amount },
}

/// The fee charged by the Lightning Service Provider (LSP) for opening a channel with the node.
/// This fee is being charged at the time of the channel creation.
/// The LSP simply subtracts this fee from an incoming payment (if this incoming payment leads to a channel creation).
Expand Down Expand Up @@ -249,8 +260,7 @@ pub struct ClearWalletInfo {
pub onchain_fee: Amount,
/// Estimate for the fee paid to the swap service.
pub swap_fee: Amount,
/// Hash that shouldn't be altered.
pub fees_hash: String,
prepare_response: PrepareOnchainPaymentResponse,
}

const MAX_FEE_PERMYRIAD: u16 = 150;
Expand Down Expand Up @@ -2327,23 +2337,11 @@ impl LightningNode {

/// Check if clearing the wallet is feasible.
///
/// If not feasible, it means the balance is either too high or too low for a reverse-swap to
/// be used.
/// Meaning that the balance is within the range of what can be reverse-swapped.
///
/// Requires network: **yes**
pub fn is_clear_wallet_feasible(&self) -> Result<bool> {
#[allow(deprecated)]
let amount_sat = self
.rt
.handle()
.block_on(self.sdk.max_reverse_swap_amount())
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to get max reverse swap amount",
)?
.total_sat;

let reverse_swap_info = self
pub fn check_clear_wallet_feasibility(&self) -> Result<RangeHit> {
let limits = self
.rt
.handle()
.block_on(
Expand All @@ -2354,59 +2352,78 @@ impl LightningNode {
RuntimeErrorCode::NodeUnavailable,
"Failed to fetch reverse swap fees",
)?;

Ok(amount_sat >= reverse_swap_info.min && amount_sat <= reverse_swap_info.max)
let balance_sat = self
.sdk
.node_info()
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to read node info",
)?
.channels_balance_msat
.as_msats()
.sats_round_down();
let exchange_rate = self.get_exchange_rate();
let range_hit = match balance_sat {
balance_sat if balance_sat < limits.min => RangeHit::Below {
min: limits.min.as_sats().to_amount_up(&exchange_rate),
},
balance_sat if balance_sat <= limits.max => RangeHit::In,
balance_sat if limits.max < balance_sat => RangeHit::Above {
max: limits.max.as_sats().to_amount_down(&exchange_rate),
},
_ => permanent_failure!("Unreachable code in check_clear_wallet_feasibility()"),
};
Ok(range_hit)
}

/// Prepares a reverse swap that sends all funds in LN channels. This is possible because the
/// route to the swap service is known, so fees can be known in advance.
///
/// The return includes fee estimates and must be provided to [`LightningNode::clear_wallet`] in
/// order to execute the clear operation.
///
/// This can fail if the balance is either too low or too high for it to be reverse-swapped.
/// The method [`LightningNode::is_clear_wallet_feasible`] can be used to check if the balance
/// The method [`LightningNode::check_clear_wallet_feasibility`] can be used to check if the balance
/// is within the required range.
///
/// Requires network: **yes**
pub fn prepare_clear_wallet(&self) -> Result<ClearWalletInfo> {
#[allow(deprecated)]
let amount_sat = self
let claim_tx_feerate = self.query_onchain_fee_rate()?;
let limits = self
.rt
.handle()
.block_on(self.sdk.max_reverse_swap_amount())
.block_on(self.sdk.onchain_payment_limits())
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to get max reverse swap amount",
)?
.total_sat;

let reverse_swap_info = self
"Failed to get on-chain payment limits",
)?;
let prepare_response = self
.rt
.handle()
.block_on(self.sdk.fetch_reverse_swap_fees(ReverseSwapFeesRequest {
send_amount_sat: Some(amount_sat),
claim_tx_feerate: None,
}))
.block_on(
self.sdk
.prepare_onchain_payment(PrepareOnchainPaymentRequest {
amount_sat: limits.max_sat,
amount_type: breez_sdk_core::SwapAmountType::Send,
claim_tx_feerate,
}),
)
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
"Failed to fetch reverse swap fees",
"Failed to prepare on-chain payment",
)?;

let total_fees_sat = reverse_swap_info.total_fees.ok_or_permanent_failure(
"No total reverse swap fee estimation provided when amount was present",
)?;
let onchain_fee_sat = reverse_swap_info.fees_claim + reverse_swap_info.fees_lockup;
let swap_fee_sat =
((amount_sat as f64) * reverse_swap_info.fees_percentage / 100_f64) as u64;
let total_fees_sat = prepare_response.total_fees;
let onchain_fee_sat = prepare_response.fees_claim + prepare_response.fees_lockup;
let swap_fee_sat = total_fees_sat - onchain_fee_sat;
let exchange_rate = self.get_exchange_rate();

Ok(ClearWalletInfo {
clear_amount: amount_sat.as_sats().to_amount_up(&exchange_rate),
clear_amount: prepare_response
.sender_amount_sat
.as_sats()
.to_amount_up(&exchange_rate),
total_estimated_fees: total_fees_sat.as_sats().to_amount_up(&exchange_rate),
onchain_fee: onchain_fee_sat.as_sats().to_amount_up(&exchange_rate),
swap_fee: swap_fee_sat.as_sats().to_amount_up(&exchange_rate),
fees_hash: reverse_swap_info.fees_hash,
prepare_response,
})
}

Expand All @@ -2424,14 +2441,11 @@ impl LightningNode {
clear_wallet_info: ClearWalletInfo,
destination_onchain_address_data: BitcoinAddressData,
) -> Result<()> {
#[allow(deprecated)]
self.rt
.handle()
.block_on(self.sdk.send_onchain(SendOnchainRequest {
amount_sat: clear_wallet_info.clear_amount.sats,
onchain_recipient_address: destination_onchain_address_data.address,
pair_hash: clear_wallet_info.fees_hash,
sat_per_vbyte: self.query_onchain_fee_rate()?,
.block_on(self.sdk.pay_onchain(PayOnchainRequest {
recipient_address: destination_onchain_address_data.address,
prepare_res: clear_wallet_info.prepare_response,
}))
.map_to_runtime_error(
RuntimeErrorCode::NodeUnavailable,
Expand Down
21 changes: 19 additions & 2 deletions src/lipalightninglib.udl
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ interface LightningNode {
BreezHealthCheckStatus get_health_status();

[Throws=LnError]
boolean is_clear_wallet_feasible();
RangeHit check_clear_wallet_feasibility();

[Throws=LnError]
ClearWalletInfo prepare_clear_wallet();
Expand Down Expand Up @@ -207,6 +207,13 @@ enum Level {
"Trace",
};

[Enum]
interface RangeHit {
Below(Amount min);
In();
Above(Amount max);
};

callback interface EventsCallback {
void payment_received(string payment_hash);
void payment_sent(string payment_hash, string payment_preimage);
Expand Down Expand Up @@ -581,12 +588,22 @@ enum InvoiceAffordability {
"Affordable",
};

dictionary PrepareOnchainPaymentResponse {
string fees_hash;
f64 fees_percentage;
u64 fees_lockup;
u64 fees_claim;
u64 sender_amount_sat;
u64 recipient_amount_sat;
u64 total_fees;
};

dictionary ClearWalletInfo {
Amount clear_amount;
Amount total_estimated_fees;
Amount onchain_fee;
Amount swap_fee;
string fees_hash;
PrepareOnchainPaymentResponse prepare_response;
};

dictionary ChannelCloseResolvingFees {
Expand Down

0 comments on commit 0140506

Please sign in to comment.