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

Commit 2a53516

Browse files
Refactor NWC to take MutinyWallet
1 parent 60ecded commit 2a53516

7 files changed

Lines changed: 262 additions & 200 deletions

File tree

mutiny-core/src/lib.rs

Lines changed: 98 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,8 @@ use ::nostr::{Event, Kind, Metadata};
7171
use bdk_chain::ConfirmationTime;
7272
use bip39::Mnemonic;
7373
use bitcoin::util::bip32::ExtendedPrivKey;
74-
use bitcoin::Network;
75-
use bitcoin::{hashes::sha256, secp256k1::PublicKey};
76-
use fedimint_core::{api::InviteCode, config::FederationId};
74+
use bitcoin::{hashes::sha256, Network};
75+
use fedimint_core::{api::InviteCode, config::FederationId, BitcoinHash};
7776
use futures::{pin_mut, select, FutureExt};
7877
use futures_util::lock::Mutex;
7978
use lightning::{log_debug, util::logger::Logger};
@@ -93,15 +92,19 @@ use mockall::{automock, predicate::*};
9392
const DEFAULT_PAYMENT_TIMEOUT: u64 = 30;
9493

9594
#[cfg_attr(test, automock)]
96-
pub(crate) trait InvoiceHandler {
95+
pub trait InvoiceHandler {
9796
fn logger(&self) -> &MutinyLogger;
9897
fn skip_hodl_invoices(&self) -> bool;
99-
fn get_outbound_payment_status(&self, payment_hash: &[u8; 32]) -> Option<HTLCStatus>;
100-
async fn pay_invoice_with_timeout(
98+
async fn get_outbound_payment_status(&self, payment_hash: &[u8; 32]) -> Option<HTLCStatus>;
99+
async fn pay_invoice(
101100
&self,
102101
invoice: &Bolt11Invoice,
103102
amt_sats: Option<u64>,
104-
timeout_secs: Option<u64>,
103+
labels: Vec<String>,
104+
) -> Result<MutinyInvoice, MutinyError>;
105+
async fn create_invoice(
106+
&self,
107+
amount: Option<u64>,
105108
labels: Vec<String>,
106109
) -> Result<MutinyInvoice, MutinyError>;
107110
}
@@ -348,6 +351,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
348351
node_manager.xprivkey,
349352
storage.clone(),
350353
node_manager.logger.clone(),
354+
stop.clone(),
351355
)?);
352356

353357
// create gluedb storage
@@ -399,15 +403,15 @@ impl<S: MutinyStorage> MutinyWallet<S> {
399403
}
400404

