Skip to content

feat(key-wallet): support for more account types + strategy to consume all utxo available - #823

Merged
xdustinface merged 1 commit into
devfrom
feat/tx-builder-update
Jun 29, 2026
Merged

feat(key-wallet): support for more account types + strategy to consume all utxo available#823
xdustinface merged 1 commit into
devfrom
feat/tx-builder-update

Conversation

@ZocoLini

@ZocoLini ZocoLini commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Generalize tx building by funding account + add All (drain) selection

  • ManagedWalletInfo::build_and_sign_transaction[_with_signer] now take a funding source (AccountTypePreference + index) and a SelectionStrategy, instead of being hardcoded to BIP44 and the default strategy.
  • New SelectionStrategy::All: drains the account, spends every spendable UTXO into a single output worth total. Under All the change address is dropped before sizing, so the fee isn't inflated by a phantom change output.
  • CoinJoin accounts are spend-only at the API level (next_receive/change_address are unimplemented! I can implemente it or return an error but I consider unimplemented a better approach since the idea is to avoid using it, it is not a library erro).
  • dashd integration test: drain a BIP32 account into a BIP44 account end-to-end.

Known limitation: there is currently no way to build a transaction with more than 500 inputs (MAX_STANDARD_TX_INPUTS) through the set_funding method because set_funding pulls all of the account's UTXOs at once. To sweep an account holding >500 UTXOs you must build the transaction manually, taking the account's UTXOs and feeding them in ≤500-input chunks. This is worth calling out because the builder can be used to sweep a CoinJoin account with All, where exceeding 500 inputs is quite likely

Summary by CodeRabbit

  • New Features

    • Added support for draining all spendable funds from one wallet account into another in a single transaction.
    • Expanded transaction building to let users choose the source account type and coin-selection method.
  • Bug Fixes

    • Improved handling for missing accounts and oversized transactions with clearer failures.
    • Prevented incorrect change output calculations when sweeping all funds.
  • Tests

    • Added coverage for full-account drain behavior and balance updates after confirmation.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ZocoLini, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c914a85e-ce29-49ef-b46f-da1b9fb91f7f

📥 Commits

Reviewing files that changed from the base of the PR and between 9649b93 and 6bbee2f.

📒 Files selected for processing (8)
  • dash-spv/tests/dashd_sync/tests_transaction.rs
  • key-wallet-ffi/src/transaction.rs
  • key-wallet-manager/src/lib.rs
  • key-wallet/src/wallet/managed_wallet_info/coin_selection.rs
  • key-wallet/src/wallet/managed_wallet_info/mod.rs
  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs
  • key-wallet/src/wallet/managed_wallet_info/transaction_building.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
📝 Walkthrough

Walkthrough

The PR adds account-preference-based managed wallet transaction building, a new all-UTXO selection strategy, builder limits and errors, updated manager/FFI wiring, and tests including a drain transaction from one account into another.

Changes

Managed wallet drain flow

Layer / File(s) Summary
Account routing and accessors
key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs, key-wallet/src/wallet/managed_wallet_info/mod.rs, key-wallet/src/wallet/managed_wallet_info/transaction_building.rs
WalletInfoInterface adds account lookup/balance helpers, CoinJoin address matches are handled explicitly, and managed-wallet transaction building resolves source/source_index through AccountTypePreference.
All selection
key-wallet/src/wallet/managed_wallet_info/coin_selection.rs
SelectionStrategy::All is added to select every spendable UTXO, compute the post-fee deliverable, and return no change.
Unsigned assembly and limits
key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs, key-wallet/src/wallet/managed_wallet_info/transaction_building.rs
TransactionBuilder suppresses change sizing for SelectionStrategy::All, caps selected inputs, assigns the single output amount, and adds BuilderError variants for missing accounts and too many inputs.
Manager and FFI forwarding
key-wallet-manager/src/lib.rs, key-wallet-ffi/src/transaction.rs
The transaction-building API forwards SelectionStrategy and account preference through the manager and FFI entry point.
Transaction tests and drain flow
key-wallet/src/wallet/managed_wallet_info/transaction_building.rs, dash-spv/tests/dashd_sync/tests_transaction.rs
The builder tests and dash-spv integration test use the new signatures, account selection, and drain transaction path.

