@@ -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,75 +810,126 @@ 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 :: PayjoinConfigError ) ?;
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 :: PayjoinConfigError ) ?;
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 :: PayjoinConfigError ) ?
843- . extract_v1 ( ) ?;
844-
845- let client = Client :: builder ( )
846- . build ( )
837+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt_30, uri)
838+ . unwrap ( )
839+ . build_recommended ( fee_rate)
847840 . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
841+ self . spawn_payjoin_sender ( labels, original_psbt, req_ctx)
842+ . await ;
843+ Ok ( ( ) )
844+ }
848845
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 ( )
858- . await
859- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?;
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+ } ;
860863
861- let mut cursor = Cursor :: new ( res. to_vec ( ) ) ;
864+ if let Err ( e) = Self :: handle_proposal_psbt (
865+ logger. clone ( ) ,
866+ wallet,
867+ original_psbt,
868+ proposal_psbt,
869+ labels,
870+ )
871+ . await
872+ {
873+ log_error ! ( logger, "Error handling payjoin proposal: {e}" ) ;
874+ }
875+ } ) ;
876+ }
862877
863- log_debug ! ( self . logger, "Processing payjoin response" ) ;
864- let proposal_psbt = ctx. process_response ( & mut cursor) . map_err ( |e| {
865- log_error ! ( self . logger, "Error processing payjoin response: {e}" ) ;
866- e
867- } ) ?;
878+ async fn poll_payjoin_sender (
879+ stop : Arc < AtomicBool > ,
880+ req_ctx : payjoin:: send:: RequestContext ,
881+ ) -> Result < bitcoin:: psbt:: Psbt , MutinyError > {
882+ let http = Client :: builder ( )
883+ . build ( )
884+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to build http client" ) ) ) ?;
885+ loop {
886+ if stop. load ( Ordering :: Relaxed ) {
887+ return Err ( MutinyError :: NotRunning ) ;
888+ }
868889
869- // convert to pdk types
870- let original_psbt = PartiallySignedTransaction :: from_str ( & original_psbt. to_string ( ) )
871- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
872- let proposal_psbt = PartiallySignedTransaction :: from_str ( & proposal_psbt. to_string ( ) )
873- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
890+ let ( req, ctx) = req_ctx
891+ . extract_v2 ( crate :: payjoin:: OHTTP_RELAYS [ 0 ] )
892+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
893+ let response = http
894+ . post ( req. url )
895+ . body ( req. body )
896+ . send ( )
897+ . await
898+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) ) ) ?;
899+ let mut reader =
900+ std:: io:: Cursor :: new ( response. bytes ( ) . await . map_err ( |_| {
901+ MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) )
902+ } ) ?) ;
903+
904+ println ! ( "Sent fallback transaction" ) ;
905+ let psbt = ctx
906+ . process_response ( & mut reader)
907+ . map_err ( MutinyError :: PayjoinResponse ) ?;
908+ if let Some ( psbt) = psbt {
909+ let psbt = bitcoin:: psbt:: Psbt :: from_str ( & psbt. to_string ( ) )
910+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "psbt conversion failed" ) ) ) ?;
911+ return Ok ( psbt) ;
912+ } else {
913+ log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
914+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
915+ }
916+ }
917+ }
874918
875- log_debug ! ( self . logger, "Sending payjoin.." ) ;
876- let tx = self
877- . wallet
919+ async fn handle_proposal_psbt (
920+ logger : Arc < MutinyLogger > ,
921+ wallet : Arc < OnChainWallet < S > > ,
922+ original_psbt : PartiallySignedTransaction ,
923+ proposal_psbt : PartiallySignedTransaction ,
924+ labels : Vec < String > ,
925+ ) -> Result < Txid , MutinyError > {
926+ log_debug ! ( logger, "Sending payjoin.." ) ;
927+ let tx = wallet
878928 . send_payjoin ( original_psbt, proposal_psbt, labels)
879929 . await ?;
880930 let txid = tx. txid ( ) ;
881- self . broadcast_transaction ( tx) . await ?;
882- log_debug ! ( self . logger, "Payjoin broadcast! TXID: {txid}" ) ;
931+ wallet . broadcast_transaction ( tx) . await ?;
932+ log_info ! ( logger, "Payjoin broadcast! TXID: {txid}" ) ;
883933 Ok ( txid)
884934 }
885935
0 commit comments