Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/code_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ jobs:
- name: Generate coverage data
run: cargo llvm-cov --all-features --branch --quiet --ignore-filename-regex "test_utils" --lcov --output-path lcov.info
env:
RUSTFLAGS: "--cfg coverage_nightly"
RUSTFLAGS: "--cfg coverage_nightly --cfg bdk_wallet_unstable"
- name: Generate HTML coverage report
run: cargo llvm-cov --all-features --branch --quiet --ignore-filename-regex "test_utils" --html
env:
RUSTFLAGS: "--cfg coverage_nightly"
RUSTFLAGS: "--cfg coverage_nightly --cfg bdk_wallet_unstable"
- name: Codecov upload
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
Expand Down
36 changes: 22 additions & 14 deletions .github/workflows/cont_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ jobs:
os:
- ubuntu-latest
- ubuntu-24.04-arm
features:
- --no-default-features --features miniscript/no-std,bdk_chain/hashbrown
- --all-features
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -37,10 +34,15 @@ jobs:
cache: true
- name: Pin dependencies for MSRV
run: ./ci/pin-msrv.sh
- name: Build + Test
- name: Build (no default features)
run: |
cargo build --workspace --all-targets ${{ matrix.features }}
cargo test --workspace ${{ matrix.features }}
cargo build --workspace --no-default-features --features miniscript/no-std,bdk_chain/hashbrown
- name: Test all features
env:
RUSTFLAGS: "--cfg bdk_wallet_unstable"
RUSTDOCFLAGS: "--cfg bdk_wallet_unstable"
run: |
cargo test --workspace --all-features

build-test-stable:
name: Build & Test Rust Stable
Expand All @@ -50,9 +52,6 @@ jobs:
os:
- ubuntu-latest
- ubuntu-24.04-arm
features:
- --no-default-features --features miniscript/no-std,bdk_chain/hashbrown
- --all-features
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -64,10 +63,15 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: true
- name: Build + Test
- name: Build (no default features)
run: |
cargo build --workspace --no-default-features --features miniscript/no-std,bdk_chain/hashbrown
- name: Test all features
env:
RUSTFLAGS: "--cfg bdk_wallet_unstable"
RUSTDOCFLAGS: "--cfg bdk_wallet_unstable"
run: |
cargo build --workspace --all-targets ${{ matrix.features }}
cargo test --workspace ${{ matrix.features }}
cargo test --workspace --all-features

check-no-std:
name: Check no_std
Expand Down Expand Up @@ -144,7 +148,9 @@ jobs:
with:
cache: true
- name: Clippy
run: cargo clippy --all-features --all-targets -- -D warnings
env:
RUSTFLAGS: "-D warnings --cfg bdk_wallet_unstable"
run: cargo clippy --all-features --all-targets

docs_check:
name: Check cargo doc
Expand All @@ -160,4 +166,6 @@ jobs:
with:
cache: true
- name: Check docs
run: RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features --no-deps
env:
RUSTDOCFLAGS: "-D warnings --cfg bdk_wallet_unstable"
run: cargo doc --workspace --all-features --no-deps
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
Cargo.lock
/.vscode
/.cargo/config.toml

*.swp
.idea
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ Contributors do not need to change this file but do need to add changelog detail
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### 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,
for example via `RUSTFLAGS="--cfg bdk_wallet_unstable"` or by adding the following to your
workspace's `.cargo/config.toml`:
```toml
[build]
rustflags = ["--cfg", "bdk_wallet_unstable"]
```

## [v3.1.0]

### Added
Expand Down
19 changes: 16 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ rust-version = "1.85.0"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs", "--cfg", "bdk_wallet_unstable"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)', 'cfg(bdk_wallet_unstable)'] }

[dependencies]
bdk_chain = { version = "0.23.3", features = ["miniscript", "serde"], default-features = false }
bdk_tx = { version = "0.2.0", default-features = false, optional = true }
bitcoin = { version = "0.32.8", features = ["serde", "base64"], default-features = false }
miniscript = { version = "12.3.5", features = ["serde"], default-features = false }
rand_core = { version = "0.6.4" }
Expand All @@ -33,14 +34,16 @@ bdk_file_store = { version = "0.22.0", optional = true }
bip39 = { version = "2.2.2", optional = true }
tempfile = { version = "3.26.0", optional = true }


[features]
default = ["std"]
std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std"]
std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std", "bdk_tx?/std"]
compiler = ["miniscript/compiler"]
all-keys = ["keys-bip39"]
keys-bip39 = ["bip39"]
rusqlite = ["bdk_chain/rusqlite"]
file_store = ["bdk_file_store"]
bdk-tx = ["dep:bdk_tx"]
test-utils = ["std", "anyhow", "tempfile"]

