Skip to content

Commit 6aef191

Browse files
feat: add Wallet::tx_details method
1 parent f0840c0 commit 6aef191

File tree

3 files changed

+85
-4
lines changed

3 files changed

+85
-4
lines changed

wallet/src/wallet/mod.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use bitcoin::{
4141
secp256k1::Secp256k1,
4242
sighash::{EcdsaSighashType, TapSighashType},
4343
transaction, Address, Amount, Block, BlockHash, FeeRate, Network, OutPoint, Psbt, ScriptBuf,
44-
Sequence, Transaction, TxOut, Txid, Weight, Witness,
44+
Sequence, SignedAmount, Transaction, TxOut, Txid, Weight, Witness,
4545
};
4646
use miniscript::{
4747
descriptor::KeyMap,
@@ -81,6 +81,7 @@ pub use changeset::ChangeSet;
8181
pub use params::*;
8282
pub use persisted::*;
8383
pub use utils::IsDust;
84+
pub use utils::TxDetails;
8485

8586
/// A Bitcoin wallet
8687
///
@@ -861,6 +862,33 @@ impl Wallet {
861862
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
862863
}
863864

865+
/// Get the [`TxDetails`] of a wallet transaction.
866+
///
867+
/// If the transaction with txid [`Txid`] cannot be found in the wallet's transactions, `None`
868+
/// is returned.
869+
pub fn tx_details(&self, txid: Txid) -> Option<TxDetails> {
870+
let tx: WalletTx = self.transactions().find(|c| c.tx_node.txid == txid)?;
871+
872+
let (sent, received) = self.sent_and_received(&tx.tx_node.tx);
873+
let fee: Option<Amount> = self.calculate_fee(&tx.tx_node.tx).ok();
874+
let fee_rate: Option<FeeRate> = self.calculate_fee_rate(&tx.tx_node.tx).ok();
875+
let balance_delta: SignedAmount = self.indexed_graph.index.net_value(&tx.tx_node.tx, ..);
876+
let chain_position = tx.chain_position;
877+
878+
let tx_details: TxDetails = TxDetails {
879+
txid,
880+
received,
881+
sent,
882+
fee,
883+
fee_rate,
884+
balance_delta,
885+
chain_position,
886+
tx: tx.tx_node.tx,
887+
};
888+
889+
Some(tx_details)
890+
}
891+
864892
/// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed).
865893
///
866894
/// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead.

wallet/src/wallet/utils.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
// You may not use this file except in accordance with one or both of these
1010
// licenses.
1111

12+
use alloc::sync::Arc;
1213
use bitcoin::secp256k1::{All, Secp256k1};
13-
use bitcoin::{absolute, relative, Amount, Script, Sequence};
14-
14+
use bitcoin::{
15+
absolute, relative, Amount, FeeRate, Script, Sequence, SignedAmount, Transaction, Txid,
16+
};
17+
use chain::{ChainPosition, ConfirmationBlockTime};
1518
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
1619

1720
use rand_core::RngCore;
@@ -133,6 +136,33 @@ pub(crate) fn shuffle_slice<T>(list: &mut [T], rng: &mut impl RngCore) {
133136

134137
pub(crate) type SecpCtx = Secp256k1<All>;
135138

139+
/// Details about a transaction affecting the wallet (relevant and canonical).
140+
#[derive(Debug)]
141+
pub struct TxDetails {
142+
/// The transaction id.
143+
pub txid: Txid,
144+
/// The sum of the transaction input amounts that spend from previous outputs tracked by this
145+
/// wallet.
146+
pub sent: Amount,
147+
/// The sum of the transaction outputs that send to script pubkeys tracked by this wallet.
148+
pub received: Amount,
149+
/// The fee paid for the transaction. Note that to calculate the fee for a transaction with
150+
/// inputs not owned by this wallet you must manually insert the TxOut(s) into the tx graph
151+
/// using the insert_txout function. If those are not available, the field will be `None`.
152+
pub fee: Option<Amount>,
153+
/// The fee rate paid for the transaction. Note that to calculate the fee rate for a
154+
/// transaction with inputs not owned by this wallet you must manually insert the TxOut(s) into
155+
/// the tx graph using the insert_txout function. If those are not available, the field will be
156+
/// `None`.
157+
pub fee_rate: Option<FeeRate>,
158+
/// The net effect of the transaction on the balance of the wallet.
159+
pub balance_delta: SignedAmount,
160+
/// The position of the transaction in the chain.
161+
pub chain_position: ChainPosition<ConfirmationBlockTime>,
162+
/// The complete [`Transaction`].
163+
pub tx: Arc<Transaction>,
164+
}
165+
136166
#[cfg(test)]
137167
mod test {
138168
// When nSequence is lower than this flag the timelock is interpreted as block-height-based,

wallet/tests/wallet.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError};
1919
use bitcoin::constants::{ChainHash, COINBASE_MATURITY};
2020
use bitcoin::hashes::Hash;
2121
use bitcoin::key::Secp256k1;
22-
use bitcoin::psbt;
2322
use bitcoin::script::PushBytesBuf;
2423
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
2524
use bitcoin::taproot::TapNodeHash;
2625
use bitcoin::{
2726
absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf,
2827
Sequence, Transaction, TxIn, TxOut, Txid, Weight,
2928
};
29+
use bitcoin::{psbt, SignedAmount};
3030
use miniscript::{descriptor::KeyMap, Descriptor, DescriptorPublicKey};
3131
use rand::rngs::StdRng;
3232
use rand::SeedableRng;
@@ -4617,3 +4617,26 @@ fn test_wallet_transactions_relevant() {
46174617
assert!(full_tx_count_before < full_tx_count_after);
46184618
assert!(canonical_tx_count_before < canonical_tx_count_after);
46194619
}
4620+
4621+
#[test]
4622+
fn test_tx_details_method() {
4623+
let (test_wallet, txid_1) = get_funded_wallet_wpkh();
4624+
let tx_details_1_option = test_wallet.tx_details(txid_1);
4625+
4626+
assert!(tx_details_1_option.is_some());
4627+
let tx_details_1 = tx_details_1_option.unwrap();
4628+
4629+
assert_eq!(
4630+
tx_details_1.txid.to_string(),
4631+
"f2a03cdfe1bb6a295b0a4bb4385ca42f95e4b2c6d9a7a59355d32911f957a5b3"
4632+
);
4633+
assert_eq!(tx_details_1.received, Amount::from_sat(50000));
4634+
assert_eq!(tx_details_1.sent, Amount::from_sat(76000));
4635+
assert_eq!(tx_details_1.fee.unwrap(), Amount::from_sat(1000));
4636+
assert_eq!(tx_details_1.balance_delta, SignedAmount::from_sat(-26000));
4637+
4638+
// Transaction id not part of the TxGraph
4639+
let txid_2 = Txid::from_raw_hash(Hash::all_zeros());
4640+
let tx_details_2_option = test_wallet.tx_details(txid_2);
4641+
assert!(tx_details_2_option.is_none());
4642+
}

0 commit comments

Comments
 (0)