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:
2026-07-13 13:53:35 -05:00
parent 46f3001360
commit f428292ea4
15 changed files with 155 additions and 18 deletions

View File

@@ -2921,7 +2921,7 @@ void App::exportAllKeys(std::function<void(const std::string&, int, int)> callba
}
}
void App::importPrivateKey(const std::string& rawKey,
void App::importPrivateKey(const std::string& rawKey, int startHeight,
std::function<void(bool, const std::string&, const std::string&)> callback)
{
if (!state_.connected || !rpc_ || !worker_) {
@@ -2947,7 +2947,7 @@ void App::importPrivateKey(const std::string& rawKey,
== services::WalletSecurityController::KeyKind::Shielded;
// Run on the worker thread — import requests a full rescan (rescan=true), so the
// synchronous curl call can take many seconds; never block the UI thread on it.
worker_->post([this, key, viewing, shielded, callback]() -> rpc::RPCWorker::MainCb {
worker_->post([this, key, viewing, shielded, startHeight, callback]() -> rpc::RPCWorker::MainCb {
std::string err, addr;
try {
rpc::RPCClient::TraceScope trace("Settings / Import key");
@@ -2956,6 +2956,8 @@ void App::importPrivateKey(const std::string& rawKey,
if (viewing) { method = "z_importviewingkey"; params = {key, "yes"}; } // watch-only
else if (shielded) { method = "z_importkey"; params = {key, "yes"}; }
else { method = "importprivkey"; params = {key, "", true}; }
// A start height (shielded RPCs only) rescans from that block instead of genesis.
if (startHeight > 0 && (viewing || shielded)) params.push_back(startHeight);
nlohmann::json r = rpc_->call(method, params);
// z_import* return {type,address}; importprivkey returns the t-address string.
if (r.is_object() && r.contains("address") && r["address"].is_string())