@@ -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 } ;
@@ -810,75 +809,126 @@ impl<S: MutinyStorage> NodeManager<S> {
810809 Ok ( enroller. process_res ( ohttp_response. as_ref ( ) , context) ?)
811810 }
812811
813- // Send v1 payjoin request
812+ // Send v2 payjoin request
814813 pub async fn send_payjoin (
815814 & self ,
816815 uri : Uri < ' _ , payjoin:: bitcoin:: address:: NetworkChecked > ,
817816 amount : u64 ,
818817 labels : Vec < String > ,
819818 fee_rate : Option < f32 > ,
820- ) -> Result < Txid , MutinyError > {
819+ ) -> Result < ( ) , MutinyError > {
821820 let address = Address :: from_str ( & uri. address . to_string ( ) )
822821 . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
823822 let original_psbt = self . wallet . create_signed_psbt ( address, amount, fee_rate) ?;
824-
823+ // TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
825824 let fee_rate = if let Some ( rate) = fee_rate {
826825 FeeRate :: from_sat_per_vb ( rate)
827826 } else {
828827 let sat_per_kwu = self . fee_estimator . get_normal_fee_rate ( ) ;
829828 FeeRate :: from_sat_per_kwu ( sat_per_kwu as f32 )
830829 } ;
831830 let fee_rate = payjoin:: bitcoin:: FeeRate :: from_sat_per_kwu ( fee_rate. sat_per_kwu ( ) as u64 ) ;
832- let original_psbt = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
831+ let original_psbt_30 = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
833832 & original_psbt. to_string ( ) ,
834833 )
835834 . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
836835 log_debug ! ( self . logger, "Creating payjoin request" ) ;
837- let ( req, ctx) =
838- payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
839- . unwrap ( )
840- . build_recommended ( fee_rate)
841- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?
842- . extract_v1 ( ) ?;
843-
844- let client = Client :: builder ( )
845- . build ( )
836+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt_30, uri)
837+ . unwrap ( )
838+ . build_recommended ( fee_rate)
846839 . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
840+ self . spawn_payjoin_sender ( labels, original_psbt, req_ctx)
841+ . await ;
842+ Ok ( ( ) )
843+ }
847844
848- log_debug ! ( self . logger, "Sending payjoin request" ) ;
849- let res = client
850- . post ( req. url )
851- . body ( req. body )
852- . header ( "Content-Type" , "text/plain" )
853- . send ( )
854- . await
855- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
856- . bytes ( )
857- . await
858- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?;
845+ async fn spawn_payjoin_sender (
846+ & self ,
847+ labels : Vec < String > ,
848+ original_psbt : bitcoin:: psbt:: Psbt ,
849+ req_ctx : payjoin:: send:: RequestContext ,
850+ ) {
851+ let wallet = self . wallet . clone ( ) ;
852+ let logger = self . logger . clone ( ) ;
853+ let stop = self . stop . clone ( ) ;
854+ utils:: spawn ( async move {
855+ let proposal_psbt = match Self :: poll_payjoin_sender ( stop, req_ctx) . await {
856+ Ok ( psbt) => psbt,
857+ Err ( e) => {
858+ log_error ! ( logger, "Error polling payjoin sender: {e}" ) ;
859+ return ;
860+ }
861+ } ;
859862
860- let mut cursor = Cursor :: new ( res. to_vec ( ) ) ;
863+ if let Err ( e) = Self :: handle_proposal_psbt (
864+ logger. clone ( ) ,
865+ wallet,
866+ original_psbt,
867+ proposal_psbt,
868+ labels,
869+ )
870+ . await
871+ {
872+ log_error ! ( logger, "Error handling payjoin proposal: {e}" ) ;
873+ }
874+ } ) ;
875+ }
861876
862- log_debug ! ( self . logger, "Processing payjoin response" ) ;
863- let proposal_psbt = ctx. process_response ( & mut cursor) . map_err ( |e| {
864- log_error ! ( self . logger, "Error processing payjoin response: {e}" ) ;
865- e
866- } ) ?;
877+ async fn poll_payjoin_sender (
878+ stop : Arc < AtomicBool > ,
879+ req_ctx : payjoin:: send:: RequestContext ,
880+ ) -> Result < bitcoin:: psbt:: Psbt , MutinyError > {
881+ let http = Client :: builder ( )
882+ . build ( )
883+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to build http client" ) ) ) ?;
884+ loop {
885+ if stop. load ( Ordering :: Relaxed ) {
886+ return Err ( MutinyError :: NotRunning ) ;
887+ }
867888
868- // convert to pdk types
869- let original_psbt = PartiallySignedTransaction :: from_str ( & original_psbt. to_string ( ) )
870- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
871- let proposal_psbt = PartiallySignedTransaction :: from_str ( & proposal_psbt. to_string ( ) )
872- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
889+ let ( req, ctx) = req_ctx
890+ . extract_v2 ( crate :: payjoin:: OHTTP_RELAYS [ 0 ] )
891+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
892+ let response = http
893+ . post ( req. url )
894+ . body ( req. body )
895+ . send ( )
896+ . await
897+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) ) ) ?;
898+ let mut reader =
899+ std:: io:: Cursor :: new ( response. bytes ( ) . await . map_err ( |_| {
900+ MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) )
901+ } ) ?) ;
902+
903+ println ! ( "Sent fallback transaction" ) ;
904+ let psbt = ctx
905+ . process_response ( & mut reader)
906+ . map_err ( MutinyError :: PayjoinResponse ) ?;
907+ if let Some ( psbt) = psbt {
908+ let psbt = bitcoin:: psbt:: Psbt :: from_str ( & psbt. to_string ( ) )
909+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "psbt conversion failed" ) ) ) ?;
910+ return Ok ( psbt) ;
911+ } else {
912+ log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
913+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
914+ }
915+ }
916+ }
873917
874- log_debug ! ( self . logger, "Sending payjoin.." ) ;
875- let tx = self
876- . wallet
918+ async fn handle_proposal_psbt (
919+ logger : Arc < MutinyLogger > ,
920+ wallet : Arc < OnChainWallet < S > > ,
921+ original_psbt : PartiallySignedTransaction ,
922+ proposal_psbt : PartiallySignedTransaction ,
923+ labels : Vec < String > ,
924+ ) -> Result < Txid , MutinyError > {
925+ log_debug ! ( logger, "Sending payjoin.." ) ;
926+ let tx = wallet
877927 . send_payjoin ( original_psbt, proposal_psbt, labels)
878928 . await ?;
879929 let txid = tx. txid ( ) ;
880- self . broadcast_transaction ( tx) . await ?;
881- log_debug ! ( self . logger, "Payjoin broadcast! TXID: {txid}" ) ;
930+ wallet . broadcast_transaction ( tx) . await ?;
931+ log_info ! ( logger, "Payjoin broadcast! TXID: {txid}" ) ;
882932 Ok ( txid)
883933 }
884934
0 commit comments