Compare commits
6 Commits
f0867084f3
...
f58d009703
| Author | SHA1 | Date | |
|---|---|---|---|
| f58d009703 | |||
| 0e2c786ebf | |||
| d54c7f9e11 | |||
| b3251e9244 | |||
| c40f4d5815 | |||
| 5547ab1cac |
94
src/app.cpp
94
src/app.cpp
@@ -532,6 +532,10 @@ void App::update()
|
||||
// Deliver a completed async send/shield result to the waiting send_tab callback.
|
||||
wallet::LiteBroadcastResult broadcast;
|
||||
if (lite_wallet_->takeBroadcastResult(broadcast)) {
|
||||
// Mirror failures into the lite Console (copyable) in addition to the toast the send UI
|
||||
// shows — transient toasts are easy to miss and impossible to copy.
|
||||
if (!broadcast.ok)
|
||||
wallet::liteLog("Send/shield failed: " + broadcast.error);
|
||||
if (lite_send_callback_) {
|
||||
lite_send_callback_(broadcast.ok, broadcast.ok ? broadcast.txid : broadcast.error);
|
||||
lite_send_callback_ = nullptr;
|
||||
@@ -2018,6 +2022,11 @@ void App::renderLiteFirstRunPrompt()
|
||||
static int progress = 0; // # words confirmed in order
|
||||
static double wrongFlashUntil = 0.0; // brief "not the next word" hint
|
||||
static bool creating = false; // async create (with failover) in flight
|
||||
// Restore-from-seed step (step 3). The seed buffer is SECRET — wiped in finish() and on Back.
|
||||
static char restoreSeed[512] = {};
|
||||
static int restoreBirthday = 0;
|
||||
static bool restoring = false; // async restore in flight
|
||||
static std::string restoreErr;
|
||||
|
||||
// The welcome page is only relevant before any wallet exists; once create has started
|
||||
// (creating) or reached reveal/verify (step>0), keep showing it through to completion even
|
||||
@@ -2031,6 +2040,10 @@ void App::renderLiteFirstRunPrompt()
|
||||
|
||||
auto finish = [&]() {
|
||||
wallet::secureWipeLiteSecret(seed);
|
||||
sodium_memzero(restoreSeed, sizeof(restoreSeed));
|
||||
restoreBirthday = 0;
|
||||
restoring = false;
|
||||
restoreErr.clear();
|
||||
words.clear();
|
||||
chips.clear();
|
||||
progress = 0;
|
||||
@@ -2101,9 +2114,7 @@ void App::renderLiteFirstRunPrompt()
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TR("lite_welcome_restore"), ImVec2(btnW, 0))) {
|
||||
ui::Notifications::instance().info(TR("lite_welcome_restore_hint"), 8.0f);
|
||||
setCurrentPage(ui::NavPage::Settings);
|
||||
finish();
|
||||
step = 3; // inline seed-entry restore (see step 3 below)
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button(TR("lite_welcome_later"),
|
||||
@@ -2156,7 +2167,7 @@ void App::renderLiteFirstRunPrompt()
|
||||
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
|
||||
finish();
|
||||
}
|
||||
} else { // step == 2
|
||||
} else if (step == 2) {
|
||||
// ── Verify: tap the words in order ──────────────────────────────────────
|
||||
ImGui::PushFont(ui::material::Type().subtitle1());
|
||||
ImGui::TextUnformatted("Confirm your backup");
|
||||
@@ -2209,6 +2220,76 @@ void App::renderLiteFirstRunPrompt()
|
||||
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
|
||||
finish();
|
||||
}
|
||||
} else if (step == 3) {
|
||||
// ── Restore from an existing seed phrase ────────────────────────────────
|
||||
ImGui::PushFont(ui::material::Type().subtitle1());
|
||||
ImGui::TextUnformatted(TR("lite_restore_title"));
|
||||
ImGui::PopFont();
|
||||
ImGui::Spacing();
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
|
||||
ImGui::TextUnformatted(TR("lite_restore_intro"));
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (restoring) {
|
||||
// Async restore (with server failover) in flight — driven by pumpAsyncOpen().
|
||||
ImGui::TextUnformatted("Restoring your wallet\xE2\x80\xA6");
|
||||
if (lite_wallet_->walletOpen()) {
|
||||
ui::Notifications::instance().success(TR("lite_restore_ok"), 6.0f);
|
||||
finish(); // wipes restoreSeed; the wallet then syncs from the lite server
|
||||
} else if (!lite_wallet_->openInProgress() &&
|
||||
!lite_wallet_->lastOpenError().empty()) {
|
||||
restoreErr = lite_wallet_->lastOpenError();
|
||||
restoring = false; // back to the form so the user can fix and retry
|
||||
}
|
||||
} else {
|
||||
ImGui::TextUnformatted(TR("lite_restore_seed_label"));
|
||||
ImGui::InputTextMultiline("##LiteRestoreSeed", restoreSeed, sizeof(restoreSeed),
|
||||
ImVec2(380.0f, ImGui::GetTextLineHeight() * 3.2f));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted(TR("lite_restore_birthday_label"));
|
||||
ImGui::SetNextItemWidth(160.0f);
|
||||
ImGui::InputInt("##LiteRestoreBirthday", &restoreBirthday);
|
||||
if (restoreBirthday < 0) restoreBirthday = 0;
|
||||
|
||||
if (!restoreErr.empty()) {
|
||||
ImGui::Spacing();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
|
||||
ImGui::TextUnformatted(restoreErr.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::Spacing(); ImGui::Spacing();
|
||||
|
||||
// Trim surrounding whitespace from the entered seed.
|
||||
std::string seedTrim(restoreSeed);
|
||||
while (!seedTrim.empty() && std::isspace((unsigned char)seedTrim.front())) seedTrim.erase(seedTrim.begin());
|
||||
while (!seedTrim.empty() && std::isspace((unsigned char)seedTrim.back())) seedTrim.pop_back();
|
||||
|
||||
ImGui::BeginDisabled(seedTrim.empty());
|
||||
if (ImGui::Button(TR("lite_restore_btn"), ImVec2(btnW, 0))) {
|
||||
wallet::LiteWalletRestoreRequest req;
|
||||
req.seedPhrase = seedTrim;
|
||||
req.birthday = static_cast<unsigned long long>(std::max(0, restoreBirthday));
|
||||
req.overwrite = lite_wallet_->walletExists(); // replace any existing wallet file
|
||||
if (lite_wallet_->beginRestoreWalletAsync(std::move(req))) {
|
||||
restoreErr.clear();
|
||||
restoring = true;
|
||||
} else {
|
||||
restoreErr = lite_wallet_->lastOpenError().empty()
|
||||
? std::string("Could not start restore")
|
||||
: lite_wallet_->lastOpenError();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Back", ImVec2(80, 0))) {
|
||||
sodium_memzero(restoreSeed, sizeof(restoreSeed));
|
||||
restoreErr.clear();
|
||||
step = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -2243,7 +2324,10 @@ void App::renderLiteUnlockPrompt()
|
||||
const bool ok = lite_wallet_->unlockWallet(pass);
|
||||
sodium_memzero(pass, sizeof(pass));
|
||||
if (ok) ui::Notifications::instance().success(TR("lite_unlock_ok"), 5.0f);
|
||||
else ui::Notifications::instance().error(TR("lite_unlock_failed"));
|
||||
else {
|
||||
wallet::liteLog(std::string("Unlock failed: ") + TR("lite_unlock_failed"));
|
||||
ui::Notifications::instance().error(TR("lite_unlock_failed"));
|
||||
}
|
||||
lite_unlock_prompt_ = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ struct SettingsPageState {
|
||||
bool confirm_delete_blockchain = false;
|
||||
bool confirm_rescan = false;
|
||||
bool confirm_restart_daemon = false;
|
||||
bool confirm_lite_redownload = false;
|
||||
effects::ScrollFadeShader fade_shader;
|
||||
};
|
||||
|
||||
@@ -1651,6 +1652,16 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
}
|
||||
|
||||
// Open the lite wallet data folder in the OS file manager (always available in
|
||||
// lite). The lite backend stores its wallet in its OWN dir (silentdragonxlite),
|
||||
// NOT the full-node getDragonXDataDir().
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton(TR("settings_open_data_dir"), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
util::Platform::openFolder(util::Platform::getLiteWalletDataDir());
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_open_data_dir"));
|
||||
|
||||
// ---- Backup & keys (open wallet only) ----------------------------------
|
||||
if (app->liteWallet() && app->liteWallet()->walletOpen()) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
@@ -1819,6 +1830,24 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
s_settingsState.lite_encryption_status.c_str());
|
||||
}
|
||||
|
||||
// ---- Maintenance: re-download blocks from the lite server ----
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().text(TypeStyle::Body2, TR("lite_maintenance"));
|
||||
|
||||
const bool scanning = app->liteWallet()->scanInProgress();
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::BeginDisabled(scanning);
|
||||
if (TactileButton(TR("lite_redownload_blocks"), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
s_settingsState.confirm_lite_redownload = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("%s", TR("tt_lite_redownload"));
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||
scanning ? TR("lite_redownload_running") : TR("lite_redownload_desc"));
|
||||
} else if (!s_settingsState.lite_export_secret.empty()) {
|
||||
// Wallet closed while a backup/secret was revealed — don't leave it in memory.
|
||||
wallet::secureWipeLiteSecret(s_settingsState.lite_export_secret);
|
||||
@@ -1898,6 +1927,13 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit button to open the wallet + blockchain data folder in the OS file manager.
|
||||
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
|
||||
if (TactileButton(TR("settings_open_data_dir"), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
util::Platform::openFolder(util::Platform::getDragonXDataDir());
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_open_data_dir"));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// RPC connection — two columns: (Host | Username) and (Port | Password)
|
||||
@@ -2640,6 +2676,36 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm: lite wallet re-download blocks (rescan from the lite server — long but safe)
|
||||
if (s_settingsState.confirm_lite_redownload) {
|
||||
if (BeginOverlayDialog(TR("confirm_lite_redownload_title"), &s_settingsState.confirm_lite_redownload, 500.0f, 0.94f)) {
|
||||
ImGui::PushFont(Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("warning"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("confirm_lite_redownload_msg"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("confirm_lite_redownload_safe"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button(TrId("cancel", "lite_redl_cancel").c_str(), ImVec2(btnW, 40))) {
|
||||
s_settingsState.confirm_lite_redownload = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TrId("lite_redownload_blocks", "lite_redl_confirm").c_str(), ImVec2(btnW, 40))) {
|
||||
if (auto* lite = app->liteWallet()) lite->startRescan();
|
||||
s_settingsState.confirm_lite_redownload = false;
|
||||
}
|
||||
EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -401,6 +401,8 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["bootstrap_restart_daemon"] = "Restart Daemon";
|
||||
strings_["bootstrap_failed"] = "Bootstrap Failed";
|
||||
strings_["tt_open_dir"] = "Click to open in file explorer";
|
||||
strings_["settings_open_data_dir"] = "Open data folder";
|
||||
strings_["tt_open_data_dir"] = "Open the folder with your wallet and blockchain data in the file manager";
|
||||
strings_["tt_rpc_host"] = "Hostname of the DragonX daemon";
|
||||
strings_["tt_rpc_user"] = "RPC authentication username";
|
||||
strings_["tt_rpc_port"] = "Port for daemon RPC connections";
|
||||
@@ -418,6 +420,14 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["confirm_rescan_safe"] = "Your wallet.dat and blockchain data are not deleted — only re-scanned.";
|
||||
strings_["confirm_restart_daemon_title"] = "Restart Daemon";
|
||||
strings_["confirm_restart_daemon_msg"] = "This stops and restarts the daemon to apply the changed options. The wallet will briefly disconnect and reconnect.";
|
||||
strings_["lite_maintenance"] = "Maintenance";
|
||||
strings_["lite_redownload_blocks"] = "Redownload blocks";
|
||||
strings_["lite_redownload_desc"] = "Re-fetch all blocks from the lite server (use if your balance or history looks wrong).";
|
||||
strings_["lite_redownload_running"] = "Re-downloading blocks…";
|
||||
strings_["tt_lite_redownload"] = "Re-download and re-scan all blocks from the lite server";
|
||||
strings_["confirm_lite_redownload_title"] = "Redownload Blocks";
|
||||
strings_["confirm_lite_redownload_msg"] = "This clears the wallet's downloaded block data and re-fetches and re-scans every block from the lite server. It can take a while; the wallet shows sync progress until it finishes.";
|
||||
strings_["confirm_lite_redownload_safe"] = "Your wallet, keys, and seed are not affected — only the block data is re-downloaded.";
|
||||
strings_["tt_encrypt"] = "Encrypt wallet.dat with a passphrase";
|
||||
strings_["tt_change_pass"] = "Change the wallet encryption passphrase";
|
||||
strings_["tt_lock"] = "Lock the wallet immediately";
|
||||
@@ -645,6 +655,12 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["lite_welcome_created"] = "Wallet created — back up your recovery phrase now in Settings → Backup & keys";
|
||||
strings_["lite_welcome_restore_hint"] = "Restore your wallet under Settings → Lite wallet request";
|
||||
strings_["lite_welcome_create_failed"] = "Could not create wallet";
|
||||
strings_["lite_restore_title"] = "Restore from seed phrase";
|
||||
strings_["lite_restore_intro"] = "Enter your recovery seed phrase. The wallet will be restored and then synced from the lite server.";
|
||||
strings_["lite_restore_seed_label"] = "Seed phrase (words separated by spaces)";
|
||||
strings_["lite_restore_birthday_label"] = "Birthday block (optional — 0 scans from the start, slower)";
|
||||
strings_["lite_restore_btn"] = "Restore wallet";
|
||||
strings_["lite_restore_ok"] = "Wallet restored — syncing from the lite server…";
|
||||
// Lite send-time unlock prompt.
|
||||
strings_["lite_unlock_title"] = "Unlock wallet";
|
||||
strings_["lite_unlock_msg"] = "Enter your passphrase to unlock the wallet for spending.";
|
||||
|
||||
@@ -219,6 +219,24 @@ std::string Platform::getDataDir()
|
||||
return getDragonXDataDir();
|
||||
}
|
||||
|
||||
std::string Platform::getLiteWalletDataDir()
|
||||
{
|
||||
// Mirror the SilentDragonXLite backend's get_zcash_data_path() for the "main" chain:
|
||||
// Windows/macOS: dirs::data_dir()/silentdragonxlite (data_dir = %APPDATA%, ~/Library/App Support)
|
||||
// Linux: ~/.silentdragonxlite
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\silentdragonxlite\\";
|
||||
}
|
||||
return getHomeDir() + "\\AppData\\Roaming\\silentdragonxlite\\";
|
||||
#elif defined(__APPLE__)
|
||||
return getHomeDir() + "/Library/Application Support/silentdragonxlite/";
|
||||
#else
|
||||
return getHomeDir() + "/.silentdragonxlite/";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Platform::getConfigDir()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -62,6 +62,15 @@ public:
|
||||
*/
|
||||
static std::string getDataDir();
|
||||
|
||||
/**
|
||||
* @brief Get the LITE wallet data directory (where the SilentDragonXLite backend stores
|
||||
* silentdragonxlite-wallet.dat). Mirrors the backend's get_zcash_data_path() for the
|
||||
* "main" chain: dirs::data_dir()/silentdragonxlite on Windows/macOS, ~/.silentdragonxlite
|
||||
* on Linux. Distinct from getDragonXDataDir() (the full-node blocks/wallet dir).
|
||||
* @return Path like ~/.silentdragonxlite/ or %APPDATA%\silentdragonxlite\
|
||||
*/
|
||||
static std::string getLiteWalletDataDir();
|
||||
|
||||
/**
|
||||
* @brief Get the config directory for storing wallet exports/backups
|
||||
* @return Path like ~/.config/ObsidianDragon/ or %APPDATA%\ObsidianDragon\
|
||||
|
||||
@@ -605,6 +605,38 @@ void LiteWalletController::startSync()
|
||||
});
|
||||
}
|
||||
|
||||
bool LiteWalletController::startRescan()
|
||||
{
|
||||
if (!walletOpen_.load()) return false;
|
||||
// Refuse if a sync/rescan is already running (would race two scans on the backend wallet lock).
|
||||
if (!syncDone_ || !syncDone_->load()) return false;
|
||||
|
||||
// Reset the done flag so refreshModel() re-enters its "scanning, publish progress only" path
|
||||
// and the UI shows progress again; clearing it BEFORE launching the thread avoids a window
|
||||
// where the worker would query balances mid-rescan.
|
||||
syncDone_->store(false);
|
||||
syncStarted_ = true;
|
||||
liteLog("Block re-download (rescan) started");
|
||||
|
||||
// The previous sync/rescan thread has finished (syncDone_ was true above); detach it before
|
||||
// reassigning syncThread_. Like startSync's thread it captures shared refs (bridge_ + syncDone_),
|
||||
// never `this`, so detaching is safe.
|
||||
if (syncThread_.joinable()) syncThread_.detach();
|
||||
|
||||
auto bridge = bridge_;
|
||||
auto done = syncDone_;
|
||||
syncThread_ = std::thread([bridge, done] {
|
||||
if (bridge) {
|
||||
// `rescan` clears the wallet's synced block cache and re-downloads/re-scans from the
|
||||
// birthday height — a blocking, uninterruptible full scan, same as `sync`.
|
||||
bridge->execute("rescan", "");
|
||||
bridge->execute("save", ""); // backend doesn't auto-save after a rescan
|
||||
}
|
||||
done->store(true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<LiteWalletAppRefreshModel> LiteWalletController::refreshModel()
|
||||
{
|
||||
if (!walletOpen_.load()) return std::nullopt;
|
||||
@@ -655,6 +687,29 @@ std::optional<LiteWalletAppRefreshModel> LiteWalletController::refreshModel()
|
||||
if (!mapped.ok) return std::nullopt;
|
||||
auto model = mapped.model;
|
||||
applyEncryption(model);
|
||||
|
||||
// `syncstatus` only reports synced_blocks/total_blocks WHILE actively scanning; once idle it
|
||||
// returns just {"syncing":"false"}, so the mapped walletHeight is 0 and the status bar showed
|
||||
// "blocks: 0" when fully synced. Query the wallet's last-scanned height (a fast local read) and
|
||||
// surface it as the synced height/tip so the block count is correct at rest.
|
||||
if (bridge_) {
|
||||
const auto h = bridge_->execute("height", "");
|
||||
if (h.ok) {
|
||||
try {
|
||||
const auto j = nlohmann::json::parse(h.value);
|
||||
if (j.is_object() && j.contains("height") && j["height"].is_number_unsigned()) {
|
||||
const unsigned long long height = j["height"].get<unsigned long long>();
|
||||
if (height > 0) {
|
||||
model.hasSyncStatus = true;
|
||||
model.sync.walletHeight = height;
|
||||
model.sync.chainHeight = height; // synced: wallet height == chain tip
|
||||
model.sync.complete = true;
|
||||
model.sync.progress = 1.0;
|
||||
}
|
||||
}
|
||||
} catch (...) { /* leave the mapped sync fields as-is */ }
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -864,6 +919,7 @@ LiteImportResult LiteWalletController::importKey(std::string spendingOrViewingKe
|
||||
LiteImportResult alt = runImport(transparentFirst ? "import" : "timport");
|
||||
if (alt.ok) out = alt;
|
||||
}
|
||||
if (!out.ok) liteLog("Key import failed: " + out.error); // error text only — never the key
|
||||
secureWipeLiteSecret(spendingOrViewingKey); // wipe our copy after both attempts
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -220,6 +220,15 @@ public:
|
||||
// op produces a ready wallet; safe to call once.
|
||||
void startSync();
|
||||
|
||||
// Re-download and re-scan every block from the lite server: runs the backend `rescan`
|
||||
// command (which clears the wallet's synced block cache and re-syncs from its birthday
|
||||
// height) on a detached thread, reusing the sync progress + refresh machinery. No-op if no
|
||||
// wallet is open or a scan is already running. True if a rescan was actually started.
|
||||
bool startRescan();
|
||||
|
||||
// True while the initial sync OR a rescan is actively scanning (not yet complete).
|
||||
bool scanInProgress() const { return syncStarted_ && !(syncDone_ && syncDone_->load()); }
|
||||
|
||||
// Generate a new address (shielded if true, else transparent) via the backend. Fast (local
|
||||
// key derivation), safe to call on the UI thread; the next refresh lists the new address.
|
||||
LiteNewAddressResult newAddress(bool shielded);
|
||||
|
||||
Reference in New Issue
Block a user