feat(settings): split import into separate key + viewing-key buttons
Backup & Data now has two distinct import actions instead of one auto-detecting dialog: - "Import Key" imports a spending key (transparent WIF or shielded z-spending key) with the strong "grants access to funds" warning. - "Import Viewing Key" imports a watch-only shielded viewing key (zxviews…) with a milder eye/watch-only note and an optional "scan from block height" field — z_importviewingkey accepts a start height, so watching a recent address needn't rescan the whole chain. The shared Material dialog (renderImportKeyDialog) branches on import_view_mode_: title, warning tone, field label, the live type indicator, and the recognition guard. A key valid for the *other* button (e.g. a spending key pasted into the viewing-key dialog) now shows a redirect hint rather than a generic "unrecognized" error. importPrivateKey gains a startHeight arg, appended to the shielded import RPCs (z_importviewingkey / z_importkey) and ignored for transparent WIF (importprivkey has no start-height param). The scan-height buffer is wiped on open/close alongside the key buffer. i18n: 9 new keys (button label + tooltip, viewing title/note/field, scan label/hint, two wrong-type redirect hints) with translations for all 8 languages; CJK subset rebuilt (+1 glyph). A modal-import-viewkey sweep surface is added for the viewing-mode dialog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
64
src/app.cpp
64
src/app.cpp
@@ -2758,9 +2758,10 @@ void App::renderImportKeyDialog()
|
||||
{
|
||||
namespace m = ui::material;
|
||||
float dp = ui::Layout::dpiScale();
|
||||
const bool viewMode = import_view_mode_; // watch-only shielded viewing key vs. spending key
|
||||
|
||||
m::OverlayDialogSpec ov;
|
||||
ov.title = TR("import_key_title");
|
||||
ov.title = viewMode ? TR("import_viewkey_title") : TR("import_key_title");
|
||||
ov.p_open = &show_import_key_;
|
||||
ov.style = m::OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 520.0f;
|
||||
@@ -2773,12 +2774,24 @@ void App::renderImportKeyDialog()
|
||||
import_status_.clear();
|
||||
import_result_address_.clear();
|
||||
import_key_reveal_ = false;
|
||||
sodium_memzero(import_key_scan_height_, sizeof(import_key_scan_height_));
|
||||
}
|
||||
// Esc dismisses (this overlay has no built-in Esc handling).
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) show_import_key_ = false;
|
||||
|
||||
// Handle-with-care note.
|
||||
m::DialogWarningHeader(TR("import_key_warn"));
|
||||
// Handle-with-care note. A spending key grants access to funds (strong warning); a viewing key
|
||||
// is watch-only (milder, informational eye note).
|
||||
if (viewMode) {
|
||||
ImGui::PushFont(m::Type().iconSmall());
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceMedium()), ICON_MD_VISIBILITY);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 6.0f * dp);
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceMedium()), "%s", TR("import_viewkey_note"));
|
||||
ImGui::PopTextWrapPos();
|
||||
} else {
|
||||
m::DialogWarningHeader(TR("import_key_warn"));
|
||||
}
|
||||
ImGui::Spacing();
|
||||
|
||||
// A running node is required — say so up front instead of failing only after Import.
|
||||
@@ -2788,7 +2801,7 @@ void App::renderImportKeyDialog()
|
||||
}
|
||||
|
||||
// Masked key field with a reveal (eye) toggle + Paste/Clear.
|
||||
ImGui::TextUnformatted(TR("import_key_field"));
|
||||
ImGui::TextUnformatted(viewMode ? TR("import_viewkey_field") : TR("import_key_field"));
|
||||
float eyeW = ImGui::GetFrameHeight();
|
||||
ImGui::SetNextItemWidth(-(eyeW + ui::Layout::spacingSm()));
|
||||
ImGuiInputTextFlags flags = import_key_reveal_ ? 0 : ImGuiInputTextFlags_Password;
|
||||
@@ -2815,31 +2828,52 @@ void App::renderImportKeyDialog()
|
||||
std::string keyTrim(import_key_input_);
|
||||
while (!keyTrim.empty() && (keyTrim.front()==' '||keyTrim.front()=='\t'||keyTrim.front()=='\n'||keyTrim.front()=='\r')) keyTrim.erase(keyTrim.begin());
|
||||
while (!keyTrim.empty() && (keyTrim.back()==' '||keyTrim.back()=='\t'||keyTrim.back()=='\n'||keyTrim.back()=='\r')) keyTrim.pop_back();
|
||||
bool recognized = services::WalletSecurityController::isRecognizedImportKey(keyTrim);
|
||||
bool viewing = services::WalletSecurityController::isViewingKey(keyTrim);
|
||||
bool isView = services::WalletSecurityController::isViewingKey(keyTrim);
|
||||
bool isSpend = services::WalletSecurityController::isRecognizedPrivateKey(keyTrim);
|
||||
bool shielded = services::WalletSecurityController::classifyPrivateKey(keyTrim)
|
||||
== services::WalletSecurityController::KeyKind::Shielded;
|
||||
// Each button accepts only its own key type; a key valid for the OTHER button is flagged with a
|
||||
// redirect hint rather than lumped in with truly unrecognized input.
|
||||
bool recognized = viewMode ? isView : isSpend;
|
||||
bool wrongType = viewMode ? isSpend : isView;
|
||||
|
||||
if (!keyTrim.empty()) {
|
||||
if (recognized) {
|
||||
const char* label = viewing ? TR("import_key_type_zview")
|
||||
: shielded ? TR("import_key_type_zspend")
|
||||
: TR("import_key_type_tkey");
|
||||
const char* label = viewMode ? TR("import_key_type_zview")
|
||||
: shielded ? TR("import_key_type_zspend")
|
||||
: TR("import_key_type_tkey");
|
||||
ImGui::PushFont(m::Type().iconSmall());
|
||||
ImGui::TextColored(m::SuccessVec4(), ICON_MD_CHECK_CIRCLE);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f * dp);
|
||||
ImGui::TextColored(m::SuccessVec4(), "%s", label);
|
||||
} else {
|
||||
const char* msg = wrongType
|
||||
? (viewMode ? TR("import_viewkey_wrong_type") : TR("import_key_wrong_type"))
|
||||
: TR("import_key_type_unknown");
|
||||
ImGui::PushFont(m::Type().iconSmall());
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), ICON_MD_HELP);
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), wrongType ? ICON_MD_WARNING : ICON_MD_HELP);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f * dp);
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "%s", TR("import_key_type_unknown"));
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "%s", msg);
|
||||
ImGui::PopTextWrapPos();
|
||||
}
|
||||
}
|
||||
ImGui::Spacing();
|
||||
|
||||
// Optional scan-from height (viewing keys only — z_importviewingkey takes a start height, so a
|
||||
// watch-only import of a recent address needn't rescan the whole chain).
|
||||
if (viewMode) {
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceMedium()), "%s", TR("import_viewkey_scan_label"));
|
||||
ImGui::SetNextItemWidth(140.0f * dp);
|
||||
ImGui::InputText("##importscanheight", import_key_scan_height_, sizeof(import_key_scan_height_),
|
||||
ImGuiInputTextFlags_CharsDecimal);
|
||||
ImGui::SameLine(0, ui::Layout::spacingSm());
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s", TR("import_viewkey_scan_hint"));
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
// Progress / result / error.
|
||||
if (import_in_progress_) {
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::Primary()), "%s%s",
|
||||
@@ -2873,7 +2907,12 @@ void App::renderImportKeyDialog()
|
||||
import_success_ = false;
|
||||
import_status_.clear();
|
||||
import_result_address_.clear();
|
||||
importPrivateKey(std::string(import_key_input_),
|
||||
int startHeight = 0; // viewing-key only; 0 = full rescan (also the transparent/spending default)
|
||||
if (viewMode && import_key_scan_height_[0]) {
|
||||
startHeight = std::atoi(import_key_scan_height_);
|
||||
if (startHeight < 0) startHeight = 0;
|
||||
}
|
||||
importPrivateKey(std::string(import_key_input_), startHeight,
|
||||
[this](bool ok, const std::string& err, const std::string& addr) {
|
||||
import_in_progress_ = false;
|
||||
import_success_ = ok;
|
||||
@@ -2894,6 +2933,7 @@ void App::renderImportKeyDialog()
|
||||
// show_import_key_ mid-frame). in_progress is left to the callback to clear.
|
||||
if (!show_import_key_) {
|
||||
sodium_memzero(import_key_input_, sizeof(import_key_input_));
|
||||
sodium_memzero(import_key_scan_height_, sizeof(import_key_scan_height_));
|
||||
import_status_.clear();
|
||||
import_result_address_.clear();
|
||||
import_success_ = false;
|
||||
|
||||
Reference in New Issue
Block a user