@@ -52,7 +52,6 @@ use payjoin::Uri;
52
52
use reqwest:: Client ;
53
53
use serde:: { Deserialize , Serialize } ;
54
54
use serde_json:: Value ;
55
- use std:: io:: Cursor ;
56
55
use std:: str:: FromStr ;
57
56
use std:: sync:: atomic:: { AtomicBool , Ordering } ;
58
57
use std:: { collections:: HashMap , ops:: Deref , sync:: Arc } ;
@@ -810,75 +809,126 @@ impl<S: MutinyStorage> NodeManager<S> {
810
809
Ok ( enroller. process_res ( ohttp_response. as_ref ( ) , context) ?)
811
810
}
812
811
813
- // Send v1 payjoin request
812
+ // Send v2 payjoin request
814
813
pub async fn send_payjoin (
815
814
& self ,
816
815
uri : Uri < ' _ , payjoin:: bitcoin:: address:: NetworkChecked > ,
817
816
amount : u64 ,
818
817
labels : Vec < String > ,
819
818
fee_rate : Option < f32 > ,
820
- ) -> Result < Txid , MutinyError > {
819
+ ) -> Result < ( ) , MutinyError > {
821
820
let address = Address :: from_str ( & uri. address . to_string ( ) )
822
821
. map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
823
822
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.
825
824
let fee_rate = if let Some ( rate) = fee_rate {
826
825
FeeRate :: from_sat_per_vb ( rate)
827
826
} else {
828
827
let sat_per_kwu = self . fee_estimator . get_normal_fee_rate ( ) ;
829
828
FeeRate :: from_sat_per_kwu ( sat_per_kwu as f32 )
830
829
} ;
831
830
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 (
833
832
& original_psbt. to_string ( ) ,
834
833
)
835
834
. map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
836
835
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)
846
839
. map_err ( |_| MutinyError :: PayjoinConfigError ) ?;
840
+ self . spawn_payjoin_sender ( labels, original_psbt, req_ctx)
841
+ . await ;
842
+ Ok ( ( ) )
843
+ }
847
844
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
+ } ;
859
862
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
+ }
861
876
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
+ }
867
888
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
+ }
873
917
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
877
927
. send_payjoin ( original_psbt, proposal_psbt, labels)
878
928
. await ?;
879
929
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}" ) ;
882
932
Ok ( txid)
883
933
}
884
934
0 commit comments