Skip to content

feat: Add Wallet::create_psbt#516

Open
ValuedMammal wants to merge 14 commits into
bitcoindevkit:masterfrom
ValuedMammal:feat/bdk-tx
Open

feat: Add Wallet::create_psbt#516
ValuedMammal wants to merge 14 commits into
bitcoindevkit:masterfrom
ValuedMammal:feat/bdk-tx

Conversation

@ValuedMammal

Copy link
Copy Markdown
Collaborator

Description

This PR introduces Wallet::create_psbt and Wallet::replace_by_fee - a new PSBT construction API built on top of bdk-tx. This is a second attempt at integrating the planning module that started with #297. The scope is mostly the same and incorporates a number of improvements noted on #297. Notably the entire surface area is gated behind two compile flags.

flags:

  • the bdk-tx Cargo feature
  • the --cfg bdk_wallet_unstable rustc flag

Enabling bdk-tx without also passing bdk_wallet_unstable is a compile error with a clear diagnostic. This allows us to ship the API for early adopters while reserving the right to break it in a minor release.

Users can opt in by adding the following to your project's .cargo/config.toml:

[build]
rustflags = ["--cfg", "bdk_wallet_unstable"]

and enable the feature in Cargo.toml:

bdk_wallet = { version = "...", features = ["bdk-tx"] }

New public API

Item Notes
Wallet::create_psbt Build a new PSBT with coin selection
Wallet::create_psbt_with_rng Same with caller-supplied entropy
Wallet::replace_by_fee Construct an RBF replacement for one or more transactions
Wallet::replace_by_fee_with_rng Same with caller-supplied entropy
PsbtParams<C> Builder for PSBT construction; C is CreateTx or ReplaceTx/Rbf
SelectionStrategy Enum: All, SingleRandomDraw, LowestFee, Custom
CreatePsbtError Error type for create_psbt
ReplaceByFeeError Error type for replace_by_fee
bdk_tx re-export For callers who need direct access to bdk_tx

TxOrdering is also generalized to TxOrdering<In, Out> to accommodate
bdk-tx's typed input/output types. TxOrdering<In, Out> adds default type parameters
(TxIn, TxOut) that are identical to the previous concrete type, so remains
semver-compatible.
Existing callers that don't use the Custom variant are unaffected.

Differences from #297

  • Non-essential API surface removed - Some wallet methods present in Implement create_psbt for Wallet #297 were trimmed; the public API is narrower and more focused.
  • bdk_tx re-export - bdk_wallet::bdk_tx is re-exported so callers who need bdk-tx types directly don't need a separate bdk_tx dependency.
  • SelectionStrategy::All replaces drain_wallet - modeling "sweep everything" as a strategy is cleaner than a PsbtParams option.
  • SelectionStrategy::Custom - users can supply their own coin-selection algorithm as a closure without having to implement a trait.
  • SingleRandomDraw correctness fix - shuffling now only runs when SingleRandomDraw is active; the extra shuffle_slice calls are removed.
  • bdk_wallet_unstable cfg gate + bdk-tx Cargo feature - the full create_psbt / replace_by_fee surface is hidden unless explicitly opted into.

Changelog notice

### Added
- feat(wallet): add unstable `Wallet::create_psbt` and `Wallet::replace_by_fee`
  behind the `bdk-tx` cargo feature and the `--cfg bdk_wallet_unstable` rustc
  flag. These APIs are explicitly **unstable**: breaking changes may land in
  minor releases without a semver bump. To opt in, enable the `bdk-tx` feature
  **and** pass `--cfg bdk_wallet_unstable` to rustc.

Before submitting

`TxOrdering` is made generic by exposing the generic from
`TxSort` function. This means we're not limited to
ordering lists of only `TxIn` and `TxOut`, which will be
useful for sorting inputs/outputs of a `bdk_tx::Selection`.

We use bitcoin `TxIn` and `TxOut` as the default type parameter
to maintain backward compatibility.
We add the `psbt::params` module along with new types
including `PsbtParams` and `SelectionStrategy`.

`PsbtParams` is mostly inspired by `TxParams` from `tx_builder.rs`,
except that we've removed support for `policy_path` in favor of
`add_assets` API.

`PsbtParams<C>` contains a type parameter `C` indicating the context
in which the parameters can be used. Methods related to PSBT
creation exist within the `CreateTx` context, and methods
related to replacements (RBF) exist within the `ReplaceTx`
context.

In `lib.rs` re-export everything under `psbt` module.

- deps: Add `bdk_tx` 0.2.0 to Cargo.toml
We use the new `PsbtParams` to add methods on `Wallet` for
creating PSBTs, including RBF transactions.

`Wallet::create_psbt` and `Wallet::replace_by_fee` each have
no-std counterparts that take an additional `impl RngCore`
parameter.

Also adds a convenience method `replace_by_fee_and_recipients`
that exposes the minimum information needed to create an RBF.

This commit re-introduces the `Wallet::insert_tx` API for adding
newly created transactions to the wallet.

Added `Wallet::transactions_with_params` that allows customizing
the internal canonicalization logic.

Added errors to `wallet::errors` module:
- `CreatePsbtError`
- `ReplaceByFeeError`
Added unit test to `psbt/params.rs`
- `test_replace_params`

