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

Commit

Permalink
Get estimated fees for channel opens from fedimint
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyRonning committed Feb 1, 2024
1 parent 622bbf3 commit 7f2e6f6
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 31 deletions.
74 changes: 58 additions & 16 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,13 +1200,11 @@ impl<S: MutinyStorage> MutinyWallet<S> {
&self,
amount: Option<u64>,
) -> Result<FedimintSweepResult, MutinyError> {
// Attempt to create federation invoice if available and below max amount
// TODO support more than one federation
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
let federation_id = &federation_ids[0];
let federation_lock = self.federations.read().await;
let fedimint_client = federation_lock
Expand All @@ -1215,13 +1213,15 @@ impl<S: MutinyStorage> MutinyWallet<S> {

// 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),
});
}

Expand All @@ -1239,7 +1239,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
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)?;
Expand All @@ -1251,23 +1251,18 @@ impl<S: MutinyStorage> MutinyWallet<S> {
};

// 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 {
Expand All @@ -1280,7 +1275,54 @@ impl<S: MutinyStorage> MutinyWallet<S> {
);
}

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<u64>,
) -> Result<Option<u64>, 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 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(
Expand All @@ -1304,7 +1346,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
}

// 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)
Expand Down
30 changes: 17 additions & 13 deletions mutiny-core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ impl<S: MutinyStorage> Node<S> {
&self,
amount_sat: u64,
route_hints: Option<Vec<PhantomRouteHints>>,
) -> Result<Bolt11Invoice, MutinyError> {
) -> Result<(Bolt11Invoice, u64), MutinyError> {
match self.lsp_client.as_ref() {
Some(lsp) => {
self.connect_peer(
Expand Down Expand Up @@ -1072,13 +1072,15 @@ impl<S: MutinyStorage> Node<S> {

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 {
Expand All @@ -1103,14 +1105,16 @@ impl<S: MutinyStorage> Node<S> {
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,
)),
}
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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![])
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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![])
Expand Down
4 changes: 2 additions & 2 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,7 @@ impl<S: MutinyStorage> NodeManager<S> {
///
/// 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<MutinyInvoice, MutinyError> {
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 {
Expand All @@ -1394,7 +1394,7 @@ impl<S: MutinyStorage> NodeManager<S> {
};
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.
Expand Down
8 changes: 8 additions & 0 deletions mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,14 @@ impl MutinyWallet {
Ok(self.inner.sweep_federation_balance(amount).await?.into())
}

/// Estimate the fee before trying to sweep from federation
pub async fn estimate_sweep_federation_fee(
&self,
amount: Option<u64>,
) -> Result<Option<u64>, MutinyJsError> {
Ok(self.inner.estimate_sweep_federation_fee(amount).await?)
}

/// Closes a channel with the given outpoint.
///
/// If force is true, the channel will be force closed.
Expand Down

0 comments on commit 7f2e6f6

Please sign in to comment.