@@ -443,10 +443,39 @@ impl WalletActor {
443443 Ok ( None ) => { }
444444
445445 Ok ( Some ( _) ) => {
446- return Produces :: ok ( Err ( Error :: SignAndBroadcastError (
447- "a previous payjoin session is pending recovery; please try again later"
448- . to_string ( ) ,
449- ) ) ) ;
446+ // If the session has a terminal marker and the tx is already in the local
447+ // wallet, the only remaining work is session cleanup — no network needed.
448+ // Attempt it here so this send can proceed without requiring a restart.
449+ let persister = PayjoinSessionPersister :: new ( self . db . clone ( ) ) ;
450+ let can_cleanup = persister
451+ . pending_txid ( )
452+ . is_some_and ( |txid| self . wallet . bdk . get_tx ( txid) . is_some ( ) ) ;
453+
454+ if !can_cleanup {
455+ return Produces :: ok ( Err ( Error :: SignAndBroadcastError (
456+ "a previous payjoin session is pending recovery; please try again later"
457+ . to_string ( ) ,
458+ ) ) ) ;
459+ }
460+
461+ // Retry persistence before cleanup — a prior broadcast may have applied the tx
462+ // in memory without flushing to disk; only drop the session once it is durable.
463+ if let Err ( error) = self . wallet . persist ( ) {
464+ warn ! ( "failed to persist wallet at send gate before payjoin cleanup: {error}" ) ;
465+ return Produces :: ok ( Err ( Error :: SignAndBroadcastError (
466+ "a previous payjoin session is pending cleanup; please try again later"
467+ . to_string ( ) ,
468+ ) ) ) ;
469+ }
470+
471+ if let Err ( error) = self . db . delete_payjoin_sender_session ( ) {
472+ warn ! ( "payjoin session cleanup at send gate failed: {error}" ) ;
473+ return Produces :: ok ( Err ( Error :: SignAndBroadcastError (
474+ "a previous payjoin session is pending cleanup; please try again later"
475+ . to_string ( ) ,
476+ ) ) ) ;
477+ }
478+ // record cleared — fall through to allow this send
450479 }
451480
452481 Err ( error) => {
@@ -713,9 +742,13 @@ impl WalletActor {
713742 return ;
714743 }
715744
716- if let Err ( error) = self . db . delete_payjoin_sender_session ( ) {
717- warn ! ( "failed to clear payjoin session record: {error}" ) ;
718- }
745+ let session_cleared = match self . db . delete_payjoin_sender_session ( ) {
746+ Ok ( ( ) ) => true ,
747+ Err ( error) => {
748+ warn ! ( "failed to clear payjoin session record: {error}" ) ;
749+ false
750+ }
751+ } ;
719752
720753 let balance = self . wallet . balance ( ) ;
721754 self . send ( Msg :: WalletBalanceChanged ( balance. into ( ) ) ) ;
@@ -724,7 +757,17 @@ impl WalletActor {
724757 self . send ( Msg :: UpdatedTransactions ( transactions) ) ;
725758
726759 send ! ( self . addr. start_transaction_watcher( txid) ) ;
727- self . send ( Msg :: PayjoinTxBroadcast ) ;
760+
761+ if session_cleared {
762+ self . send ( Msg :: PayjoinTxBroadcast ) ;
763+ } else {
764+ // TX is broadcast and wallet-persisted, but the session record remains.
765+ // initiate_payment will reject new sends until the record is gone.
766+ // The next startup will clear it automatically via the wallet pre-check.
767+ self . send ( Msg :: SendFlowError ( SendFlowErrorAlert :: SignAndBroadcast (
768+ "transaction was broadcast; restart the app to unlock sending" . to_string ( ) ,
769+ ) ) ) ;
770+ }
728771 }
729772
730773 /// Broadcasts `tx` and calls `finalize_payjoin_terminal` on success. On broadcast failure,
@@ -752,10 +795,12 @@ impl WalletActor {
752795
753796 let node_client = self . node_client ( ) . await . ok ( ) . cloned ( ) ;
754797
755- let already_known = if let Some ( client) = node_client {
756- matches ! ( client. get_transaction( txid) . await , Ok ( Some ( ref found) ) if found. compute_txid( ) == txid)
757- } else {
758- false
798+ let already_known = match node_client {
799+ None => false ,
800+ Some ( client) => {
801+ let result = client. get_transaction ( txid) . await ;
802+ matches ! ( result, Ok ( Some ( ref found) ) if found. compute_txid( ) == txid)
803+ }
759804 } ;
760805
761806 if already_known {
0 commit comments