// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "key_export_dialog.h" #include "../../app.h" #include "../../rpc/rpc_client.h" #include "../../rpc/rpc_worker.h" #include "../../util/i18n.h" #include "../schema/ui_schema.h" #include "../material/draw_helpers.h" #include "../material/type.h" #include "../material/colors.h" #include "../widgets/qr_code.h" #include "imgui.h" namespace dragonx { namespace ui { // Static member initialization bool KeyExportDialog::s_open = false; bool KeyExportDialog::s_fetching = false; bool KeyExportDialog::s_show_key = false; bool KeyExportDialog::s_show_qr = false; std::uintptr_t KeyExportDialog::s_qr_tex = 0; std::string KeyExportDialog::s_qr_cached; KeyExportDialog::KeyType KeyExportDialog::s_key_type = KeyExportDialog::KeyType::Private; std::string KeyExportDialog::s_address; std::string KeyExportDialog::s_key; std::string KeyExportDialog::s_error; void KeyExportDialog::releaseQr() { if (s_qr_tex) { FreeQRTexture(s_qr_tex); s_qr_tex = 0; } s_qr_cached.clear(); s_show_qr = false; } void KeyExportDialog::show(const std::string& address, KeyType type) { s_open = true; s_fetching = false; s_show_key = false; releaseQr(); s_key_type = type; s_address = address; s_key.clear(); s_error.clear(); } bool KeyExportDialog::isOpen() { return s_open; } void KeyExportDialog::render(App* app) { if (!s_open) return; const char* title = (s_key_type == KeyType::Private) ? TR("export_private_key") : TR("export_viewing_key"); auto& S = schema::UI(); auto win = S.window("dialogs.key-export"); auto warningBox = S.drawElement("dialogs.key-export", "warning-box"); auto addrInput = S.input("dialogs.key-export", "address-input"); auto revealBtn = S.button("dialogs.key-export", "reveal-button"); auto keyDisplay = S.drawElement("dialogs.key-export", "key-display"); auto copyBtn = S.button("dialogs.key-export", "copy-button"); auto closeBtn = S.button("dialogs.key-export", "close-button"); if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) { ImGui::Spacing(); // Warning section with colored background ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.6f, 0.2f, 0.2f, 0.3f)); // Auto-size the warning box to its (wrapping, font-scaled) text so it never clips it. (void)warningBox; ImGui::BeginChild("WarningBox", ImVec2(-1, 0), ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders); ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), " %s", TR("warning_upper")); ImGui::Spacing(); if (s_key_type == KeyType::Private) { ImGui::TextWrapped(" %s", TR("key_export_private_warning")); } else { ImGui::TextWrapped(" %s", TR("key_export_viewing_warning")); } ImGui::EndChild(); ImGui::PopStyleColor(); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Address display ImGui::Text("%s", TR("address_label")); // Determine if it's a z-address (longer) or t-address bool is_zaddr = s_address.length() > 50; if (is_zaddr) { // Use multiline for z-addresses char addr_buf[512]; strncpy(addr_buf, s_address.c_str(), sizeof(addr_buf) - 1); addr_buf[sizeof(addr_buf) - 1] = '\0'; // Fit the field to the wrapped address (no excess empty space below it). (void)addrInput; const float addrFieldH = ImGui::CalcTextSize(addr_buf, nullptr, false, ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2.0f).y + ImGui::GetStyle().FramePadding.y * 2.0f + 4.0f; ImGui::InputTextMultiline("##Address", addr_buf, sizeof(addr_buf), ImVec2(-1, addrFieldH), ImGuiInputTextFlags_ReadOnly); } else { char addr_buf[128]; strncpy(addr_buf, s_address.c_str(), sizeof(addr_buf) - 1); addr_buf[sizeof(addr_buf) - 1] = '\0'; ImGui::SetNextItemWidth(-1); ImGui::InputText("##Address", addr_buf, sizeof(addr_buf), ImGuiInputTextFlags_ReadOnly); } ImGui::Spacing(); // Key display section const char* key_label = (s_key_type == KeyType::Private) ? TR("key_export_private_key") : TR("key_export_viewing_key"); ImGui::Text("%s", key_label); if (s_fetching) { ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("key_export_fetching")); } else if (!s_error.empty()) { ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str()); } else if (s_key.empty()) { // Show button to fetch key if (material::StyledButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) { s_fetching = true; // Check if z-address or t-address bool is_zaddress = (s_address.length() > 50 || s_address[0] == 'z'); if (s_key_type == KeyType::Private) { // Export private key std::string addr = s_address; std::string method = is_zaddress ? "z_exportkey" : "dumpprivkey"; if (app->worker()) { app->worker()->post([rpc = app->rpc(), addr, method]() -> rpc::RPCWorker::MainCb { std::string key; std::string error; try { rpc::RPCClient::TraceScope trace("Settings / Export key"); auto result = rpc->call(method, {addr}); key = result.get(); } catch (const std::exception& e) { error = e.what(); } return [key, error]() { if (error.empty()) { s_key = key; s_show_key = false; // Don't show by default } else { s_error = error; } s_fetching = false; }; }); } } else { // Export viewing key (only for z-addresses) if (is_zaddress) { std::string addr = s_address; if (app->worker()) { app->worker()->post([rpc = app->rpc(), addr]() -> rpc::RPCWorker::MainCb { std::string key; std::string error; try { rpc::RPCClient::TraceScope trace("Settings / Export viewing key"); auto result = rpc->call("z_exportviewingkey", {addr}); key = result.get(); } catch (const std::exception& e) { error = e.what(); } return [key, error]() { if (error.empty()) { s_key = key; s_show_key = true; // Viewing keys are less sensitive } else { s_error = error; } s_fetching = false; }; }); } } else { s_error = TR("key_export_viewing_keys_zonly"); s_fetching = false; } } } ImGui::SameLine(); ImGui::TextDisabled("%s", TR("key_export_click_retrieve")); } else { // Key has been fetched - display it // Fit the field to the wrapped key (same length whether shown or masked). (void)keyDisplay; char key_buf[1024]; if (s_show_key) { strncpy(key_buf, s_key.c_str(), sizeof(key_buf) - 1); } else { std::string masked(s_key.length(), '*'); strncpy(key_buf, masked.c_str(), sizeof(key_buf) - 1); } key_buf[sizeof(key_buf) - 1] = '\0'; const float keyFieldH = ImGui::CalcTextSize(key_buf, nullptr, false, ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2.0f).y + ImGui::GetStyle().FramePadding.y * 2.0f + 4.0f; ImGui::InputTextMultiline("##Key", key_buf, sizeof(key_buf), ImVec2(-1, keyFieldH), ImGuiInputTextFlags_ReadOnly); // Action row: Show/Hide · Copy · QR. Auto-width buttons (size 0) so the label text never // clips at the user's font scale, and ONE shared font so they all match. ImGui::Spacing(); ImFont* actionFont = S.resolveFont(copyBtn.font); if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(0, 0), actionFont)) { s_show_key = !s_show_key; if (!s_show_key) s_show_qr = false; // hiding the key also hides its QR } ImGui::SameLine(); if (material::StyledButton(TR("copy_to_clipboard"), ImVec2(0, 0), actionFont)) { // Auto-clearing clipboard: the key (as sensitive as the seed) is wiped after ~45s. app->copySecretToClipboard(s_key); } // QR (only once revealed) — for scanning the key into another wallet. if (s_show_key) { ImGui::SameLine(); if (material::StyledButton(s_show_qr ? TR("hide_qr") : TR("show_qr"), ImVec2(0, 0), actionFont)) { s_show_qr = !s_show_qr; } } if (s_show_qr && s_show_key && !s_key.empty()) { if (s_qr_cached != s_key) { // (re)generate the QR texture when the key changes if (s_qr_tex) { FreeQRTexture(s_qr_tex); s_qr_tex = 0; } int qw = 0, qh = 0; s_qr_tex = GenerateQRTexture(s_key.c_str(), &qw, &qh); s_qr_cached = s_key; } if (s_qr_tex) { ImGui::Spacing(); const float qrSize = 180.0f; ImGui::SetCursorPosX((ImGui::GetWindowWidth() - qrSize) * 0.5f); RenderQRCode(s_qr_tex, qrSize); } } } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); // Close button float button_width = closeBtn.width; float avail_width = ImGui::GetContentRegionAvail().x; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail_width - button_width) / 2.0f); if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) { s_open = false; // Clear sensitive data s_key.clear(); s_show_key = false; releaseQr(); } material::EndOverlayDialog(); } // Dialog dismissed any other way (scrim click / Esc): drop the key + its QR texture. if (!s_open) { if (!s_key.empty()) s_key.clear(); s_show_key = false; releaseQr(); } } } // namespace ui } // namespace dragonx