@@ -54,7 +54,6 @@ use reqwest::Client;
5454use serde:: { Deserialize , Serialize } ;
5555use serde_json:: Value ;
5656use std:: cmp:: max;
57- use std:: io:: Cursor ;
5857use std:: str:: FromStr ;
5958use std:: sync:: atomic:: { AtomicBool , Ordering } ;
6059use std:: { collections:: HashMap , ops:: Deref , sync:: Arc } ;
@@ -698,76 +697,127 @@ impl<S: MutinyStorage> NodeManager<S> {
698697 Ok ( enroller. process_res ( ohttp_response. as_ref ( ) , context) ?)
699698 }
700699
701- // Send v1 payjoin request
700+ // Send v2 payjoin request
702701 pub async fn send_payjoin (
703702 & self ,
704703 uri : Uri < ' _ , payjoin:: bitcoin:: address:: NetworkChecked > ,
705704 amount : u64 ,
706705 labels : Vec < String > ,
707706 fee_rate : Option < f32 > ,
708- ) -> Result < Txid , MutinyError > {
707+ ) -> Result < ( ) , MutinyError > {
709708 let address = Address :: from_str ( & uri. address . to_string ( ) )
710709 . map_err ( |_| MutinyError :: InvalidArgumentsError ) ?;
711710 let original_psbt = self . wallet . create_signed_psbt ( address, amount, fee_rate) ?;
712-
711+ // TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
713712 let fee_rate = if let Some ( rate) = fee_rate {
714713 FeeRate :: from_sat_per_vb ( rate)
715714 } else {
716715 let sat_per_kwu = self . fee_estimator . get_normal_fee_rate ( ) ;
717716 FeeRate :: from_sat_per_kwu ( sat_per_kwu as f32 )
718717 } ;
719718 let fee_rate = payjoin:: bitcoin:: FeeRate :: from_sat_per_kwu ( fee_rate. sat_per_kwu ( ) as u64 ) ;
720- let original_psbt = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
719+ let original_psbt_30 = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
721720 & original_psbt. to_string ( ) ,
722721 )
723722 . map_err ( |_| MutinyError :: WalletOperationFailed ) ?;
724723 log_debug ! ( self . logger, "Creating payjoin request" ) ;
725- let ( req, ctx) =
726- payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
727- . unwrap ( )
728- . build_recommended ( fee_rate)
729- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
730- . extract_v1 ( ) ?;
724+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt_30, uri)
725+ . unwrap ( )
726+ . build_recommended ( fee_rate)
727+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
728+ self . spawn_payjoin_sender ( labels, original_psbt, req_ctx)
729+ . await ;
730+ Ok ( ( ) )
731+ }
731732
732- let client = Client :: builder ( )
733- . build ( )
734- . map_err ( |e| MutinyError :: Other ( e. into ( ) ) ) ?;
733+ async fn spawn_payjoin_sender (
734+ & self ,
735+ labels : Vec < String > ,
736+ original_psbt : bitcoin:: psbt:: Psbt ,
737+ req_ctx : payjoin:: send:: RequestContext ,
738+ ) {
739+ let wallet = self . wallet . clone ( ) ;
740+ let logger = self . logger . clone ( ) ;
741+ let stop = self . stop . clone ( ) ;
742+ utils:: spawn ( async move {
743+ let proposal_psbt = match Self :: poll_payjoin_sender ( stop, req_ctx) . await {
744+ Ok ( psbt) => psbt,
745+ Err ( e) => {
746+ log_error ! ( logger, "Error polling payjoin sender: {e}" ) ;
747+ return ;
748+ }
749+ } ;
735750
736- log_debug ! ( self . logger, "Sending payjoin request" ) ;
737- let res = client
738- . post ( req. url )
739- . body ( req. body )
740- . header ( "Content-Type" , "text/plain" )
741- . send ( )
742- . await
743- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
744- . bytes ( )
751+ if let Err ( e) = Self :: handle_proposal_psbt (
752+ logger. clone ( ) ,
753+ wallet,
754+ original_psbt,
755+ proposal_psbt,
756+ labels,
757+ )
745758 . await
746- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?;
747-
748- let mut cursor = Cursor :: new ( res. to_vec ( ) ) ;
759+ {
760+ // Ensure ResponseError is logged with debug formatting
761+ log_error ! ( logger, "Error handling payjoin proposal: {:?}" , e) ;
762+ }
763+ } ) ;
764+ }
749765
750- log_debug ! ( self . logger, "Processing payjoin response" ) ;
751- let proposal_psbt = ctx. process_response ( & mut cursor) . map_err ( |e| {
752- // unrecognized error contents may only appear in debug logs and will not Display
753- log_debug ! ( self . logger, "Payjoin response error: {:?}" , e) ;
754- e
755- } ) ?;
766+ async fn poll_payjoin_sender (
767+ stop : Arc < AtomicBool > ,
768+ req_ctx : payjoin:: send:: RequestContext ,
769+ ) -> Result < bitcoin:: psbt:: Psbt , MutinyError > {
770+ let http = Client :: builder ( )
771+ . build ( )
772+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to build http client" ) ) ) ?;
773+ loop {
774+ if stop. load ( Ordering :: Relaxed ) {
775+ return Err ( MutinyError :: NotRunning ) ;
776+ }
756777
757- // convert to pdk types
758- let original_psbt = PartiallySignedTransaction :: from_str ( & original_psbt. to_string ( ) )
759- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
760- let proposal_psbt = PartiallySignedTransaction :: from_str ( & proposal_psbt. to_string ( ) )
761- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
778+ let ( req, ctx) = req_ctx
779+ . extract_v2 ( crate :: payjoin:: OHTTP_RELAYS [ 0 ] )
780+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
781+ let response = http
782+ . post ( req. url )
783+ . body ( req. body )
784+ . send ( )
785+ . await
786+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) ) ) ?;
787+ let mut reader =
788+ std:: io:: Cursor :: new ( response. bytes ( ) . await . map_err ( |_| {
789+ MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) )
790+ } ) ?) ;
791+
792+ println ! ( "Sent fallback transaction" ) ;
793+ let psbt = ctx
794+ . process_response ( & mut reader)
795+ . map_err ( MutinyError :: PayjoinResponse ) ?;
796+ if let Some ( psbt) = psbt {
797+ let psbt = bitcoin:: psbt:: Psbt :: from_str ( & psbt. to_string ( ) )
798+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "psbt conversion failed" ) ) ) ?;
799+ return Ok ( psbt) ;
800+ } else {
801+ log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
802+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
803+ }
804+ }
805+ }
762806
763- log_debug ! ( self . logger, "Sending payjoin.." ) ;
764- let tx = self
765- . wallet
807+ async fn handle_proposal_psbt (
808+ logger : Arc < MutinyLogger > ,
809+ wallet : Arc < OnChainWallet < S > > ,
810+ original_psbt : PartiallySignedTransaction ,
811+ proposal_psbt : PartiallySignedTransaction ,
812+ labels : Vec < String > ,
813+ ) -> Result < Txid , MutinyError > {
814+ log_debug ! ( logger, "Sending payjoin.." ) ;
815+ let tx = wallet
766816 . send_payjoin ( original_psbt, proposal_psbt, labels)
767817 . await ?;
768818 let txid = tx. txid ( ) ;
769- self . broadcast_transaction ( tx) . await ?;
770- log_debug ! ( self . logger, "Payjoin broadcast! TXID: {txid}" ) ;
819+ wallet . broadcast_transaction ( tx) . await ?;
820+ log_info ! ( logger, "Payjoin broadcast! TXID: {txid}" ) ;
771821 Ok ( txid)
772822 }
773823
0 commit comments