@@ -6,7 +6,7 @@ use bitcoin::{absolute, transaction, Sequence};
66use miniscript:: bitcoin;
77use miniscript:: psbt:: PsbtExt ;
88
9- use crate :: { Finalizer , Input , Output } ;
9+ use crate :: { apply_anti_fee_sniping , Finalizer , Input , Output } ;
1010
1111const FALLBACK_SEQUENCE : bitcoin:: Sequence = bitcoin:: Sequence :: ENABLE_LOCKTIME_NO_RBF ;
1212
@@ -44,6 +44,9 @@ pub struct PsbtParams {
4444 ///
4545 /// [`non_witness_utxo`]: bitcoin::psbt::Input::non_witness_utxo
4646 pub mandate_full_tx_for_segwit_v0 : bool ,
47+
48+ /// Whether to use BIP326 anti-fee-sniping
49+ pub enable_anti_fee_sniping : bool ,
4750}
4851
4952impl Default for PsbtParams {
@@ -53,6 +56,7 @@ impl Default for PsbtParams {
5356 fallback_locktime : absolute:: LockTime :: ZERO ,
5457 fallback_sequence : FALLBACK_SEQUENCE ,
5558 mandate_full_tx_for_segwit_v0 : true ,
59+ enable_anti_fee_sniping : true ,
5660 }
5761 }
5862}
@@ -70,6 +74,10 @@ pub enum CreatePsbtError {
7074 Psbt ( bitcoin:: psbt:: Error ) ,
7175 /// Update psbt output with descriptor error.
7276 OutputUpdate ( miniscript:: psbt:: OutputUpdateError ) ,
77+ /// Invalid locktime
78+ InvalidLockTime ( absolute:: LockTime ) ,
79+ /// Invalid height
80+ InvalidHeight ( u32 ) ,
7381}
7482
7583impl core:: fmt:: Display for CreatePsbtError {
@@ -90,6 +98,12 @@ impl core::fmt::Display for CreatePsbtError {
9098 CreatePsbtError :: OutputUpdate ( output_update_error) => {
9199 Display :: fmt ( & output_update_error, f)
92100 }
101+ CreatePsbtError :: InvalidLockTime ( locktime) => {
102+ write ! ( f, "The locktime - {}, is invalid" , locktime)
103+ }
104+ CreatePsbtError :: InvalidHeight ( height) => {
105+ write ! ( f, "The height - {}, is invalid" , height)
106+ }
93107 }
94108 }
95109}
@@ -127,7 +141,7 @@ impl Selection {
127141
128142 /// Create psbt.
129143 pub fn create_psbt ( & self , params : PsbtParams ) -> Result < bitcoin:: Psbt , CreatePsbtError > {
130- let mut psbt = bitcoin :: Psbt :: from_unsigned_tx ( bitcoin:: Transaction {
144+ let mut tx = bitcoin:: Transaction {
131145 version : params. version ,
132146 lock_time : Self :: _accumulate_max_locktime (
133147 self . inputs
@@ -146,8 +160,23 @@ impl Selection {
146160 } )
147161 . collect ( ) ,
148162 output : self . outputs . iter ( ) . map ( |output| output. txout ( ) ) . collect ( ) ,
149- } )
150- . map_err ( CreatePsbtError :: Psbt ) ?;
163+ } ;
164+
165+ if params. enable_anti_fee_sniping {
166+ let rbf_enabled = params. fallback_sequence . is_rbf ( ) ;
167+ let height = params
168+ . fallback_locktime
169+ . is_block_height ( )
170+ . then ( || params. fallback_locktime . to_consensus_u32 ( ) )
171+ . ok_or ( CreatePsbtError :: InvalidLockTime ( params. fallback_locktime ) ) ?;
172+
173+ let current_height = bitcoin:: absolute:: Height :: from_consensus ( height)
174+ . map_err ( |_conversion_error| CreatePsbtError :: InvalidHeight ( height) ) ?;
175+
176+ apply_anti_fee_sniping ( & mut tx, & self . inputs , current_height, rbf_enabled) ;
177+ } ;
178+
179+ let mut psbt = bitcoin:: Psbt :: from_unsigned_tx ( tx) . map_err ( CreatePsbtError :: Psbt ) ?;
151180
152181 for ( plan_input, psbt_input) in self . inputs . iter ( ) . zip ( psbt. inputs . iter_mut ( ) ) {
153182 if let Some ( finalized_psbt_input) = plan_input. psbt_input ( ) {
0 commit comments