Sequence Diagram(s)

sequenceDiagram
  participant Test as dash-spv test_drain_account_into_another
  participant Manager as WalletManager<ManagedWalletInfo>
  participant ManagedWalletInfo
  participant CoinSelector
  participant TransactionBuilder
  participant Dashd as dashd regtest

  Test->>Manager: build_and_sign_transaction(..., SelectionStrategy::All)
  Manager->>ManagedWalletInfo: resolve source account and build transaction
  ManagedWalletInfo->>CoinSelector: select_coins_with_size(All, ...)
  ManagedWalletInfo->>TransactionBuilder: assemble_unsigned(...)
  TransactionBuilder-->>ManagedWalletInfo: unsigned transaction
  Test->>Dashd: broadcast, mine, and resync
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

ready-for-review

Suggested reviewers

  • xdustinface
  • llbartekll

Poem

🐰 I hopped through coins and swept them neat,
From BIP32 burrow to BIP44 seat.
One output glowed, no change to hide,
With a twitch of whiskers, the fee rode by.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clear and matches the main change: generalized account types plus a new strategy to spend all available UTXOs.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tx-builder-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ZocoLini
ZocoLini force-pushed the feat/tx-builder-update branch from bd92c00 to 748cf33 Compare June 25, 2026 10:28
@ZocoLini
ZocoLini requested a review from xdustinface June 25, 2026 10:33
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.79070% with 64 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.15%. Comparing base (ddb59d9) to head (6bbee2f).

Files with missing lines Patch % Lines
...wallet/managed_wallet_info/transaction_building.rs 69.62% 24 Missing ⚠️
...allet/managed_wallet_info/wallet_info_interface.rs 0.00% 18 Missing ⚠️
.../wallet/managed_wallet_info/transaction_builder.rs 47.61% 11 Missing ⚠️
key-wallet-manager/src/lib.rs 0.00% 4 Missing ⚠️
key-wallet/src/wallet/managed_wallet_info/mod.rs 0.00% 4 Missing ⚠️
key-wallet-ffi/src/transaction.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #823      +/-   ##
==========================================
- Coverage   73.18%   73.15%   -0.04%     
==========================================
  Files         323      323              
  Lines       72295    72438     +143     
==========================================
+ Hits        52911    52989      +78     
- Misses      19384    19449      +65     
Flag Coverage Δ
core 76.94% <ø> (ø)
ffi 47.71% <0.00%> (-0.02%) ⬇️
rpc 20.00% <ø> (ø)
spv 90.26% <ø> (-0.09%) ⬇️
wallet 71.77% <63.90%> (-0.05%) ⬇️
Files with missing lines Coverage Δ
...t/src/wallet/managed_wallet_info/coin_selection.rs 82.85% <100.00%> (+1.81%) ⬆️
key-wallet-ffi/src/transaction.rs 0.00% <0.00%> (ø)
key-wallet-manager/src/lib.rs 75.26% <0.00%> (-0.54%) ⬇️
key-wallet/src/wallet/managed_wallet_info/mod.rs 66.38% <0.00%> (-2.31%) ⬇️
.../wallet/managed_wallet_info/transaction_builder.rs 83.47% <47.61%> (-1.43%) ⬇️
...allet/managed_wallet_info/wallet_info_interface.rs 73.85% <0.00%> (-6.65%) ⬇️
...wallet/managed_wallet_info/transaction_building.rs 86.94% <69.62%> (-1.47%) ⬇️

... and 3 files with indirect coverage changes

@ZocoLini
ZocoLini force-pushed the feat/tx-builder-update branch from 748cf33 to 9649b93 Compare June 25, 2026 10:52
@ZocoLini
ZocoLini marked this pull request as ready for review June 25, 2026 10:52

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs (2)

83-86: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Skip change-address derivation for drain transactions.

set_funding derives a change address before assemble_unsigned clears it for SelectionStrategy::All, so a sweep can still advance/mark change-address state even though no change output is emitted.

