@@ -71,9 +71,8 @@ use ::nostr::{Event, Kind, Metadata};
7171use bdk_chain:: ConfirmationTime ;
7272use bip39:: Mnemonic ;
7373use 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 } ;
7776use futures:: { pin_mut, select, FutureExt } ;
7877use futures_util:: lock:: Mutex ;
7978use lightning:: { log_debug, util:: logger:: Logger } ;
@@ -93,15 +92,19 @@ use mockall::{automock, predicate::*};
9392const 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+
11351178async fn create_federations < S : MutinyStorage > (
11361179 storage : & S ,
11371180 c : & MutinyWalletConfig ,
0 commit comments