@@ -62,7 +62,6 @@ use reqwest::Client;
6262use serde:: { Deserialize , Serialize } ;
6363use serde_json:: Value ;
6464use std:: cmp:: max;
65- use std:: io:: Cursor ;
6665use std:: str:: FromStr ;
6766use std:: sync:: atomic:: { AtomicBool , Ordering } ;
6867#[ cfg( not( target_arch = "wasm32" ) ) ]
@@ -755,7 +754,7 @@ impl<S: MutinyStorage> NodeManager<S> {
755754 ) )
756755 }
757756
758- // Send v1 payjoin request
757+ // Send v2 payjoin request
759758 pub async fn send_payjoin (
760759 & self ,
761760 uri : Uri < ' _ , NetworkUnchecked > ,
@@ -768,65 +767,112 @@ impl<S: MutinyStorage> NodeManager<S> {
768767 . map_err ( |_| MutinyError :: IncorrectNetwork ) ?;
769768 let address = uri. address . clone ( ) ;
770769 let original_psbt = self . wallet . create_signed_psbt ( address, amount, fee_rate) ?;
771-
770+ // TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
772771 let fee_rate = if let Some ( rate) = fee_rate {
773772 FeeRate :: from_sat_per_vb ( rate)
774773 } else {
775774 let sat_per_kwu = self . fee_estimator . get_normal_fee_rate ( ) ;
776775 FeeRate :: from_sat_per_kwu ( sat_per_kwu as f32 )
777776 } ;
778777 let fee_rate = payjoin:: bitcoin:: FeeRate :: from_sat_per_kwu ( fee_rate. sat_per_kwu ( ) as u64 ) ;
779- let original_psbt = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
780- & original_psbt. to_string ( ) ,
781- )
782- . map_err ( |_| MutinyError :: WalletOperationFailed ) ?;
783778 log_debug ! ( self . logger, "Creating payjoin request" ) ;
784- let ( req, ctx) =
785- payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
786- . unwrap ( )
787- . build_recommended ( fee_rate)
788- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
789- . extract_v1 ( ) ?;
779+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
780+ . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
781+ . build_recommended ( fee_rate)
782+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
783+ self . spawn_payjoin_sender ( labels, original_psbt. clone ( ) , req_ctx)
784+ . await ;
785+ Ok ( original_psbt. extract_tx ( ) . txid ( ) )
786+ }
790787
791- let client = Client :: builder ( )
792- . build ( )
793- . map_err ( |e| MutinyError :: Other ( e. into ( ) ) ) ?;
788+ async fn spawn_payjoin_sender (
789+ & self ,
790+ labels : Vec < String > ,
791+ original_psbt : bitcoin:: psbt:: Psbt ,
792+ req_ctx : payjoin:: send:: RequestContext ,
793+ ) {
794+ let wallet = self . wallet . clone ( ) ;
795+ let logger = self . logger . clone ( ) ;
796+ let stop = self . stop . clone ( ) ;
797+ utils:: spawn ( async move {
798+ let proposal_psbt = match Self :: poll_payjoin_sender ( stop, req_ctx) . await {
799+ Ok ( psbt) => psbt,
800+ Err ( e) => {
801+ log_error ! ( logger, "Error polling payjoin sender: {e}" ) ;
802+ return ;
803+ }
804+ } ;
794805
795- log_debug ! ( self . logger, "Sending payjoin request" ) ;
796- let res = client
797- . post ( req. url )
798- . body ( req. body )
799- . header ( "Content-Type" , "text/plain" )
800- . send ( )
801- . await
802- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
803- . bytes ( )
806+ if let Err ( e) = Self :: handle_proposal_psbt (
807+ logger. clone ( ) ,
808+ wallet,
809+ original_psbt,
810+ proposal_psbt,
811+ labels,
812+ )
804813 . await
805- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?;
806-
807- let mut cursor = Cursor :: new ( res. to_vec ( ) ) ;
814+ {
815+ // Ensure ResponseError is logged with debug formatting
816+ log_error ! ( logger, "Error handling payjoin proposal: {:?}" , e) ;
817+ }
818+ } ) ;
819+ }
808820
809- log_debug ! ( self . logger, "Processing payjoin response" ) ;
810- let proposal_psbt = ctx. process_response ( & mut cursor) . map_err ( |e| {
811- // unrecognized error contents may only appear in debug logs and will not Display
812- log_debug ! ( self . logger, "Payjoin response error: {:?}" , e) ;
813- e
814- } ) ?;
821+ async fn poll_payjoin_sender (
822+ stop : Arc < AtomicBool > ,
823+ mut req_ctx : payjoin:: send:: RequestContext ,
824+ ) -> Result < bitcoin:: psbt:: Psbt , MutinyError > {
825+ let http = Client :: builder ( )
826+ . build ( )
827+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to build http client" ) ) ) ?;
828+ loop {
829+ if stop. load ( Ordering :: Relaxed ) {
830+ return Err ( MutinyError :: NotRunning ) ;
831+ }
815832
816- // convert to pdk types
817- let original_psbt = PartiallySignedTransaction :: from_str ( & original_psbt. to_string ( ) )
818- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
819- let proposal_psbt = PartiallySignedTransaction :: from_str ( & proposal_psbt. to_string ( ) )
820- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
833+ let ( req, ctx) = req_ctx
834+ . extract_v2 ( crate :: payjoin:: OHTTP_RELAYS [ 0 ] . to_owned ( ) )
835+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
836+ let response = http
837+ . post ( req. url )
838+ . header ( "Content-Type" , "message/ohttp-req" )
839+ . body ( req. body )
840+ . send ( )
841+ . await
842+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) ) ) ?;
843+ let mut reader =
844+ std:: io:: Cursor :: new ( response. bytes ( ) . await . map_err ( |_| {
845+ MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) )
846+ } ) ?) ;
847+
848+ let psbt = ctx
849+ . process_response ( & mut reader)
850+ . map_err ( MutinyError :: PayjoinResponse ) ?;
851+ if let Some ( psbt) = psbt {
852+ let psbt = bitcoin:: psbt:: Psbt :: from_str ( & psbt. to_string ( ) )
853+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "psbt conversion failed" ) ) ) ?;
854+ return Ok ( psbt) ;
855+ } else {
856+ log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
857+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
858+ }
859+ }
860+ }
821861
822- log_debug ! ( self . logger, "Sending payjoin.." ) ;
823- let tx = self
824- . wallet
862+ async fn handle_proposal_psbt (
863+ logger : Arc < MutinyLogger > ,
864+ wallet : Arc < OnChainWallet < S > > ,
865+ original_psbt : PartiallySignedTransaction ,
866+ proposal_psbt : PartiallySignedTransaction ,
867+ labels : Vec < String > ,
868+ ) -> Result < Txid , MutinyError > {
869+ log_debug ! ( logger, "Sending payjoin.." ) ;
870+ let tx = wallet
825871 . send_payjoin ( original_psbt, proposal_psbt, labels)
826872 . await ?;
827873 let txid = tx. txid ( ) ;
828- self . broadcast_transaction ( tx) . await ?;
829- log_debug ! ( self . logger, "Payjoin broadcast! TXID: {txid}" ) ;
874+ wallet . broadcast_transaction ( tx) . await ?;
875+ log_info ! ( logger, "Payjoin broadcast! TXID: {txid}" ) ;
830876 Ok ( txid)
831877 }
832878
0 commit comments