@@ -52,7 +52,6 @@ use payjoin::Uri;
5252use reqwest:: Client ;
5353use serde:: { Deserialize , Serialize } ;
5454use serde_json:: Value ;
55- use std:: io:: Cursor ;
5655use std:: str:: FromStr ;
5756use std:: sync:: atomic:: { AtomicBool , Ordering } ;
5857use std:: { collections:: HashMap , ops:: Deref , sync:: Arc } ;
@@ -811,76 +810,127 @@ impl<S: MutinyStorage> NodeManager<S> {
811810 Ok ( enroller. process_res ( ohttp_response. as_ref ( ) , context) ?)
812811 }
813812
814- // Send v1 payjoin request
813+ // Send v2 payjoin request
815814 pub async fn send_payjoin (
816815 & self ,
817816 uri : Uri < ' _ , payjoin:: bitcoin:: address:: NetworkChecked > ,
818817 amount : u64 ,
819818 labels : Vec < String > ,
820819 fee_rate : Option < f32 > ,
821- ) -> Result < Txid , MutinyError > {
820+ ) -> Result < ( ) , MutinyError > {
822821 let address = Address :: from_str ( & uri. address . to_string ( ) )
823822 . map_err ( |_| MutinyError :: InvalidArgumentsError ) ?;
824823 let original_psbt = self . wallet . create_signed_psbt ( address, amount, fee_rate) ?;
825-
824+ // TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
826825 let fee_rate = if let Some ( rate) = fee_rate {
827826 FeeRate :: from_sat_per_vb ( rate)
828827 } else {
829828 let sat_per_kwu = self . fee_estimator . get_normal_fee_rate ( ) ;
830829 FeeRate :: from_sat_per_kwu ( sat_per_kwu as f32 )
831830 } ;
832831 let fee_rate = payjoin:: bitcoin:: FeeRate :: from_sat_per_kwu ( fee_rate. sat_per_kwu ( ) as u64 ) ;
833- let original_psbt = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
832+ let original_psbt_30 = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
834833 & original_psbt. to_string ( ) ,
835834 )
836835 . map_err ( |_| MutinyError :: WalletOperationFailed ) ?;
837836 log_debug ! ( self . logger, "Creating payjoin request" ) ;
838- let ( req, ctx) =
839- payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
840- . unwrap ( )
841- . build_recommended ( fee_rate)
842- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
843- . extract_v1 ( ) ?;
837+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt_30, uri)
838+ . unwrap ( )
839+ . build_recommended ( fee_rate)
840+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
841+ self . spawn_payjoin_sender ( labels, original_psbt, req_ctx)
842+ . await ;
843+ Ok ( ( ) )
844+ }
844845
845- let client = Client :: builder ( )
846- . build ( )
847- . map_err ( |e| MutinyError :: Other ( e. into ( ) ) ) ?;
846+ async fn spawn_payjoin_sender (
847+ & self ,
848+ labels : Vec < String > ,
849+ original_psbt : bitcoin:: psbt:: Psbt ,
850+ req_ctx : payjoin:: send:: RequestContext ,
851+ ) {
852+ let wallet = self . wallet . clone ( ) ;
853+ let logger = self . logger . clone ( ) ;
854+ let stop = self . stop . clone ( ) ;
855+ utils:: spawn ( async move {
856+ let proposal_psbt = match Self :: poll_payjoin_sender ( stop, req_ctx) . await {
857+ Ok ( psbt) => psbt,
858+ Err ( e) => {
859+ log_error ! ( logger, "Error polling payjoin sender: {e}" ) ;
860+ return ;
861+ }
862+ } ;
848863
849- log_debug ! ( self . logger, "Sending payjoin request" ) ;
850- let res = client
851- . post ( req. url )
852- . body ( req. body )
853- . header ( "Content-Type" , "text/plain" )
854- . send ( )
855- . await
856- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
857- . bytes ( )
864+ if let Err ( e) = Self :: handle_proposal_psbt (
865+ logger. clone ( ) ,
866+ wallet,
867+ original_psbt,
868+ proposal_psbt,
869+ labels,
870+ )
858871 . await
859- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?;
860-
861- let mut cursor = Cursor :: new ( res. to_vec ( ) ) ;
872+ {
873+ // Ensure ResponseError is logged with debug formatting
874+ log_error ! ( logger, "Error handling payjoin proposal: {:?}" , e) ;
875+ }
876+ } ) ;
877+ }
862878
863- log_debug ! ( self . logger, "Processing payjoin response" ) ;
864- let proposal_psbt = ctx. process_response ( & mut cursor) . map_err ( |e| {
865- // unrecognized error contents may only appear in debug logs and will not Display
866- log_debug ! ( self . logger, "Payjoin response error: {:?}" , e) ;
867- e
868- } ) ?;
879+ async fn poll_payjoin_sender (
880+ stop : Arc < AtomicBool > ,
881+ req_ctx : payjoin:: send:: RequestContext ,
882+ ) -> Result < bitcoin:: psbt:: Psbt , MutinyError > {
883+ let http = Client :: builder ( )
884+ . build ( )
885+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to build http client" ) ) ) ?;
886+ loop {
887+ if stop. load ( Ordering :: Relaxed ) {
888+ return Err ( MutinyError :: NotRunning ) ;
889+ }
869890
870- // convert to pdk types
871- let original_psbt = PartiallySignedTransaction :: from_str ( & original_psbt. to_string ( ) )
872- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
873- let proposal_psbt = PartiallySignedTransaction :: from_str ( & proposal_psbt. to_string ( ) )
874- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
891+ let ( req, ctx) = req_ctx
892+ . extract_v2 ( crate :: payjoin:: OHTTP_RELAYS [ 0 ] )
893+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
894+ let response = http
895+ . post ( req. url )
896+ . body ( req. body )
897+ . send ( )
898+ . await
899+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) ) ) ?;
900+ let mut reader =
901+ std:: io:: Cursor :: new ( response. bytes ( ) . await . map_err ( |_| {
902+ MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) )
903+ } ) ?) ;
904+
905+ println ! ( "Sent fallback transaction" ) ;
906+ let psbt = ctx
907+ . process_response ( & mut reader)
908+ . map_err ( MutinyError :: PayjoinResponse ) ?;
909+ if let Some ( psbt) = psbt {
910+ let psbt = bitcoin:: psbt:: Psbt :: from_str ( & psbt. to_string ( ) )
911+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "psbt conversion failed" ) ) ) ?;
912+ return Ok ( psbt) ;
913+ } else {
914+ log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
915+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
916+ }
917+ }
918+ }
875919
876- log_debug ! ( self . logger, "Sending payjoin.." ) ;
877- let tx = self
878- . wallet
920+ async fn handle_proposal_psbt (
921+ logger : Arc < MutinyLogger > ,
922+ wallet : Arc < OnChainWallet < S > > ,
923+ original_psbt : PartiallySignedTransaction ,
924+ proposal_psbt : PartiallySignedTransaction ,
925+ labels : Vec < String > ,
926+ ) -> Result < Txid , MutinyError > {
927+ log_debug ! ( logger, "Sending payjoin.." ) ;
928+ let tx = wallet
879929 . send_payjoin ( original_psbt, proposal_psbt, labels)
880930 . await ?;
881931 let txid = tx. txid ( ) ;
882- self . broadcast_transaction ( tx) . await ?;
883- log_debug ! ( self . logger, "Payjoin broadcast! TXID: {txid}" ) ;
932+ wallet . broadcast_transaction ( tx) . await ?;
933+ log_info ! ( logger, "Payjoin broadcast! TXID: {txid}" ) ;
884934 Ok ( txid)
885935 }
886936
0 commit comments