Skip to content

Commit d147f66

Browse files
committed
feat(bdk)!: remove TransactionDetails, change Wallet::get_tx, TxBuilder::finish return types
BREAKING CHANGES: Removed - TransactionDetails struct Changed - Wallet::get_tx now returns CanonicalTx instead of TransactionDetails - TxBuilder::finish now returns only a PartiallySignedTransaction Added - TransactionAmount struct with tx sent and received amounts - Wallet::sent_and_received function returns TransactionAmount - Wallet::fee_amount and Wallet::fee_rate functions for tx fees - Wallet::get_txout function for all known wallet txouts Fixed - impacted tests
1 parent 8f38e96 commit d147f66

File tree

11 files changed

+441
-337
lines changed

11 files changed

+441
-337
lines changed

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/types.rs

Lines changed: 7 additions & 32 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,38 +234,13 @@ impl Utxo {
234234
}
235235
}
236236

237-
/// A wallet transaction
237+
/// A `Transaction` sent and received amounts.
238238
#[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.
239+
pub struct TransactionAmounts {
240+
/// amount sent, in sats
249241
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-
}
242+
/// amount received, in sats
243+
pub received: u64,
269244
}
270245

271246
#[cfg(test)]

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: 95 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use core::fmt;
3939
use core::ops::Deref;
4040
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
4141

42+
use bdk_chain::indexed_tx_graph::Indexer;
4243
#[allow(unused_imports)]
4344
use log::{debug, error, info, trace};
4445

@@ -427,27 +428,97 @@ impl<D> Wallet<D> {
427428
.next()
428429
}
429430

430-
/// Return a single transactions made and received by the wallet
431-
///
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> {
431+
/// Returns a `TxOut` known by this wallet corresponding to `outpoint` if it exists in the
432+
/// wallet's database whether spent or not.
433+
pub fn get_txout(&self, outpoint: &OutPoint) -> Option<TxOut> {
434+
self.indexed_graph
435+
.graph()
436+
.all_txouts()
437+
.filter_map(|(op, txo)| {
438+
if op.eq(outpoint) {
439+
Some(txo.clone())
440+
} else {
441+
None
442+
}
443+
})
444+
.next()
445+
}
446+
447+
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in sats.
448+
/// If the `Wallet` is missing a TxOut for an input returns None.
449+
pub fn fee_amount(&self, tx: &Transaction) -> Option<u64> {
450+
let txouts: Option<Vec<TxOut>> = tx
451+
.input
452+
.iter()
453+
.map(|txin| self.get_txout(&txin.previous_output))
454+
.collect();
455+
456+
txouts.map(|inputs| {
457+
let input_amount: u64 = inputs.iter().map(|i| i.value).sum();
458+
let output_amount: u64 = tx.output.iter().map(|o| o.value).sum();
459+
input_amount
460+
.checked_sub(output_amount)
461+
.expect("input amount must be greater than output amount")
462+
})
463+
}
464+
465+
/// The transaction's `FeeRate`. If the `Wallet` is missing a `TxOut` for an input returns None.
466+
pub fn fee_rate(&self, tx: &Transaction) -> Option<FeeRate> {
467+
let fee_amount = self.fee_amount(tx);
468+
fee_amount.map(|fee| {
469+
let weight = tx.weight();
470+
FeeRate::from_wu(fee, weight)
471+
})
472+
}
473+
474+
/// Return `TransactionAmounts` for a `Transaction` in relation to the `Wallet` and it's
475+
/// descriptors.
476+
pub fn sent_and_received(&self, tx: &Transaction) -> TransactionAmounts {
477+
let index = &self.indexed_graph.index;
478+
479+
let received = tx
480+
.output
481+
.iter()
482+
.map(|txout| {
483+
if index.index_of_spk(&txout.script_pubkey).is_some() {
484+
txout.value
485+
} else {
486+
0
487+
}
488+
})
489+
.sum();
490+
491+
let sent = tx
492+
.input
493+
.iter()
494+
.map(|txin| {
495+
if let Some((_, txout)) = index.txout(txin.previous_output) {
496+
txout.value
497+
} else {
498+
0
499+
}
500+
})
501+
.sum();
502+
503+
TransactionAmounts { sent, received }
504+
}
505+
506+
/// Return a single `CanonicalTx` made and received by the wallet or `None` if it doesn't
507+
/// exist in the wallet
508+
pub fn get_tx(
509+
&self,
510+
txid: Txid,
511+
) -> Option<CanonicalTx<'_, Transaction, ConfirmationTimeAnchor>> {
435512
let graph = self.indexed_graph.graph();
436513

437-
let canonical_tx = CanonicalTx {
514+
Some(CanonicalTx {
438515
observed_as: graph.get_chain_position(
439516
&self.chain,
440517
self.chain.tip().unwrap_or_default(),
441518
txid,
442519
)?,
443520
node: graph.get_tx_node(txid)?,
444-
};
445-
446-
Some(new_tx_details(
447-
&self.indexed_graph,
448-
canonical_tx,
449-
include_raw,
450-
))
521+
})
451522
}
452523

