Skip to content

Commit 27084ae

Browse files
evanlinjinclaude
andcommitted
refactor!: collapse Selection into TxTemplate
Selection was a redundant intermediate: the Selection -> TxTemplate transition was total (could not fail, was pure), so the type boundary only existed to be crossed. Removing it shortens the pipeline: selector.try_finalize()? .apply_anti_fee_sniping(tip, &mut rng)? .create_psbt(PsbtBuildParams::default())? TxTemplateParams disappears with it. Its two fields become methods on the template itself, parallel to the existing set_locktime: TxTemplateParams::min_version -> TxTemplate::set_version(v)? TxTemplateParams::fallback_sequence -> TxTemplate::set_fallback_sequence(s) New TxTemplate::set_version validates against BIP-68 (SetVersionError::RelativeTimelockRequiresV2 if v < 2 and any input has a relative timelock); set_fallback_sequence is infallible. Setters keep the validation discoverable from the type without forcing every caller to pass params. API renames: Selector::try_finalize() -> Option<TxTemplate> (was Selection) InputCandidates::into_selection -> into_tx_template IntoSelectionError -> IntoTxTemplateError Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 430043e commit 27084ae

8 files changed

Lines changed: 149 additions & 202 deletions

File tree

examples/anti_fee_sniping.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv};
33
use bdk_tx::{
44
filter_unspendable, group_by_spk, selection_algorithm_lowest_fee_bnb, Output, PsbtBuildParams,
5-
SelectorParams, TxTemplateParams,
5+
SelectorParams,
66
};
77
use bitcoin::{absolute::LockTime, key::Secp256k1, Amount, FeeRate};
88
use miniscript::Descriptor;
@@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
7272
.all_candidates()
7373
.regroup(group_by_spk())
7474
.filter(filter_unspendable(tip_height, Some(tip_time)))
75-
.into_selection(
75+
.into_tx_template(
7676
selection_algorithm_lowest_fee_bnb(longterm_feerate, 100_000),
7777
SelectorParams {
7878
// For waste optimization when deciding change.
@@ -91,7 +91,6 @@ fn main() -> anyhow::Result<()> {
9191
let selection_inputs = selection.inputs().to_vec();
9292

9393
let (psbt, _) = selection
94-
.into_tx_template(TxTemplateParams::default())
9594
.apply_anti_fee_sniping(tip_height, &mut rand::thread_rng())?
9695
.create_psbt(PsbtBuildParams::default())?;
9796

examples/synopsis.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv};
22
use bdk_tx::{
33
filter_unspendable, group_by_spk, selection_algorithm_lowest_fee_bnb, Output, PsbtBuildParams,
4-
SelectorParams, Signer, TxTemplateParams,
4+
SelectorParams, Signer,
55
};
66
use bitcoin::{key::Secp256k1, Amount, FeeRate};
77
use miniscript::Descriptor;
@@ -52,7 +52,7 @@ fn main() -> anyhow::Result<()> {
5252
.all_candidates()
5353
.regroup(group_by_spk())
5454
.filter(filter_unspendable(tip_height, Some(tip_mtp)))
55-
.into_selection(
55+
.into_tx_template(
5656
selection_algorithm_lowest_fee_bnb(longterm_feerate, 100_000),
5757
SelectorParams {
5858
// For waste-optimization when deciding change.
@@ -67,7 +67,6 @@ fn main() -> anyhow::Result<()> {
6767
)
6868
},
6969
)?
70-
.into_tx_template(TxTemplateParams::default())
7170
.create_psbt(PsbtBuildParams::default())?;
7271

7372
let _ = psbt.sign(&signer, &secp);
@@ -121,7 +120,7 @@ fn main() -> anyhow::Result<()> {
121120

122121
let selection = rbf_candidates
123122
// Do coin selection.
124-
.into_selection(
123+
.into_tx_template(
125124
// Coin selection algorithm.
126125
selection_algorithm_lowest_fee_bnb(longterm_feerate, 100_000),
127126
SelectorParams {
@@ -154,9 +153,7 @@ fn main() -> anyhow::Result<()> {
154153
.collect::<Vec<_>>()
155154
);
156155

157-
let (mut psbt, finalizer) = selection
158-
.into_tx_template(TxTemplateParams::default())
159-
.create_psbt(PsbtBuildParams::default())?;
156+
let (mut psbt, finalizer) = selection.create_psbt(PsbtBuildParams::default())?;
160157
psbt.sign(&signer, &secp).expect("failed to sign");
161158
assert!(
162159
finalizer.finalize(&mut psbt).is_finalized(),

src/finalizer.rs

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ use miniscript::{bitcoin, plan::Plan, psbt::PsbtInputSatisfier};
2323
/// # Example
2424
///
2525
/// ```rust,no_run
26-
/// # use bdk_tx::{PsbtBuildParams, TxTemplateParams};
26+
/// # use bdk_tx::PsbtBuildParams;
2727
/// # let secp = bitcoin::secp256k1::Secp256k1::new();
2828
/// # let keymap = std::collections::BTreeMap::new();
29-
/// # let selection: bdk_tx::Selection = unimplemented!();
30-
/// let (mut psbt, finalizer) = selection
31-
/// .into_tx_template(TxTemplateParams::default())
32-
/// .create_psbt(PsbtBuildParams::default())?;
29+
/// # let template: bdk_tx::TxTemplate = unimplemented!();
30+
/// let (mut psbt, finalizer) = template.create_psbt(PsbtBuildParams::default())?;
3331
///
3432
/// // Sign the PSBT using your preferred method.
3533
/// let signer = bdk_tx::Signer(keymap);
@@ -164,7 +162,7 @@ impl FinalizeMap {
164162
#[cfg_attr(coverage_nightly, coverage(off))]
165163
#[cfg(test)]
166164
mod tests {
167-
use crate::{Finalizer, Output, PsbtBuildParams, Selection, Signer, TxTemplateParams};
165+
use crate::{Finalizer, Output, PsbtBuildParams, Signer, TxTemplate};
168166
use bitcoin::secp256k1::Secp256k1;
169167
use bitcoin::{absolute, transaction, Amount, ScriptBuf, TxIn, TxOut};
170168
use miniscript::bitcoin;
@@ -216,11 +214,9 @@ mod tests {
216214
fn test_finalize_single_input() -> anyhow::Result<()> {
217215
let (input, keymap) = create_input_from_descriptor_at(TR_XPRV, 0)?;
218216
let output = Output::with_script(ScriptBuf::new(), Amount::from_sat(9_000));
219-
let selection = Selection::new(vec![input], vec![output]);
217+
let selection = TxTemplate::from_parts(vec![input], vec![output]);
220218

221-
let (mut psbt, finalizer) = selection
222-
.into_tx_template(TxTemplateParams::default())
223-
.create_psbt(PsbtBuildParams::default())?;
219+
let (mut psbt, finalizer) = selection.create_psbt(PsbtBuildParams::default())?;
224220

225221
let secp = Secp256k1::new();
226222
let signer = Signer(keymap);
@@ -237,11 +233,9 @@ mod tests {
237233
fn test_finalize_sets_final_script_sig() -> anyhow::Result<()> {
238234
let (input, keymap) = create_input_from_descriptor_at(PKH_XPRV, 0)?;
239235
let output = Output::with_script(ScriptBuf::new(), Amount::from_sat(9_000));
240-
let selection = Selection::new(vec![input], vec![output]);
236+
let selection = TxTemplate::from_parts(vec![input], vec![output]);
241237

242-
let (mut psbt, finalizer) = selection
243-
.into_tx_template(TxTemplateParams::default())
244-
.create_psbt(PsbtBuildParams::default())?;
238+
let (mut psbt, finalizer) = selection.create_psbt(PsbtBuildParams::default())?;
245239

246240
let secp = Secp256k1::new();
247241
let signer = Signer(keymap);
@@ -261,17 +255,15 @@ mod tests {
261255
let taproot_output_descriptor = derive_descriptor_at(TR_XPRV, 10)?;
262256
let wpkh_output_descriptor = derive_descriptor_at(WPKH_XPRV, 11)?;
263257

264-
let selection = Selection::new(
258+
let selection = TxTemplate::from_parts(
265259
vec![input_0, input_1, input_2],
266260
vec![
267261
Output::with_descriptor(taproot_output_descriptor, Amount::from_sat(20_000)),
268262
Output::with_descriptor(wpkh_output_descriptor, Amount::from_sat(22_000)),
269263
],
270264
);
271265

272-
let (mut psbt, finalizer) = selection
273-
.into_tx_template(TxTemplateParams::default())
274-
.create_psbt(PsbtBuildParams::default())?;
266+
let (mut psbt, finalizer) = selection.create_psbt(PsbtBuildParams::default())?;
275267

276268
assert!(!psbt.outputs[0].tap_key_origins.is_empty());
277269
assert!(psbt.outputs[0].tap_internal_key.is_some());
@@ -317,17 +309,15 @@ mod tests {
317309
input_0.plan().cloned().expect("plan must exist"),
318310
)]);
319311

320-
let selection = Selection::new(
312+
let selection = TxTemplate::from_parts(
321313
vec![input_0, input_1],
322314
vec![
323315
Output::with_descriptor(taproot_output_descriptor, Amount::from_sat(20_000)),
324316
Output::with_descriptor(wpkh_output_descriptor, Amount::from_sat(22_000)),
325317
],
326318
);
327319

328-
let (mut psbt, _) = selection
329-
.into_tx_template(TxTemplateParams::default())
330-
.create_psbt(PsbtBuildParams::default())?;
320+
let (mut psbt, _) = selection.create_psbt(PsbtBuildParams::default())?;
331321

332322
let tap_key_origins = psbt.outputs[0].tap_key_origins.clone();
333323
let tap_internal_key = psbt.outputs[0].tap_internal_key;
@@ -359,17 +349,15 @@ mod tests {
359349
let (input, _) = create_input_from_descriptor_at(TR_XPRV, 0)?;
360350
let taproot_output_descriptor = derive_descriptor_at(TR_XPRV, 10)?;
361351
let wpkh_output_descriptor = derive_descriptor_at(WPKH_XPRV, 11)?;
362-
let selection = Selection::new(
352+
let selection = TxTemplate::from_parts(
363353
vec![input],
364354
vec![
365355
Output::with_descriptor(taproot_output_descriptor, Amount::from_sat(20_000)),
366356
Output::with_descriptor(wpkh_output_descriptor, Amount::from_sat(22_000)),
367357
],
368358
);
369359

370-
let (mut psbt, finalizer) = selection
371-
.into_tx_template(TxTemplateParams::default())
372-
.create_psbt(PsbtBuildParams::default())?;
360+
let (mut psbt, finalizer) = selection.create_psbt(PsbtBuildParams::default())?;
373361

374362
let tap_key_origins = psbt.outputs[0].tap_key_origins.clone();
375363
let tap_internal_key = psbt.outputs[0].tap_internal_key;
@@ -394,11 +382,9 @@ mod tests {
394382
fn test_already_finalized_input() -> anyhow::Result<()> {
395383
let (input, keymap) = create_input_from_descriptor_at(TR_XPRV, 0)?;
396384
let output = Output::with_script(ScriptBuf::new(), Amount::from_sat(9_000));
397-
let selection = Selection::new(vec![input], vec![output]);
385+
let selection = TxTemplate::from_parts(vec![input], vec![output]);
398386

399-
let (mut psbt, finalizer) = selection
400-
.into_tx_template(TxTemplateParams::default())
401-
.create_psbt(PsbtBuildParams::default())?;
387+
let (mut psbt, finalizer) = selection.create_psbt(PsbtBuildParams::default())?;
402388

403389
let secp = Secp256k1::new();
404390
let signer = Signer(keymap);

src/input_candidates.rs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use miniscript::bitcoin;
77

88
use crate::collections::{BTreeMap, HashSet};
99
use crate::{
10-
CannotMeetTarget, FeeRateExt, Input, InputGroup, Selection, Selector, SelectorError,
11-
SelectorParams,
10+
CannotMeetTarget, FeeRateExt, Input, InputGroup, Selector, SelectorError, SelectorParams,
11+
TxTemplate,
1212
};
1313

1414
/// Input candidates.
@@ -191,30 +191,28 @@ impl InputCandidates {
191191
self
192192
}
193193

194-
/// Attempt to convert the input candidates into a valid [`Selection`] with a given
195-
/// `algorithm` and selector `params`.
196-
pub fn into_selection<A, E>(
194+
/// Run coin selection with `algorithm` and selector `params`, returning a [`TxTemplate`].
195+
pub fn into_tx_template<A, E>(
197196
self,
198197
algorithm: A,
199198
params: SelectorParams,
200-
) -> Result<Selection, IntoSelectionError<E>>
199+
) -> Result<TxTemplate, IntoTxTemplateError<E>>
201200
where
202201
A: FnMut(&mut Selector) -> Result<(), E>,
203202
{
204-
let mut selector = Selector::new(&self, params).map_err(IntoSelectionError::Selector)?;
203+
let mut selector = Selector::new(&self, params).map_err(IntoTxTemplateError::Selector)?;
205204
selector
206205
.select_with_algorithm(algorithm)
207-
.map_err(IntoSelectionError::SelectionAlgorithm)?;
208-
let selection = selector
206+
.map_err(IntoTxTemplateError::SelectionAlgorithm)?;
207+
selector
209208
.try_finalize()
210-
.ok_or(IntoSelectionError::CannotMeetTarget(CannotMeetTarget))?;
211-
Ok(selection)
209+
.ok_or(IntoTxTemplateError::CannotMeetTarget(CannotMeetTarget))
212210
}
213211
}
214212

215213
/// Occurs when we cannot find a solution for selection.
216214
#[derive(Debug)]
217-
pub enum IntoSelectionError<E> {
215+
pub enum IntoTxTemplateError<E> {
218216
/// Coin selector returned an error
219217
Selector(SelectorError),
220218
/// Selection algorithm failed.
@@ -223,22 +221,22 @@ pub enum IntoSelectionError<E> {
223221
CannotMeetTarget(CannotMeetTarget),
224222
}
225223

226-
impl<E: fmt::Display> fmt::Display for IntoSelectionError<E> {
224+
impl<E: fmt::Display> fmt::Display for IntoTxTemplateError<E> {
227225
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228226
match self {
229-
IntoSelectionError::Selector(error) => {
227+
IntoTxTemplateError::Selector(error) => {
230228
write!(f, "{error}")
231229
}
232-
IntoSelectionError::SelectionAlgorithm(error) => {
230+
IntoTxTemplateError::SelectionAlgorithm(error) => {
233231
write!(f, "selection algorithm failed: {error}")
234232
}
235-
IntoSelectionError::CannotMeetTarget(error) => write!(f, "{error}"),
233+
IntoTxTemplateError::CannotMeetTarget(error) => write!(f, "{error}"),
236234
}
237235
}
238236
}
239237

240238
#[cfg(feature = "std")]
241-
impl<E: fmt::Debug + fmt::Display> std::error::Error for IntoSelectionError<E> {}
239+
impl<E: fmt::Debug + fmt::Display> std::error::Error for IntoTxTemplateError<E> {}
242240

243241
/// Select for lowest fee with bnb
244242
pub fn selection_algorithm_lowest_fee_bnb(

src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ mod input_candidates;
1717
mod no_std_rand;
1818
mod output;
1919
mod rbf;
20-
mod selection;
2120
mod selector;
2221
mod signer;
2322
mod tx_template;
@@ -33,7 +32,6 @@ use miniscript::{DefiniteDescriptorKey, Descriptor};
3332
use no_std_rand::*;
3433
pub use output::*;
3534
pub use rbf::*;
36-
pub use selection::*;
3735
pub use selector::*;
3836
pub use signer::*;
3937
pub use tx_template::*;

src/selection.rs

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/selector.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use miniscript::bitcoin;
44

55
use crate::{
66
DefiniteDescriptor, FeeRateExt, Input, InputCandidates, InputGroup, Output, ScriptSource,
7-
Selection,
7+
TxTemplate,
88
};
99
use alloc::boxed::Box;
1010
use alloc::vec::Vec;
@@ -483,10 +483,10 @@ impl<'c> Selector<'c> {
483483
Some(has_drain)
484484
}
485485

486-
/// Try get final selection.
486+
/// Try to finalize the selection into a [`TxTemplate`].
487487
///
488-
/// Return `None` if target is not met yet.
489-
pub fn try_finalize(&self) -> Option<Selection> {
488+
/// Returns `None` if the target is not yet met.
489+
pub fn try_finalize(&self) -> Option<TxTemplate> {
490490
if !self.inner.is_target_met(self.target) {
491491
return None;
492492
}
@@ -506,7 +506,7 @@ impl<'c> Selector<'c> {
506506
Amount::from_sat(maybe_change.value),
507507
)));
508508
}
509-
Some(Selection::new(inputs, outputs))
509+
Some(TxTemplate::from_parts(inputs, outputs))
510510
}
511511
}
512512

0 commit comments

Comments
 (0)