Skip to content
Draft
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 examples/bitcoind_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn main() -> anyhow::Result<()> {
let address = wallet.reveal_next_address(KeychainKind::External).address;
println!("Wallet address: {address}");

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance before syncing: {}", balance.total());

let wallet_tip = wallet.latest_checkpoint();
Expand Down Expand Up @@ -186,7 +186,7 @@ fn main() -> anyhow::Result<()> {
}
}
let wallet_tip_end = wallet.latest_checkpoint();
let balance = wallet.balance();
let balance = wallet.balance(1);
println!(
"Synced {} blocks in {}s",
blocks_received,
Expand Down
6 changes: 3 additions & 3 deletions examples/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn main() -> Result<(), anyhow::Error> {
wallet.persist(&mut db)?;
println!("Generated Address: {address}");

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance before syncing: {}", balance.total());

println!("Performing Full Sync...");
Expand Down Expand Up @@ -70,7 +70,7 @@ fn main() -> Result<(), anyhow::Error> {
wallet.apply_update(update)?;
wallet.persist(&mut db)?;

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance after full sync: {}", balance.total());
println!(
"Wallet has {} transactions and {} utxos after full sync",
Expand Down Expand Up @@ -166,7 +166,7 @@ fn main() -> Result<(), anyhow::Error> {
}
wallet.persist(&mut db)?;

let balance_after_sync = wallet.balance();
let balance_after_sync = wallet.balance(1);
println!("Wallet balance after sync: {}", balance_after_sync.total());
println!(
"Wallet has {} transactions and {} utxos after partial sync",
Expand Down
6 changes: 3 additions & 3 deletions examples/esplora_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async fn main() -> Result<(), anyhow::Error> {
wallet.persist(&mut db)?;
println!("Next unused address: ({}) {address}", address.index);

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance before syncing: {}", balance.total());

println!("Full Sync...");
Expand All @@ -64,7 +64,7 @@ async fn main() -> Result<(), anyhow::Error> {
wallet.persist(&mut db)?;
println!();

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance after full sync: {}", balance.total());
println!(
"Wallet has {} transactions and {} utxos after full sync",
Expand Down Expand Up @@ -175,7 +175,7 @@ async fn main() -> Result<(), anyhow::Error> {

wallet.persist(&mut db)?;

let balance_after_sync = wallet.balance();
let balance_after_sync = wallet.balance(1);
println!("Wallet balance after sync: {}", balance_after_sync.total());
println!(
"Wallet has {} transactions and {} utxos after partial sync",
Expand Down
6 changes: 3 additions & 3 deletions examples/esplora_blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn main() -> Result<(), anyhow::Error> {
address.index, address.address
);

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance before syncing: {}", balance.total());

println!("Full Sync...");
Expand All @@ -64,7 +64,7 @@ fn main() -> Result<(), anyhow::Error> {
wallet.persist(&mut db)?;
println!();

let balance = wallet.balance();
let balance = wallet.balance(1);
println!("Wallet balance after syncing: {}", balance.total());

if balance.total() < SEND_AMOUNT {
Expand Down Expand Up @@ -156,7 +156,7 @@ fn main() -> Result<(), anyhow::Error> {
}
wallet.persist(&mut db)?;

let balance_after_sync = wallet.balance();
let balance_after_sync = wallet.balance(1);
println!("Wallet balance after sync: {}", balance_after_sync.total());
println!(
"Wallet has {} transactions and {} utxos",
Expand Down
2 changes: 1 addition & 1 deletion examples/replace_by_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn main() -> anyhow::Result<()> {

println!(
"Wallet funded with {}\n",
wallet.balance().total().display_dynamic()
wallet.balance(1).total().display_dynamic()
);
println!("Creating first sweep transaction (tx1)...");

Expand Down
68 changes: 57 additions & 11 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use bdk_chain::{
},
tx_graph::{CalculateFeeError, CanonicalTx, TxGraph, TxUpdate},
BlockId, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, DescriptorExt,
FullTxOut, Indexed, IndexedTxGraph, Indexer, Merge,
Eligibility, FullTxOut, Indexed, IndexedTxGraph, Indexer, Merge, Trust,
};
use bitcoin::{
absolute,
Expand Down Expand Up @@ -1151,16 +1151,62 @@ impl Wallet {
txs
}

/// Return the balance, separated into available, trusted-pending, untrusted-pending, and
/// immature values.
pub fn balance(&self) -> Balance {
self.tx_graph.graph().balance(
&self.chain,
self.chain.tip().block_id(),
CanonicalizationParams::default(),
self.tx_graph.index.outpoints().iter().cloned(),
|&(k, _), _| k == KeychainKind::Internal,
)
/// Return the balance, separated into available, trusted-pending, untrusted-pending, and immature values.
///
/// A pending output is trusted only when its entire unconfirmed ancestry spends coins we own.
/// If any unconfirmed ancestor pulls in a foreign or unknown output, the output is untrusted.
///
/// # Arguments
///
/// * `min_confirmations` - How many confirmations an output needs to count as settled. `0` and `1` behave identically. It defines the `is_settled` predicate that bdk_chain's `classify_outpoints` uses to draw the confirmed/pending boundary.
///
// NOTE: depends on `CanonicalView` (bitcoindevkit/bdk#2246), not yet in a published `bdk_chain` release.
pub fn balance(&self, min_confirmations: u32) -> Balance {
let graph = self.tx_graph.graph();
let index = &self.tx_graph.index;
let chain_tip = self.chain.tip().block_id();
let tip_height = chain_tip.height;

// A tx pulls in untrusted funds if any of its inputs spends an output we don't own (foreign spk, or unknown to our graph).
// Transitive taint through unconfirmed ancestry is handled by `classify_outpoints`, which calls this on every unsettled ancestor.
let does_taint = |ctx: &CanonicalTx<ChainPosition<ConfirmationBlockTime>>| {
ctx.tx.input.iter().any(|txin| {
let op = txin.previous_output;
!op.is_null()
&& graph
.get_txout(op)
.map(|txo| index.index_of_spk(txo.script_pubkey.clone()).is_none())
.unwrap_or(true)
})
};

let min_confirmations = min_confirmations.max(1);
let is_settled = move |pos: &ChainPosition<ConfirmationBlockTime>| {
pos.confirmation_height_upper_bound()
.is_some_and(|h| tip_height - h + 1 >= min_confirmations)
};

let view = self
.chain
.canonical_view(graph, chain_tip, CanonicalParams::default());

let mut balance = Balance::default();
for (txout, eligibility) in view.classify_outpoints(
index.outpoints().iter().map(|(_, op)| *op),
does_taint,
is_settled,
) {
let bucket = match eligibility {
Eligibility::Settled => &mut balance.confirmed,
Eligibility::Immature => &mut balance.immature,
Eligibility::Unsettled(Trust::Trusted) => &mut balance.trusted_pending,
Eligibility::Unsettled(Trust::Untrusted | Trust::Unknown) => {
&mut balance.untrusted_pending
}
};
*bucket += txout.txout.value;
}
balance
}

/// Add an external signer
Expand Down
2 changes: 1 addition & 1 deletion tests/create_psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ fn test_create_psbt_utxo_filter() {
);
}
assert_eq!(wallet.list_unspent().count(), 4);
assert_eq!(wallet.balance().total().to_sat(), 2100);
assert_eq!(wallet.balance(1).total().to_sat(), 2100);

let mut params = PsbtParams::default();
params.fee_rate(FeeRate::ZERO);
Expand Down
2 changes: 1 addition & 1 deletion tests/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn test_psbt_multiple_internalkey_signers() {

let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
let (mut wallet, _) = get_funded_wallet(&desc, change_desc);
let to_spend = wallet.balance().total();
let to_spend = wallet.balance(1).total();
let send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.drain_to(send_to.script_pubkey()).drain_wallet();
Expand Down
Loading