Skip to content

Commit 16452c5

Browse files
committed
feat(wallet)!: remove TransactionDetails from bdk::Wallet API
Added - spk_txout_index::TotalSentReceived struct for tx total sent and received amounts - Wallet::total_send_received and SpkTxOutIndex::total_sent_received functions - Wallet::calculate_fee and Wallet::calculate_fee_rate functions - Wallet::error::CalculateFeeError BREAKING CHANGES: Removed - TransactionDetails struct Changed - Wallet::get_tx now returns CanonicalTx instead of TransactionDetails - TxBuilder::finish now returns only a PartiallySignedTransaction
1 parent 8f38e96 commit 16452c5

File tree

14 files changed

+457
-341
lines changed

14 files changed

+457
-341
lines changed

crates/bdk/src/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ pub enum Error {
8989
Psbt(bitcoin::util::psbt::Error),
9090
}
9191

92+
/// Errors returned by `Wallet::calculate_fee`.
93+
#[derive(Debug)]
94+
pub enum CalculateFeeError {
95+
/// Missing `TxOut` for one of the inputs of the tx
96+
MissingTxOut,
97+
/// When the transaction is invalid according to the graph it has a negative fee
98+
NegativeFee(i64),
99+
}
100+
92101
/// Errors returned by miniscript when updating inconsistent PSBTs
93102
#[derive(Debug, Clone)]
94103
pub enum MiniscriptPsbtError {

crates/bdk/src/keys/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ fn expand_multi_keys<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
754754
let (key_map, valid_networks) = key_maps_networks.into_iter().fold(
755755
(KeyMap::default(), any_network()),
756756
|(mut keys_acc, net_acc), (key, net)| {
757-
keys_acc.extend(key.into_iter());
757+
keys_acc.extend(key);
758758
let net_acc = merge_networks(&net_acc, &net);
759759

760760
(keys_acc, net_acc)

crates/bdk/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extern crate bip39;
2929

3030
#[allow(unused_imports)]
3131
#[macro_use]
32-
pub(crate) mod error;
32+
pub mod error;
3333
pub mod descriptor;
3434
pub mod keys;
3535
pub mod psbt;

crates/bdk/src/types.rs

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use core::convert::AsRef;
1414
use core::ops::Sub;
1515

1616
use bdk_chain::ConfirmationTime;
17-
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
18-
use bitcoin::{hash_types::Txid, util::psbt};
17+
use bitcoin::blockdata::transaction::{OutPoint, TxOut};
18+
use bitcoin::util::psbt;
1919

2020
use serde::{Deserialize, Serialize};
2121

@@ -234,40 +234,6 @@ impl Utxo {
234234
}
235235
}
236236

237-
/// A wallet transaction
238-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
239-
pub struct TransactionDetails {
240-
/// Optional transaction
241-
pub transaction: Option<Transaction>,
242-
/// Transaction id
243-
pub txid: Txid,
244-
/// Received value (sats)
245-
/// Sum of owned outputs of this transaction.
246-
pub received: u64,
247-
/// Sent value (sats)
248-
/// Sum of owned inputs of this transaction.
249-
pub sent: u64,
250-
/// Fee value in sats if it was available.
251-
pub fee: Option<u64>,
252-
/// If the transaction is confirmed, contains height and Unix timestamp of the block containing the
253-
/// transaction, unconfirmed transaction contains `None`.
254-
pub confirmation_time: ConfirmationTime,
255-
}
256-
257-
impl PartialOrd for TransactionDetails {
258-
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
259-
Some(self.cmp(other))
260-
}
261-
}
262-
263-
impl Ord for TransactionDetails {
264-
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
265-
self.confirmation_time
266-
.cmp(&other.confirmation_time)
267-
.then_with(|| self.txid.cmp(&other.txid))
268-
}
269-
}
270-
271237
#[cfg(test)]
272238
mod tests {
273239
use super::*;

crates/bdk/src/wallet/coin_selection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
//! // create wallet, sync, ...
8282
//!
8383
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
84-
//! let (psbt, details) = {
84+
//! let psbt = {
8585
//! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
8686
//! builder.add_recipient(to_address.script_pubkey(), 50_000);
8787
//! builder.finish()?

crates/bdk/src/wallet/mod.rs

Lines changed: 43 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use bdk_chain::{
2626
local_chain::{self, LocalChain, UpdateNotConnectedError},
2727
tx_graph::{CanonicalTx, TxGraph},
2828
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut,
29-
IndexedTxGraph, Persist, PersistBackend,
29+
IndexedTxGraph, Persist, PersistBackend, TotalSentReceived,
3030
};
3131
use bitcoin::consensus::encode::serialize;
3232
use bitcoin::secp256k1::Secp256k1;
@@ -65,7 +65,7 @@ use crate::descriptor::{
6565
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
6666
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
6767
};
68-
use crate::error::{Error, MiniscriptPsbtError};
68+
use crate::error::{CalculateFeeError, Error, MiniscriptPsbtError};
6969
use crate::psbt::PsbtUtils;
7070
use crate::signer::SignerError;
7171
use crate::types::*;
@@ -427,27 +427,49 @@ impl<D> Wallet<D> {
427427
.next()
428428
}
429429

430-
/// Return a single transactions made and received by the wallet
430+
/// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
431431
///
432-
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
433-
/// `include_raw` is `true`.
434-
pub fn get_tx(&self, txid: Txid, include_raw: bool) -> Option<TransactionDetails> {
432+
/// Note `tx` does not have to be in the graph for this to work.
433+
pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
434+
match self.indexed_graph.graph().calculate_fee(tx) {
435+
None => Err(CalculateFeeError::MissingTxOut),
436+
Some(fee) if fee < 0 => Err(CalculateFeeError::NegativeFee(fee)),
437+
Some(fee) => Ok(u64::try_from(fee).unwrap()),
438+
}
439+
}
440+
441+
/// Calculate the `FeeRate` for a given transaction.
442+
///
443+
/// Note `tx` does not have to be in the graph for this to work.
444+
pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<FeeRate, CalculateFeeError> {
445+
self.calculate_fee(tx).map(|fee| {
446+
let weight = tx.weight();
447+
FeeRate::from_wu(fee, weight)
448+
})
449+
}
450+
451+
/// Return `TotalSentReceived` for a `Transaction` in relation to the `Wallet` and it's
452+
/// descriptors.
453+
pub fn total_sent_received(&self, tx: &Transaction) -> TotalSentReceived {
454+
self.indexed_graph.index.total_sent_received(tx)
455+
}
456+
457+
/// Return a single `CanonicalTx` made and received by the wallet or `None` if it doesn't
458+
/// exist in the wallet
459+
pub fn get_tx(
460+
&self,
461+
txid: Txid,
462+
) -> Option<CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>> {
435463
let graph = self.indexed_graph.graph();
436464

437-
let canonical_tx = CanonicalTx {
465+
Some(CanonicalTx {
438466
observed_as: graph.get_chain_position(
439467
&self.chain,
440468
self.chain.tip().unwrap_or_default(),
441469
txid,
442470
)?,
443471
node: graph.get_tx_node(txid)?,
444-
};
445-
446-
Some(new_tx_details(
447-
&self.indexed_graph,
448-
canonical_tx,
449-
include_raw,
450-
))
472+
})
451473
}
452474

453475
/// Add a new checkpoint to the wallet's internal view of the chain.
@@ -599,7 +621,7 @@ impl<D> Wallet<D> {
599621
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
600622
/// # let mut wallet = doctest_wallet!();
601623
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
602-
/// let (psbt, details) = {
624+
/// let psbt = {
603625
/// let mut builder = wallet.build_tx();
604626
/// builder
605627
/// .add_recipient(to_address.script_pubkey(), 50_000);
@@ -624,7 +646,7 @@ impl<D> Wallet<D> {
624646
&mut self,
625647
coin_selection: Cs,
626648
params: TxParams,
627-
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
649+
) -> Result<psbt::PartiallySignedTransaction, Error>
628650
where
629651
D: PersistBackend<ChangeSet>,
630652
{
@@ -969,20 +991,8 @@ impl<D> Wallet<D> {
969991
// sort input/outputs according to the chosen algorithm
970992
params.ordering.sort_tx(&mut tx);
971993

972-
let txid = tx.txid();
973-
let sent = coin_selection.local_selected_amount();
974994
let psbt = self.complete_transaction(tx, coin_selection.selected, params)?;
975-
976-
let transaction_details = TransactionDetails {
977-
transaction: None,
978-
txid,
979-
confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
980-
received,
981-
sent,
982-
fee: Some(fee_amount),
983-
};
984-
985-
Ok((psbt, transaction_details))
995+
Ok(psbt)
986996
}
987997

988998
/// Bump the fee of a transaction previously created with this wallet.
@@ -1001,7 +1011,7 @@ impl<D> Wallet<D> {
10011011
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
10021012
/// # let mut wallet = doctest_wallet!();
10031013
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
1004-
/// let (mut psbt, _) = {
1014+
/// let mut psbt = {
10051015
/// let mut builder = wallet.build_tx();
10061016
/// builder
10071017
/// .add_recipient(to_address.script_pubkey(), 50_000)
@@ -1011,7 +1021,7 @@ impl<D> Wallet<D> {
10111021
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
10121022
/// let tx = psbt.extract_tx();
10131023
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee
1014-
/// let (mut psbt, _) = {
1024+
/// let mut psbt = {
10151025
/// let mut builder = wallet.build_fee_bump(tx.txid())?;
10161026
/// builder
10171027
/// .fee_rate(FeeRate::from_sat_per_vb(5.0));
@@ -1171,7 +1181,7 @@ impl<D> Wallet<D> {
11711181
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
11721182
/// # let mut wallet = doctest_wallet!();
11731183
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
1174-
/// let (mut psbt, _) = {
1184+
/// let mut psbt = {
11751185
/// let mut builder = wallet.build_tx();
11761186
/// builder.add_recipient(to_address.script_pubkey(), 50_000);
11771187
/// builder.finish()?
@@ -1721,7 +1731,7 @@ impl<D> Wallet<D> {
17211731
Ok(changed)
17221732
}
17231733

1724-
/// Commits all curently [`staged`] changed to the persistence backend returning and error when
1734+
/// Commits all currently [`staged`] changed to the persistence backend returning and error when
17251735
/// this fails.
17261736
///
17271737
/// This returns whether the `update` resulted in any changes.
@@ -1812,61 +1822,6 @@ fn new_local_utxo(
18121822
}
18131823
}
18141824

1815-
fn new_tx_details(
1816-
indexed_graph: &IndexedTxGraph<ConfirmationTimeAnchor, KeychainTxOutIndex<KeychainKind>>,
1817-
canonical_tx: CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>,
1818-
include_raw: bool,
1819-
) -> TransactionDetails {
1820-
let graph = indexed_graph.graph();
1821-
let index = &indexed_graph.index;
1822-
let tx = canonical_tx.node.tx;
1823-
1824-
let received = tx
1825-
.output
1826-
.iter()
1827-
.map(|txout| {
1828-
if index.index_of_spk(&txout.script_pubkey).is_some() {
1829-
txout.value
1830-
} else {
1831-
0
1832-
}
1833-
})
1834-
.sum();
1835-
1836-
let sent = tx
1837-
.input
1838-
.iter()
1839-
.map(|txin| {
1840-
if let Some((_, txout)) = index.txout(txin.previous_output) {
1841-
txout.value
1842-
} else {
1843-
0
1844-
}
1845-
})
1846-
.sum();
1847-
1848-
let inputs = tx
1849-
.input
1850-
.iter()
1851-
.map(|txin| {
1852-
graph
1853-
.get_txout(txin.previous_output)
1854-
.map(|txout| txout.value)
1855-
})
1856-
.sum::<Option<u64>>();
1857-
let outputs = tx.output.iter().map(|txout| txout.value).sum();
1858-
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
1859-
1860-
TransactionDetails {
1861-
transaction: if include_raw { Some(tx.clone()) } else { None },
1862-
txid: canonical_tx.node.txid,
1863-
received,
1864-
sent,
1865-
fee,
1866-
confirmation_time: canonical_tx.observed_as.cloned().into(),
1867-
}
1868-
}
1869-
18701825
#[macro_export]
18711826
#[doc(hidden)]
18721827
/// Macro for getting a wallet for use in a doctest

crates/bdk/src/wallet/tx_builder.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
//! .do_not_spend_change()
3333
//! // Turn on RBF signaling
3434
//! .enable_rbf();
35-
//! let (psbt, tx_details) = tx_builder.finish()?;
35+
//! let psbt = tx_builder.finish()?;
3636
//! # Ok::<(), bdk::Error>(())
3737
//! ```
3838
@@ -48,10 +48,7 @@ use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
4848

4949
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
5050
use super::ChangeSet;
51-
use crate::{
52-
types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
53-
TransactionDetails,
54-
};
51+
use crate::types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo};
5552
use crate::{Error, Utxo, Wallet};
5653
/// Context in which the [`TxBuilder`] is valid
5754
pub trait TxBuilderContext: core::fmt::Debug + Default + Clone {}
@@ -85,7 +82,7 @@ impl TxBuilderContext for BumpFee {}
8582
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
8683
/// # let addr2 = addr1.clone();
8784
/// // chaining
88-
/// let (psbt1, details) = {
85+
/// let psbt1 = {
8986
/// let mut builder = wallet.build_tx();
9087
/// builder
9188
/// .ordering(TxOrdering::Untouched)
@@ -95,7 +92,7 @@ impl TxBuilderContext for BumpFee {}
9592
/// };
9693
///
9794
/// // non-chaining
98-
/// let (psbt2, details) = {
95+
/// let psbt2 = {
9996
/// let mut builder = wallet.build_tx();
10097
/// builder.ordering(TxOrdering::Untouched);
10198
/// for addr in &[addr1, addr2] {
@@ -527,7 +524,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
527524
/// Returns the [`BIP174`] "PSBT" and summary details about the transaction.
528525
///
529526
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
530-
pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
527+
pub fn finish(self) -> Result<Psbt, Error>
531528
where
532529
D: PersistBackend<ChangeSet>,
533530
{
@@ -637,7 +634,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> {
637634
/// .drain_to(to_address.script_pubkey())
638635
/// .fee_rate(FeeRate::from_sat_per_vb(5.0))
639636
/// .enable_rbf();
640-
/// let (psbt, tx_details) = tx_builder.finish()?;
637+
/// let psbt = tx_builder.finish()?;
641638
/// # Ok::<(), bdk::Error>(())
642639
/// ```
643640
///

0 commit comments

Comments
 (0)