[dev-dependencies]
Expand All @@ -49,6 +52,7 @@ assert_matches = "1.5.0"
bdk_bitcoind_rpc = { version = "0.22.0" }
bdk_electrum = { version = "0.23.2" }
bdk_esplora = { version = "0.22.1", features = ["async-https", "blocking-https", "tokio"] }
bdk_tx = { version = "0.2.0" }
bdk_wallet = { path = ".", features = ["rusqlite", "file_store", "test-utils"] }
clap = { version = "4.5.60", features = ["derive", "env"] }
ctrlc = "3.5.2"
Expand Down Expand Up @@ -77,3 +81,12 @@ name = "esplora_blocking"

[[example]]
name = "bitcoind_rpc"

[[example]]
name = "psbt"
required-features = ["bdk-tx"]

[[example]]
name = "replace_by_fee"
required-features = ["bdk-tx"]

118 changes: 118 additions & 0 deletions examples/psbt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#![allow(clippy::print_stdout)]

use std::collections::HashMap;
use std::str::FromStr;

use bdk_chain::BlockId;
use bdk_chain::ConfirmationBlockTime;
use bdk_wallet::psbt::{PsbtParams, SelectionStrategy::*};
use bdk_wallet::test_utils::*;
use bdk_wallet::{KeychainKind::External, Wallet};
use bitcoin::{consensus, secp256k1::rand, Address, Amount, TxIn, TxOut};
use rand::Rng;

// This example shows how to create a PSBT using BDK Wallet.

const NETWORK: bitcoin::Network = bitcoin::Network::Signet;
const SEND_TO: &str = "tb1pw3g5qvnkryghme7pyal228ekj6vq48zc5k983lqtlr2a96n4xw0q5ejknw";
const AMOUNT: Amount = Amount::from_sat(42_000);
const FEERATE: f64 = 2.0; // sat/vb

fn main() -> anyhow::Result<()> {
let (desc, change_desc) = get_test_wpkh_and_change_desc();

// Create wallet and fund it.
let mut wallet = Wallet::create(desc, change_desc)
.network(NETWORK)
.create_wallet_no_persist()?;

fund_wallet(&mut wallet)?;

// Create PSBT Signer, external to the wallet
let signer = {
let secp = wallet.secp_ctx();
let (_, external_keymap) = miniscript::Descriptor::parse_descriptor(secp, desc)?;
let (_, internal_keymap) = miniscript::Descriptor::parse_descriptor(secp, change_desc)?;
bdk_tx::Signer(external_keymap.into_iter().chain(internal_keymap).collect())
};

let utxos = wallet
.list_unspent()
.map(|output| (output.outpoint, output))
.collect::<HashMap<_, _>>();

// Build params.
let mut params = PsbtParams::default();
let addr = Address::from_str(SEND_TO)?.require_network(NETWORK)?;
let feerate = feerate_unchecked(FEERATE);
params
.add_recipients([(addr, AMOUNT)])
.fee_rate(feerate)
.coin_selection(SingleRandomDraw);

// Create PSBT (which also returns the Finalizer).
let (mut psbt, finalizer) = wallet.create_psbt(params)?;

let tx = &psbt.unsigned_tx;
for txin in &tx.input {
let op = txin.previous_output;
let output = utxos.get(&op).unwrap();
println!("TxIn: {}", output.txout.value);
}
for txout in &tx.output {
println!("TxOut: {}", txout.value);
}

let _ = psbt
.sign(&signer, wallet.secp_ctx())
.map_err(|(_, errors)| anyhow::anyhow!("failed to sign PSBT: {errors:?}"))?;

println!("Signed: {}", !psbt.inputs[0].partial_sigs.is_empty());
let finalize_res = finalizer.finalize(&mut psbt);
println!("Finalized: {}", finalize_res.is_finalized());

let tx = psbt.extract_tx()?;
let feerate = wallet.calculate_fee_rate(&tx)?;
println!("Fee rate: {} sat/vb", bdk_wallet::floating_rate!(feerate));

println!("{}", consensus::encode::serialize_hex(&tx));

Ok(())
}

fn fund_wallet(wallet: &mut Wallet) -> anyhow::Result<()> {
let anchor = ConfirmationBlockTime {
block_id: BlockId {
height: 260071,
hash: "000000099f67ae6469d1ad0525d756e24d4b02fbf27d65b3f413d5feb367ec48".parse()?,
},
confirmation_time: 1752184658,
};
insert_checkpoint(wallet, anchor.block_id);

let mut rng = rand::thread_rng();

// Fund wallet with several random utxos
for i in 0..21 {
let addr = wallet.reveal_next_address(External).address;
let value = 10_000 * (i + 1) + (100 * rng.gen_range(0..10));
let tx = bitcoin::Transaction {
lock_time: bitcoin::absolute::LockTime::ZERO,
version: bitcoin::transaction::Version::TWO,
input: vec![TxIn::default()],
output: vec![TxOut {
script_pubkey: addr.script_pubkey(),
value: Amount::from_sat(value),
}],
};
insert_tx_anchor(wallet, tx, anchor.block_id);
}

let tip = BlockId {
height: 260171,
hash: "0000000b9efb77450e753ae9fd7be9f69219511c27b6e95c28f4126f3e1591c3".parse()?,
};
insert_checkpoint(wallet, tip);

Ok(())
}
Loading