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

Commit 7f2e6f6

Browse files
Get estimated fees for channel opens from fedimint
1 parent 622bbf3 commit 7f2e6f6

File tree

4 files changed

+85
-31
lines changed

4 files changed

+85
-31
lines changed

mutiny-core/src/lib.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,13 +1200,11 @@ impl<S: MutinyStorage> MutinyWallet<S> {
12001200
&self,
12011201
amount: Option<u64>,
12021202
) -> Result<FedimintSweepResult, MutinyError> {
1203-
// Attempt to create federation invoice if available and below max amount
1203+
// TODO support more than one federation
12041204
let federation_ids = self.list_federation_ids().await?;
12051205
if federation_ids.is_empty() {
1206-
return Err(MutinyError::BadAmountError);
1206+
return Err(MutinyError::NotFound);
12071207
}
1208-
1209-
// TODO support more than one federation
12101208
let federation_id = &federation_ids[0];
12111209
let federation_lock = self.federations.read().await;
12121210
let fedimint_client = federation_lock
@@ -1215,13 +1213,15 @@ impl<S: MutinyStorage> MutinyWallet<S> {
12151213

12161214
// if the user provided amount, this is easy
12171215
if let Some(amt) = amount {
1218-
let inv = self.node_manager.create_invoice(amt).await?;
1216+
let (inv, fee) = self.node_manager.create_invoice(amt).await?;
12191217
let pay_res = fedimint_client
12201218
.pay_invoice(inv.bolt11.expect("create inv had one job"), vec![])
12211219
.await?;
1220+
let total_fees_paid = pay_res.fees_paid.unwrap_or(0) + fee;
1221+
12221222
return Ok(FedimintSweepResult {
12231223
amount: amt,
1224-
fees: pay_res.fees_paid,
1224+
fees: Some(total_fees_paid),
12251225
});
12261226
}
12271227

@@ -1239,7 +1239,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
12391239
log_debug!(self.logger, "max spendable: {}", amt);
12401240

12411241
// try to get an invoice for this exact amount
1242-
let inv = self.node_manager.create_invoice(amt).await?;
1242+
let (inv, fee) = self.node_manager.create_invoice(amt).await?;
12431243

12441244
// check if we can afford that invoice
12451245
let inv_amt = inv.amount_sats.ok_or(MutinyError::BadAmountError)?;
@@ -1251,23 +1251,18 @@ impl<S: MutinyStorage> MutinyWallet<S> {
12511251
};
12521252

12531253
// if invoice amount changed, create a new invoice
1254-
let inv_to_pay = if first_invoice_amount != inv_amt {
1254+
let (inv_to_pay, fee) = if first_invoice_amount != inv_amt {
12551255
self.node_manager
12561256
.create_invoice(first_invoice_amount)
12571257
.await?
12581258
} else {
1259-
inv.clone()
1259+
(inv.clone(), fee)
12601260
};
12611261

12621262
log_debug!(self.logger, "attempting payment from fedimint client");
1263-
let mut final_result = FedimintSweepResult {
1264-
amount: first_invoice_amount,
1265-
fees: None,
1266-
};
12671263
let first_invoice_res = fedimint_client
12681264
.pay_invoice(inv_to_pay.bolt11.expect("create inv had one job"), vec![])
12691265
.await?;
1270-
final_result.fees = first_invoice_res.fees_paid;
12711266

12721267
let remaining_balance = fedimint_client.get_balance().await?;
12731268
if remaining_balance > 0 {
@@ -1280,7 +1275,54 @@ impl<S: MutinyStorage> MutinyWallet<S> {
12801275
);
12811276
}
12821277

1283-
Ok(final_result)
1278+
Ok(FedimintSweepResult {
1279+
amount: first_invoice_amount,
1280+
fees: Some(first_invoice_res.fees_paid.unwrap_or(0) + fee),
1281+
})
1282+
}
1283+
1284+
pub async fn estimate_sweep_federation_fee(
1285+
&self,
1286+
amount: Option<u64>,
1287+
) -> Result<Option<u64>, MutinyError> {
1288+
if let Some(0) = amount {
1289+
return Ok(None);
1290+
}
1291+
1292+
// TODO support more than one federation
1293+
let federation_ids = self.list_federation_ids().await?;
1294+
if federation_ids.is_empty() {
1295+
return Err(MutinyError::NotFound);
1296+
}
1297+
1298+
let federation_id = &federation_ids[0];
1299+
let federation_lock = self.federations.read().await;
1300+
let fedimint_client = federation_lock
1301+
.get(federation_id)
1302+
.ok_or(MutinyError::NotFound)?;
1303+
1304+
// if the user provided amount, this is easy
1305+
if let Some(amt) = amount {
1306+
let (_inv, fee) = self.node_manager.create_invoice(amt).await?;
1307+
return Ok(Some(fee));
1308+
}
1309+
1310+
// If no amount, figure out the amount to send over
1311+
let current_balance = fedimint_client.get_balance().await?;
1312+
log_debug!(
1313+
self.logger,
1314+
"current fedimint client balance: {}",
1315+
current_balance
1316+
);
1317+
1318+
let fees = fedimint_client.gateway_fee().await?;
1319+
let amt = max_spendable_amount(current_balance, fees.clone())
1320+
.map_or(Err(MutinyError::InsufficientBalance), Ok)?;
1321+
log_debug!(self.logger, "max spendable: {}", amt);
1322+
1323+
// try to get an invoice for this exact amount
1324+
let (_inv, fee) = self.node_manager.create_invoice(amt).await?;
1325+
Ok(Some(fee))
12841326
}
12851327

12861328
async fn create_lightning_invoice(
@@ -1304,7 +1346,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
13041346
}
13051347

13061348
// Fallback to node_manager invoice creation if no federation invoice created
1307-
let inv = self.node_manager.create_invoice(amount).await?;
1349+
let (inv, _fee) = self.node_manager.create_invoice(amount).await?;
13081350
self.storage
13091351
.set_invoice_labels(inv.bolt11.clone().expect("just created"), labels)?;
13101352
Ok(inv)

mutiny-core/src/node.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ impl<S: MutinyStorage> Node<S> {
973973
&self,
974974
amount_sat: u64,
975975
route_hints: Option<Vec<PhantomRouteHints>>,
976-
) -> Result<Bolt11Invoice, MutinyError> {
976+
) -> Result<(Bolt11Invoice, u64), MutinyError> {
977977
match self.lsp_client.as_ref() {
978978
Some(lsp) => {
979979
self.connect_peer(
@@ -1072,13 +1072,15 @@ impl<S: MutinyStorage> Node<S> {
10721072

10731073
log_debug!(self.logger, "Got wrapped invoice from LSP: {lsp_invoice}");
10741074

1075-
Ok(lsp_invoice)
1075+
Ok((lsp_invoice, lsp_fee_sat))
10761076
}
10771077
AnyLsp::Lsps(client) => {
10781078
if has_inbound_capacity {
1079-
Ok(self
1080-
.create_internal_invoice(Some(amount_sat), None, route_hints)
1081-
.await?)
1079+
Ok((
1080+
self.create_internal_invoice(Some(amount_sat), None, route_hints)
1081+
.await?,
1082+
0,
1083+
))
10821084
} else {
10831085
let lsp_invoice = match client
10841086
.get_lsp_invoice(InvoiceRequest {
@@ -1103,14 +1105,16 @@ impl<S: MutinyStorage> Node<S> {
11031105
return Err(e);
11041106
}
11051107
};
1106-
Ok(lsp_invoice)
1108+
Ok((lsp_invoice, 0))
11071109
}
11081110
}
11091111
}
11101112
}
1111-
None => Ok(self
1112-
.create_internal_invoice(Some(amount_sat), None, route_hints)
1113-
.await?),
1113+
None => Ok((
1114+
self.create_internal_invoice(Some(amount_sat), None, route_hints)
1115+
.await?,
1116+
0,
1117+
)),
11141118
}
11151119
}
11161120

@@ -2417,7 +2421,7 @@ mod tests {
24172421

24182422
let amount_sats = 1_000;
24192423

2420-
let invoice = node.create_invoice(amount_sats, None).await.unwrap();
2424+
let invoice = node.create_invoice(amount_sats, None).await.unwrap().0;
24212425

24222426
assert_eq!(invoice.amount_milli_satoshis(), Some(amount_sats * 1000));
24232427
match invoice.description() {
@@ -2448,7 +2452,7 @@ mod tests {
24482452
let storage = MemoryStorage::default();
24492453
let node = create_node(storage).await;
24502454

2451-
let invoice = node.create_invoice(10_000, None).await.unwrap();
2455+
let invoice = node.create_invoice(10_000, None).await.unwrap().0;
24522456

24532457
let result = node
24542458
.pay_invoice_with_timeout(&invoice, None, None, vec![])
@@ -2571,7 +2575,7 @@ mod wasm_test {
25712575

25722576
let amount_sats = 1_000;
25732577

2574-
let invoice = node.create_invoice(amount_sats, None).await.unwrap();
2578+
let invoice = node.create_invoice(amount_sats, None).await.unwrap().0;
25752579

25762580
assert_eq!(invoice.amount_milli_satoshis(), Some(amount_sats * 1000));
25772581
match invoice.description() {
@@ -2602,7 +2606,7 @@ mod wasm_test {
26022606
let storage = MemoryStorage::default();
26032607
let node = create_node(storage).await;
26042608

2605-
let invoice = node.create_invoice(10_000, None).await.unwrap();
2609+
let invoice = node.create_invoice(10_000, None).await.unwrap().0;
26062610

26072611
let result = node
26082612
.pay_invoice_with_timeout(&invoice, None, None, vec![])

mutiny-core/src/nodemanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,7 @@ impl<S: MutinyStorage> NodeManager<S> {
13691369
///
13701370
/// If the manager has more than one node it will create a phantom invoice.
13711371
/// If there is only one node it will create an invoice just for that node.
1372-
pub async fn create_invoice(&self, amount: u64) -> Result<MutinyInvoice, MutinyError> {
1372+
pub async fn create_invoice(&self, amount: u64) -> Result<(MutinyInvoice, u64), MutinyError> {
13731373
let nodes = self.nodes.lock().await;
13741374
let use_phantom = nodes.len() > 1 && self.lsp_config.is_none();
13751375
if nodes.len() == 0 {
@@ -1394,7 +1394,7 @@ impl<S: MutinyStorage> NodeManager<S> {
13941394
};
13951395
let invoice = first_node.create_invoice(amount, route_hints).await?;
13961396

1397-
Ok(invoice.into())
1397+
Ok((invoice.0.into(), invoice.1))
13981398
}
13991399

14001400
/// Pays a lightning invoice from either a specified node or the first available node.

mutiny-wasm/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,14 @@ impl MutinyWallet {
10041004
Ok(self.inner.sweep_federation_balance(amount).await?.into())
10051005
}
10061006

1007+
/// Estimate the fee before trying to sweep from federation
1008+
pub async fn estimate_sweep_federation_fee(
1009+
&self,
1010+
amount: Option<u64>,
1011+
) -> Result<Option<u64>, MutinyJsError> {
1012+
Ok(self.inner.estimate_sweep_federation_fee(amount).await?)
1013+
}
1014+
10071015
/// Closes a channel with the given outpoint.
10081016
///
10091017
/// If force is true, the channel will be force closed.

0 commit comments

Comments
 (0)