From 42db752f14a415735e50b8e78a27048ce05fa708 Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Thu, 1 Feb 2024 11:11:37 -0600 Subject: [PATCH] Get estimated fees for channel opens from fedimint --- mutiny-core/src/lib.rs | 71 +++++++++++++++++++++++++++------- mutiny-core/src/node.rs | 30 +++++++------- mutiny-core/src/nodemanager.rs | 4 +- 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 97afa58f4..af47c0de5 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1203,7 +1203,7 @@ impl MutinyWallet { // Attempt to create federation invoice if available and below max amount let federation_ids = self.list_federation_ids().await?; if federation_ids.is_empty() { - return Err(MutinyError::BadAmountError); + return Err(MutinyError::NotFound); } // TODO support more than one federation @@ -1215,13 +1215,15 @@ impl MutinyWallet { // if the user provided amount, this is easy if let Some(amt) = amount { - let inv = self.node_manager.create_invoice(amt).await?; + let (inv, fee) = self.node_manager.create_invoice(amt).await?; let pay_res = fedimint_client .pay_invoice(inv.bolt11.expect("create inv had one job"), vec![]) .await?; + let total_fees_paid = pay_res.fees_paid.unwrap_or(0) + fee; + return Ok(FedimintSweepResult { amount: amt, - fees: pay_res.fees_paid, + fees: Some(total_fees_paid), }); } @@ -1239,7 +1241,7 @@ impl MutinyWallet { log_debug!(self.logger, "max spendable: {}", amt); // try to get an invoice for this exact amount - let inv = self.node_manager.create_invoice(amt).await?; + let (inv, fee) = self.node_manager.create_invoice(amt).await?; // check if we can afford that invoice let inv_amt = inv.amount_sats.ok_or(MutinyError::BadAmountError)?; @@ -1251,23 +1253,18 @@ impl MutinyWallet { }; // if invoice amount changed, create a new invoice - let inv_to_pay = if first_invoice_amount != inv_amt { + let (inv_to_pay, fee) = if first_invoice_amount != inv_amt { self.node_manager .create_invoice(first_invoice_amount) .await? } else { - inv.clone() + (inv.clone(), fee) }; log_debug!(self.logger, "attempting payment from fedimint client"); - let mut final_result = FedimintSweepResult { - amount: first_invoice_amount, - fees: None, - }; let first_invoice_res = fedimint_client .pay_invoice(inv_to_pay.bolt11.expect("create inv had one job"), vec![]) .await?; - final_result.fees = first_invoice_res.fees_paid; let remaining_balance = fedimint_client.get_balance().await?; if remaining_balance > 0 { @@ -1280,7 +1277,55 @@ impl MutinyWallet { ); } - Ok(final_result) + Ok(FedimintSweepResult { + amount: first_invoice_amount, + fees: Some(first_invoice_res.fees_paid.unwrap_or(0) + fee), + }) + } + + pub async fn estimate_sweep_federation_fee( + &self, + amount: Option, + ) -> Result, MutinyError> { + if let Some(0) = amount { + return Ok(None); + } + + // Attempt to create federation invoice if available and below max amount + let federation_ids = self.list_federation_ids().await?; + if federation_ids.is_empty() { + return Err(MutinyError::NotFound); + } + + // TODO support more than one federation + let federation_id = &federation_ids[0]; + let federation_lock = self.federations.read().await; + let fedimint_client = federation_lock + .get(federation_id) + .ok_or(MutinyError::NotFound)?; + + // if the user provided amount, this is easy + if let Some(amt) = amount { + let (_inv, fee) = self.node_manager.create_invoice(amt).await?; + return Ok(Some(fee)); + } + + // If no amount, figure out the amount to send over + let current_balance = fedimint_client.get_balance().await?; + log_debug!( + self.logger, + "current fedimint client balance: {}", + current_balance + ); + + let fees = fedimint_client.gateway_fee().await?; + let amt = max_spendable_amount(current_balance, fees.clone()) + .map_or(Err(MutinyError::InsufficientBalance), Ok)?; + log_debug!(self.logger, "max spendable: {}", amt); + + // try to get an invoice for this exact amount + let (_inv, fee) = self.node_manager.create_invoice(amt).await?; + Ok(Some(fee)) } async fn create_lightning_invoice( @@ -1304,7 +1349,7 @@ impl MutinyWallet { } // Fallback to node_manager invoice creation if no federation invoice created - let inv = self.node_manager.create_invoice(amount).await?; + let (inv, _fee) = self.node_manager.create_invoice(amount).await?; self.storage .set_invoice_labels(inv.bolt11.clone().expect("just created"), labels)?; Ok(inv) diff --git a/mutiny-core/src/node.rs b/mutiny-core/src/node.rs index f8c2d2818..278b04b1d 100644 --- a/mutiny-core/src/node.rs +++ b/mutiny-core/src/node.rs @@ -973,7 +973,7 @@ impl Node { &self, amount_sat: u64, route_hints: Option>, - ) -> Result { + ) -> Result<(Bolt11Invoice, u64), MutinyError> { match self.lsp_client.as_ref() { Some(lsp) => { self.connect_peer( @@ -1072,13 +1072,15 @@ impl Node { log_debug!(self.logger, "Got wrapped invoice from LSP: {lsp_invoice}"); - Ok(lsp_invoice) + Ok((lsp_invoice, lsp_fee_sat)) } AnyLsp::Lsps(client) => { if has_inbound_capacity { - Ok(self - .create_internal_invoice(Some(amount_sat), None, route_hints) - .await?) + Ok(( + self.create_internal_invoice(Some(amount_sat), None, route_hints) + .await?, + 0, + )) } else { let lsp_invoice = match client .get_lsp_invoice(InvoiceRequest { @@ -1103,14 +1105,16 @@ impl Node { return Err(e); } }; - Ok(lsp_invoice) + Ok((lsp_invoice, 0)) } } } } - None => Ok(self - .create_internal_invoice(Some(amount_sat), None, route_hints) - .await?), + None => Ok(( + self.create_internal_invoice(Some(amount_sat), None, route_hints) + .await?, + 0, + )), } } @@ -2417,7 +2421,7 @@ mod tests { let amount_sats = 1_000; - let invoice = node.create_invoice(amount_sats, None).await.unwrap(); + let invoice = node.create_invoice(amount_sats, None).await.unwrap().0; assert_eq!(invoice.amount_milli_satoshis(), Some(amount_sats * 1000)); match invoice.description() { @@ -2448,7 +2452,7 @@ mod tests { let storage = MemoryStorage::default(); let node = create_node(storage).await; - let invoice = node.create_invoice(10_000, None).await.unwrap(); + let invoice = node.create_invoice(10_000, None).await.unwrap().0; let result = node .pay_invoice_with_timeout(&invoice, None, None, vec![]) @@ -2571,7 +2575,7 @@ mod wasm_test { let amount_sats = 1_000; - let invoice = node.create_invoice(amount_sats, None).await.unwrap(); + let invoice = node.create_invoice(amount_sats, None).await.unwrap().0; assert_eq!(invoice.amount_milli_satoshis(), Some(amount_sats * 1000)); match invoice.description() { @@ -2602,7 +2606,7 @@ mod wasm_test { let storage = MemoryStorage::default(); let node = create_node(storage).await; - let invoice = node.create_invoice(10_000, None).await.unwrap(); + let invoice = node.create_invoice(10_000, None).await.unwrap().0; let result = node .pay_invoice_with_timeout(&invoice, None, None, vec![]) diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index 7dd0411cf..bcdcca316 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -1369,7 +1369,7 @@ impl NodeManager { /// /// If the manager has more than one node it will create a phantom invoice. /// If there is only one node it will create an invoice just for that node. - pub async fn create_invoice(&self, amount: u64) -> Result { + pub async fn create_invoice(&self, amount: u64) -> Result<(MutinyInvoice, u64), MutinyError> { let nodes = self.nodes.lock().await; let use_phantom = nodes.len() > 1 && self.lsp_config.is_none(); if nodes.len() == 0 { @@ -1394,7 +1394,7 @@ impl NodeManager { }; let invoice = first_node.create_invoice(amount, route_hints).await?; - Ok(invoice.into()) + Ok((invoice.0.into(), invoice.1)) } /// Pays a lightning invoice from either a specified node or the first available node.