feat(migrate): daemon-update prompt + migrate-to-seed UX polish + i18n

Fixes the Windows migrate-to-seed failure (the old bundled daemon lacked
z_exportmnemonic) and rounds out the flow:

- seed_wallet_creator: actionable "daemon too old" message instead of the
  raw "Method not found" RPC error.
- Startup daemon-update prompt: if the wallet bundles a newer node than the
  installed one, offer to update it (once per version, keyed on a new
  daemon_update_prompted_size setting; never silently clobbers a custom
  node). "Install bundled" now also works when attached to an external /
  leftover daemon (RPC-stops it, then swaps + restarts).
- Migrate-to-seed Intro pre-flight: detect an already-mnemonic wallet
  (offer backup instead), an old daemon, or a locked/disconnected wallet
  before offering to create anything.
- No-funds path: a zero-balance wallet can adopt the new seed wallet
  directly (behind an acknowledgement checkbox) instead of a dead sweep.
- Loading spinners on the Working/Sweeping/Adopting steps; sweep coverage
  for the new intro / no-funds surfaces.
- Translations for all 18 new strings across 8 languages (+ rebuilt CJK
  subset font).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-07 22:16:47 -05:00
parent 48c79567d7
commit 82e61da62f
17 changed files with 418 additions and 22 deletions

View File

@@ -2879,13 +2879,43 @@ void App::showSeedMigrationDialog()
seed_migration_step_ = SeedMigrationStep::Confirming;
} else {
seed_migration_step_ = SeedMigrationStep::Sweep;
seed_migration_balance_loaded_ = false;
seed_migration_nofunds_confirmed_ = false;
refreshSeedMigrationBalance();
}
} else {
seed_migration_step_ = SeedMigrationStep::Intro;
// Fresh start: the Intro step will pre-flight the wallet (legacy vs already-seeded vs old
// daemon) before offering to create anything.
seed_migration_precheck_ = SeedMigrationPrecheck::Pending;
seed_migration_precheck_started_ = false;
}
}
// Intro pre-flight: probe z_exportmnemonic on the CURRENT wallet so we can branch the Intro:
// ok → the wallet is already mnemonic-backed (offer backup, not a pointless migration)
// "not derived" → a legacy wallet (proceed with create)
// "Method not found" → the daemon is too old to create a seed wallet (explain + bail)
// The probe needs an unlocked, connected wallet; the Intro gates on that before calling this.
void App::beginSeedMigrationPrecheck()
{
seed_migration_precheck_started_ = true;
seed_migration_precheck_ = SeedMigrationPrecheck::Pending;
exportSeedPhrase([this](bool ok, bool noMnemonic, const std::string& /*phrase*/,
const std::string& error) {
// exportSeedPhrase wipes the phrase after this callback — we only need the classification.
if (ok)
seed_migration_precheck_ = SeedMigrationPrecheck::AlreadyMnemonic;
else if (noMnemonic)
seed_migration_precheck_ = SeedMigrationPrecheck::Legacy;
else if (error.find("Method not found") != std::string::npos ||
error.find("-32601") != std::string::npos)
seed_migration_precheck_ = SeedMigrationPrecheck::DaemonTooOld;
else
seed_migration_precheck_ = SeedMigrationPrecheck::CheckFailed; // transient — allow re-check
});
}
void App::refreshSeedMigrationBalance()
{
if (!rpc_ || !worker_ || !state_.connected) return;
@@ -2898,7 +2928,7 @@ void App::refreshSeedMigrationBalance()
total = std::stod(res["total"].get<std::string>());
ok = true;
} catch (...) {}
return [this, total, ok]() { if (ok) seed_migration_balance_ = total; };
return [this, total, ok]() { if (ok) { seed_migration_balance_ = total; seed_migration_balance_loaded_ = true; } };
});
}