Skip to content

feat: introduce TxTemplate as an intermediate stage#73

Open
evanlinjin wants to merge 6 commits into
bitcoindevkit:masterfrom
evanlinjin:feature/tx-template
Open

feat: introduce TxTemplate as an intermediate stage#73
evanlinjin wants to merge 6 commits into
bitcoindevkit:masterfrom
evanlinjin:feature/tx-template

Conversation

@evanlinjin

@evanlinjin evanlinjin commented May 20, 2026

Copy link
Copy Markdown
Member

Description

Closes #57.

The transaction-building API had grown awkward, and a revisit traced the awkwardness back to how anti-fee-sniping (AFS) was bolted on. The concrete symptoms:

  • Non-AFS callers paid for AFS. Enabling AFS to live inside create_psbt meant everyone had to reckon with two extra error variants and an extra input.
  • AFS was filed under the wrong concept. AFS isn't a PSBT concern at all — it's just logic that decides a transaction's locktime and input sequences.
  • PsbtParams was doing two jobs. It carried both bitcoin-transaction-shape fields (version, min_locktime, sequence) and PSBT-specific emission options. Because AFS depended on those tx-shape fields, it was structurally pinned inside the PSBT step and couldn't move out.

The right place for AFS is before PSBT creation — it decides tx fields, then those feed into emission. That mirrors Bitcoin Core, where DiscourageFeeSniping runs as a step before the transaction is built. But AFS couldn't be moved without first untangling the tx-shape fields from PsbtParams — i.e. an architectural change, not a local one.

This PR introduces that change: a TxTemplate stage that sits between Selector and Psbt and owns the fully-resolved tx shape (version, locktime, fallback sequence, per-input sequence, ordering).

Selector::try_finalize() → TxTemplate → SealedTxTemplate → (Psbt, Finalizer) | Transaction

Changelog notice

  • Renamed Selection to TxTemplate, now the single workspace for transaction shaping (version, locktime, fallback sequence, per-input sequence, ordering, anti-fee-sniping, emission).
  • Selector::try_finalize now returns Option<TxTemplate>; InputCandidates::into_selection is now into_tx_template, returning Result<TxTemplate, IntoTxTemplateError> (was IntoSelectionError).
  • Split PsbtParams into BuildPsbtParams (emission-only). Tx-shape options moved to TxTemplate setters: set_version, set_locktime, set_fallback_sequence, apply_anti_fee_sniping.
  • Anti-fee-sniping is now terminal: apply_anti_fee_sniping consumes the TxTemplate and returns a new SealedTxTemplate that exposes only reads and emission, so the tx shape cannot be mutated after AFS. TxTemplate derefs to SealedTxTemplate.
  • PSBT emission is build_psbt (renamed from create_psbt) and returns (Psbt, Finalizer); its params/error are BuildPsbtParams / BuildPsbtError. shuffle_inputs is now consuming (returns Self).
  • Behaviour change: previously-silent locktime handling is now explicit. A wrong-unit min_locktime was silently ignored and a below-CLTV value silently clamped; set_locktime now returns SetLockTimeError::UnitMismatch / BelowInputCltv, and set_version returns SetVersionError::RelativeTimelockRequiresV2. The hardcoded ENABLE_RBF_NO_LOCKTIME fallback sequence is now configurable via set_fallback_sequence (default unchanged).

Before submitting

@evanlinjin

This comment was marked as outdated.

@evanlinjin
evanlinjin force-pushed the feature/tx-template branch 3 times, most recently from 27084ae to e9c2962 Compare May 20, 2026 05:21
@evanlinjin
evanlinjin force-pushed the feature/tx-template branch from e9c2962 to de8bfc5 Compare June 15, 2026 16:07
@evanlinjin
evanlinjin marked this pull request as ready for review June 15, 2026 18:12
@evanlinjin evanlinjin self-assigned this Jun 15, 2026
@evanlinjin
evanlinjin force-pushed the feature/tx-template branch 2 times, most recently from 78bb570 to 0f1c8b2 Compare June 16, 2026 22:26

@noahjoeris noahjoeris left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cACK 0f1c8b2