As per coding guidelines, key-wallet/**/*.rs: "Use gap limit and staged address generation instead of unbounded address derivation in Rust address pool implementations."

Proposed fix
     pub fn set_funding(mut self, funds_acc: &mut ManagedCoreFundsAccount, acc: &Account) -> Self {
         self.inputs = funds_acc.utxos.values().cloned().collect();
-        self.change_addr = funds_acc.next_change_address(Some(&acc.account_xpub), true).ok();
+        self.change_addr = if self.selection_strategy == SelectionStrategy::All {
+            None
+        } else {
+            funds_acc.next_change_address(Some(&acc.account_xpub), true).ok()
+        };
         self
     }

Also applies to: 256-260

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs` around
lines 83 - 86, `TransactionBuilder::set_funding` is deriving and consuming a
change address even for drain/sweep flows, which can advance
`ManagedCoreFundsAccount` state unnecessarily. Update the funding setup so
change-address derivation is skipped when the transaction will use
`SelectionStrategy::All` (or equivalent drain mode), and keep
`assemble_unsigned` as the place that clears/omits change handling for those
cases. Use the existing `set_funding` and `assemble_unsigned` flow, plus
`ManagedCoreFundsAccount::next_change_address`, to ensure no change address is
generated or marked for sweeps.

Source: Coding guidelines


294-319: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not validate drains against the caller’s placeholder output amount.

For SelectionStrategy::All, line 319 overwrites the only output with total_input - fee, but line 294 can still reject the transaction if the caller supplied a non-zero placeholder amount larger than the account balance.

Proposed fix
-        if total_input < total_output + selection.estimated_fee {
+        let spend_amount_for_checks =
+            if self.selection_strategy == SelectionStrategy::All { 0 } else { total_output };
+
+        if total_input < spend_amount_for_checks + selection.estimated_fee {
             return Err(BuilderError::InsufficientFunds {
                 available: total_input,
-                required: total_output + selection.estimated_fee,
+                required: spend_amount_for_checks + selection.estimated_fee,
             });
         }
 
         let change_amount =
-            total_input.saturating_sub(total_output).saturating_sub(selection.estimated_fee);
+            total_input.saturating_sub(spend_amount_for_checks).saturating_sub(selection.estimated_fee);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs` around
lines 294 - 319, The drain path in `TransactionBuilder` is still being checked
against the caller’s placeholder output amount before `SelectionStrategy::All`
overwrites it. Adjust the insufficient-funds validation in the builder logic so
the `All` strategy skips validating against `total_output` and instead only
ensures the balance covers the fee, then let the existing
`tx_outputs`/single-output rewrite set the final amount.
key-wallet-ffi/src/transaction.rs (1)

88-99: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Expose the new source account and selection strategy through FFI.

This FFI entrypoint still hardcodes BIP44 + BranchAndBound, so FFI callers cannot build BIP32/CoinJoin-funded transactions or use the new drain flow.

Consider adding FFI-safe enums/parameters, or a dedicated drain entrypoint if this legacy API must keep its current ABI.

Also applies to: 140-144

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@key-wallet-ffi/src/transaction.rs` around lines 88 - 99, The FFI entrypoint
wallet_build_and_sign_transaction still hardcodes the source account derivation
and coin selection behavior, so callers cannot choose BIP32/CoinJoin funding or
the new drain flow. Update the FFI surface around
wallet_build_and_sign_transaction (and the related drain path referenced by the
same comment) to accept FFI-safe parameters or enums for the source account and
selection strategy, or add a separate drain-specific entrypoint if the legacy
ABI must remain unchanged. Ensure the Rust-side transaction builder uses those
incoming values instead of always forcing BIP44 and BranchAndBound.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@key-wallet/src/wallet/managed_wallet_info/mod.rs`:
- Around line 177-179: The CoinJoin match arm in `managed_wallet_info::mod.rs`
currently panics via `unimplemented!`, but these public library methods should
not crash callers; update the `AccountTypePreference::CoinJoin` handling in both
affected match arms to return `None` just like the other “no address available”
cases. Use the same fix in the corresponding address lookup methods so
`CoinJoin` remains supported without panicking.

