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,71 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "note_consolidation.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
namespace dragonx {
|
||||
namespace services {
|
||||
|
||||
std::vector<NoteGroup> groupSpendableNotes(const nlohmann::json& zListUnspent)
|
||||
{
|
||||
std::map<std::string, NoteGroup> byAddress;
|
||||
if (zListUnspent.is_array()) {
|
||||
for (const auto& n : zListUnspent) {
|
||||
if (!n.is_object()) continue;
|
||||
const std::string address = n.value("address", std::string());
|
||||
if (address.empty()) continue;
|
||||
if (n.value("confirmations", 0) < 1) continue; // z_mergetoaddress spends confirmed notes only
|
||||
if (!n.value("spendable", true)) continue; // watch-only: no key to spend it
|
||||
NoteGroup& g = byAddress[address];
|
||||
g.address = address;
|
||||
++g.notes;
|
||||
g.amount += n.value("amount", 0.0);
|
||||
}
|
||||
}
|
||||
std::vector<NoteGroup> out;
|
||||
out.reserve(byAddress.size());
|
||||
for (auto& kv : byAddress) out.push_back(std::move(kv.second));
|
||||
std::sort(out.begin(), out.end(), [](const NoteGroup& a, const NoteGroup& b) {
|
||||
if (a.notes != b.notes) return a.notes > b.notes;
|
||||
return a.address < b.address;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool worthConsolidating(const NoteGroup& group, double fee)
|
||||
{
|
||||
// The daemon rejects a merge whose fee is more than what it nets (merged value < 2 x fee).
|
||||
return group.notes >= 2 && group.amount > 2.0 * fee;
|
||||
}
|
||||
|
||||
int estimateBatches(int notes, int limit)
|
||||
{
|
||||
if (notes < 2) return 0;
|
||||
if (limit < 2) limit = 2;
|
||||
return notes / limit + ((notes % limit) >= 2 ? 1 : 0);
|
||||
}
|
||||
|
||||
AddressRun consolidateInPlace(const std::string& address, const MergeCall& call, int maxBatches)
|
||||
{
|
||||
AddressRun run;
|
||||
run.address = address;
|
||||
for (int b = 0; b < maxBatches; ++b) {
|
||||
MergeCallResult r = call(address);
|
||||
if (!r.error.empty()) {
|
||||
run.error = r.error; // earlier batches (if any) are already submitted and stand
|
||||
break;
|
||||
}
|
||||
run.opids.push_back(r.opid);
|
||||
run.notesMerged += r.merging;
|
||||
run.remaining = r.remaining;
|
||||
if (r.remaining < 2) break; // one leftover note can't be merged into its own address
|
||||
}
|
||||
return run;
|
||||
}
|
||||
|
||||
} // namespace services
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user