Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit 5a6cf57

Browse files
Merge pull request #1170 from MutinyWallet/sweep-federation
Allow swapping between federations
2 parents cb64198 + 4e39b80 commit 5a6cf57

File tree

2 files changed

+119
-34
lines changed

2 files changed

+119
-34
lines changed

mutiny-core/src/lib.rs

+78-32
Original file line numberDiff line numberDiff line change
@@ -1596,31 +1596,47 @@ impl<S: MutinyStorage> MutinyWallet<S> {
15961596
pub async fn sweep_federation_balance(
15971597
&self,
15981598
amount: Option<u64>,
1599+
from_federation_id: Option<FederationId>,
1600+
to_federation_id: Option<FederationId>,
15991601
) -> Result<FedimintSweepResult, MutinyError> {
1600-
// TODO support more than one federation
16011602
let federation_ids = self.list_federation_ids().await?;
16021603
if federation_ids.is_empty() {
16031604
return Err(MutinyError::NotFound);
16041605
}
1605-
let federation_id = &federation_ids[0];
1606+
let from_federation_id = from_federation_id.unwrap_or(federation_ids[0]);
16061607
let federation_lock = self.federations.read().await;
1607-
let fedimint_client = federation_lock
1608-
.get(federation_id)
1608+
let from_fedimint_client = federation_lock
1609+
.get(&from_federation_id)
16091610
.ok_or(MutinyError::NotFound)?;
16101611

1612+
// decide to sweep to secondary federation or lightning node
1613+
let to_federation_client = match to_federation_id {
1614+
Some(f) => Some(federation_lock.get(&f).ok_or(MutinyError::NotFound)?),
1615+
None => None,
1616+
};
1617+
16111618
let labels = vec![SWAP_LABEL.to_string()];
16121619

16131620
// if the user provided amount, this is easy
16141621
if let Some(amt) = amount {
1615-
let (inv, fee) = self
1616-
.node_manager
1617-
.create_invoice(amt, labels.clone())
1618-
.await?;
1622+
let (inv, fee) = match to_federation_client {
1623+
Some(f) => {
1624+
// swap from one federation to another
1625+
let inv = f.get_invoice(amt, labels.clone()).await?;
1626+
(inv, 0)
1627+
}
1628+
None => {
1629+
// use the lightning node if no to federation selected
1630+
self.node_manager
1631+
.create_invoice(amt, labels.clone())
1632+
.await?
1633+
}
1634+
};
16191635

16201636
let bolt_11 = inv.bolt11.expect("create inv had one job");
16211637
self.storage
16221638
.set_invoice_labels(bolt_11.clone(), labels.clone())?;
1623-
let pay_res = fedimint_client.pay_invoice(bolt_11, labels).await?;
1639+
let pay_res = from_fedimint_client.pay_invoice(bolt_11, labels).await?;
16241640
let total_fees_paid = pay_res.fees_paid.unwrap_or(0) + fee;
16251641

16261642
return Ok(FedimintSweepResult {
@@ -1630,24 +1646,33 @@ impl<S: MutinyStorage> MutinyWallet<S> {
16301646
}
16311647

16321648
// If no amount, figure out the amount to send over
1633-
let current_balance = fedimint_client.get_balance().await?;
1649+
let current_balance = from_fedimint_client.get_balance().await?;
16341650
log_debug!(
16351651
self.logger,
16361652
"current fedimint client balance: {}",
16371653
current_balance
16381654
);
16391655

1640-
let fees = fedimint_client.gateway_fee().await?;
1656+
let fees = from_fedimint_client.gateway_fee().await?;
16411657
// FIXME: this is still producing off by one. check round down
16421658
let amt = max_spendable_amount(current_balance, &fees)
16431659
.map_or(Err(MutinyError::InsufficientBalance), Ok)?;
16441660
log_debug!(self.logger, "max spendable: {}", amt);
16451661

16461662
// try to get an invoice for this exact amount
1647-
let (inv, fee) = self
1648-
.node_manager
1649-
.create_invoice(amt, labels.clone())
1650-
.await?;
1663+
let (inv, fee) = match to_federation_client {
1664+
Some(f) => {
1665+
// swap from one federation to another
1666+
let inv = f.get_invoice(amt, labels.clone()).await?;
1667+
(inv, 0)
1668+
}
1669+
None => {
1670+
// use the lightning node if no to federation selected
1671+
self.node_manager
1672+
.create_invoice(amt, labels.clone())
1673+
.await?
1674+
}
1675+
};
16511676

16521677
// check if we can afford that invoice
16531678
let inv_amt = inv.amount_sats.ok_or(MutinyError::BadAmountError)?;
@@ -1660,9 +1685,19 @@ impl<S: MutinyStorage> MutinyWallet<S> {
16601685

16611686
// if invoice amount changed, create a new invoice
16621687
let (inv_to_pay, fee) = if first_invoice_amount != inv_amt {
1663-
self.node_manager
1664-
.create_invoice(first_invoice_amount, labels.clone())
1665-
.await?
1688+
match to_federation_client {
1689+
Some(f) => {
1690+
// swap from one federation to another
1691+
let inv = f.get_invoice(amt, labels.clone()).await?;
1692+
(inv, 0)
1693+
}
1694+
None => {
1695+
// use the lightning node if no to federation selected
1696+
self.node_manager
1697+
.create_invoice(amt, labels.clone())
1698+
.await?
1699+
}
1700+
}
16661701
} else {
16671702
(inv.clone(), fee)
16681703
};
@@ -1671,9 +1706,9 @@ impl<S: MutinyStorage> MutinyWallet<S> {
16711706
let bolt_11 = inv_to_pay.bolt11.expect("create inv had one job");
16721707
self.storage
16731708
.set_invoice_labels(bolt_11.clone(), labels.clone())?;
1674-
let first_invoice_res = fedimint_client.pay_invoice(bolt_11, labels).await?;
1709+
let first_invoice_res = from_fedimint_client.pay_invoice(bolt_11, labels).await?;
16751710

1676-
let remaining_balance = fedimint_client.get_balance().await?;
1711+
let remaining_balance = from_fedimint_client.get_balance().await?;
16771712
if remaining_balance > 0 {
16781713
// there was a remainder when there shouldn't have been
16791714
// for now just log this, it is probably just a millisat/1 sat difference
@@ -1695,31 +1730,38 @@ impl<S: MutinyStorage> MutinyWallet<S> {
16951730
pub async fn estimate_sweep_federation_fee(
16961731
&self,
16971732
amount: Option<u64>,
1733+
from_federation_id: Option<FederationId>,
1734+
to_federation_id: Option<FederationId>,
16981735
) -> Result<Option<u64>, MutinyError> {
16991736
if let Some(0) = amount {
17001737
return Ok(None);
17011738
}
17021739

1703-
// TODO support more than one federation
17041740
let federation_ids = self.list_federation_ids().await?;
17051741
if federation_ids.is_empty() {
17061742
return Err(MutinyError::NotFound);
17071743
}
17081744

1709-
let federation_id = &federation_ids[0];
1745+
let from_federation_id = from_federation_id.unwrap_or(federation_ids[0]);
17101746
let federation_lock = self.federations.read().await;
17111747
let fedimint_client = federation_lock
1712-
.get(federation_id)
1748+
.get(&from_federation_id)
17131749
.ok_or(MutinyError::NotFound)?;
17141750
let fees = fedimint_client.gateway_fee().await?;
17151751

17161752
let (lsp_fee, federation_fee) = {
17171753
if let Some(amt) = amount {
17181754
// if the user provided amount, this is easy
1719-
(
1720-
self.node_manager.get_lsp_fee(amt).await?,
1721-
(calc_routing_fee_msat(amt as f64 * 1_000.0, &fees) / 1_000.0).floor() as u64,
1722-
)
1755+
let incoming_fee = if to_federation_id.is_some() {
1756+
0
1757+
} else {
1758+
self.node_manager.get_lsp_fee(amt).await?
1759+
};
1760+
1761+
let outgoing_fee =
1762+
(calc_routing_fee_msat(amt as f64 * 1_000.0, &fees) / 1_000.0).floor() as u64;
1763+
1764+
(incoming_fee, outgoing_fee)
17231765
} else {
17241766
// If no amount, figure out the amount to send over
17251767
let current_balance = fedimint_client.get_balance().await?;
@@ -1733,11 +1775,15 @@ impl<S: MutinyStorage> MutinyWallet<S> {
17331775
.map_or(Err(MutinyError::InsufficientBalance), Ok)?;
17341776
log_debug!(self.logger, "max spendable: {}", amt);
17351777

1736-
// try to get an invoice for this exact amount
1737-
(
1738-
self.node_manager.get_lsp_fee(amt).await?,
1739-
current_balance - amt,
1740-
)
1778+
let incoming_fee = if to_federation_id.is_some() {
1779+
0
1780+
} else {
1781+
self.node_manager.get_lsp_fee(amt).await?
1782+
};
1783+
1784+
let outgoing_fee = current_balance - amt;
1785+
1786+
(incoming_fee, outgoing_fee)
17411787
}
17421788
};
17431789

mutiny-wasm/src/lib.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -1059,16 +1059,55 @@ impl MutinyWallet {
10591059
pub async fn sweep_federation_balance(
10601060
&self,
10611061
amount: Option<u64>,
1062+
from_federation_id: Option<String>,
1063+
to_federation_id: Option<String>,
10621064
) -> Result<FedimintSweepResult, MutinyJsError> {
1063-
Ok(self.inner.sweep_federation_balance(amount).await?.into())
1065+
let from_federation_id = match from_federation_id {
1066+
Some(f) => {
1067+
Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?)
1068+
}
1069+
None => None,
1070+
};
1071+
1072+
let to_federation_id = match to_federation_id {
1073+
Some(f) => {
1074+
Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?)
1075+
}
1076+
None => None,
1077+
};
1078+
1079+
Ok(self
1080+
.inner
1081+
.sweep_federation_balance(amount, from_federation_id, to_federation_id)
1082+
.await?
1083+
.into())
10641084
}
10651085

10661086
/// Estimate the fee before trying to sweep from federation
10671087
pub async fn estimate_sweep_federation_fee(
10681088
&self,
10691089
amount: Option<u64>,
1090+
from_federation_id: Option<String>,
1091+
to_federation_id: Option<String>,
10701092
) -> Result<Option<u64>, MutinyJsError> {
1071-
Ok(self.inner.estimate_sweep_federation_fee(amount).await?)
1093+
let from_federation_id = match from_federation_id {
1094+
Some(f) => {
1095+
Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?)
1096+
}
1097+
None => None,
1098+
};
1099+
1100+
let to_federation_id = match to_federation_id {
1101+
Some(f) => {
1102+
Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?)
1103+
}
1104+
None => None,
1105+
};
1106+
1107+
Ok(self
1108+
.inner
1109+
.estimate_sweep_federation_fee(amount, from_federation_id, to_federation_id)
1110+
.await?)
10721111
}
10731112

10741113
/// Closes a channel with the given outpoint.

0 commit comments

Comments
 (0)