From 0e2c786ebfb7dbe4943fba3a8cbc1043bee96abb Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 16 Jun 2026 02:11:19 -0500 Subject: [PATCH] fix(lite): welcome "Restore from seed" now prompts for the seed inline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On first run the lite welcome screen's "Restore from seed" button only showed a hint toast and bounced the user to Settings, dismissing the welcome with no wallet open — it never prompted for a seed. Add a real restore step to the welcome wizard: a seed-phrase field + optional birthday height, which calls beginRestoreWalletAsync() (same server failover as create/open), shows "Restoring…" progress, then completes (wallet syncs) or surfaces the error to retry. The seed buffer is wiped on success/Back and in finish(). (The Settings -> Lite -> Restore path already prompted for a seed; this fixes the first-run welcome path.) Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++--- src/util/i18n.cpp | 6 ++++ 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 45eaadf..5176854 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -2022,6 +2022,11 @@ void App::renderLiteFirstRunPrompt() static int progress = 0; // # words confirmed in order static double wrongFlashUntil = 0.0; // brief "not the next word" hint static bool creating = false; // async create (with failover) in flight + // Restore-from-seed step (step 3). The seed buffer is SECRET — wiped in finish() and on Back. + static char restoreSeed[512] = {}; + static int restoreBirthday = 0; + static bool restoring = false; // async restore in flight + static std::string restoreErr; // The welcome page is only relevant before any wallet exists; once create has started // (creating) or reached reveal/verify (step>0), keep showing it through to completion even @@ -2035,6 +2040,10 @@ void App::renderLiteFirstRunPrompt() auto finish = [&]() { wallet::secureWipeLiteSecret(seed); + sodium_memzero(restoreSeed, sizeof(restoreSeed)); + restoreBirthday = 0; + restoring = false; + restoreErr.clear(); words.clear(); chips.clear(); progress = 0; @@ -2105,9 +2114,7 @@ void App::renderLiteFirstRunPrompt() } ImGui::SameLine(); if (ImGui::Button(TR("lite_welcome_restore"), ImVec2(btnW, 0))) { - ui::Notifications::instance().info(TR("lite_welcome_restore_hint"), 8.0f); - setCurrentPage(ui::NavPage::Settings); - finish(); + step = 3; // inline seed-entry restore (see step 3 below) } ImGui::Spacing(); if (ImGui::Button(TR("lite_welcome_later"), @@ -2160,7 +2167,7 @@ void App::renderLiteFirstRunPrompt() ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f); finish(); } - } else { // step == 2 + } else if (step == 2) { // ── Verify: tap the words in order ────────────────────────────────────── ImGui::PushFont(ui::material::Type().subtitle1()); ImGui::TextUnformatted("Confirm your backup"); @@ -2213,6 +2220,76 @@ void App::renderLiteFirstRunPrompt() ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f); finish(); } + } else if (step == 3) { + // ── Restore from an existing seed phrase ──────────────────────────────── + ImGui::PushFont(ui::material::Type().subtitle1()); + ImGui::TextUnformatted(TR("lite_restore_title")); + ImGui::PopFont(); + ImGui::Spacing(); + ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f); + ImGui::TextUnformatted(TR("lite_restore_intro")); + ImGui::PopTextWrapPos(); + ImGui::Spacing(); + + if (restoring) { + // Async restore (with server failover) in flight — driven by pumpAsyncOpen(). + ImGui::TextUnformatted("Restoring your wallet\xE2\x80\xA6"); + if (lite_wallet_->walletOpen()) { + ui::Notifications::instance().success(TR("lite_restore_ok"), 6.0f); + finish(); // wipes restoreSeed; the wallet then syncs from the lite server + } else if (!lite_wallet_->openInProgress() && + !lite_wallet_->lastOpenError().empty()) { + restoreErr = lite_wallet_->lastOpenError(); + restoring = false; // back to the form so the user can fix and retry + } + } else { + ImGui::TextUnformatted(TR("lite_restore_seed_label")); + ImGui::InputTextMultiline("##LiteRestoreSeed", restoreSeed, sizeof(restoreSeed), + ImVec2(380.0f, ImGui::GetTextLineHeight() * 3.2f)); + ImGui::Spacing(); + ImGui::TextUnformatted(TR("lite_restore_birthday_label")); + ImGui::SetNextItemWidth(160.0f); + ImGui::InputInt("##LiteRestoreBirthday", &restoreBirthday); + if (restoreBirthday < 0) restoreBirthday = 0; + + if (!restoreErr.empty()) { + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f); + ImGui::TextUnformatted(restoreErr.c_str()); + ImGui::PopTextWrapPos(); + ImGui::PopStyleColor(); + } + ImGui::Spacing(); ImGui::Spacing(); + + // Trim surrounding whitespace from the entered seed. + std::string seedTrim(restoreSeed); + while (!seedTrim.empty() && std::isspace((unsigned char)seedTrim.front())) seedTrim.erase(seedTrim.begin()); + while (!seedTrim.empty() && std::isspace((unsigned char)seedTrim.back())) seedTrim.pop_back(); + + ImGui::BeginDisabled(seedTrim.empty()); + if (ImGui::Button(TR("lite_restore_btn"), ImVec2(btnW, 0))) { + wallet::LiteWalletRestoreRequest req; + req.seedPhrase = seedTrim; + req.birthday = static_cast(std::max(0, restoreBirthday)); + req.overwrite = lite_wallet_->walletExists(); // replace any existing wallet file + if (lite_wallet_->beginRestoreWalletAsync(std::move(req))) { + restoreErr.clear(); + restoring = true; + } else { + restoreErr = lite_wallet_->lastOpenError().empty() + ? std::string("Could not start restore") + : lite_wallet_->lastOpenError(); + } + } + ImGui::EndDisabled(); + ImGui::SameLine(); + if (ImGui::Button("Back", ImVec2(80, 0))) { + sodium_memzero(restoreSeed, sizeof(restoreSeed)); + restoreErr.clear(); + step = 0; + } + } } ImGui::EndPopup(); } diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 12e3511..5672c7b 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -655,6 +655,12 @@ void I18n::loadBuiltinEnglish() strings_["lite_welcome_created"] = "Wallet created — back up your recovery phrase now in Settings → Backup & keys"; strings_["lite_welcome_restore_hint"] = "Restore your wallet under Settings → Lite wallet request"; strings_["lite_welcome_create_failed"] = "Could not create wallet"; + strings_["lite_restore_title"] = "Restore from seed phrase"; + strings_["lite_restore_intro"] = "Enter your recovery seed phrase. The wallet will be restored and then synced from the lite server."; + strings_["lite_restore_seed_label"] = "Seed phrase (words separated by spaces)"; + strings_["lite_restore_birthday_label"] = "Birthday block (optional — 0 scans from the start, slower)"; + strings_["lite_restore_btn"] = "Restore wallet"; + strings_["lite_restore_ok"] = "Wallet restored — syncing from the lite server…"; // Lite send-time unlock prompt. strings_["lite_unlock_title"] = "Unlock wallet"; strings_["lite_unlock_msg"] = "Enter your passphrase to unlock the wallet for spending.";