Skip to content

Bring back TxDetails #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use bitcoin::{
secp256k1::Secp256k1,
sighash::{EcdsaSighashType, TapSighashType},
transaction, Address, Amount, Block, BlockHash, FeeRate, Network, OutPoint, Psbt, ScriptBuf,
Sequence, Transaction, TxOut, Txid, Weight, Witness,
Sequence, SignedAmount, Transaction, TxOut, Txid, Weight, Witness,
};
use miniscript::{
descriptor::KeyMap,
Expand Down Expand Up @@ -72,7 +72,7 @@ use crate::wallet::{
error::{BuildFeeBumpError, CreateTxError, MiniscriptPsbtError},
signer::{SignOptions, SignerError, SignerOrdering, SignersContainer, TransactionSigner},
tx_builder::{FeePolicy, TxBuilder, TxParams},
utils::{check_nsequence_rbf, After, Older, SecpCtx},
utils::{check_nsequence_rbf, After, Older, SecpCtx, TxDetails},
};

// re-exports
Expand Down Expand Up @@ -861,6 +861,33 @@ impl Wallet {
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
}

/// Get the [`TxDetails`] of a wallet transaction.
///
/// If the transaction with txid [`Txid`] cannot be found in the wallet's transactions, `None`
/// is returned.
pub fn tx_details(&self, txid: Txid) -> Option<TxDetails> {
let tx: WalletTx = self.transactions().find(|c| c.tx_node.txid == txid)?;

let (sent, received) = self.sent_and_received(&tx.tx_node.tx);
let fee: Option<Amount> = self.calculate_fee(&tx.tx_node.tx).ok();
let fee_rate: Option<FeeRate> = self.calculate_fee_rate(&tx.tx_node.tx).ok();
let balance_delta: SignedAmount = self.indexed_graph.index.net_value(&tx.tx_node.tx, ..);
let chain_position = tx.chain_position;

let tx_details: TxDetails = TxDetails {
txid,
received,
sent,
fee,
fee_rate,
balance_delta,
chain_position,
tx: tx.tx_node.tx,
};

Some(tx_details)
}

/// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed).
///
/// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead.
Expand Down
34 changes: 32 additions & 2 deletions wallet/src/wallet/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
// You may not use this file except in accordance with one or both of these
// licenses.

use alloc::sync::Arc;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::{absolute, relative, Amount, Script, Sequence};

use bitcoin::{
absolute, relative, Amount, FeeRate, Script, Sequence, SignedAmount, Transaction, Txid,
};
use chain::{ChainPosition, ConfirmationBlockTime};
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};

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

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

/// Details about a transaction affecting the wallet (relevant and canonical).
#[derive(Debug)]
pub struct TxDetails {
/// The transaction id.
pub txid: Txid,
/// The sum of the transaction input amounts that spend from previous outputs tracked by this
/// wallet.
pub sent: Amount,
/// The sum of the transaction outputs that send to script pubkeys tracked by this wallet.
pub received: Amount,
/// The fee paid for the transaction. Note that to calculate the fee for a transaction with
/// inputs not owned by this wallet you must manually insert the TxOut(s) into the tx graph
/// using the insert_txout function. If those are not available, the field will be `None`.
pub fee: Option<Amount>,
/// The fee rate paid for the transaction. Note that to calculate the fee rate for a
/// transaction with inputs not owned by this wallet you must manually insert the TxOut(s) into
/// the tx graph using the insert_txout function. If those are not available, the field will be
/// `None`.
pub fee_rate: Option<FeeRate>,
/// The net effect of the transaction on the balance of the wallet.
pub balance_delta: SignedAmount,
/// The position of the transaction in the chain.
pub chain_position: ChainPosition<ConfirmationBlockTime>,
/// The complete [`Transaction`].
pub tx: Arc<Transaction>,
}

#[cfg(test)]
mod test {
// When nSequence is lower than this flag the timelock is interpreted as block-height-based,
Expand Down
26 changes: 25 additions & 1 deletion wallet/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError};
use bitcoin::constants::{ChainHash, COINBASE_MATURITY};
use bitcoin::hashes::Hash;
use bitcoin::key::Secp256k1;
use bitcoin::psbt;
use bitcoin::script::PushBytesBuf;
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
use bitcoin::taproot::TapNodeHash;
use bitcoin::{
absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf,
Sequence, Transaction, TxIn, TxOut, Txid, Weight,
};
use bitcoin::{psbt, SignedAmount};
use miniscript::{descriptor::KeyMap, Descriptor, DescriptorPublicKey};
use rand::rngs::StdRng;
use rand::SeedableRng;
Expand Down Expand Up @@ -4315,3 +4315,27 @@ fn test_wallet_transactions_relevant() {
assert!(full_tx_count_before < full_tx_count_after);
assert!(canonical_tx_count_before < canonical_tx_count_after);
}

#[test]
fn test_tx_details_method() {
let (test_wallet, txid_1) = get_funded_wallet_wpkh();
let tx_details_1_option = test_wallet.tx_details(txid_1);
dbg!(&tx_details_1_option);

assert!(tx_details_1_option.is_some());
let tx_details_1 = tx_details_1_option.unwrap();

assert_eq!(
tx_details_1.txid.to_string(),
"f2a03cdfe1bb6a295b0a4bb4385ca42f95e4b2c6d9a7a59355d32911f957a5b3"
);
assert_eq!(tx_details_1.received, Amount::from_sat(50000));
assert_eq!(tx_details_1.sent, Amount::from_sat(76000));
assert_eq!(tx_details_1.fee.unwrap(), Amount::from_sat(1000));
assert_eq!(tx_details_1.balance_delta, SignedAmount::from_sat(-26000));

// Transaction id not part of the TxGraph
let txid_2 = Txid::from_raw_hash(Hash::all_zeros());
let tx_details_2_option = test_wallet.tx_details(txid_2);
assert!(tx_details_2_option.is_none());
}
Loading