diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 83177fd..303f5dc 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -121,6 +121,13 @@ struct SettingsPageState { bool lite_restore_overwrite = false; std::string lite_lifecycle_status; std::string lite_lifecycle_summary; + // Backup & keys (only populated for an open lite wallet). lite_export_secret holds the + // revealed seed/private-keys backup and is SECRET: securely wiped on hide / new export / + // import. lite_import_key is the import input buffer (wiped right after submission). + std::string lite_export_secret; + std::string lite_export_label; + char lite_import_key[512] = ""; + std::string lite_backup_status; bool mine_when_idle = false; int mine_idle_delay = 120; bool idle_thread_scaling = false; @@ -1724,6 +1731,89 @@ void RenderSettingsPage(App* app) { s_settingsState.lite_lifecycle_summary.c_str()); } } + + // ---- Backup & keys (open wallet only) ---------------------------------- + if (app->liteWallet() && app->liteWallet()->walletOpen()) { + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + Type().text(TypeStyle::Body2, "Backup & keys"); + + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + if (TactileButton("Show seed##LiteExportSeed", ImVec2(0, 0), S.resolveFont("button"))) { + auto r = app->liteWallet()->exportSeed(); + wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret); + if (r.ok) { + s_settingsState.lite_export_secret = r.seedPhrase; + s_settingsState.lite_export_label = "Seed phrase — write it down, never share it"; + s_settingsState.lite_backup_status.clear(); + } else { + s_settingsState.lite_export_label.clear(); + s_settingsState.lite_backup_status = r.error; + } + wallet::secureWipeLiteSecret(r.seedPhrase); // wipe the result copy + } + ImGui::SameLine(0, Layout::spacingSm()); + if (TactileButton("Show private keys##LiteExportKeys", ImVec2(0, 0), S.resolveFont("button"))) { + auto r = app->liteWallet()->exportPrivateKeys(); + wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret); + if (r.ok) { + s_settingsState.lite_export_secret = r.privateKeysJson; + s_settingsState.lite_export_label = "Private keys — anyone with these can spend your funds"; + s_settingsState.lite_backup_status.clear(); + } else { + s_settingsState.lite_export_label.clear(); + s_settingsState.lite_backup_status = r.error; + } + wallet::secureWipeLiteSecret(r.privateKeysJson); + } + + // Revealed secret: shown read-only (no extra copies), with copy + wipe. + if (!s_settingsState.lite_export_secret.empty()) { + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), + s_settingsState.lite_export_label.c_str()); + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + ImGui::PushTextWrapPos(0.0f); + ImGui::TextWrapped("%s", s_settingsState.lite_export_secret.c_str()); + ImGui::PopTextWrapPos(); + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + if (TactileButton("Copy##LiteExportCopy", ImVec2(0, 0), S.resolveFont("button"))) { + ImGui::SetClipboardText(s_settingsState.lite_export_secret.c_str()); + } + ImGui::SameLine(0, Layout::spacingSm()); + if (TactileButton("Hide & wipe##LiteExportHide", ImVec2(0, 0), S.resolveFont("button"))) { + wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret); + s_settingsState.lite_export_label.clear(); + } + } + + // Import a spending/viewing key (history appears after the next sync). + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Import key"); + ImGui::SameLine(leftX - sectionOrigin.x + liteLabelW); + ImGui::SetNextItemWidth(liteInputW); + ImGui::InputText("##LiteImportKey", s_settingsState.lite_import_key, + sizeof(s_settingsState.lite_import_key), + ImGuiInputTextFlags_Password); + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + if (TactileButton("Import##LiteImportKeyBtn", ImVec2(0, 0), S.resolveFont("button"))) { + const auto r = app->liteWallet()->importKey(s_settingsState.lite_import_key); + sodium_memzero(s_settingsState.lite_import_key, sizeof(s_settingsState.lite_import_key)); + s_settingsState.lite_backup_status = + r.ok ? "Key imported — run a sync to scan its history" : r.error; + } + + if (!s_settingsState.lite_backup_status.empty()) { + ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), + s_settingsState.lite_backup_status.c_str()); + } + } else if (!s_settingsState.lite_export_secret.empty()) { + // Wallet closed while a backup was revealed — don't leave it in memory. + wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret); + s_settingsState.lite_export_label.clear(); + } } else { float rpcLblW = std::max(