Security (P0): - Fix sidebar remaining interactive behind lock screen - Extend auto-lock idle detection to include active widget interactions - Distinguish missing PIN vault from wrong PIN; auto-switch to passphrase Blocking UX (P1): - Add 15s timeout for encryption state check to prevent indefinite loading - Show restart reason in loading overlay after wallet encryption - Add Force Quit button on shutdown screen after 10s - Warn user if embedded daemon fails to start during wizard completion Polish (P2): - Use configured explorer URL in Receive tab instead of hardcoded URL - Increase request memo buffer from 256 to 512 bytes to match Send tab - Extend notification duration to 5s for critical operations (tx sent, wallet encrypted, key import, backup, export) - Add Reduce Motion accessibility setting (disables page fade + balance lerp) - Show estimated remaining time during mining thread benchmark - Add staleness indicator to market price data (warning after 5 min) New i18n keys: incorrect_pin, incorrect_passphrase, pin_not_set, restarting_after_encryption, force_quit, reduce_motion, tt_reduce_motion, ago, wizard_daemon_start_failed
249 lines
9.2 KiB
C++
249 lines
9.2 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#include "export_all_keys_dialog.h"
|
|
#include "../../app.h"
|
|
#include "../../rpc/rpc_client.h"
|
|
#include "../../rpc/rpc_worker.h"
|
|
#include "../../util/i18n.h"
|
|
#include "../../util/platform.h"
|
|
#include "../notifications.h"
|
|
#include "../schema/ui_schema.h"
|
|
#include "../material/draw_helpers.h"
|
|
#include "../material/type.h"
|
|
#include "../theme.h"
|
|
#include "../../embedded/IconsMaterialDesign.h"
|
|
#include "imgui.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <ctime>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
|
|
using json = nlohmann::json;
|
|
|
|
// Static state
|
|
static bool s_open = false;
|
|
static bool s_exporting = false;
|
|
static std::string s_status;
|
|
static std::string s_exported_keys;
|
|
static int s_total_addresses = 0;
|
|
static int s_exported_count = 0;
|
|
static bool s_include_z = true;
|
|
static bool s_include_t = true;
|
|
static char s_filename[256] = "";
|
|
|
|
void ExportAllKeysDialog::show()
|
|
{
|
|
s_open = true;
|
|
s_exporting = false;
|
|
s_status.clear();
|
|
s_exported_keys.clear();
|
|
s_total_addresses = 0;
|
|
s_exported_count = 0;
|
|
s_include_z = true;
|
|
s_include_t = true;
|
|
|
|
// Generate default filename with timestamp
|
|
std::time_t now = std::time(nullptr);
|
|
char timebuf[32];
|
|
std::strftime(timebuf, sizeof(timebuf), "%Y%m%d_%H%M%S", std::localtime(&now));
|
|
snprintf(s_filename, sizeof(s_filename), "dragonx_keys_%s.txt", timebuf);
|
|
}
|
|
|
|
bool ExportAllKeysDialog::isOpen()
|
|
{
|
|
return s_open;
|
|
}
|
|
|
|
void ExportAllKeysDialog::render(App* app)
|
|
{
|
|
if (!s_open) return;
|
|
|
|
auto& S = schema::UI();
|
|
auto win = S.window("dialogs.export-all-keys");
|
|
auto exportBtn = S.button("dialogs.export-all-keys", "export-button");
|
|
auto closeBtn = S.button("dialogs.export-all-keys", "close-button");
|
|
|
|
if (material::BeginOverlayDialog(TR("export_keys_title"), &s_open, win.width, 0.94f)) {
|
|
// Warning
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
|
ImGui::PushFont(material::Type().iconSmall());
|
|
ImGui::Text(ICON_MD_WARNING);
|
|
ImGui::PopFont();
|
|
ImGui::SameLine(0, 4.0f);
|
|
ImGui::TextWrapped("%s", TR("export_keys_danger"));
|
|
ImGui::PopStyleColor();
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
if (s_exporting) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
|
|
// Options
|
|
ImGui::Text("%s", TR("export_keys_options"));
|
|
ImGui::Checkbox(TR("export_keys_include_z"), &s_include_z);
|
|
ImGui::Checkbox(TR("export_keys_include_t"), &s_include_t);
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Filename
|
|
ImGui::Text("%s", TR("output_filename"));
|
|
ImGui::SetNextItemWidth(-1);
|
|
ImGui::InputText("##Filename", s_filename, sizeof(s_filename));
|
|
|
|
ImGui::Spacing();
|
|
ImGui::TextDisabled("%s", TR("file_save_location"));
|
|
|
|
if (s_exporting) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
// Export button
|
|
if (s_exporting) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
|
|
if (material::StyledButton(TR("export_keys_btn"), ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) {
|
|
if (!s_include_z && !s_include_t) {
|
|
Notifications::instance().warning("Select at least one address type");
|
|
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
|
Notifications::instance().error("Not connected to daemon");
|
|
} else {
|
|
s_exporting = true;
|
|
s_exported_keys.clear();
|
|
s_exported_count = 0;
|
|
s_status = "Exporting keys...";
|
|
|
|
const auto& state = app->getWalletState();
|
|
|
|
// Count total addresses to export
|
|
s_total_addresses = 0;
|
|
if (s_include_z) s_total_addresses += static_cast<int>(state.z_addresses.size());
|
|
if (s_include_t) s_total_addresses += static_cast<int>(state.t_addresses.size());
|
|
|
|
if (s_total_addresses == 0) {
|
|
s_exporting = false;
|
|
s_status = "No addresses to export";
|
|
return;
|
|
}
|
|
|
|
// Collect addresses to export (copy for worker thread)
|
|
std::vector<std::string> z_addrs, t_addrs;
|
|
if (s_include_z) {
|
|
for (const auto& a : state.z_addresses) z_addrs.push_back(a.address);
|
|
}
|
|
if (s_include_t) {
|
|
for (const auto& a : state.t_addresses) t_addrs.push_back(a.address);
|
|
}
|
|
std::string filename(s_filename);
|
|
|
|
// Run all key exports on worker thread
|
|
if (app->worker()) {
|
|
app->worker()->post([rpc = app->rpc(), z_addrs, t_addrs, filename]() -> rpc::RPCWorker::MainCb {
|
|
std::string keys;
|
|
int exported = 0;
|
|
int total = static_cast<int>(z_addrs.size() + t_addrs.size());
|
|
|
|
// Header
|
|
keys = "# DragonX Wallet - Private Keys Export\n";
|
|
keys += "# Generated: ";
|
|
std::time_t now = std::time(nullptr);
|
|
char timebuf[64];
|
|
std::strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\n", std::localtime(&now));
|
|
keys += timebuf;
|
|
keys += "# KEEP THIS FILE SECURE!\n\n";
|
|
|
|
// Export Z-addresses
|
|
if (!z_addrs.empty()) {
|
|
keys += "# === Z-Addresses (Shielded) ===\n\n";
|
|
for (const auto& addr : z_addrs) {
|
|
try {
|
|
auto result = rpc->call("z_exportkey", {addr});
|
|
if (result.is_string()) {
|
|
keys += "# Address: " + addr + "\n";
|
|
keys += result.get<std::string>() + "\n\n";
|
|
}
|
|
} catch (...) {}
|
|
exported++;
|
|
}
|
|
}
|
|
|
|
// Export T-addresses
|
|
if (!t_addrs.empty()) {
|
|
keys += "# === T-Addresses (Transparent) ===\n\n";
|
|
for (const auto& addr : t_addrs) {
|
|
try {
|
|
auto result = rpc->call("dumpprivkey", {addr});
|
|
if (result.is_string()) {
|
|
keys += "# Address: " + addr + "\n";
|
|
keys += result.get<std::string>() + "\n\n";
|
|
}
|
|
} catch (...) {}
|
|
exported++;
|
|
}
|
|
}
|
|
|
|
// Save to file (still on worker thread)
|
|
std::string configDir = util::Platform::getConfigDir();
|
|
std::string filepath = configDir + "/" + filename;
|
|
bool writeOk = false;
|
|
{
|
|
std::ofstream file(filepath);
|
|
if (file.is_open()) {
|
|
file << keys;
|
|
file.close();
|
|
writeOk = true;
|
|
}
|
|
}
|
|
|
|
return [exported, total, filepath, writeOk]() {
|
|
s_exported_count = exported;
|
|
s_exporting = false;
|
|
if (writeOk) {
|
|
s_status = "Exported to: " + filepath;
|
|
Notifications::instance().success(TR("export_keys_success"), 5.0f);
|
|
} else {
|
|
s_status = "Failed to write file";
|
|
Notifications::instance().error("Failed to save key file");
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s_exporting) {
|
|
ImGui::EndDisabled();
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled(TR("export_keys_progress"), s_exported_count, s_total_addresses);
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
|
s_open = false;
|
|
}
|
|
|
|
// Status
|
|
if (!s_status.empty()) {
|
|
ImGui::Spacing();
|
|
ImGui::TextWrapped("%s", s_status.c_str());
|
|
}
|
|
material::EndOverlayDialog();
|
|
}
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace dragonx
|