453524
/// Add a new checkpoint to the wallet's internal view of the chain.
@@ -524,6 +595,10 @@ impl<D> Wallet<D> {
524595
let changeset: ChangeSet = self.indexed_graph.insert_tx(&tx, anchor, last_seen).into();
525596
let changed = !changeset.is_empty();
526597
self.persist.stage(changeset);
598+
599+
let additions = self.indexed_graph.index.index_tx(&tx);
600+
self.indexed_graph.index.apply_additions(additions);
601+
527602
Ok(changed)
528603
}
529604

@@ -599,7 +674,7 @@ impl<D> Wallet<D> {
599674
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
600675
/// # let mut wallet = doctest_wallet!();
601676
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
602-
/// let (psbt, details) = {
677+
/// let psbt = {
603678
/// let mut builder = wallet.build_tx();
604679
/// builder
605680
/// .add_recipient(to_address.script_pubkey(), 50_000);
@@ -624,7 +699,7 @@ impl<D> Wallet<D> {
624699
&mut self,
625700
coin_selection: Cs,
626701
params: TxParams,
627-
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
702+
) -> Result<psbt::PartiallySignedTransaction, Error>
628703
where
629704
D: PersistBackend<ChangeSet>,
630705
{
@@ -969,20 +1044,8 @@ impl<D> Wallet<D> {
9691044
// sort input/outputs according to the chosen algorithm
9701045
params.ordering.sort_tx(&mut tx);
9711046

972-
let txid = tx.txid();
973-
let sent = coin_selection.local_selected_amount();
9741047
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))
1048+
Ok(psbt)
9861049
}
9871050

9881051
/// Bump the fee of a transaction previously created with this wallet.
@@ -1001,7 +1064,7 @@ impl<D> Wallet<D> {
10011064
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
10021065
/// # let mut wallet = doctest_wallet!();
10031066
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
1004-
/// let (mut psbt, _) = {
1067+
/// let mut psbt = {
10051068
/// let mut builder = wallet.build_tx();
10061069
/// builder
10071070
/// .add_recipient(to_address.script_pubkey(), 50_000)
@@ -1011,7 +1074,7 @@ impl<D> Wallet<D> {
10111074
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
10121075
/// let tx = psbt.extract_tx();
10131076
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee
1014-
/// let (mut psbt, _) = {
1077+
/// let mut psbt = {
10151078
/// let mut builder = wallet.build_fee_bump(tx.txid())?;
10161079
/// builder
10171080
/// .fee_rate(FeeRate::from_sat_per_vb(5.0));
@@ -1171,7 +1234,7 @@ impl<D> Wallet<D> {
11711234
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
11721235
/// # let mut wallet = doctest_wallet!();
11731236
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
1174-
/// let (mut psbt, _) = {
1237+
/// let mut psbt = {
11751238
/// let mut builder = wallet.build_tx();
11761239
/// builder.add_recipient(to_address.script_pubkey(), 50_000);
11771240
/// builder.finish()?
@@ -1721,7 +1784,7 @@ impl<D> Wallet<D> {
17211784
Ok(changed)
17221785
}
17231786

1724-
/// Commits all curently [`staged`] changed to the persistence backend returning and error when
1787+
/// Commits all currently [`staged`] changed to the persistence backend returning and error when
17251788
/// this fails.
17261789
///
17271790
/// This returns whether the `update` resulted in any changes.
@@ -1812,61 +1875,6 @@ fn new_local_utxo(
18121875
}
18131876
}
18141877

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-
18701878
#[macro_export]
18711879
#[doc(hidden)]
18721880
/// 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)