@@ -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" ) ) ]
@@ -756,78 +755,126 @@ impl<S: MutinyStorage> NodeManager<S> {
756755 ) )
757756 }
758757
759- // Send v1 payjoin request
758+ // Send v2 payjoin request
760759 pub async fn send_payjoin (
761760 & self ,
762761 uri : Uri < ' _ , NetworkUnchecked > ,
763762 amount : u64 ,
764763 labels : Vec < String > ,
765764 fee_rate : Option < f32 > ,
766- ) -> Result < Txid , MutinyError > {
765+ ) -> Result < ( ) , MutinyError > {
767766 let uri = uri
768767 . require_network ( self . network )
769768 . map_err ( |_| MutinyError :: IncorrectNetwork ) ?;
770769 let address = uri. address . clone ( ) ;
771770 let original_psbt = self . wallet . create_signed_psbt ( address, amount, fee_rate) ?;
772-
771+ // TODO ensure this creates a pending tx in the UI. Ensure locked UTXO.
773772 let fee_rate = if let Some ( rate) = fee_rate {
774773 FeeRate :: from_sat_per_vb ( rate)
775774 } else {
776775 let sat_per_kwu = self . fee_estimator . get_normal_fee_rate ( ) ;
777776 FeeRate :: from_sat_per_kwu ( sat_per_kwu as f32 )
778777 } ;
779778 let fee_rate = payjoin:: bitcoin:: FeeRate :: from_sat_per_kwu ( fee_rate. sat_per_kwu ( ) as u64 ) ;
780- let original_psbt = payjoin:: bitcoin:: psbt:: PartiallySignedTransaction :: from_str (
781- & original_psbt. to_string ( ) ,
782- )
783- . map_err ( |_| MutinyError :: WalletOperationFailed ) ?;
784779 log_debug ! ( self . logger, "Creating payjoin request" ) ;
785- let ( req, ctx) =
786- payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
787- . unwrap ( )
788- . build_recommended ( fee_rate)
789- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
790- . extract_v1 ( ) ?;
780+ let req_ctx = payjoin:: send:: RequestBuilder :: from_psbt_and_uri ( original_psbt. clone ( ) , uri)
781+ . unwrap ( )
782+ . build_recommended ( fee_rate)
783+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
784+ self . spawn_payjoin_sender ( labels, original_psbt, req_ctx)
785+ . await ;
786+ Ok ( ( ) )
787+ }
791788
792- let client = Client :: builder ( )
793- . build ( )
794- . map_err ( |e| MutinyError :: Other ( e. into ( ) ) ) ?;
789+ async fn spawn_payjoin_sender (
790+ & self ,
791+ labels : Vec < String > ,
792+ original_psbt : bitcoin:: psbt:: Psbt ,
793+ req_ctx : payjoin:: send:: RequestContext ,
794+ ) {
795+ let wallet = self . wallet . clone ( ) ;
796+ let logger = self . logger . clone ( ) ;
797+ let stop = self . stop . clone ( ) ;
798+ utils:: spawn ( async move {
799+ let proposal_psbt = match Self :: poll_payjoin_sender ( stop, req_ctx) . await {
800+ Ok ( psbt) => psbt,
801+ Err ( e) => {
802+ log_error ! ( logger, "Error polling payjoin sender: {e}" ) ;
803+ return ;
804+ }
805+ } ;
795806
796- log_debug ! ( self . logger, "Sending payjoin request" ) ;
797- let res = client
798- . post ( req. url )
799- . body ( req. body )
800- . header ( "Content-Type" , "text/plain" )
801- . send ( )
802- . await
803- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?
804- . bytes ( )
807+ if let Err ( e) = Self :: handle_proposal_psbt (
808+ logger. clone ( ) ,
809+ wallet,
810+ original_psbt,
811+ proposal_psbt,
812+ labels,
813+ )
805814 . await
806- . map_err ( |_| MutinyError :: PayjoinCreateRequest ) ?;
807-
808- let mut cursor = Cursor :: new ( res. to_vec ( ) ) ;
815+ {
816+ // Ensure ResponseError is logged with debug formatting
817+ log_error ! ( logger, "Error handling payjoin proposal: {:?}" , e) ;
818+ }
819+ } ) ;
820+ }
809821
810- log_debug ! ( self . logger, "Processing payjoin response" ) ;
811- let proposal_psbt = ctx. process_response ( & mut cursor) . map_err ( |e| {
812- // unrecognized error contents may only appear in debug logs and will not Display
813- log_debug ! ( self . logger, "Payjoin response error: {:?}" , e) ;
814- e
815- } ) ?;
822+ async fn poll_payjoin_sender (
823+ stop : Arc < AtomicBool > ,
824+ mut req_ctx : payjoin:: send:: RequestContext ,
825+ ) -> Result < bitcoin:: psbt:: Psbt , MutinyError > {
826+ let http = Client :: builder ( )
827+ . build ( )
828+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to build http client" ) ) ) ?;
829+ loop {
830+ if stop. load ( Ordering :: Relaxed ) {
831+ return Err ( MutinyError :: NotRunning ) ;
832+ }
816833
817- // convert to pdk types
818- let original_psbt = PartiallySignedTransaction :: from_str ( & original_psbt. to_string ( ) )
819- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
820- let proposal_psbt = PartiallySignedTransaction :: from_str ( & proposal_psbt. to_string ( ) )
821- . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
834+ let ( req, ctx) = req_ctx
835+ . extract_v2 ( crate :: payjoin:: OHTTP_RELAYS [ 0 ] . to_owned ( ) )
836+ . map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
837+ let response = http
838+ . post ( req. url )
839+ . header ( "Content-Type" , "message/ohttp-req" )
840+ . body ( req. body )
841+ . send ( )
842+ . await
843+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) ) ) ?;
844+ let mut reader =
845+ std:: io:: Cursor :: new ( response. bytes ( ) . await . map_err ( |_| {
846+ MutinyError :: Other ( anyhow ! ( "failed to parse payjoin response" ) )
847+ } ) ?) ;
848+
849+ println ! ( "Sent fallback transaction" ) ;
850+ let psbt = ctx
851+ . process_response ( & mut reader)
852+ . map_err ( MutinyError :: PayjoinResponse ) ?;
853+ if let Some ( psbt) = psbt {
854+ let psbt = bitcoin:: psbt:: Psbt :: from_str ( & psbt. to_string ( ) )
855+ . map_err ( |_| MutinyError :: Other ( anyhow ! ( "psbt conversion failed" ) ) ) ?;
856+ return Ok ( psbt) ;
857+ } else {
858+ log:: info!( "No response yet for POST payjoin request, retrying some seconds" ) ;
859+ std:: thread:: sleep ( std:: time:: Duration :: from_secs ( 5 ) ) ;
860+ }
861+ }
862+ }
822863
823- log_debug ! ( self . logger, "Sending payjoin.." ) ;
824- let tx = self
825- . wallet
864+ async fn handle_proposal_psbt (
865+ logger : Arc < MutinyLogger > ,
866+ wallet : Arc < OnChainWallet < S > > ,
867+ original_psbt : PartiallySignedTransaction ,
868+ proposal_psbt : PartiallySignedTransaction ,
869+ labels : Vec < String > ,
870+ ) -> Result < Txid , MutinyError > {
871+ log_debug ! ( logger, "Sending payjoin.." ) ;
872+ let tx = wallet
826873 . send_payjoin ( original_psbt, proposal_psbt, labels)
827874 . await ?;
828875 let txid = tx. txid ( ) ;
829- self . broadcast_transaction ( tx) . await ?;
830- log_debug ! ( self . logger, "Payjoin broadcast! TXID: {txid}" ) ;
876+ wallet . broadcast_transaction ( tx) . await ?;
877+ log_info ! ( logger, "Payjoin broadcast! TXID: {txid}" ) ;
831878 Ok ( txid)
832879 }
833880
0 commit comments