feat(migration): second backup acknowledgement + confirm Discard and Sweep
Migrate-to-seed is irreversible and fund-moving, so its gates are strengthened (UI only — the sweep/adopt/wipe logic, z_mergetoaddress args, amounts and destination are byte-identical): - Continue now requires a second, consequence-stating acknowledgement checkbox (funds move and are recoverable only via the seed phrase), not just "I wrote it down". - Discard, Sweep-all and Sweep-remaining each take a second confirming click (stating the amount being moved) instead of firing on one click; the confirm body runs verbatim only after confirmation, and arming never survives a close/reopen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+94
-34
@@ -4762,6 +4762,9 @@ void App::renderSeedMigrationDialog()
|
||||
if (!seed_migration_seed_.empty())
|
||||
sodium_memzero(&seed_migration_seed_[0], seed_migration_seed_.size());
|
||||
seed_migration_seed_.clear();
|
||||
// Disarm the inline confirms so an armed state can never survive a close/reopen.
|
||||
seed_migration_confirm_discard_ = false;
|
||||
seed_migration_confirm_sweep_ = false;
|
||||
};
|
||||
auto close = [this, &wipeSeed]() { wipeSeed(); show_seed_migration_ = false; };
|
||||
|
||||
@@ -4895,11 +4898,15 @@ void App::renderSeedMigrationDialog()
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::Checkbox(TR("mig_backed_up"), &seed_migration_backed_up_);
|
||||
ImGui::Checkbox(TR("mig_understood"), &seed_migration_understood_);
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::OnSurfaceMedium());
|
||||
ImGui::TextWrapped("%s", TR("mig_step1_done"));
|
||||
ImGui::PopStyleColor();
|
||||
if (!seed_migration_backed_up_) ImGui::BeginDisabled();
|
||||
// Continue requires BOTH acknowledgements, and is suppressed while the Discard confirm is armed.
|
||||
const bool ackOk = seed_migration_backed_up_ && seed_migration_understood_;
|
||||
const bool continueDisabled = !ackOk || seed_migration_confirm_discard_;
|
||||
if (continueDisabled) ImGui::BeginDisabled();
|
||||
if (ui::material::TactileButton(TR("mig_continue_sweep"), ImVec2(170 * dp, 0))) {
|
||||
wipeSeed(); // the sweep/adopt steps only use the address, never the seed itself
|
||||
seed_migration_step_ = SeedMigrationStep::Sweep;
|
||||
@@ -4907,23 +4914,40 @@ void App::renderSeedMigrationDialog()
|
||||
seed_migration_nofunds_confirmed_ = false;
|
||||
refreshSeedMigrationBalance();
|
||||
}
|
||||
if (!seed_migration_backed_up_) ImGui::EndDisabled();
|
||||
if (continueDisabled) ImGui::EndDisabled();
|
||||
if (!ackOk && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ui::material::Tooltip("%s", TR("mig_ack_required"));
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("mig_discard"), ImVec2(160 * dp, 0))) {
|
||||
// Throw away the isolated new wallet + clear the pending state.
|
||||
if (!seed_migration_temp_dir_.empty()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(seed_migration_temp_dir_, ec);
|
||||
// Discard now takes a second click: first click arms a confirm, since it deletes the new wallet.
|
||||
if (!seed_migration_confirm_discard_) {
|
||||
if (ui::material::TactileButton(TR("mig_discard"), ImVec2(160 * dp, 0)))
|
||||
seed_migration_confirm_discard_ = true;
|
||||
}
|
||||
if (seed_migration_confirm_discard_) {
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::ReadableError()));
|
||||
ImGui::TextWrapped("%s", TR("mig_discard_confirm"));
|
||||
ImGui::PopStyleColor();
|
||||
if (ui::material::TactileButton(TR("mig_discard_yes"), ImVec2(160 * dp, 0))) {
|
||||
// Throw away the isolated new wallet + clear the pending state. (body unchanged)
|
||||
if (!seed_migration_temp_dir_.empty()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove_all(seed_migration_temp_dir_, ec);
|
||||
}
|
||||
if (settings_) {
|
||||
settings_->setSeedMigrationPending(false);
|
||||
settings_->setSeedMigrationDest("");
|
||||
settings_->setSeedMigrationTempDir("");
|
||||
settings_->save();
|
||||
}
|
||||
seed_migration_temp_dir_.clear();
|
||||
seed_migration_dest_.clear();
|
||||
seed_migration_confirm_discard_ = false;
|
||||
close();
|
||||
}
|
||||
if (settings_) {
|
||||
settings_->setSeedMigrationPending(false);
|
||||
settings_->setSeedMigrationDest("");
|
||||
settings_->setSeedMigrationTempDir("");
|
||||
settings_->save();
|
||||
}
|
||||
seed_migration_temp_dir_.clear();
|
||||
seed_migration_dest_.clear();
|
||||
close();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("cancel"), ImVec2(110 * dp, 0)))
|
||||
seed_migration_confirm_discard_ = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -4999,17 +5023,35 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::Spacing();
|
||||
{
|
||||
const bool canSweep = !state_.isLocked() && seed_migration_balance_ > 0.0;
|
||||
if (!canSweep) ImGui::BeginDisabled();
|
||||
if (ui::material::TactileButton(TR("mig_sweep_all"), ImVec2(170 * dp, 0)))
|
||||
beginSweepToSeedWallet();
|
||||
if (!canSweep) ImGui::EndDisabled();
|
||||
if (!canSweep) seed_migration_confirm_sweep_ = false; // never keep a stale armed state
|
||||
if (!seed_migration_confirm_sweep_) {
|
||||
if (!canSweep) ImGui::BeginDisabled();
|
||||
if (ui::material::TactileButton(TR("mig_sweep_all"), ImVec2(170 * dp, 0)))
|
||||
seed_migration_confirm_sweep_ = true; // first click arms the confirm
|
||||
if (!canSweep) ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0)))
|
||||
refreshSeedMigrationBalance();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0)))
|
||||
close(); // pending state persists; resumes here next time
|
||||
} else {
|
||||
// Armed: state the amount being moved before the single sweep transaction fires.
|
||||
char cbuf[160]; snprintf(cbuf, sizeof(cbuf), TR("mig_sweep_confirm"), seed_migration_balance_);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::ReadableError()));
|
||||
ImGui::TextWrapped("%s", cbuf);
|
||||
ImGui::PopStyleColor();
|
||||
if (!canSweep) ImGui::BeginDisabled();
|
||||
if (ui::material::TactileButton(TR("mig_sweep_confirm_yes"), ImVec2(170 * dp, 0))) {
|
||||
seed_migration_confirm_sweep_ = false;
|
||||
beginSweepToSeedWallet(); // unchanged
|
||||
}
|
||||
if (!canSweep) ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("cancel"), ImVec2(110 * dp, 0)))
|
||||
seed_migration_confirm_sweep_ = false;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0)))
|
||||
refreshSeedMigrationBalance();
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0)))
|
||||
close(); // pending state persists; resumes here next time
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5049,17 +5091,35 @@ void App::renderSeedMigrationDialog()
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError());
|
||||
ImGui::TextWrapped("%s", TR("mig_remainder"));
|
||||
ImGui::PopStyleColor();
|
||||
if (ui::material::TactileButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0)))
|
||||
beginSweepToSeedWallet();
|
||||
if (!seed_migration_confirm_sweep_) {
|
||||
if (ui::material::TactileButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0)))
|
||||
seed_migration_confirm_sweep_ = true; // first click arms
|
||||
} else {
|
||||
char rcbuf[160];
|
||||
snprintf(rcbuf, sizeof(rcbuf), TR("mig_sweep_confirm"), seed_migration_legacy_remaining_);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::ReadableError()));
|
||||
ImGui::TextWrapped("%s", rcbuf);
|
||||
ImGui::PopStyleColor();
|
||||
if (ui::material::TactileButton(TR("mig_sweep_confirm_yes"), ImVec2(180 * dp, 0))) {
|
||||
seed_migration_confirm_sweep_ = false;
|
||||
beginSweepToSeedWallet(); // unchanged
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("cancel"), ImVec2(110 * dp, 0)))
|
||||
seed_migration_confirm_sweep_ = false;
|
||||
}
|
||||
} else {
|
||||
ImGui::TextColored(kMedium, "%s", TR("mig_waiting_mine"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0)))
|
||||
pollSweepStatus(/*forceBalance=*/true);
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0)))
|
||||
close(); // pending state + txid persist; resumes here next time
|
||||
// Refresh / Later are hidden while the sweep confirm is armed, to keep that row uncluttered.
|
||||
if (!seed_migration_confirm_sweep_) {
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("refresh"), ImVec2(110 * dp, 0)))
|
||||
pollSweepStatus(/*forceBalance=*/true);
|
||||
ImGui::SameLine();
|
||||
if (ui::material::TactileButton(TR("mig_later"), ImVec2(100 * dp, 0)))
|
||||
close(); // pending state + txid persist; resumes here next time
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user