From 3ced965e9601e5b87bb3babaa63708fe1e24f1a7 Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Tue, 7 May 2024 13:26:32 -0500 Subject: [PATCH 1/3] Allow from federation for sweeps --- mutiny-core/src/lib.rs | 6 +++--- mutiny-wasm/src/lib.rs | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 52a209518..2e57ba978 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1590,16 +1590,16 @@ impl MutinyWallet { pub async fn sweep_federation_balance( &self, amount: Option, + from_federation_id: Option, ) -> Result { - // TODO support more than one federation let federation_ids = self.list_federation_ids().await?; if federation_ids.is_empty() { return Err(MutinyError::NotFound); } - let federation_id = &federation_ids[0]; + let federation_id = from_federation_id.unwrap_or(federation_ids[0]); let federation_lock = self.federations.read().await; let fedimint_client = federation_lock - .get(federation_id) + .get(&federation_id) .ok_or(MutinyError::NotFound)?; let labels = vec![SWAP_LABEL.to_string()]; diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index 635726442..db5d1aa16 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -1059,8 +1059,20 @@ impl MutinyWallet { pub async fn sweep_federation_balance( &self, amount: Option, + from_federation_id: Option, ) -> Result { - Ok(self.inner.sweep_federation_balance(amount).await?.into()) + let from_federation_id = match from_federation_id { + Some(f) => { + Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?) + } + None => None, + }; + + Ok(self + .inner + .sweep_federation_balance(amount, from_federation_id) + .await? + .into()) } /// Estimate the fee before trying to sweep from federation From f5b6ee531c909601e23aa4736fc50e7ac486b009 Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Tue, 7 May 2024 13:54:39 -0500 Subject: [PATCH 2/3] Specify to federation for sweeps --- mutiny-core/src/lib.rs | 73 +++++++++++++++++++++++++++++++----------- mutiny-wasm/src/lib.rs | 10 +++++- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 2e57ba978..26dc0806b 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1591,30 +1591,46 @@ impl MutinyWallet { &self, amount: Option, from_federation_id: Option, + to_federation_id: Option, ) -> Result { let federation_ids = self.list_federation_ids().await?; if federation_ids.is_empty() { return Err(MutinyError::NotFound); } - let federation_id = from_federation_id.unwrap_or(federation_ids[0]); + let from_federation_id = from_federation_id.unwrap_or(federation_ids[0]); let federation_lock = self.federations.read().await; - let fedimint_client = federation_lock - .get(&federation_id) + let from_fedimint_client = federation_lock + .get(&from_federation_id) .ok_or(MutinyError::NotFound)?; + // decide to sweep to secondary federation or lightning node + let to_federation_client = match to_federation_id { + Some(f) => Some(federation_lock.get(&f).ok_or(MutinyError::NotFound)?), + None => None, + }; + let labels = vec![SWAP_LABEL.to_string()]; // if the user provided amount, this is easy if let Some(amt) = amount { - let (inv, fee) = self - .node_manager - .create_invoice(amt, labels.clone()) - .await?; + let (inv, fee) = match to_federation_client { + Some(f) => { + // swap from one federation to another + let inv = f.get_invoice(amt, labels.clone()).await?; + (inv, 0) + } + None => { + // use the lightning node if no to federation selected + self.node_manager + .create_invoice(amt, labels.clone()) + .await? + } + }; let bolt_11 = inv.bolt11.expect("create inv had one job"); self.storage .set_invoice_labels(bolt_11.clone(), labels.clone())?; - let pay_res = fedimint_client.pay_invoice(bolt_11, labels).await?; + let pay_res = from_fedimint_client.pay_invoice(bolt_11, labels).await?; let total_fees_paid = pay_res.fees_paid.unwrap_or(0) + fee; return Ok(FedimintSweepResult { @@ -1624,24 +1640,33 @@ impl MutinyWallet { } // If no amount, figure out the amount to send over - let current_balance = fedimint_client.get_balance().await?; + let current_balance = from_fedimint_client.get_balance().await?; log_debug!( self.logger, "current fedimint client balance: {}", current_balance ); - let fees = fedimint_client.gateway_fee().await?; + let fees = from_fedimint_client.gateway_fee().await?; // FIXME: this is still producing off by one. check round down let amt = max_spendable_amount(current_balance, &fees) .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, labels.clone()) - .await?; + let (inv, fee) = match to_federation_client { + Some(f) => { + // swap from one federation to another + let inv = f.get_invoice(amt, labels.clone()).await?; + (inv, 0) + } + None => { + // use the lightning node if no to federation selected + self.node_manager + .create_invoice(amt, labels.clone()) + .await? + } + }; // check if we can afford that invoice let inv_amt = inv.amount_sats.ok_or(MutinyError::BadAmountError)?; @@ -1654,9 +1679,19 @@ impl MutinyWallet { // if invoice amount changed, create a new invoice let (inv_to_pay, fee) = if first_invoice_amount != inv_amt { - self.node_manager - .create_invoice(first_invoice_amount, labels.clone()) - .await? + match to_federation_client { + Some(f) => { + // swap from one federation to another + let inv = f.get_invoice(amt, labels.clone()).await?; + (inv, 0) + } + None => { + // use the lightning node if no to federation selected + self.node_manager + .create_invoice(amt, labels.clone()) + .await? + } + } } else { (inv.clone(), fee) }; @@ -1665,9 +1700,9 @@ impl MutinyWallet { let bolt_11 = inv_to_pay.bolt11.expect("create inv had one job"); self.storage .set_invoice_labels(bolt_11.clone(), labels.clone())?; - let first_invoice_res = fedimint_client.pay_invoice(bolt_11, labels).await?; + let first_invoice_res = from_fedimint_client.pay_invoice(bolt_11, labels).await?; - let remaining_balance = fedimint_client.get_balance().await?; + let remaining_balance = from_fedimint_client.get_balance().await?; if remaining_balance > 0 { // there was a remainder when there shouldn't have been // for now just log this, it is probably just a millisat/1 sat difference diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index db5d1aa16..d8abe638f 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -1060,6 +1060,7 @@ impl MutinyWallet { &self, amount: Option, from_federation_id: Option, + to_federation_id: Option, ) -> Result { let from_federation_id = match from_federation_id { Some(f) => { @@ -1068,9 +1069,16 @@ impl MutinyWallet { None => None, }; + let to_federation_id = match to_federation_id { + Some(f) => { + Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?) + } + None => None, + }; + Ok(self .inner - .sweep_federation_balance(amount, from_federation_id) + .sweep_federation_balance(amount, from_federation_id, to_federation_id) .await? .into()) } From 4e39b80016fe633e5ed1a1aeda904bebf130739b Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Tue, 7 May 2024 19:30:34 -0500 Subject: [PATCH 3/3] Account for federation sweep estimate for multi --- mutiny-core/src/lib.rs | 35 +++++++++++++++++++++++------------ mutiny-wasm/src/lib.rs | 21 ++++++++++++++++++++- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 26dc0806b..3c1fb54d6 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1724,31 +1724,38 @@ impl MutinyWallet { pub async fn estimate_sweep_federation_fee( &self, amount: Option, + from_federation_id: Option, + to_federation_id: Option, ) -> Result, MutinyError> { if let Some(0) = amount { return Ok(None); } - // TODO support more than one federation let federation_ids = self.list_federation_ids().await?; if federation_ids.is_empty() { return Err(MutinyError::NotFound); } - let federation_id = &federation_ids[0]; + let from_federation_id = from_federation_id.unwrap_or(federation_ids[0]); let federation_lock = self.federations.read().await; let fedimint_client = federation_lock - .get(federation_id) + .get(&from_federation_id) .ok_or(MutinyError::NotFound)?; let fees = fedimint_client.gateway_fee().await?; let (lsp_fee, federation_fee) = { if let Some(amt) = amount { // if the user provided amount, this is easy - ( - self.node_manager.get_lsp_fee(amt).await?, - (calc_routing_fee_msat(amt as f64 * 1_000.0, &fees) / 1_000.0).floor() as u64, - ) + let incoming_fee = if to_federation_id.is_some() { + 0 + } else { + self.node_manager.get_lsp_fee(amt).await? + }; + + let outgoing_fee = + (calc_routing_fee_msat(amt as f64 * 1_000.0, &fees) / 1_000.0).floor() as u64; + + (incoming_fee, outgoing_fee) } else { // If no amount, figure out the amount to send over let current_balance = fedimint_client.get_balance().await?; @@ -1762,11 +1769,15 @@ impl MutinyWallet { .map_or(Err(MutinyError::InsufficientBalance), Ok)?; log_debug!(self.logger, "max spendable: {}", amt); - // try to get an invoice for this exact amount - ( - self.node_manager.get_lsp_fee(amt).await?, - current_balance - amt, - ) + let incoming_fee = if to_federation_id.is_some() { + 0 + } else { + self.node_manager.get_lsp_fee(amt).await? + }; + + let outgoing_fee = current_balance - amt; + + (incoming_fee, outgoing_fee) } }; diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index d8abe638f..b0033a227 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -1087,8 +1087,27 @@ impl MutinyWallet { pub async fn estimate_sweep_federation_fee( &self, amount: Option, + from_federation_id: Option, + to_federation_id: Option, ) -> Result, MutinyJsError> { - Ok(self.inner.estimate_sweep_federation_fee(amount).await?) + let from_federation_id = match from_federation_id { + Some(f) => { + Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?) + } + None => None, + }; + + let to_federation_id = match to_federation_id { + Some(f) => { + Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?) + } + None => None, + }; + + Ok(self + .inner + .estimate_sweep_federation_fee(amount, from_federation_id, to_federation_id) + .await?) } /// Closes a channel with the given outpoint.