diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index 40700ef93..8840b5d0a 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -1204,18 +1204,16 @@ impl MutinyWallet { .get(federation_id) .ok_or(MutinyError::NotFound)?; + 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).await?; - self.storage.set_invoice_labels( - inv.bolt11.clone().expect("just created"), - vec![SWAP_LABEL.to_string()], - )?; + let (inv, fee) = self + .node_manager + .create_invoice(amt, labels.clone()) + .await?; let pay_res = fedimint_client - .pay_invoice( - inv.bolt11.expect("create inv had one job"), - vec![SWAP_LABEL.to_string()], - ) + .pay_invoice(inv.bolt11.expect("create inv had one job"), labels.clone()) .await?; let total_fees_paid = pay_res.fees_paid.unwrap_or(0) + fee; @@ -1239,7 +1237,10 @@ impl MutinyWallet { 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?; + let (inv, fee) = 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)?; @@ -1253,22 +1254,15 @@ 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) + .create_invoice(first_invoice_amount, labels.clone()) .await? } else { (inv.clone(), fee) }; - self.storage.set_invoice_labels( - inv_to_pay.bolt11.clone().expect("just created"), - vec![SWAP_LABEL.to_string()], - )?; log_debug!(self.logger, "attempting payment from fedimint client"); let first_invoice_res = fedimint_client - .pay_invoice( - inv_to_pay.bolt11.expect("create inv had one job"), - vec![SWAP_LABEL.to_string()], - ) + .pay_invoice(inv_to_pay.bolt11.expect("create inv had one job"), labels) .await?; let remaining_balance = fedimint_client.get_balance().await?; @@ -1362,9 +1356,8 @@ impl MutinyWallet { } // Fallback to node_manager invoice creation if no federation invoice created - let (inv, _fee) = self.node_manager.create_invoice(amount).await?; - self.storage - .set_invoice_labels(inv.bolt11.clone().expect("just created"), labels)?; + let (inv, _fee) = self.node_manager.create_invoice(amount, labels).await?; + Ok(inv) } diff --git a/mutiny-core/src/node.rs b/mutiny-core/src/node.rs index 876fb289a..01ad9b7a8 100644 --- a/mutiny-core/src/node.rs +++ b/mutiny-core/src/node.rs @@ -1025,6 +1025,7 @@ impl Node { &self, amount_sat: u64, route_hints: Option>, + labels: Vec, ) -> Result<(Bolt11Invoice, u64), MutinyError> { match self.lsp_client.as_ref() { Some(lsp) => { @@ -1093,6 +1094,7 @@ impl Node { Some(amount_minus_fee), Some(lsp_fee.fee_amount_msat), route_hints, + labels, ) .await?; @@ -1129,8 +1131,13 @@ impl Node { AnyLsp::Lsps(client) => { if has_inbound_capacity { Ok(( - self.create_internal_invoice(Some(amount_sat), None, route_hints) - .await?, + self.create_internal_invoice( + Some(amount_sat), + None, + route_hints, + labels, + ) + .await?, 0, )) } else { @@ -1147,6 +1154,7 @@ impl Node { invoice.clone(), Some(amount_sat * 1_000), Some(lsp_fee.fee_amount_msat), + labels, ) .await?; @@ -1163,7 +1171,7 @@ impl Node { } } None => Ok(( - self.create_internal_invoice(Some(amount_sat), None, route_hints) + self.create_internal_invoice(Some(amount_sat), None, route_hints, labels) .await?, 0, )), @@ -1175,6 +1183,7 @@ impl Node { amount_sat: Option, fee_amount_msat: Option, route_hints: Option>, + labels: Vec, ) -> Result { let amount_msat = amount_sat.map(|s| s * 1_000); // Set description to empty string to make smallest possible invoice/QR code @@ -1228,7 +1237,7 @@ impl Node { MutinyError::InvoiceCreationFailed })?; - self.save_invoice_payment_info(invoice.clone(), amount_msat, fee_amount_msat) + self.save_invoice_payment_info(invoice.clone(), amount_msat, fee_amount_msat, labels) .await?; log_info!(self.logger, "SUCCESS: generated invoice: {invoice}"); @@ -1241,6 +1250,7 @@ impl Node { invoice: Bolt11Invoice, amount_msat: Option, fee_amount_msat: Option, + labels: Vec, ) -> Result<(), MutinyError> { let last_update = utils::now().as_secs(); let payment_hash = PaymentHash(invoice.payment_hash().into_inner()); @@ -1265,6 +1275,8 @@ impl Node { MutinyError::InvoiceCreationFailed })?; + self.persister.storage.set_invoice_labels(invoice, labels)?; + Ok(()) } @@ -2473,7 +2485,10 @@ mod tests { let amount_sats = 1_000; - let invoice = node.create_invoice(amount_sats, None).await.unwrap().0; + let (invoice, _) = node + .create_invoice(amount_sats, None, vec![]) + .await + .unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(amount_sats * 1000)); match invoice.description() { @@ -2504,7 +2519,7 @@ mod tests { let storage = MemoryStorage::default(); let node = create_node(storage).await; - let invoice = node.create_invoice(10_000, None).await.unwrap().0; + let invoice = node.create_invoice(10_000, None, vec![]).await.unwrap().0; let result = node .pay_invoice_with_timeout(&invoice, None, None, vec![]) @@ -2599,6 +2614,7 @@ mod tests { #[cfg(target_arch = "wasm32")] mod wasm_test { use crate::event::{MillisatAmount, PaymentInfo}; + use crate::labels::LabelStorage; use crate::storage::MemoryStorage; use crate::test_utils::create_node; use crate::HTLCStatus; @@ -2626,8 +2642,13 @@ mod wasm_test { let now = crate::utils::now().as_secs(); let amount_sats = 1_000; + let label = "test".to_string(); + let labels = vec![label.clone()]; - let invoice = node.create_invoice(amount_sats, None).await.unwrap().0; + let (invoice, _) = node + .create_invoice(amount_sats, None, labels.clone()) + .await + .unwrap(); assert_eq!(invoice.amount_milli_satoshis(), Some(amount_sats * 1000)); match invoice.description() { @@ -2649,8 +2670,21 @@ mod wasm_test { assert_eq!(from_storage.amount_sats, Some(amount_sats)); assert_eq!(from_storage.status, HTLCStatus::Pending); assert_eq!(from_storage.fees_paid, None); + assert_eq!(from_storage.labels, labels.clone()); assert!(from_storage.inbound); assert!(from_storage.last_updated >= now); + + // check labels + + let invoice_labels = storage.get_invoice_labels().unwrap(); + assert_eq!(invoice_labels.len(), 1); + assert_eq!(invoice_labels.get(&invoice).cloned(), Some(labels)); + + let label_item = storage.get_label("test").unwrap().unwrap(); + + assert!(label_item.last_used_time >= now); + assert!(label_item.addresses.is_empty()); + assert_eq!(label_item.invoices, vec![invoice]); } #[test] @@ -2658,7 +2692,7 @@ mod wasm_test { let storage = MemoryStorage::default(); let node = create_node(storage).await; - let invoice = node.create_invoice(10_000, None).await.unwrap().0; + let invoice = node.create_invoice(10_000, None, vec![]).await.unwrap().0; let result = node .pay_invoice_with_timeout(&invoice, None, None, vec![]) diff --git a/mutiny-core/src/nodemanager.rs b/mutiny-core/src/nodemanager.rs index b8db0942c..711e5fb3f 100644 --- a/mutiny-core/src/nodemanager.rs +++ b/mutiny-core/src/nodemanager.rs @@ -1369,7 +1369,11 @@ impl NodeManager { /// /// 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, u64), MutinyError> { + pub async fn create_invoice( + &self, + amount: u64, + labels: Vec, + ) -> 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 { @@ -1392,7 +1396,9 @@ impl NodeManager { } else { return Err(MutinyError::WalletOperationFailed); }; - let invoice = first_node.create_invoice(amount, route_hints).await?; + let invoice = first_node + .create_invoice(amount, route_hints, labels) + .await?; Ok((invoice.0.into(), invoice.1)) } diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index bcf5cbfa3..1e9995eb3 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -1087,20 +1087,17 @@ impl MutinyWallet { let activity = self.inner.node_manager.get_label_activity(&label).await?; let mut activity: Vec = activity.into_iter().map(|a| a.into()).collect(); - // add contact to the activity item if it is one - let Some(contact) = self.inner.node_manager.get_contact(&label)? else { - return Ok(JsValue::from_serde(&activity)?); + // add contact to the activity item it has one, otherwise return the activity list + let contact = match self.inner.node_manager.get_contact(&label)? { + Some(contact) => contact, + None => return Ok(JsValue::from_serde(&activity)?), }; + // if we have a contact, add it to the activity item, remove it as a label + // This is the same as we do in get_activity for a in activity.iter_mut() { - // find labels that have a contact and add them to the item - for a_label in a.labels.iter() { - if label == *a_label { - a.contacts - .push(TagItem::from((a_label.clone(), contact.clone()))); - } - } - // remove labels that have the contact to prevent duplicates + a.contacts + .push(TagItem::from((label.clone(), contact.clone()))); a.labels.retain(|l| l != &label); }