Skip to content

Commit 40d5af3

Browse files
committed
feat(wallet)!: 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 40d5af3

File tree

11 files changed

+433
-334
lines changed

11 files changed

+433
-334
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: 90 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -427,27 +427,97 @@ impl<D> Wallet<D> {
427427
.next()
428428
}
429429

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

437-
let canonical_tx = CanonicalTx {
513+
Some(CanonicalTx {
438514
observed_as: graph.get_chain_position(
439515
&self.chain,
440516
self.chain.tip().unwrap_or_default(),
441517
txid,
442518
)?,
443519
node: graph.get_tx_node(txid)?,
444-
};
445-
446-
Some(new_tx_details(
447-
&self.indexed_graph,
448-
canonical_tx,
449-
include_raw,
450-
))
520+
})
451521
}
452522

453523
/// Add a new checkpoint to the wallet's internal view of the chain.
@@ -599,7 +669,7 @@ impl<D> Wallet<D> {
599669
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
600670
/// # let mut wallet = doctest_wallet!();
601671
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
602-
/// let (psbt, details) = {
672+
/// let psbt = {
603673
/// let mut builder = wallet.build_tx();
604674
/// builder
605675
/// .add_recipient(to_address.script_pubkey(), 50_000);
@@ -624,7 +694,7 @@ impl<D> Wallet<D> {
624694
&mut self,
625695
coin_selection: Cs,
626696
params: TxParams,
627-
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
697+
) -> Result<psbt::PartiallySignedTransaction, Error>
628698
where
629699
D: PersistBackend<ChangeSet>,
630700
{
@@ -969,20 +1039,8 @@ impl<D> Wallet<D> {
9691039
// sort input/outputs according to the chosen algorithm
9701040
params.ordering.sort_tx(&mut tx);
9711041

972-
let txid = tx.txid();
973-
let sent = coin_selection.local_selected_amount();
9741042
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))
1043+
Ok(psbt)
9861044
}
9871045

9881046
/// Bump the fee of a transaction previously created with this wallet.
@@ -1001,7 +1059,7 @@ impl<D> Wallet<D> {
10011059
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
10021060
/// # let mut wallet = doctest_wallet!();
10031061
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
1004-
/// let (mut psbt, _) = {
1062+
/// let mut psbt = {
10051063
/// let mut builder = wallet.build_tx();
10061064
/// builder
10071065
/// .add_recipient(to_address.script_pubkey(), 50_000)
@@ -1011,7 +1069,7 @@ impl<D> Wallet<D> {
10111069
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
10121070
/// let tx = psbt.extract_tx();
10131071
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee
1014-
/// let (mut psbt, _) = {
1072+
/// let mut psbt = {
10151073
/// let mut builder = wallet.build_fee_bump(tx.txid())?;
10161074
/// builder
10171075
/// .fee_rate(FeeRate::from_sat_per_vb(5.0));
@@ -1171,7 +1229,7 @@ impl<D> Wallet<D> {
11711229
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
11721230
/// # let mut wallet = doctest_wallet!();
11731231
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
1174-
/// let (mut psbt, _) = {
1232+
/// let mut psbt = {
11751233
/// let mut builder = wallet.build_tx();
11761234
/// builder.add_recipient(to_address.script_pubkey(), 50_000);
11771235
/// builder.finish()?
@@ -1721,7 +1779,7 @@ impl<D> Wallet<D> {
17211779
Ok(changed)
17221780
}
17231781

1724-
/// Commits all curently [`staged`] changed to the persistence backend returning and error when
1782+
/// Commits all currently [`staged`] changed to the persistence backend returning and error when
17251783
/// this fails.
17261784
///
17271785
/// This returns whether the `update` resulted in any changes.
@@ -1812,61 +1870,6 @@ fn new_local_utxo(
18121870
}
18131871
}
18141872

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-
18701873
#[macro_export]
18711874
#[doc(hidden)]
18721875
/// 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)