401405
// if we don't have any nodes, create one
402-
let first_node = {
403-
match mw.node_manager.list_nodes().await?.pop() {
404-
Some(node) => node,
405-
None => mw.node_manager.new_node().await?.pubkey,
406+
match mw.node_manager.list_nodes().await?.pop() {
407+
Some(_) => (),
408+
None => {
409+
mw.node_manager.new_node().await?;
406410
}
407411
};
408412

409413
// start the nostr wallet connect background process
410-
mw.start_nostr_wallet_connect(first_node).await;
414+
mw.start_nostr_wallet_connect().await;
411415

412416
Ok(mw)
413417
}
@@ -437,12 +441,14 @@ impl<S: MutinyStorage> MutinyWallet<S> {
437441
}
438442

439443
/// Starts a background process that will watch for nostr wallet connect events
440-
pub(crate) async fn start_nostr_wallet_connect(&self, from_node: PublicKey) {
444+
pub(crate) async fn start_nostr_wallet_connect(&self) {
441445
let nostr = self.nostr.clone();
442-
let nm = self.node_manager.clone();
446+
let logger = self.logger.clone();
447+
let stop = self.stop.clone();
448+
let self_clone = self.clone();
443449
utils::spawn(async move {
444450
loop {
445-
if nm.stop.load(Ordering::Relaxed) {
451+
if stop.load(Ordering::Relaxed) {
446452
break;
447453
};
448454

@@ -457,19 +463,20 @@ impl<S: MutinyStorage> MutinyWallet<S> {
457463
// clear in-active profiles, we used to have disabled and archived profiles
458464
// but now we just delete profiles
459465
if let Err(e) = nostr.remove_inactive_profiles() {
460-
log_warn!(nm.logger, "Failed to clear in-active NWC profiles: {e}");
466+
log_warn!(logger, "Failed to clear in-active NWC profiles: {e}");
461467
}
462468

463469
// if a single-use profile's payment was successful in the background,
464470
// we can safely clear it now
465-
let node = nm.get_node(&from_node).await.expect("failed to get node");
466-
if let Err(e) = nostr.clear_successful_single_use_profiles(&node) {
467-
log_warn!(nm.logger, "Failed to clear in-active NWC profiles: {e}");
471+
if let Err(e) = nostr
472+
.clear_successful_single_use_profiles(&self_clone)
473+
.await
474+
{
475+
log_warn!(logger, "Failed to clear in-active NWC profiles: {e}");
468476
}
469-
drop(node);
470477

471478
if let Err(e) = nostr.clear_expired_nwc_invoices().await {
472-
log_warn!(nm.logger, "Failed to clear expired NWC invoices: {e}");
479+
log_warn!(logger, "Failed to clear expired NWC invoices: {e}");
473480
}
474481

475482
// clear successful single-use profiles
@@ -514,15 +521,15 @@ impl<S: MutinyStorage> MutinyWallet<S> {
514521
match notification {
515522
Ok(RelayPoolNotification::Event(_url, event)) => {
516523
if event.kind == Kind::WalletConnectRequest && event.verify().is_ok() {
517-
match nostr.handle_nwc_request(event, &nm, &from_node).await {
524+
match nostr.handle_nwc_request(event, &self_clone).await {
518525
Ok(Some(event)) => {
519526
if let Err(e) = client.send_event(event).await {
520-
log_warn!(nm.logger, "Error sending NWC event: {e}");
527+
log_warn!(logger, "Error sending NWC event: {e}");
521528
}
522529
}
523530
Ok(None) => {} // no response
524531
Err(e) => {
525-
log_error!(nm.logger, "Error handling NWC request: {e}");
532+
log_error!(logger, "Error handling NWC request: {e}");
526533
}
527534
}
528535
}
@@ -535,15 +542,15 @@ impl<S: MutinyStorage> MutinyWallet<S> {
535542
}
536543
}
537544
_ = delay_fut => {
538-
if nm.stop.load(Ordering::Relaxed) {
545+
if stop.load(Ordering::Relaxed) {
539546
break;
540547
}
541548
}
542549
_ = filter_check_fut => {
543550
// Check if the filters have changed
544551
let current_filters = nostr.get_nwc_filters();
545552
if current_filters != last_filters {
546-
log_debug!(nm.logger, "subscribing to new nwc filters");
553+
log_debug!(logger, "subscribing to new nwc filters");
547554
client.subscribe(current_filters.clone()).await;
548555
last_filters = current_filters;
549556
}
@@ -554,7 +561,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
554561
}
555562

556563
if let Err(e) = client.disconnect().await {
557-
log_warn!(nm.logger, "Error disconnecting from relays: {e}");
564+
log_warn!(logger, "Error disconnecting from relays: {e}");
558565
}
559566
}
560567
});
@@ -653,34 +660,10 @@ impl<S: MutinyStorage> MutinyWallet<S> {
653660
let invoice = if self.config.safe_mode {
654661
None
655662
} else {
656-
// Check if a federation exists
657-
let federation_ids = self.list_federation_ids().await?;
658-
if !federation_ids.is_empty() {
659-
// Use the first federation for simplicity
660-
let federation_id = &federation_ids[0];
661-
let fedimint_client = self.federations.lock().await.get(federation_id).cloned();
662-
663-
match fedimint_client {
664-
Some(client) => {
665-
// Try to create an invoice using the federation
666-
match client
667-
.get_invoice(amount.unwrap_or_default(), labels.clone())
668-
.await
669-
{
670-
Ok(inv) => Some(inv.bolt11.ok_or(MutinyError::WalletOperationFailed)?),
671-
Err(_) => None, // Handle the error or fallback to node_manager invoice creation
672-
}
673-
}
674-
None => None, // No federation client found, fallback to node_manager invoice creation
675-
}
676-
} else {
677-
// Fallback to node_manager invoice creation if no federation is found
678-
let inv = self
679-
.node_manager
680-
.create_invoice(amount, labels.clone())
681-
.await?;
682-
Some(inv.bolt11.ok_or(MutinyError::WalletOperationFailed)?)
683-
}
663+
self.create_lightning_invoice(amount, labels.clone())
664+
.await
665+
.ok()
666+
.and_then(|invoice| invoice.bolt11)
684667
};
685668

686669
let Ok(address) = self.node_manager.get_new_address(labels.clone()) else {
@@ -695,6 +678,32 @@ impl<S: MutinyStorage> MutinyWallet<S> {
695678
})
696679
}
697680

681+
async fn create_lightning_invoice(
682+
&self,
683+
amount: Option<u64>,
684+
labels: Vec<String>,
685+
) -> Result<MutinyInvoice, MutinyError> {
686+
let federation_ids = self.list_federation_ids().await?;
687+
688+
// Attempt to create federation invoice
689+
if !federation_ids.is_empty() {
690+
let federation_id = &federation_ids[0];
691+
let fedimint_client = self.federations.lock().await.get(federation_id).cloned();
692+
693+
if let Some(client) = fedimint_client {
694+
if let Ok(inv) = client
695+
.get_invoice(amount.unwrap_or_default(), labels.clone())
696+
.await
697+
{
698+
return Ok(inv);
699+
}
700+
}
701+
}
702+
703+
// Fallback to node_manager invoice creation if no federation invoice created
704+
self.node_manager.create_invoice(amount, labels).await
705+
}
706+
698707
/// Gets the current balance of the wallet.
699708
/// This includes both on-chain, lightning funds, and federations.
700709
///
@@ -1132,6 +1141,40 @@ impl<S: MutinyStorage> MutinyWallet<S> {
11321141
}
11331142
}
11341143

1144+
impl<S: MutinyStorage> InvoiceHandler for MutinyWallet<S> {
1145+
fn logger(&self) -> &MutinyLogger {
1146+
self.logger.as_ref()
1147+
}
1148+
1149+
fn skip_hodl_invoices(&self) -> bool {
1150+
self.config.skip_hodl_invoices
1151+
}
1152+
1153+
async fn get_outbound_payment_status(&self, payment_hash: &[u8; 32]) -> Option<HTLCStatus> {
1154+
self.get_invoice_by_hash(&sha256::Hash::hash(payment_hash))
1155+
.await
1156+
.ok()
1157+
.map(|p| p.status)
1158+
}
1159+
1160+
async fn pay_invoice(
1161+
&self,
1162+
invoice: &Bolt11Invoice,
1163+
amt_sats: Option<u64>,
1164+
labels: Vec<String>,
1165+
) -> Result<MutinyInvoice, MutinyError> {
1166+
self.pay_invoice(invoice, amt_sats, labels).await
1167+
}
1168+
1169+
async fn create_invoice(
1170+
&self,
1171+
amount: Option<u64>,
1172+
labels: Vec<String>,
1173+
) -> Result<MutinyInvoice, MutinyError> {
1174+
self.create_lightning_invoice(amount, labels).await
1175+
}
1176+
}
1177+
11351178
async fn create_federations<S: MutinyStorage>(
11361179
storage: &S,
11371180
c: &MutinyWalletConfig,

mutiny-core/src/node.rs

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::ldkstorage::{persist_monitor, ChannelOpenParams};
12
use crate::lsp::{InvoiceRequest, LspConfig};
23
use crate::messagehandler::MutinyMessageHandler;
34
use crate::nodemanager::ChannelClosure;
@@ -22,10 +23,6 @@ use crate::{
2223
use crate::{fees::P2WSH_OUTPUT_SIZE, peermanager::connect_peer_if_necessary};
2324
use crate::{keymanager::PhantomKeysManager, scorer::HubPreferentialScorer};
2425
use crate::{labels::LabelStorage, DEFAULT_PAYMENT_TIMEOUT};
25-
use crate::{
26-
ldkstorage::{persist_monitor, ChannelOpenParams},
27-
InvoiceHandler,
28-
};
2926
use anyhow::{anyhow, Context};
3027
use bdk::FeeRate;
3128
use bitcoin::hashes::{hex::ToHex, sha256::Hash as Sha256};
@@ -186,7 +183,6 @@ pub(crate) struct Node<S: MutinyStorage> {
186183
pub(crate) lsp_client: Option<AnyLsp<S>>,
187184
pub(crate) sync_lock: Arc<Mutex<()>>,
188185
stop: Arc<AtomicBool>,
189-
pub skip_hodl_invoices: bool,
190186
#[cfg(target_arch = "wasm32")]
191187
websocket_proxy_addr: String,
192188
}
@@ -209,7 +205,6 @@ impl<S: MutinyStorage> Node<S> {
209205
logger: Arc<MutinyLogger>,
210206
do_not_connect_peers: bool,
211207
empty_state: bool,
212-
skip_hodl_invoices: bool,
213208
#[cfg(target_arch = "wasm32")] websocket_proxy_addr: String,
214209
) -> Result<Self, MutinyError> {
215210
log_info!(logger, "initializing a new node: {uuid}");
@@ -728,7 +723,6 @@ impl<S: MutinyStorage> Node<S> {
728723
lsp_client,
729724
sync_lock,
730725
stop,
731-
skip_hodl_invoices,
732726
#[cfg(target_arch = "wasm32")]
733727
websocket_proxy_addr,
734728
})
@@ -1160,7 +1154,7 @@ impl<S: MutinyStorage> Node<S> {
11601154
.read_payment_info(payment_hash.as_inner(), false, &self.logger)
11611155
{
11621156
Some(payment_info) => Ok((payment_info, false)),
1163-
None => Err(MutinyError::InvoiceInvalid),
1157+
None => Err(MutinyError::NotFound),
11641158
}
11651159
}
11661160

@@ -2057,33 +2051,6 @@ pub(crate) fn default_user_config() -> UserConfig {
20572051
}
20582052
}
20592053

2060-
impl<S: MutinyStorage> InvoiceHandler for Node<S> {
2061-
fn logger(&self) -> &MutinyLogger {
2062-
self.logger.as_ref()
2063-
}
2064-
2065-
fn skip_hodl_invoices(&self) -> bool {
2066-
self.skip_hodl_invoices
2067-
}
2068-
2069-
fn get_outbound_payment_status(&self, payment_hash: &[u8; 32]) -> Option<HTLCStatus> {
2070-
self.persister
2071-
.read_payment_info(payment_hash, false, &self.logger)
2072-
.map(|p| p.status)
2073-
}
2074-
2075-
async fn pay_invoice_with_timeout(
2076-
&self,
2077-
invoice: &Bolt11Invoice,
2078-
amt_sats: Option<u64>,
2079-
timeout_secs: Option<u64>,
2080-
labels: Vec<String>,
2081-
) -> Result<MutinyInvoice, MutinyError> {
2082-
self.pay_invoice_with_timeout(invoice, amt_sats, timeout_secs, labels)
2083-
.await
2084-
}
2085-
}
2086-
20872054
#[cfg(test)]
20882055
#[cfg(not(target_arch = "wasm32"))]
20892056
mod tests {

mutiny-core/src/nodemanager.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,6 @@ pub struct NodeManager<S: MutinyStorage> {
433433
pub(crate) logger: Arc<MutinyLogger>,
434434
bitcoin_price_cache: Arc<Mutex<HashMap<String, (f32, Duration)>>>,
435435
do_not_connect_peers: bool,
436-
skip_hodl_invoices: bool,
437436
pub safe_mode: bool,
438437
}
439438

@@ -580,7 +579,6 @@ impl<S: MutinyStorage> NodeManager<S> {
580579
logger.clone(),
581580
c.do_not_connect_peers,
582581
false,
583-
c.skip_hodl_invoices,
584582
#[cfg(target_arch = "wasm32")]
585583
websocket_proxy_addr.clone(),
586584
)
@@ -667,7 +665,6 @@ impl<S: MutinyStorage> NodeManager<S> {
667665
bitcoin_price_cache: Arc::new(Mutex::new(price_cache)),
668666
do_not_connect_peers: c.do_not_connect_peers,
669667
safe_mode: c.safe_mode,
670-
skip_hodl_invoices: c.skip_hodl_invoices,
671668
};
672669

673670
Ok(nm)
@@ -2382,7 +2379,6 @@ pub(crate) async fn create_new_node_from_node_manager<S: MutinyStorage>(
23822379
node_manager.logger.clone(),
23832380
node_manager.do_not_connect_peers,
23842381
false,
2385-
node_manager.skip_hodl_invoices,
23862382
#[cfg(target_arch = "wasm32")]
23872383
node_manager.websocket_proxy_addr.clone(),
23882384
)

0 commit comments

Comments
 (0)