Nice improvements, thanks.

Comment thread examples/synopsis.rs
Comment thread src/tx_template.rs Outdated
Comment thread src/input_candidates.rs Outdated
evanlinjin and others added 6 commits July 13, 2026 13:57
Pure rename — same struct, same methods, same parameters. No behaviour
change. The next commit adds the resolved tx-shape fields (version,
lock_time, fallback_sequence), the corresponding setters, and the
PSBT/AFS pipeline that consumes them.

  Selection             -> TxTemplate
  Selection::new        -> TxTemplate::from_parts (still pub(crate))
  IntoSelectionError    -> IntoTxTemplateError
  InputCandidates::into_selection -> into_tx_template
  Selector::try_finalize() -> Option<TxTemplate>

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes bitcoindevkit#57. TxTemplate now owns the resolved tx-shape fields and the
methods that mutate them. The selector hands you a TxTemplate already
configured with sensible defaults; everything else is method calls on
it.

New fields on TxTemplate:
  - version            (default V2)
  - lock_time          (= max(input CLTV) or ZERO)
  - fallback_sequence  (default ENABLE_RBF_NO_LOCKTIME)

New setters with validation:
  - set_version       -> SetVersionError::RelativeTimelockRequiresV2
  - set_locktime      -> SetLockTimeError::{BelowInputCltv, UnitMismatch}
  - set_fallback_sequence

The PSBT/AFS pipeline is restructured around these fields:

  - PsbtParams -> PsbtBuildParams (PSBT-only knobs; version/locktime
    /AFS removed)
  - CreatePsbtError -> BuildPsbtError
  - create_psbt(params) -> (Psbt, Finalizer)  (was just Psbt)
  - anti-fee-sniping moves off PsbtParams::anti_fee_sniping into
    TxTemplate::apply_anti_fee_sniping(tip, &mut rng), a separate
    chainable step that composes the public set_locktime /
    Input::set_sequence
  - to_unsigned_tx() materializes the tx for non-PSBT signing flows

Chain ergonomics: sort_inputs_by / shuffle_inputs (etc.) now consume
self and return Self. into_finalizer is dropped — Finalizer comes from
create_psbt or from Finalizer::new for callers that want it standalone.

What was previously silent is now an explicit error:
  - min_locktime of the wrong unit was silently ignored
  - min_locktime below an input's CLTV was silently clamped up
  Both now error via SetLockTimeError. Setting v < 2 with a relative-
  timelock input errors via SetVersionError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
create_psbt no longer needs an RNG (AFS — the only consumer — takes
its own rng explicitly), so the create_psbt_with_rng wrapper and its
thread_rng() call were dead weight. Collapses both into a single
create_psbt(self, params) and moves rand to dev-dependencies.

The library now depends only on rand_core (for the RngCore trait) +
miniscript + bdk_coin_select.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Settle on the "build" verb so the method, params, and error type agree:
create_psbt -> build_psbt, PsbtBuildParams -> BuildPsbtParams (also fixing
the word order). Move BuildPsbtParams/BuildPsbtError into a new build_psbt
module; the build_psbt method stays inherent on TxTemplate since it touches
private fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fee-sniping

apply_anti_fee_sniping now consumes the template and returns a SealedTxTemplate
exposing only reads + emission, so version/locktime/sequence/ordering can't be
changed after AFS. TxTemplate wraps SealedTxTemplate and derefs to it for the
shared read/emit surface; the free afs helper mutates &mut self in place and the
method does the sealing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@evanlinjin
evanlinjin force-pushed the feature/tx-template branch from 0e7d063 to d5ef9f6 Compare July 13, 2026 14:01
@ValuedMammal

Copy link
Copy Markdown
Collaborator

I'm not sold on the idea of TxTemplate I think it adds a layer of indirection with little benefit.

@evanlinjin

evanlinjin commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

@ValuedMammal TxTemplate is Selection repurposed. Commit 1 is that pure-rename commit. There is no added indirection.

Could you be a bit more specific on where you think the proposed API is problematic? Which costs are you weighing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce TxTemplate as a state between Selection and Psbt

4 participants