11use anyhow:: { anyhow, Result } ;
2+
23use bdk:: { wallet:: tx_builder:: TxOrdering , FeeRate , TransactionDetails } ;
3- use bitcoin:: consensus:: serialize;
4- use payjoin:: send:: Configuration ;
5- use payjoin:: { PjUri , PjUriExt } ;
4+
5+ use bitcoin:: {
6+ consensus:: serialize,
7+ psbt:: { Input , Psbt } ,
8+ Amount , TxIn ,
9+ } ;
10+
11+ use payjoin:: { send:: Configuration , PjUri , PjUriExt } ;
612
713use crate :: {
814 bitcoin:: {
@@ -43,24 +49,57 @@ pub async fn create_payjoin(
4349 fee_rate : Option < FeeRate > ,
4450 pj_uri : PjUri < ' _ > , // TODO specify Uri<PayJoinParams>
4551) -> Result < TransactionDetails > {
52+ let enacted_fee_rate = fee_rate. unwrap_or_default ( ) ;
4653 let ( psbt, details) = {
4754 let locked_wallet = wallet. lock ( ) . await ;
4855 let mut builder = locked_wallet. build_tx ( ) ;
49- for invoice in invoices {
56+ for invoice in & invoices {
5057 builder. add_recipient ( invoice. address . script_pubkey ( ) , invoice. amount ) ;
5158 }
52- builder. enable_rbf ( ) . fee_rate ( fee_rate . unwrap_or_default ( ) ) ;
59+ builder. enable_rbf ( ) . fee_rate ( enacted_fee_rate ) ;
5360 builder. finish ( ) ?
5461 } ;
5562
5663 debug ! ( format!( "Request PayJoin transaction: {details:#?}" ) ) ;
5764 debug ! ( "Unsigned Original PSBT:" , base64:: encode( & serialize( & psbt) ) ) ;
58- let original_psbt = sign_original_psbt ( wallet, psbt) . await ?;
65+ let original_psbt = sign_original_psbt ( wallet, psbt. clone ( ) ) . await ?;
5966 info ! ( "Original PSBT successfully signed" ) ;
6067
61- // TODO use fee_rate
62- let pj_params = Configuration :: non_incentivizing ( ) ;
63- let ( req, ctx) = pj_uri. create_pj_request ( original_psbt, pj_params) ?;
68+ let additional_fee_index = psbt
69+ . outputs
70+ . clone ( )
71+ . into_iter ( )
72+ . enumerate ( )
73+ . find ( |( _, output) | {
74+ invoices. iter ( ) . all ( |invoice| {
75+ output. redeem_script != Some ( invoice. address . script_pubkey ( ) )
76+ && output. witness_script != Some ( invoice. address . script_pubkey ( ) )
77+ } )
78+ } )
79+ . map ( |( i, _) | i) ;
80+
81+ let pj_params = match additional_fee_index {
82+ Some ( index) => {
83+ let amount_available = psbt
84+ . clone ( )
85+ . unsigned_tx
86+ . output
87+ . get ( index)
88+ . map ( |o| Amount :: from_sat ( o. value ) )
89+ . unwrap_or_default ( ) ;
90+ const P2TR_INPUT_WEIGHT : usize = 58 ; // bitmask is taproot only
91+ let recommended_fee = Amount :: from_sat ( enacted_fee_rate. fee_wu ( P2TR_INPUT_WEIGHT ) ) ;
92+ let max_additional_fee = std:: cmp:: min (
93+ recommended_fee,
94+ amount_available, // offer amount available if recommendation is not
95+ ) ;
96+
97+ Configuration :: with_fee_contribution ( max_additional_fee, Some ( index) )
98+ }
99+ None => Configuration :: non_incentivizing ( ) ,
100+ } ;
101+
102+ let ( req, ctx) = pj_uri. create_pj_request ( original_psbt. clone ( ) , pj_params) ?;
64103 info ! ( "Built PayJoin request" ) ;
65104 let response = reqwest:: Client :: new ( )
66105 . post ( req. url )
@@ -77,7 +116,8 @@ pub async fn create_payjoin(
77116 return Err ( anyhow ! ( "Error performing payjoin: {res}" ) ) ;
78117 }
79118
80- let payjoin_psbt = ctx. process_response ( res. as_bytes ( ) ) ?;
119+ let payjoin_psbt = ctx. process_response ( & mut res. as_bytes ( ) ) ?;
120+ let payjoin_psbt = add_back_original_input ( & original_psbt, payjoin_psbt) ;
81121
82122 debug ! (
83123 "Proposed PayJoin PSBT:" ,
@@ -88,3 +128,34 @@ pub async fn create_payjoin(
88128
89129 Ok ( tx)
90130}
131+
132+ /// Unlike Bitcoin Core's walletprocesspsbt RPC, BDK's finalize_psbt only checks
133+ /// if the script in the PSBT input map matches the descriptor and does not
134+ /// check whether it has control of the OutPoint specified in the unsigned_tx's
135+ /// TxIn. So the original_psbt input data needs to be added back into
136+ /// payjoin_psbt without overwriting receiver input.
137+ fn add_back_original_input ( original_psbt : & Psbt , payjoin_psbt : Psbt ) -> Psbt {
138+ // input_pairs is only used here. It may be added to payjoin, rust-bitcoin, or BDK in time.
139+ fn input_pairs ( psbt : & Psbt ) -> Box < dyn Iterator < Item = ( TxIn , Input ) > + ' _ > {
140+ Box :: new (
141+ psbt. unsigned_tx
142+ . input
143+ . iter ( )
144+ . cloned ( ) // Clone each TxIn for better ergonomics than &muts
145+ . zip ( psbt. inputs . iter ( ) . cloned ( ) ) , // Clone each Input too
146+ )
147+ }
148+
149+ let mut original_inputs = input_pairs ( original_psbt) . peekable ( ) ;
150+
151+ for ( proposed_txin, mut proposed_psbtin) in input_pairs ( & payjoin_psbt) {
152+ if let Some ( ( original_txin, original_psbtin) ) = original_inputs. peek ( ) {
153+ if proposed_txin. previous_output == original_txin. previous_output {
154+ proposed_psbtin. witness_utxo = original_psbtin. witness_utxo . clone ( ) ;
155+ proposed_psbtin. non_witness_utxo = original_psbtin. non_witness_utxo . clone ( ) ;
156+ }
157+ original_inputs. next ( ) ;
158+ }
159+ }
160+ payjoin_psbt
161+ }
0 commit comments