1+ use alloc:: { boxed:: Box , vec:: Vec } ;
12use core:: fmt:: { Debug , Display } ;
2- use std:: vec:: Vec ;
33
44use bdk_coin_select:: FeeRate ;
5- use bitcoin:: { absolute, transaction, Sequence } ;
5+ use bitcoin:: {
6+ absolute:: { self , LockTime } ,
7+ transaction, Psbt , Sequence ,
8+ } ;
69use miniscript:: bitcoin;
710use miniscript:: psbt:: PsbtExt ;
811
9- use crate :: { Finalizer , Input , Output } ;
12+ use crate :: { apply_anti_fee_sniping , Finalizer , Input , Output } ;
1013
1114const FALLBACK_SEQUENCE : bitcoin:: Sequence = bitcoin:: Sequence :: ENABLE_LOCKTIME_NO_RBF ;
1215
@@ -44,6 +47,9 @@ pub struct PsbtParams {
4447 ///
4548 /// [`non_witness_utxo`]: bitcoin::psbt::Input::non_witness_utxo
4649 pub mandate_full_tx_for_segwit_v0 : bool ,
50+
51+ /// Whether to use BIP326 anti-fee-sniping
52+ pub enable_anti_fee_sniping : bool ,
4753}
4854
4955impl Default for PsbtParams {
@@ -53,6 +59,7 @@ impl Default for PsbtParams {
5359 fallback_locktime : absolute:: LockTime :: ZERO ,
5460 fallback_sequence : FALLBACK_SEQUENCE ,
5561 mandate_full_tx_for_segwit_v0 : true ,
62+ enable_anti_fee_sniping : false ,
5663 }
5764 }
5865}
@@ -63,13 +70,19 @@ pub enum CreatePsbtError {
6370 /// Attempted to mix locktime types.
6471 LockTypeMismatch ,
6572 /// Missing tx for legacy input.
66- MissingFullTxForLegacyInput ( Input ) ,
73+ MissingFullTxForLegacyInput ( Box < Input > ) ,
6774 /// Missing tx for segwit v0 input.
68- MissingFullTxForSegwitV0Input ( Input ) ,
75+ MissingFullTxForSegwitV0Input ( Box < Input > ) ,
6976 /// Psbt error.
7077 Psbt ( bitcoin:: psbt:: Error ) ,
7178 /// Update psbt output with descriptor error.
7279 OutputUpdate ( miniscript:: psbt:: OutputUpdateError ) ,
80+ /// Invalid locktime
81+ InvalidLockTime ( absolute:: LockTime ) ,
82+ /// Invalid height
83+ InvalidHeight ( u32 ) ,
84+ /// Unsupported version for anti fee snipping
85+ UnsupportedVersion ( transaction:: Version ) ,
7386}
7487
7588impl core:: fmt:: Display for CreatePsbtError {
@@ -90,6 +103,15 @@ impl core::fmt::Display for CreatePsbtError {
90103 CreatePsbtError :: OutputUpdate ( output_update_error) => {
91104 Display :: fmt ( & output_update_error, f)
92105 }
106+ CreatePsbtError :: InvalidLockTime ( locktime) => {
107+ write ! ( f, "The locktime - {}, is invalid" , locktime)
108+ }
109+ CreatePsbtError :: InvalidHeight ( height) => {
110+ write ! ( f, "The height - {}, is invalid" , height)
111+ }
112+ CreatePsbtError :: UnsupportedVersion ( version) => {
113+ write ! ( f, "Unsupported version {}" , version)
114+ }
93115 }
94116 }
95117}
@@ -127,7 +149,7 @@ impl Selection {
127149
128150 /// Create psbt.
129151 pub fn create_psbt ( & self , params : PsbtParams ) -> Result < bitcoin:: Psbt , CreatePsbtError > {
130- let mut psbt = bitcoin :: Psbt :: from_unsigned_tx ( bitcoin:: Transaction {
152+ let mut tx = bitcoin:: Transaction {
131153 version : params. version ,
132154 lock_time : Self :: _accumulate_max_locktime (
133155 self . inputs
@@ -146,8 +168,21 @@ impl Selection {
146168 } )
147169 . collect ( ) ,
148170 output : self . outputs . iter ( ) . map ( |output| output. txout ( ) ) . collect ( ) ,
149- } )
150- . map_err ( CreatePsbtError :: Psbt ) ?;
171+ } ;
172+
173+ if params. enable_anti_fee_sniping {
174+ let rbf_enabled = tx. is_explicitly_rbf ( ) ;
175+ let current_height = match tx. lock_time {
176+ LockTime :: Blocks ( height) => height,
177+ LockTime :: Seconds ( _) => {
178+ return Err ( CreatePsbtError :: InvalidLockTime ( tx. lock_time ) ) ;
179+ }
180+ } ;
181+
182+ apply_anti_fee_sniping ( & mut tx, & self . inputs , current_height, rbf_enabled) ?;
183+ } ;
184+
185+ let mut psbt = Psbt :: from_unsigned_tx ( tx) . map_err ( CreatePsbtError :: Psbt ) ?;
151186
152187 for ( plan_input, psbt_input) in self . inputs . iter ( ) . zip ( psbt. inputs . iter_mut ( ) ) {
153188 if let Some ( finalized_psbt_input) = plan_input. psbt_input ( ) {
@@ -167,16 +202,16 @@ impl Selection {
167202 psbt_input. non_witness_utxo = plan_input. prev_tx ( ) . cloned ( ) ;
168203 if psbt_input. non_witness_utxo . is_none ( ) {
169204 if witness_version. is_none ( ) {
170- return Err ( CreatePsbtError :: MissingFullTxForLegacyInput (
205+ return Err ( CreatePsbtError :: MissingFullTxForLegacyInput ( Box :: new (
171206 plan_input. clone ( ) ,
172- ) ) ;
207+ ) ) ) ;
173208 }
174209 if params. mandate_full_tx_for_segwit_v0
175210 && witness_version == Some ( bitcoin:: WitnessVersion :: V0 )
176211 {
177- return Err ( CreatePsbtError :: MissingFullTxForSegwitV0Input (
212+ return Err ( CreatePsbtError :: MissingFullTxForSegwitV0Input ( Box :: new (
178213 plan_input. clone ( ) ,
179- ) ) ;
214+ ) ) ) ;
180215 }
181216 }
182217 continue ;
0 commit comments