feat(wallet): consolidate notes per address, keeping each address's funds in place
Users who keep funds apart under different labelled addresses can now shed notes (smaller wallet, faster scans/witness rebuilds) without the funds being merged into a single address. - Merge/Consolidate dialog: "Where should the funds go?" - Into one address (unchanged) or Keep in each address. The latter lists shielded addresses with 2+ spendable notes (label, note count, amount; too-small ones disabled) and merges each address's notes back into that address with z_mergetoaddress [addr] addr. Addresses with more notes than the batch size get several batches (the daemon locks a batch's notes before returning, so calls take disjoint notes); the dialog suggests a second run after those confirm. Per-address progress; ops keep running if the dialog is closed. The Consolidate (wallet-size) preset opens in this mode; Merge keeps "Into one". Shielded only: a transparent address can't take a merge's output. - services/note_consolidation: groupSpendableNotes, worthConsolidating, estimateBatches, consolidateInPlace (pure; driven by a merge-call function), with unit tests against a stand-in following dragonxd's rules. - fake_dragonxd: optional note wallet (FAKE_WALLET_NOTES, FAKE_MERGE_SECS) with z_mergetoaddress and its operations. - i18n: 17 new keys in all 8 languages; CJK subset rebuilt. - UI sweep: modal-consolidate target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#pragma once
|
||||
|
||||
// Consolidating notes WITHOUT moving funds between addresses: each address's notes are merged back into
|
||||
// that same address (z_mergetoaddress [addr] addr), so funds kept apart under different labels stay apart
|
||||
// while the wallet sheds notes (fewer notes = smaller wallet, faster witness rebuilds and scans).
|
||||
// Shielded only: a transparent address can't receive a non-coinbase output on DragonX (private chain).
|
||||
// Pure apart from the merge call it is handed, so the test target can drive it.
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dragonx {
|
||||
namespace services {
|
||||
|
||||
// One shielded address's spendable notes (confirmed, spendable), from z_listunspent.
|
||||
struct NoteGroup {
|
||||
std::string address;
|
||||
int notes = 0;
|
||||
double amount = 0.0;
|
||||
};
|
||||
|
||||
// Spendable shielded notes grouped by address, most notes first. Transparent UTXOs and unconfirmed or
|
||||
// watch-only notes are left out (z_mergetoaddress only spends confirmed notes it has keys for).
|
||||
std::vector<NoteGroup> groupSpendableNotes(const nlohmann::json& zListUnspent);
|
||||
|
||||
// Worth consolidating in place: at least two notes, and more than one batch's fee to work with.
|
||||
bool worthConsolidating(const NoteGroup& group, double fee);
|
||||
|
||||
// Merge calls needed to bring `notes` down when each call takes at most `limit` of them. A lone leftover
|
||||
// note isn't a call (the daemon refuses to merge one note into its own address).
|
||||
int estimateBatches(int notes, int limit);
|
||||
|
||||
// What one z_mergetoaddress call reported.
|
||||
struct MergeCallResult {
|
||||
std::string opid;
|
||||
int merging = 0; // notes this call is merging
|
||||
int remaining = 0; // notes of the address it left for a later call
|
||||
std::string error; // non-empty: the call failed (nothing was submitted)
|
||||
};
|
||||
using MergeCall = std::function<MergeCallResult(const std::string& address)>;
|
||||
|
||||
// The outcome for one address.
|
||||
struct AddressRun {
|
||||
std::string address;
|
||||
std::vector<std::string> opids; // one per submitted batch
|
||||
int notesMerged = 0;
|
||||
int remaining = 0; // notes still left after the last batch
|
||||
std::string error; // the call that stopped it, if any (earlier batches still went out)
|
||||
};
|
||||
|
||||
// Submit merges of `address` into itself until fewer than two of its notes are left unmerged. The daemon
|
||||
// locks a batch's notes before the call returns, so each call takes the next batch. At most maxBatches.
|
||||
AddressRun consolidateInPlace(const std::string& address, const MergeCall& call, int maxBatches = 20);
|
||||
|
||||
} // namespace services
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user