// 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 #include #include #include 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(state.z_addresses.size()); if (s_include_t) s_total_addresses += static_cast(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 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(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() + "\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() + "\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