fix(lite): welcome "Restore from seed" now prompts for the seed inline

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 02:11:19 -05:00
parent d54c7f9e11
commit 0e2c786ebf
2 changed files with 87 additions and 4 deletions

View File

@@ -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<unsigned long long>(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();
}

View File

@@ -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.";