To tests/add_foreign_utxo.rs added
- `test_add_planned_psbt_input`

To tests/psbt.rs added
- `test_create_psbt`
- `test_create_psbt_insufficient_funds_error`
- `test_create_psbt_maturity_height`
- `test_create_psbt_cltv`
- `test_create_psbt_cltv_timestamp`
- `test_create_psbt_csv`
- `test_replace_by_fee_and_recpients`
- `test_replace_by_fee_replaces_descendant_fees`
- `test_replace_by_fee_confirmed_tx_error`
- `test_replace_by_fee_no_inputs_from_original`
- `test_create_psbt_utxo_filter`

Plus several Sequence fallback and override scenarios
- `test_create_psbt_fallback_sequence_applied_to_coin_selected_input`
- `test_create_psbt_fallback_sequence_skipped_for_csv_input`
- `test_create_psbt_sequence_override_manually_selected_input`
- `test_create_psbt_sequence_override_takes_precedence_over_fallback`
- `test_create_psbt_sequence_override_csv_conflict_returns_error`

To tests/wallet.rs added
- `test_spend_non_canonical_txout`

- `test-utils`: Add `insert_tx_anchor` test helper for adding
a transaction to the wallet with associated anchor block.
…ansaction>>

The empty txs case is now caught in `replace_by_fee_with_rng`,
which returns `ReplaceByFeeError::NoOriginalTransactions` when
`params.replace` is empty.

Adds a test in `tests/psbt.rs` covering the error path.
Adds two `CreatePsbtError` variants to cover distinct failure modes
when building a PSBT:

- `NoRecipients`: no recipients were added, or `drain_wallet` was set
    without an explicit `change_script`.

- `AllOutputsBelowDust`: after coin selection the only output fell
    below dust and was dropped to fees, leaving the transaction with
    zero outputs.

Early-exit guards in `create_psbt_with_rng` and `replace_by_fee_with_rng`
return `NoRecipients`. The post-selection guard in `create_psbt_from_selector`
returns `AllOutputsBelowDust`.

Tests:
- `test_create_psbt_no_recipients_error`
- `test_create_psbt_drain_wallet_change_below_dust_error`
- `test_replace_by_fee_drain_wallet_change_below_dust_error`
- Move `add_planned_input` to `impl PsbtParams<CreateTx>`. It was
  previously available on `PsbtParams<C>` (all contexts), allowing
  inputs to be erroneously registered after the state transition.

- Fix `replace()` to preserve pre-registered planned inputs. A `Transaction`
  cannot reconstruct `Input` metadata (psbt fields, satisfaction weight, etc.),
  so planned inputs must be added before calling `replace_txs`. We remove any
  planned input whose `prev_txid` is directly in the replacement set.

- Add `ReplaceByFeeError::ConflictingInput(OutPoint)` in
  `replace_by_fee_with_rng` after computing the full `to_replace` set,
  and validate every manually-selected input against it.

- Add `test_replace_tx_with_planned_input`,
  `test_replace_strips_conflicting_planned_input`, and
  `test_replace_by_fee_conflicting_input_descendant`.
Shuffling the input candidates now occurs only in the case
that SingleRandomDraw is the chosen selection strategy.
As such, the extra calls to `shuffle_slice` can be
removed.
`bdk_tx` is now an optional dependency controlled by the new
`bdk-tx` feature. All `create_psbt`/`replace_by_fee` surface area
is gated behind `cfg(all(bdk_wallet_unstable, feature = "bdk-tx"))`.
Enabling the `bdk-tx` feature without also setting
`--cfg bdk_wallet_unstable` in RUSTFLAGS is a compile error with a
useful error message.

The justfile and CI are updated to pass the `bdk_wallet_unstable`
flag in order to test all features.

All `create_psbt` and `replace_by_fee` tests are consolidated into a new
`tests/create_psbt.rs` gated by `bdk_wallet_unstable`. `.gitignore` is
updated to exclude a local `.cargo/config.toml`, and an Unreleased
CHANGELOG entry documents the opt-in requirement for users.
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.11838% with 132 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.71%. Comparing base (75e0d30) to head (c9b8b79).

Files with missing lines Patch % Lines
src/psbt/params.rs 86.52% 50 Missing and 2 partials ⚠️
src/wallet/mod.rs 89.76% 31 Missing and 17 partials ⚠️
src/wallet/error.rs 0.00% 31 Missing ⚠️
src/lib.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #516      +/-   ##
==========================================
+ Coverage   81.15%   81.71%   +0.55%     
==========================================
  Files          24       25       +1     
  Lines        5552     6438     +886     
  Branches      247      290      +43     
==========================================
+ Hits         4506     5261     +755     
- Misses        970     1082     +112     
- Partials       76       95      +19     
Flag Coverage Δ
rust 81.71% <85.11%> (+0.55%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@oleonardolima oleonardolima moved this to Needs Review in BDK Wallet Jul 18, 2026
@oleonardolima oleonardolima added this to the Wallet 3.2.0 milestone Jul 18, 2026
@oleonardolima oleonardolima added the new feature New feature or request label Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New feature or request

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

2 participants