feat(wallet): migrate-to-seed, Phase 2 — sweep + adopt (fund-safety gated)
Second half of "migrate a legacy wallet to a seed wallet": sweep all funds into the new seed wallet, then adopt it as the primary wallet. - Sweep (beginSweepToSeedWallet): z_mergetoaddress ["ANY_TADDR","ANY_ZADDR"] -> the new wallet's z-address, at the default fee, limits 0,0. The opid is tracked via the existing send-callback pipeline; the txid is persisted. - Confirming gate (fund-safety): after the sweep, pollSweepStatus() polls the tx confirmations + legacy balance. "Make this my wallet" only unlocks once the sweep is MINED (>= 1 conf) AND the old wallet is empty — so wallet.dat is never swapped while the funds could still bounce back or a remainder is left. A too-big-for-one-tx remainder offers a "Sweep remaining" re-sweep. - Adopt (beginAdoptSeedWallet, background): stop daemon -> move legacy wallet.dat to a timestamped .bak (never deleted; restored on failure) -> install the new wallet -> restart with -rescan. Exception-safe; daemon_restarting_ gates tryConnect + is always cleared; skips the restart while shutting down; beginShutdown joins the task first. - Resume: a pending migration reopens at Sweep, or at Confirming if the sweep txid is already recorded (re-derived from the chain) — never re-sweeps blind. - Secret hygiene: the seed is wiped leaving ShowSeed, on any modal dismiss (detected after BeginOverlayDialog), and in App::~App() (same fix applied to the seed-backup dialog). Built both variants; tests + hygiene pass. Two rounds of parallel adversarial review (12 findings fixed, then a clean re-verify) gate this fund-moving code. The migration modal uses English literals (as renderBackupDialog does); it gets translated once the flow is finalized. Live sweep test against a funded wallet is the next step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -258,6 +258,10 @@ public:
|
||||
void setSeedMigrationDest(const std::string& v) { seed_migration_dest_ = v; }
|
||||
std::string getSeedMigrationTempDir() const { return seed_migration_temp_dir_; }
|
||||
void setSeedMigrationTempDir(const std::string& v) { seed_migration_temp_dir_ = v; }
|
||||
// The sweep transaction id, persisted once the sweep is submitted — non-empty means the
|
||||
// migration is past the sweep, so a resume goes to the confirm/adopt stage (not sweep again).
|
||||
std::string getSeedMigrationSweepTxid() const { return seed_migration_sweep_txid_; }
|
||||
void setSeedMigrationSweepTxid(const std::string& v) { seed_migration_sweep_txid_ = v; }
|
||||
|
||||
// Security — auto-lock timeout (seconds; 0 = disabled)
|
||||
int getAutoLockTimeout() const { return auto_lock_timeout_; }
|
||||
@@ -461,6 +465,7 @@ private:
|
||||
bool seed_migration_pending_ = false;
|
||||
std::string seed_migration_dest_;
|
||||
std::string seed_migration_temp_dir_;
|
||||
std::string seed_migration_sweep_txid_;
|
||||
int auto_lock_timeout_ = 900; // 15 minutes
|
||||
int unlock_duration_ = 600; // 10 minutes
|
||||
bool pin_enabled_ = false;
|
||||
|
||||
Reference in New Issue
Block a user