---

Outside diff comments:
In `@key-wallet-ffi/src/transaction.rs`:
- Around line 88-99: The FFI entrypoint wallet_build_and_sign_transaction still
hardcodes the source account derivation and coin selection behavior, so callers
cannot choose BIP32/CoinJoin funding or the new drain flow. Update the FFI
surface around wallet_build_and_sign_transaction (and the related drain path
referenced by the same comment) to accept FFI-safe parameters or enums for the
source account and selection strategy, or add a separate drain-specific
entrypoint if the legacy ABI must remain unchanged. Ensure the Rust-side
transaction builder uses those incoming values instead of always forcing BIP44
and BranchAndBound.

In `@key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs`:
- Around line 83-86: `TransactionBuilder::set_funding` is deriving and consuming
a change address even for drain/sweep flows, which can advance
`ManagedCoreFundsAccount` state unnecessarily. Update the funding setup so
change-address derivation is skipped when the transaction will use
`SelectionStrategy::All` (or equivalent drain mode), and keep
`assemble_unsigned` as the place that clears/omits change handling for those
cases. Use the existing `set_funding` and `assemble_unsigned` flow, plus
`ManagedCoreFundsAccount::next_change_address`, to ensure no change address is
generated or marked for sweeps.
- Around line 294-319: The drain path in `TransactionBuilder` is still being
checked against the caller’s placeholder output amount before
`SelectionStrategy::All` overwrites it. Adjust the insufficient-funds validation
in the builder logic so the `All` strategy skips validating against
`total_output` and instead only ensures the balance covers the fee, then let the
existing `tx_outputs`/single-output rewrite set the final amount.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ff46c80b-d76f-4aa0-955c-10843dc294ec

📥 Commits

Reviewing files that changed from the base of the PR and between d9fe0a3 and 9649b93.

📒 Files selected for processing (8)
  • dash-spv/tests/dashd_sync/tests_transaction.rs
  • key-wallet-ffi/src/transaction.rs
  • key-wallet-manager/src/lib.rs
  • key-wallet/src/wallet/managed_wallet_info/coin_selection.rs
  • key-wallet/src/wallet/managed_wallet_info/mod.rs
  • key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs
  • key-wallet/src/wallet/managed_wallet_info/transaction_building.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs

Comment thread key-wallet/src/wallet/managed_wallet_info/mod.rs
llbartekll
llbartekll previously approved these changes Jun 26, 2026
@github-actions github-actions Bot added the merge-conflict The PR conflicts with the target branch. label Jun 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

@xdustinface xdustinface left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good. But i wonder, how will users with > 500 utxos be able to drain them? Maybe we should cap the All strategy at 500 and call it Consolidate or something instead?

Im fine with it either way i guess but its worth a thought. Apparently it's less likely to be relevant for CoinJoin at least.

Image

Comment thread key-wallet/src/wallet/managed_wallet_info/mod.rs
@ZocoLini

Copy link
Copy Markdown
Collaborator Author

I like All, it better describes what the CoinSelector selects.

About what to do with accounts with +500 UTXOs, we had a discussion about it in the standup, the options I like the most is:

  • Expose a build method in the builder to split the UTXO into multiple Transactions. This would be easy to do hard to allow user to customize how UTXO are splitted
  • The other option is to expose TransactionBuilder though FFI for non Rust consumers and encourage Rust consumer to use the builder so they can build the transactions the way they want

Then, talking about the unimplemented!, I don't want to return an error honestly, what about debug_assert(false, "explanation") and the returning None so only debug builds panic??

@xdustinface

Copy link
Copy Markdown
Collaborator

Then, talking about the unimplemented!, I don't want to return an error honestly, what about debug_assert(false, "explanation") and the returning None so only debug builds panic??

Sure, go for it.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 29, 2026
@ZocoLini
ZocoLini force-pushed the feat/tx-builder-update branch from 96976c2 to 6bbee2f Compare June 29, 2026 09:46
@github-actions github-actions Bot removed the merge-conflict The PR conflicts with the target branch. label Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants