fix(wallet): surface the real z_getnewaddress error; sweep coinbase in migrate-to-seed

From a legacy-wallet migration support case (mined T-funds, 3y-old Silent Dragon wallet.dat): the balance
shows but new-address and migrate both fail with unhelpful/silent results. Two fixes:

1) Real address-creation error. createNewAddress() swallowed the daemon's z_getnewaddress error and the UI
   showed a generic "Failed to create shielded address". It now keeps the daemon's reason (friendly-mapped
   via friendlyRpcError) in last_address_error_, which the Receive tab and request-payment dialog show
   instead of the generic string — so a legacy/degraded wallet sees the actual "HD seed not found". And when
   that specific error appears it flips wallet_degraded_ + warns once, even for an EXTERNAL daemon
   (detectWalletDegraded() only scans the embedded daemon's log and would otherwise miss it).

2) Migrate-to-seed sweep skipped coinbase. beginSweepToSeedWallet()'s z_mergetoaddress ignores coinbase
   UTXOs, so a miner's coinbase-only balance produced "Could not find any funds to merge", the legacy
   balance never reached ~0, and the adopt gate stalled silently. The sweep now falls back to
   z_shieldcoinbase("*", dest, fee, 50) to the SAME shielded destination when the merge reports no funds.
   z_shieldcoinbase returns an opid just like the merge, so the single-opid adopt gate (opid -> txid ->
   confirm -> balance ~0 -> adopt) is unchanged, and a batch limit / mixed wallet drains via the existing
   "Sweep remaining" re-sweep.

Fund-path: the z_mergetoaddress call and its args are byte-identical; the fallback only ADDS a
z_shieldcoinbase to the same dest when merge finds nothing, and nothing is adopted until the swept funds
confirm and the legacy balance is ~0 (gate unchanged). Runtime-verified behavior (clean build + ctest; the
RPC paths are not pure-testable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 19:47:38 -05:00
co-authored by Claude Opus 4.8
parent a558a2a271
commit 6c2a277751
4 changed files with 53 additions and 9 deletions
+6 -4
View File
@@ -333,10 +333,11 @@ static void RenderAddressDropdown(App* app, float width) {
} else {
s_generating_address = true;
if (s_addr_type_filter != 2) {
app->createNewZAddress([](const std::string& addr) {
app->createNewZAddress([app](const std::string& addr) {
s_generating_address = false;
if (addr.empty())
Notifications::instance().error(TR("failed_create_shielded"));
Notifications::instance().error(app->lastAddressError().empty()
? std::string(TR("failed_create_shielded")) : app->lastAddressError());
else {
s_pending_select_address = addr;
Notifications::instance().success(TR("new_shielded_created"));
@@ -400,10 +401,11 @@ static void RenderMiningAddressNotice(App* app, const WalletState& state, float
s_cached_qr_data.clear();
} else {
s_generating_address = true; // no shielded address yet: make one, as "New" does
app->createNewZAddress([](const std::string& addr) {
app->createNewZAddress([app](const std::string& addr) {
s_generating_address = false;
if (addr.empty())
Notifications::instance().error(TR("failed_create_shielded"));
Notifications::instance().error(app->lastAddressError().empty()
? std::string(TR("failed_create_shielded")) : app->lastAddressError());
else {
s_pending_select_address = addr;
Notifications::instance().success(TR("new_shielded_created"));
+3 -2
View File
@@ -119,10 +119,11 @@ void RequestPaymentDialog::render(App* app)
if (material::TactileButton(TR("request_create_shielded"), ImVec2(0, 0), S.resolveFont(actionBtn.font))) {
if (app->isConnected()) {
s_creating_address = true;
app->createNewZAddress([](const std::string& addr) {
app->createNewZAddress([app](const std::string& addr) {
s_creating_address = false;
if (addr.empty()) {
Notifications::instance().error(TR("failed_create_shielded"));
Notifications::instance().error(app->lastAddressError().empty()
? std::string(TR("failed_create_shielded")) : app->lastAddressError());
} else {
Notifications::instance().success(TR("new_shielded_created"));
// Pre-select the new address if the dialog is still open and none chosen.