Skip to content

feat: Add Wallet::all_tx_details() - #1068

Open
MusabYK wants to merge 6 commits into
bitcoindevkit:masterfrom
MusabYK:expose-wallet-all-tx-details
Open

MusabYK wants to merge 6 commits into
bitcoindevkit:masterfrom
MusabYK:expose-wallet-all-tx-details

Conversation

@MusabYK

@MusabYK MusabYK commented Aug 10, 2026

Copy link
Copy Markdown

Description

Wallet currently exposes two methods for querying transactions:

transactions(&self) -> Vec: Returns all canonical transactions, but only provides basic information (txid, chain position, raw transaction). It lacks rich metadata such as fee, fee_rate, sent, received, and balance_delta.

tx_details(&self, txid: Arc) -> Option: Returns rich transaction details, but only for a single transaction at a time.

When building user interfaces like a transaction history screen in foreign language bindings (e.g. Dart), consumers are forced into an N+1 FFI round-trips for every transaction, this creates unnecessary overhead across language boundaries and inflates boilerplate in down-stream bindings.

all_tx_details() does this in a single call from the caller's perspective.

Notes to the reviewers

If maintainers are aligned with this approach, I would be happy to add tests and docs.

Additional implementation finding

While implementing the this API, it was found that it only solve first part of the performance issue. The original Rust-side approach of calling wallet.tx_details() once per transaction also introduces repeated transaction-history scans.

In bdk_wallet 3.1.0, Wallet::tx_details(txid) locates the requested transaction by scanning self.transactions()

This means the optimization has two dimensions:

  1. FFI boundary: reduce N + 1 FFI calls to a single bulk call.
  2. Rust implementation: avoid repeatedly scanning the wallet transaction history for each transaction.

The implementation now derives TxDetails directly from the already-fetched WalletTx values returned by transactions_sort_by() without performing the repeated lookup.

Documentation

Changelog

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing
  • I've added exactly one changelog:* label
  • I've linked the relevant upstream docs or specs above

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

@MusabYK MusabYK changed the title Add Wallet::all_tx_details() feat: Add Wallet::all_tx_details() Aug 10, 2026
@reez

reez commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

This sort of API was talked about in bdk_wallet#100 and scoped down to tx_details in #201

But I'm not sure if the N+1 FFI roundtrip cost you're describing was discussed?

Mostly just adding a comment here just to add some additional context. Definitely understand why its useful/valuable.

@MusabYK

MusabYK commented Aug 10, 2026

Copy link
Copy Markdown
Author

@reez Thanks for the context, i actually searched for such but couldn't find. bdk_wallet#100 identified the same underlying composition problem but FFI roundtrip was not factored, this PR is actually motivated by that cost when those APIs are consumed through FFI (serd) + developer ergonomics.

Comment thread bdk-ffi/src/wallet.rs Outdated

txids
.into_iter()
.filter_map(|txid| wallet.tx_details(txid))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid calling wallet.tx_details() once per txid here? In bdk_wallet 3.1.0, tx_details() scans self.transactions() to locate each transaction, so this repeatedly scans the full history and makes the bulk API O(n²). Since this method targets transaction-history performance, could we derive details from the already-fetched WalletTxs or expose an upstream bulk API?

@MusabYK MusabYK Aug 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @j-kon thanks for pointing that out, let me look into it.

@MusabYK

MusabYK commented Aug 13, 2026

Copy link
Copy Markdown
Author

Hi @j-kon. I tried restructuring the all_tx_details method to derive the details directly from the already-fetched WalletTxs like this:

`pub fn all_tx_details(&self) -> Veccrate::types::TxDetails {
let wallet = self.get_wallet();

  wallet
      .transactions_sort_by(|tx1, tx2| {
          tx2.chain_position.cmp(&tx1.chain_position)
      })
      .into_iter()
      .map(|canonical_tx| {
          let tx = canonical_tx.tx_node.tx;
          let txid = canonical_tx.tx_node.txid;

          let (sent, received) = wallet.sent_and_received(&tx);
          let fee = wallet.calculate_fee(&tx).ok();
          let fee_rate = wallet.calculate_fee_rate(&tx).ok();

          let balance_delta = received.to_sat() as i64 - sent.to_sat() as i64;

          bdk_wallet::TxDetails {
              txid,
              sent,
              received,
              fee,
              fee_rate,
              balance_delta: bdk_wallet::bitcoin::SignedAmount::from_sat(
                  balance_delta,
              ),
              chain_position: canonical_tx.chain_position,
              tx,
          }
          .into()
      })
      .collect()

}`

But I'm not sure if the balance_delta workaround calculation above is ok to use. In bdk_wallet 3.1.0, tx_details() derives it from:
self.tx_graph.index.net_value(&tx.tx_node.tx, ..) and tx_graph is private to Wallet.

And given that bdk_wallet#100 discussion is scoped down to tx_details. My understanding is that adding an all_tx_details() API directly to bdk_wallet would probably be easier. but it would not fit the goal of keeping the bdk_wallet API composable and application-independent as per bdk_wallet#100.

The blocker I'm hitting is the transaction-graph-derived balance_delta. So should we adopt this method or is there an existing public API in bdk_wallet/bdk_chain that can calculate the same balance_delta from an already-fetched WalletTx, or a supported way for the FFI crate to access the relevant transaction graph/index without relying on private Wallet internals?

@j-kon

j-kon commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

balance_delta = received - sent is correct; upstream bdk_chain::net_value() uses the same calculation. Please mirror its checked conversion rather than using to_sat() as i64:

let balance_delta = received.to_signed().expect("valid SignedAmount")
    - sent.to_signed().expect("valid SignedAmount");

This direct construction looks reasonable for avoiding the repeated transaction scans. Please also update the test to use a funded wallet and compare the resulting fields and ordering against tx_details().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants