@@ -459,6 +459,45 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
459459 self
460460 }
461461
462+ /// Excludes any outpoints whose enclosing transaction has fewer than `min_confirms`
463+ /// confirmations.
464+ ///
465+ /// `min_confirms` is the minimum number of confirmations a transaction must have in order for
466+ /// its outpoints to remain spendable.
467+ /// - Passing `0` will include all transactions (no filtering).
468+ /// - Passing `1` will exclude all unconfirmed transactions (equivalent to
469+ /// `exclude_unconfirmed`).
470+ /// - Passing `6` will only allow outpoints from transactions with at least 6 confirmations.
471+ ///
472+ /// If you chain this with other filtering methods, the final set of unspendable outpoints will
473+ /// be the union of all filters.
474+ pub fn exclude_below_confirmations ( & mut self , min_confirms : u32 ) -> & mut Self {
475+ let tip_height = self . wallet . latest_checkpoint ( ) . height ( ) ;
476+ let to_exclude = self
477+ . wallet
478+ . list_unspent ( )
479+ . filter ( |utxo| {
480+ utxo. chain_position
481+ . confirmation_height_upper_bound ( )
482+ . map_or ( 0 , |h| tip_height. saturating_add ( 1 ) . saturating_sub ( h) )
483+ < min_confirms
484+ } )
485+ . map ( |utxo| utxo. outpoint ) ;
486+ for op in to_exclude {
487+ self . params . unspendable . insert ( op) ;
488+ }
489+ self
490+ }
491+
492+ /// Exclude outpoints whose enclosing transaction is unconfirmed.
493+ ///
494+ /// This is a shorthand for [`exclude_below_confirmations(1)`].
495+ ///
496+ /// [`exclude_below_confirmations(1)`]: Self::exclude_below_confirmations
497+ pub fn exclude_unconfirmed ( & mut self ) -> & mut Self {
498+ self . exclude_below_confirmations ( 1 )
499+ }
500+
462501 /// Sign with a specific sig hash
463502 ///
464503 /// **Use this option very carefully**
@@ -1088,6 +1127,96 @@ mod test {
10881127 assert_eq ! ( filtered[ 0 ] . keychain, KeychainKind :: Internal ) ;
10891128 }
10901129
1130+ #[ test]
1131+ fn test_exclude_unconfirmed ( ) {
1132+ use crate :: test_utils:: * ;
1133+ use bdk_chain:: BlockId ;
1134+ use bitcoin:: { hashes:: Hash , BlockHash , Network } ;
1135+
1136+ let mut wallet = Wallet :: create_single ( get_test_tr_single_sig ( ) )
1137+ . network ( Network :: Regtest )
1138+ . create_wallet_no_persist ( )
1139+ . unwrap ( ) ;
1140+ let recipient = wallet. next_unused_address ( KeychainKind :: External ) . address ;
1141+
1142+ insert_checkpoint (
1143+ & mut wallet,
1144+ BlockId {
1145+ height : 1 ,
1146+ hash : BlockHash :: all_zeros ( ) ,
1147+ } ,
1148+ ) ;
1149+ insert_checkpoint (
1150+ & mut wallet,
1151+ BlockId {
1152+ height : 2 ,
1153+ hash : BlockHash :: all_zeros ( ) ,
1154+ } ,
1155+ ) ;
1156+ receive_output (
1157+ & mut wallet,
1158+ Amount :: ONE_BTC ,
1159+ ReceiveTo :: Block ( chain:: ConfirmationBlockTime {
1160+ block_id : BlockId {
1161+ height : 1 ,
1162+ hash : BlockHash :: all_zeros ( ) ,
1163+ } ,
1164+ confirmation_time : 1 ,
1165+ } ) ,
1166+ ) ;
1167+ receive_output (
1168+ & mut wallet,
1169+ Amount :: ONE_BTC * 2 ,
1170+ ReceiveTo :: Block ( chain:: ConfirmationBlockTime {
1171+ block_id : BlockId {
1172+ height : 2 ,
1173+ hash : BlockHash :: all_zeros ( ) ,
1174+ } ,
1175+ confirmation_time : 2 ,
1176+ } ) ,
1177+ ) ;
1178+ receive_output ( & mut wallet, Amount :: ONE_BTC * 3 , ReceiveTo :: Mempool ( 100 ) ) ;
1179+
1180+ // Exclude nothing.
1181+ {
1182+ let mut builder = wallet. build_tx ( ) ;
1183+ builder
1184+ . fee_rate ( FeeRate :: ZERO )
1185+ . exclude_below_confirmations ( 0 )
1186+ . drain_wallet ( )
1187+ . drain_to ( recipient. script_pubkey ( ) ) ;
1188+ let tx = builder. finish ( ) . unwrap ( ) ;
1189+ let output = tx. unsigned_tx . output . first ( ) . expect ( "must have one output" ) ;
1190+ assert_eq ! ( output. value, Amount :: ONE_BTC * 6 ) ;
1191+ }
1192+
1193+ // Exclude < 1 conf (a.k.a exclude unconfirmed).
1194+ {
1195+ let mut builder = wallet. build_tx ( ) ;
1196+ builder
1197+ . fee_rate ( FeeRate :: ZERO )
1198+ . exclude_below_confirmations ( 1 )
1199+ . drain_wallet ( )
1200+ . drain_to ( recipient. script_pubkey ( ) ) ;
1201+ let tx = builder. finish ( ) . unwrap ( ) ;
1202+ let output = tx. unsigned_tx . output . first ( ) . expect ( "must have one output" ) ;
1203+ assert_eq ! ( output. value, Amount :: ONE_BTC * 3 ) ;
1204+ }
1205+
1206+ // Exclude < 2 conf (a.k.a need at least 2 conf)
1207+ {
1208+ let mut builder = wallet. build_tx ( ) ;
1209+ builder
1210+ . fee_rate ( FeeRate :: ZERO )
1211+ . exclude_below_confirmations ( 2 )
1212+ . drain_wallet ( )
1213+ . drain_to ( recipient. script_pubkey ( ) ) ;
1214+ let tx = builder. finish ( ) . unwrap ( ) ;
1215+ let output = tx. unsigned_tx . output . first ( ) . expect ( "must have one output" ) ;
1216+ assert_eq ! ( output. value, Amount :: ONE_BTC ) ;
1217+ }
1218+ }
1219+
10911220 #[ test]
10921221 fn test_build_fee_bump_remove_change_output_single_desc ( ) {
10931222 use crate :: test_utils:: * ;
0 commit comments