feat(lite): async wallet creation with server failover
Mirror the async-open path for wallet creation. beginOpenExisting() and beginCreateWallet() now both delegate to beginAsyncLifecycle(bool create), which runs the backend init on a detached thread and walks the failover server list (preferred server first, then all usable defaults), reporting the preferred server's error on total failure. The first-run wizard's Create button drives this through a non-blocking "creating" poll state so the UI no longer freezes while the backend contacts a (possibly flaky) lightwalletd. The created seed response is securely wiped immediately and read back via exportSeed for the reveal/verify steps. Safe because litelib_initialize_new contacts the server before writing any wallet file and LightClient::new errors if a wallet already exists, so a failed candidate leaves no partial state. Tests: fake backend's initialize_new now honors the dead/warmup server substrings; testLiteWalletControllerOpenFailover gains a create-failover case (preferred dead, fallback good -> walletOpen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
61
src/app.cpp
61
src/app.cpp
@@ -1935,10 +1935,12 @@ void App::renderLiteFirstRunPrompt()
|
||||
static std::vector<std::pair<std::string, bool>> chips; // shuffled (word, consumed)
|
||||
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
|
||||
|
||||
// The welcome page is only relevant before any wallet exists; once create has started the
|
||||
// reveal/verify (step>0), keep showing it through to completion even though the wallet is open.
|
||||
if (step == 0 && (lite_wallet_->walletOpen() || lite_wallet_->walletExists())) return;
|
||||
// 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
|
||||
// though the wallet becomes open.
|
||||
if (step == 0 && !creating && (lite_wallet_->walletOpen() || lite_wallet_->walletExists())) return;
|
||||
|
||||
if (!ImGui::IsPopupOpen("##LiteFirstRun")) ImGui::OpenPopup("##LiteFirstRun");
|
||||
ImGuiViewport* vp = ImGui::GetMainViewport();
|
||||
@@ -1951,6 +1953,7 @@ void App::renderLiteFirstRunPrompt()
|
||||
chips.clear();
|
||||
progress = 0;
|
||||
step = 0;
|
||||
creating = false;
|
||||
lite_firstrun_dismissed_ = true;
|
||||
ImGui::CloseCurrentPopup();
|
||||
};
|
||||
@@ -1980,37 +1983,51 @@ void App::renderLiteFirstRunPrompt()
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::Spacing(); ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button(TR("lite_welcome_create"), ImVec2(btnW, 0))) {
|
||||
const auto result = lite_wallet_->createWallet(wallet::LiteWalletCreateRequest{});
|
||||
if (result.walletReady) {
|
||||
// Read the freshly generated seed back so we can show + verify it.
|
||||
auto s = lite_wallet_->exportSeed();
|
||||
if (creating) {
|
||||
// Async create (with server failover) is in flight — driven to completion by
|
||||
// App::update()'s pumpAsyncOpen(). Poll the controller for the outcome.
|
||||
ImGui::TextUnformatted("Creating your wallet\xE2\x80\xA6");
|
||||
if (lite_wallet_->walletOpen()) {
|
||||
auto s = lite_wallet_->exportSeed(); // read the new seed back (local, fast)
|
||||
if (s.ok && !s.seedPhrase.empty()) {
|
||||
seed = s.seedPhrase;
|
||||
birthday = s.birthday;
|
||||
wallet::secureWipeLiteSecret(s.seedPhrase);
|
||||
words = splitWords(seed);
|
||||
creating = false;
|
||||
step = 1;
|
||||
} else {
|
||||
// Created, but couldn't reveal the seed now — fall back to Settings backup.
|
||||
ui::Notifications::instance().success(TR("lite_welcome_created"), 8.0f);
|
||||
setCurrentPage(ui::NavPage::Settings);
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
ui::Notifications::instance().warning(TR("lite_welcome_create_failed"));
|
||||
} else if (!lite_wallet_->openInProgress() &&
|
||||
!lite_wallet_->lastOpenError().empty()) {
|
||||
ui::Notifications::instance().warning(
|
||||
std::string("Create failed: ") + lite_wallet_->lastOpenError());
|
||||
creating = false; // back to the buttons so the user can retry
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Button(TR("lite_welcome_create"), ImVec2(btnW, 0))) {
|
||||
// Async create with the same server failover as open (no UI freeze; a dead
|
||||
// server falls through to the next). On success the wizard reveals the seed.
|
||||
if (lite_wallet_->beginCreateWallet()) {
|
||||
creating = true;
|
||||
} else {
|
||||
ui::Notifications::instance().warning(TR("lite_welcome_create_failed"));
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button(TR("lite_welcome_later"),
|
||||
ImVec2(btnW * 2 + ImGui::GetStyle().ItemSpacing.x, 0))) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button(TR("lite_welcome_later"),
|
||||
ImVec2(btnW * 2 + ImGui::GetStyle().ItemSpacing.x, 0))) {
|
||||
finish();
|
||||
}
|
||||
} else if (step == 1) {
|
||||
// ── Reveal the seed + birthday with backup warnings ─────────────────────
|
||||
|
||||
Reference in New Issue
Block a user