feat(wallet): migrate-to-seed, Phase 1 — isolated seed-wallet creation
First, fund-safe step of "migrate a legacy wallet to a seed wallet": mint a brand-new BIP39 mnemonic wallet in an ISOLATED throwaway datadir so the user can later sweep funds into it. No funds move and the real wallet.dat / main daemon are never touched — the isolated node runs concurrently on its own port. - EmbeddedDaemon: one-shot isolated-datadir override (setNextStartOverride) consumed on the next start(); a skip-default-port-check for an isolated instance beside the main daemon; and a tcpPortInUse() probe. The datadir's basename must be the assetchain name (DRAGONX) and the daemon reads <datadir>/DRAGONX.conf itself, so no -conf is passed (both learned from live testing against the hd-transparent-keys daemon). - SeedWalletCreator (src/daemon): picks a free port, writes a throwaway conf, starts an isolated dragonxd with -usemnemonic=1 -connect=0, waits for RPC, calls z_exportmnemonic + z_getnewaddress (the sweep target), stops it, and keeps/scrubs the temp datadir. Off-thread; the seed is wiped after use. - App: beginCreateSeedWallet (background) -> pumpSeedMigration (main-thread handoff) -> renderSeedMigrationDialog (Intro -> Working -> Show seed -> Error), reusing the seed_display grid + clipboard auto-clear. - Settings: "Migrate to seed…" button (full-node gated) + persisted pending migration state (dest address + temp datadir) for Phase 2 to adopt. Validated end-to-end against the hd-transparent-keys daemon: the isolated node comes up in ~4s and returns a 24-word mnemonic + a shielded z-address. Requires that daemon (the bundled Jun-28 build lacks z_exportmnemonic). The migration modal uses English literals for now (as renderBackupDialog does); the whole flow gets translated once Phase 2 finalizes it. Phase 2 (sweep + adopt) is next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
#include "wallet/lite_wallet_state_mapper.h" // LiteWalletAppRefreshModel for lite chat harvest
|
||||
#include "daemon/daemon_controller.h"
|
||||
#include "daemon/embedded_daemon.h"
|
||||
#include "daemon/seed_wallet_creator.h"
|
||||
#include "daemon/xmrig_manager.h"
|
||||
#include "util/pool_registry.h"
|
||||
#include "ui/notifications.h"
|
||||
@@ -2926,6 +2927,75 @@ void App::maybeRemindSeedBackup()
|
||||
});
|
||||
}
|
||||
|
||||
void App::beginCreateSeedWallet()
|
||||
{
|
||||
if (seed_migration_in_flight_) return;
|
||||
seed_migration_in_flight_ = true;
|
||||
seed_migration_step_ = SeedMigrationStep::Working;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(seed_migration_mutex_);
|
||||
seed_migration_done_ = false;
|
||||
seed_migration_progress_ = "Starting…";
|
||||
}
|
||||
// Runs the isolated create on a background thread; the main thread picks up progress + the
|
||||
// result in pumpSeedMigration(). No funds move and the real wallet is never touched.
|
||||
async_tasks_.submit("Create seed wallet", [this](const util::AsyncTaskManager::Token&) {
|
||||
auto res = daemon::SeedWalletCreator::create(/*keepDatadir=*/true,
|
||||
[this](const std::string& msg) {
|
||||
std::lock_guard<std::mutex> lk(seed_migration_mutex_);
|
||||
seed_migration_progress_ = msg;
|
||||
});
|
||||
std::lock_guard<std::mutex> lk(seed_migration_mutex_);
|
||||
seed_migration_ok_ = res.ok;
|
||||
seed_migration_r_seed_ = res.seedPhrase;
|
||||
seed_migration_r_dest_ = res.destAddress;
|
||||
seed_migration_r_tmp_ = res.tempDatadir;
|
||||
seed_migration_r_err_ = res.error;
|
||||
seed_migration_done_ = true;
|
||||
if (!res.seedPhrase.empty()) sodium_memzero(&res.seedPhrase[0], res.seedPhrase.size());
|
||||
});
|
||||
}
|
||||
|
||||
void App::pumpSeedMigration()
|
||||
{
|
||||
if (!seed_migration_in_flight_) return;
|
||||
bool done = false, ok = false;
|
||||
std::string seed, dest, tmp, err;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(seed_migration_mutex_);
|
||||
seed_migration_status_ = seed_migration_progress_; // live progress for the Working step
|
||||
done = seed_migration_done_;
|
||||
if (done) {
|
||||
ok = seed_migration_ok_;
|
||||
seed = std::move(seed_migration_r_seed_);
|
||||
dest = std::move(seed_migration_r_dest_);
|
||||
tmp = std::move(seed_migration_r_tmp_);
|
||||
err = std::move(seed_migration_r_err_);
|
||||
seed_migration_done_ = false;
|
||||
seed_migration_r_seed_.clear();
|
||||
}
|
||||
}
|
||||
if (!done) return;
|
||||
seed_migration_in_flight_ = false;
|
||||
if (ok) {
|
||||
seed_migration_seed_ = std::move(seed);
|
||||
seed_migration_dest_ = std::move(dest);
|
||||
seed_migration_temp_dir_ = std::move(tmp);
|
||||
seed_migration_backed_up_ = false;
|
||||
seed_migration_step_ = SeedMigrationStep::ShowSeed;
|
||||
// Persist the pending migration so a later sweep/adopt step can find the new wallet.
|
||||
if (settings_) {
|
||||
settings_->setSeedMigrationPending(true);
|
||||
settings_->setSeedMigrationDest(seed_migration_dest_);
|
||||
settings_->setSeedMigrationTempDir(seed_migration_temp_dir_);
|
||||
settings_->save();
|
||||
}
|
||||
} else {
|
||||
seed_migration_status_ = err.empty() ? std::string("Could not create the seed wallet.") : err;
|
||||
seed_migration_step_ = SeedMigrationStep::Error;
|
||||
}
|
||||
}
|
||||
|
||||
void App::backupWallet(const std::string& destination, std::function<void(bool, const std::string&)> callback)
|
||||
{
|
||||
if (!state_.connected || !rpc_) {
|
||||
|
||||
Reference in New Issue
Block a user