diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 1a4b33bb8..102af45a0 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1596,31 +1596,47 @@ impl MutinyWallet { pub async fn sweep_federation_balance( &self, amount: Option, + from_federation_id: Option, + to_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 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 { @@ -1630,24 +1646,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)?; @@ -1660,9 +1685,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) }; @@ -1671,9 +1706,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 @@ -1695,31 +1730,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?; @@ -1733,11 +1775,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 635726442..b0033a227 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -1059,16 +1059,55 @@ impl MutinyWallet { pub async fn sweep_federation_balance( &self, amount: Option, + from_federation_id: Option, + to_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, + }; + + 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, to_federation_id) + .await? + .into()) } /// Estimate the fee before trying to sweep from federation 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.