let outpoints = recv_graph.index.outpoints().clone();
let balance = recv_graph
.canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
- .balance(outpoints, |_, _| true);
+ .balance(outpoints, |_, _| true, 1);
Ok(balance)
}
let op = graph.index.outpoints().clone();
let bal = graph
.canonical_view(chain, chain_tip, CanonicalizationParams::default())
- .balance(op, |_, _| false);
+ .balance(op, |_, _| false, 1);
assert_eq!(bal.total(), AMOUNT * TX_CT as u64);
}
/// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
/// (`O`) for convenience. If `O` is not necessary, the caller can use `()`, or
/// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
+ ///
+ /// ### Minimum confirmations
+ ///
+ /// `min_confirmations` specifies the minimum number of confirmations required for a transaction
+ /// to be counted as confirmed in the returned [`Balance`]. Transactions with fewer than
+ /// `min_confirmations` will be treated as trusted pending (assuming the `trust_predicate`
+ /// returns `true`).
+ ///
+ /// - `min_confirmations = 0`: Include all confirmed transactions (same as `1`)
+ /// - `min_confirmations = 1`: Standard behavior - require at least 1 confirmation
+ /// - `min_confirmations = 6`: High security - require at least 6 confirmations
+ ///
+ /// Note: `0` and `1` behave identically since confirmed transactions always have ≥1
+ /// confirmation.
pub fn balance<'v, O: Clone + 'v>(
&'v self,
outpoints: impl IntoIterator<Item = (O, OutPoint)> + 'v,
mut trust_predicate: impl FnMut(&O, ScriptBuf) -> bool,
+ min_confirmations: u32,
) -> Balance {
let mut immature = Amount::ZERO;
let mut trusted_pending = Amount::ZERO;
for (spk_i, txout) in self.filter_unspent_outpoints(outpoints) {
match &txout.chain_position {
- ChainPosition::Confirmed { .. } => {
- if txout.is_confirmed_and_spendable(self.tip.height) {
+ ChainPosition::Confirmed { anchor, .. } => {
+ let confirmation_height = anchor.confirmation_height_upper_bound();
+ let confirmations = self
+ .tip
+ .height
+ .saturating_sub(confirmation_height)
+ .saturating_add(1);
+ let min_confirmations = min_confirmations.max(1); // 0 and 1 behave identically
+
+ if confirmations < min_confirmations {
+ // Not enough confirmations, treat as trusted/untrusted pending
+ if trust_predicate(&spk_i, txout.txout.script_pubkey) {
+ trusted_pending += txout.txout.value;
+ } else {
+ untrusted_pending += txout.txout.value;
+ }
+ } else if txout.is_confirmed_and_spendable(self.tip.height) {
confirmed += txout.txout.value;
} else if !txout.is_mature(self.tip.height) {
immature += txout.txout.value;
.balance(
graph.index.outpoints().iter().cloned(),
|_, spk: ScriptBuf| trusted_spks.contains(&spk),
+ 1,
);
let confirmed_txouts_txid = txouts
.balance(
env.indexer.outpoints().iter().cloned(),
|_, spk: ScriptBuf| env.indexer.index_of_spk(spk).is_some(),
+ 1,
);
assert_eq!(
balance, scenario.exp_balance,
let outpoints = recv_graph.index.outpoints().clone();
let balance = recv_graph
.canonical_view(recv_chain, chain_tip, CanonicalizationParams::default())
- .balance(outpoints, |_, _| true);
+ .balance(outpoints, |_, _| true, 1);
Ok(balance)
}
synced_to.block_id(),
CanonicalizationParams::default(),
)
- .balance(graph.index.outpoints().iter().cloned(), |(k, _), _| {
- k == &Keychain::Internal
- })
+ .balance(
+ graph.index.outpoints().iter().cloned(),
+ |(k, _), _| k == &Keychain::Internal,
+ 1,
+ )
};
println!(
"[{:>10}s] synced to {} @ {} | total: {}",
synced_to.block_id(),
CanonicalizationParams::default(),
)
- .balance(graph.index.outpoints().iter().cloned(), |(k, _), _| {
- k == &Keychain::Internal
- })
+ .balance(
+ graph.index.outpoints().iter().cloned(),
+ |(k, _), _| k == &Keychain::Internal,
+ 1,
+ )
};
println!(
"[{:>10}s] synced to {} @ {} / {} | total: {}",
chain.get_chain_tip()?,
CanonicalizationParams::default(),
)?
- .balance(graph.index.outpoints().iter().cloned(), |(k, _), _| {
- k == &Keychain::Internal
- });
+ .balance(
+ graph.index.outpoints().iter().cloned(),
+ |(k, _), _| k == &Keychain::Internal,
+ 1,
+ );
let confirmed_total = balance.confirmed + balance.immature;
let unconfirmed_total = balance.untrusted_pending + balance.trusted_pending;