- Rename the portfolio heading "MY DRGX" -> "PORTFOLIO".
- Group cards are now clickable: clicking one opens the editor directly on that
group (edit mode), with a hover highlight + hand cursor.
- PortfolioEntry gains an `icon` (project_icons wallet-icon name) and a `color`
(packed IM_COL32 accent); both are persisted in settings.json.
- Editor edit-mode gains an icon picker (the same wallet icon set as the overview
tab's address-label dialog, via material::project_icons) with a "None" option,
and a color picker (preset accent palette + a default/no-color swatch).
- Group cards render the chosen icon to the left of the name and, when a color
is set, a left accent bar + the icon tinted in that color.
Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change — needs
a screenshot check of the Market portfolio + the editor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the Market tab rework, per feedback:
- Chart is more compact: a modest responsive height (~22% of the tab, floored
at 110px) instead of stretching to fill.
- Chart now has a time axis. The price series is one sample per ~60s market
refresh (RefreshScheduler::kPrice), so the x-axis is labelled in approximate
minutes-ago ("~45m … ~15m … now") instead of only "Now" — you can now read
the interval/span of each plot point.
- Portfolio: removed the top-level card background — the summary (fiat hero +
24h change + BTC, DRGX balance + Z/T, ratio bar) now floats directly under the
MY DRGX header, and the "GROUPS" heading is gone.
- Group cards redesigned: a max width (280px, shrinks on narrow tabs) so they
float rather than stretch; the group name is larger (subtitle font) top-left;
the DRGX + fiat amounts moved to the card's top-right corner; and more inner
padding around the content.
Removed the now-unused portfolio_groups i18n key. Full-node + Lite build clean;
ctest 1/1; hygiene clean. Rendering change — needs a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Market tab is non-scrolling, but an oversized fixed-height chart ate ~65%
of the height and squeezed the portfolio into a cramped strip, with custom
groups rendered as tiny afterthought rows at the very bottom.
- Precompute section geometry (hero header + portfolio card height) up front and
let the price chart ABSORB the remaining vertical space, capped at its schema
height and floored at 130px*vs so it stays useful. The chart now shrinks to
make room instead of dominating. Replaces the barely-used SectionBudget.
- Portfolio summary refined: fiat value stays the hero with the 24h change beside
it and the BTC value right-aligned; DRGX balance + Z/T breakdown on the next
row; a full-width shielded/transparent ratio bar + % label.
- Custom groups now render as a wrapping grid of mini glass cards (label + DRGX +
fiat each), sized to the card width, instead of a cramped one-line list. A
"GROUPS" subheader separates them from the all-funds summary. The card grows to
fit the grid.
New i18n key portfolio_groups ("GROUPS"). Full-node + Lite build clean; ctest
1/1; hygiene clean. Rendering change — needs a screenshot check of the Market tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings::getDefaultPath and AddressBook::getDefaultPath each hand-rolled the
same per-platform #ifdef (Win SHGetFolderPath / macOS Application Support /
XDG ~/.config) with the DRAGONX_APP_NAME variant suffix. Route both through
util::Platform::getConfigDir() instead — one place owns the split — and add
the missing __APPLE__ branch to getConfigDir so it too returns the
macOS-native ~/Library/Application Support/<AppName>.
This also fixes a latent macOS inconsistency: transaction_history_cache
already resolves via getConfigDir(), which lacked the __APPLE__ branch, so on
macOS the tx cache landed in ~/.config while settings/addressbook went to
~/Library/Application Support. Now all three agree. No migration needed:
Linux/Windows paths are unchanged, and macOS packaging is not yet shipped.
Removes ~50 lines of duplicated #ifdef.
Full-node + Lite build clean; ctest 1/1; hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
XmrigUpdater and DaemonUpdater carried byte-identical libcurl code —
httpGet, downloadToFile, and the write/progress callbacks — including the
security-critical TLS-verify (SSL_VERIFYPEER=1 / SSL_VERIFYHOST=2) and
max-body-size options, duplicated across two signature-verified
download-and-execute files that had to be hand-kept in sync.
Hoist that machinery into src/util/http_download.{h,cpp}:
- util::httpGetString(url, logTag)
- util::httpDownloadToFile(url, dest, logTag, maxBytes, onProgress)
threading progress + cancellation through a std::function (returning false
aborts the transfer) instead of the class `this` pointer. Each updater's
httpGet/downloadToFile become one-line forwarders that bind their own log
tag and archive-size cap (64 MiB miner / 256 MiB node, both preserved).
Every curl option is preserved byte-identically (verified by diffing the
setopt lines against the pre-change code — the only deltas are the
now-parameterized MAXFILESIZE cap and the XFERINFODATA payload). No behavior
change; the TLS/size-guard options now live in exactly one place.
Deliberately scoped DOWN per the audit's own guidance: the divergent
per-updater logic (release DTOs/parsers, checksum-table parsing, version
compare, and installResolved's per-member-SHA vs transitive-trust verify,
install strategy, and chmod scope) stays in each class — NOT merged behind a
base-class/virtual-hook layer, which would raise review cost on
signature-verified execute-code and risk a subtle security regression. The
shared-DTO/parser consolidation (~77 test refs of churn on stable code) is
left as a separate, lower-value follow-up.
Full-node + Lite build clean; ctest 1/1; source-hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mechanical, behavior-preserving consolidations from the audit. Rendering
extractions were kept byte-equivalent; sites that couldn't be made identical
were left as-is (noted below).
Shared helpers:
- util::truncateMiddle(s,maxLen) / (s,front,back) in text_format.h — replaces
6 file-local middle-ellipsis truncators + several inline substr sites
(send/receive/transactions/balance_recent_tx/explorer + app.cpp + 3 dialogs).
Carries the maxLen<=3 guard, fixing the latent unsigned-underflow copies.
- material::LoadingDots() in draw_helpers.h — one animated-ellipsis source for
8 copy-pasted spinner sites (identical GetTime()*3 phase preserved).
- Reuse the existing FormatHashrate() in explorer_tab + peers_tab (dropped two
inline hashrate ladders).
- material::DrawButtonGlassOverlay() — the glass-fill/rim/tactile overlay block
shared by TactileButton / TactileSmallButton / schema TactileButton.
- material::CollapsibleHeader() — the invisible-button + label + chevron idiom
(3 of 5 settings_page sites; RPC/Debug headers skipped — non-identical).
- material::GlassCardScope (RAII) — the ChannelsSplit/Indent/DrawGlassPanel/
ChannelsMerge card scaffold (5 settings_page cards; About/send/receive skipped
— different padding / logo interleaving).
- material::DialogWarningHeader()/DialogConfirmFooter() — warning header +
50/50 Cancel/danger footer across the confirm dialogs; button height moved
from a hardcoded 40px into ui.toml (components.overlay-dialog.confirm-btn-height).
- File-local enterLowSpec()/exitLowSpec() collapse the 3 low-spec snapshot copies.
i18n: wrapped hardcoded English in the shared balance render paths and the
whole Lite lifecycle/security section in TR(), with English defaults added to
loadBuiltinEnglish() (res/lang/*.json left for the translation tooling — TR
falls back to English, so output is unchanged).
Full-node + Lite build clean; ctest 1/1; hygiene clean. These are rendering
changes — the Settings page (cards/headers/confirm dialogs), all tactile
buttons, and the Lite settings section warrant a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the RPC collapsible header from "RPC CONNECTION" to "RPC Connection..." and
render it in the body2 font so it matches the "Tools & Actions..." toggle exactly.
Full-node build clean; hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a button to the left of "Open data folder" that opens the ObsidianDragon
config folder (util::Platform::getObsidianDragonDir() — ~/.config/ObsidianDragon
etc.: settings, themes, logs), distinct from the daemon data/blockchain folder.
The two buttons render as a right-aligned group on the data-dir row, each an
explicit button-font width so the group's right edge lands exactly on the card
edge (no overflow). New i18n: settings_open_app_dir / tt_open_app_dir.
Full-node build clean; hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single-column left every row hugging the left with a blank right half (and a
split RPC row); two-column variants left a bottom-corner gap. Rework the card as
a 2x2 grid on wide full-node windows so both halves carry real content:
TOP-LEFT NODE/Data | TOP-RIGHT RPC
BOTTOM-LEFT SECURITY | BOTTOM-RIGHT DAEMON BINARY
with the two top cells sharing a row bottom, a thin vertical + horizontal
divider, and the Daemon "Advanced" destructive buttons dropping to a full-width
4-button strip below the grid when expanded.
Gated behind one `useGrid` flag (supportsFullNodeLifecycleActions() &&
availWidth >= node-grid-breakpoint, default 900). When false — narrow windows and
lite — it falls through to the existing single full-width column, unchanged
(one-flag-revertible). Wallet Size moved to its own row (removes a mid-row gap in
both layouts). RPC / Security / Daemon rebased to per-cell X/width via aliases;
Advanced buttons factored into one lambda so the disabled predicates live in a
single place. Design produced + adversarially critiqued via a multi-agent pass.
New: components.settings-page.node-grid-breakpoint (ui.toml), "rpc_connection"
i18n string. Full-node + lite build clean; ctest green; hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the configurable-portfolio UI on the persisted model:
- The portfolio card keeps the "all funds" summary and now renders each custom
entry below it as a compact row (label · summed DRGX · USD), live-computed via
SumPortfolioBalance over the wallet's per-address balances; the card grows to
fit the entries.
- A right-aligned "Manage…" button on the card header opens a modal editor
(material overlay dialog): list entries with Edit/Delete, "Add entry", and an
edit form = label field + quick "All shielded / All transparent / Clear"
selectors + a checklist of wallet addresses (with balances). Persists to
Settings on each mutation.
- i18n English defaults for the new portfolio_* strings.
- Test namespace fix (AddressInfo is dragonx::, not dragonx::data::).
Full-node + lite build clean; ctest green; source hygiene clean. Remaining:
the Overview-tab right-click "add/remove address to a label" integration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero-behavior-change cleanup ahead of the console refactor:
- Delete dead API/members: addCommandResult (unused), handleSelection (declared, no
def), isAutoScrollEnabled (no callers), and scroll_to_bottom_ (written 8× but never
read) with all its writes.
- Drop the unused screenToTextPos `line_height` parameter (+ 4 call sites) and the unused
`segEnd` local — clears the two -Wunused warnings.
- Remove the toolbar filter checkboxes' dead `static bool s_prev_*` change-detectors,
whose only effect was setting the dead scroll_to_bottom_.
- Fix the console_new_lines format/arg mismatch (format takes one %d; the call passed a
spurious plural-suffix arg).
- Fix the channel/color incoherence: a "[daemon] error:" line kept red text but got a
blue daemon bar — the prefix no longer downgrades an error channel.
- Delete 11 orphaned lite_console_* i18n keys left over from the console merge (keep
lite_console_help_passthrough).
Full-node + lite build clean, ctest green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Right-clicking the output now offers "Copy <value>" when a long alphanumeric token
(txid/blockhash/address, >=16 chars) is under the cursor — resolved via screenToTextPos.
Uses right-click so it never conflicts with the left-drag text selection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
renderOutput already draws a clickable "N new lines" jump-to-bottom indicator
(console_tab.cpp ~996), so the pill added earlier duplicated it — and reused the
"console_new_lines" format string incorrectly. Remove the pill and the stray i18n entry;
the existing indicator stands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Highlight the matched substring (case-insensitive) in every visible line with a
translucent yellow behind the text, drawn per wrap-segment like the selection highlight.
- Show a live match count next to the filter input ("N matches", red when zero).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface new_lines_since_scroll_ (already tracked) as a floating pill at the output
panel's bottom-right whenever the user is scrolled up with unseen output; clicking it
re-enables auto-scroll and snaps to the bottom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two consoles were separate implementations (the rich full-node ConsoleTab and a
minimal RenderLiteConsoleTab). Introduce ConsoleCommandExecutor to abstract the only
real differences — command execution + log source — so both variants share the one rich
terminal (the lite console now gains selection, zoom, copy, and JSON-colored output).
- console_command_executor.{h,cpp}: interface + FullNodeConsoleExecutor (dragonxd log
ingestion + RPC command execution, result queue) and LiteConsoleExecutor (lite
diagnostics ring + backend runConsoleCommand/takeConsoleResult, connection/sync status).
- ConsoleTab::render() now takes a ConsoleCommandExecutor&; log ingestion, command
submission, and command results flow through it. The RPC command-reference popup and
the daemon/errors/rpc-trace/app filter toggles are gated on the executor's capabilities;
the toolbar status dot + a status header come from the executor.
- Safety preserved: `clear`/`cls` is intercepted as a view-only clear in the shared UI so
it is NEVER forwarded (the lite backend's `clear` wipes tx history); command output is
not persisted to diagnostics.
- App owns the executor (created per variant) and routes both NavPages to console_tab_;
lite_console_tab.{h,cpp} deleted.
Needs runtime verification of BOTH consoles (full-node RPC + lite: clear, seed/export
secrecy, sync status, command results).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fetch /coins/dragonx-2/tickers once per session (App::refreshExchanges, reusing the
price-fetch worker path) and build the exchange registry at runtime into
state.market.exchanges; parseCoinGeckoTickers groups tickers by venue. The Market tab
sources from this live list and falls back to the compiled-in registry (now including
OurBit + Nonkyc.io) when offline. Fixes the missing OurBit pair and auto-tracks future
listings.
- Fix stale selection defaults (TradeOgre / DRGX-BTC -> Nonkyc.io / DRGX/USDT) that never
matched the registry, and make the attribution generic ("Price data from CoinGecko").
- Reframe the single-asset portfolio card as a DRGX holdings dashboard: relabel to
"MY DRGX" and show the 24h change on the holdings value (colored by direction).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Data + service layer for supporting multiple mining pools:
- pool_registry: KnownPool table (pool.dragonx.is + pool.dragonx.cc) carrying
each pool's stratum host, xmrig algo, and stats-API schema; pure helpers
findKnownPoolByUrl / resolvePoolAlgo / parsePoolHashrate / chooseWeightedPool
(weighted-random by hashrate with incumbent stickiness).
- pool_stats_service: background, schema-aware hashrate fetcher (libcurl, TLS,
browser UA for Cloudflare) modelled on XmrigUpdater.
- settings: pool_select_mode (manual | auto_balance), string-serialized.
- i18n strings + unit tests (both stats schemas, algo resolution, weighted pick).
Note: pool.dragonx.cc's stratum host is us.dragonx.cc:3333 — the .cc domain is
only the Cloudflare-proxied web/API host and does not accept stratum on :3333.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a full-node daemon updater (util/DaemonUpdater + daemon_download_dialog)
reachable from Settings -> NODE & SECURITY: downloads/verifies (SHA-256 +
enforced ed25519 signature) and atomically installs the latest dragonxd from
the project Gitea, with a "Restart daemon now" step. Add a shared "Browse all
releases..." picker (release_list_view) to both the miner and daemon updaters
so users can pin older/pre-release builds. Pure no-I/O cores
(daemon_updater_core / xmrig_updater_core) are unit-tested; sign-daemon-release.sh
signs release archives offline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified by running the app against a live node and watching a real rescan. Three
issues that only surfaced at runtime:
- Wrong RPC name: this daemon (hush/komodo) exposes the runtime rescan as
"rescan <height>", not bitcoin's "rescanblockchain". runtimeRescan() and
RPCClient::rescanBlockchain() used the bitcoin name and failed with "Method not
found" on every node. Corrected to "rescan".
- Witness/rescan progress never surfaced during a rescan: the daemon-output parser
that drives it was gated behind rpcConnected, but a heavy rescan holds cs_main so
getinfo times out and the RPC reads disconnected — silencing the parser exactly
when it's needed. The parser reads the daemon's stdout pipe (no RPC), so it now
runs whenever the daemon process is alive. It also now parses INLINE on the main
thread instead of via fast_worker_, so it can't be starved when the worker is
blocked on a getrescaninfo call (which waits on cs_main during a witness rebuild).
- Witness rebuild has TWO sub-phases with different scales — the initial-witness
pass ("Setting Initial Sapling Witness for tx <hash>, <i> of <N>") and the cache
walk ("Building Witnesses for block <h> <frac> complete, <n> remaining"). Tracking
them with one monotonic value pinned the bar at the initial pass's ~100% through
the whole cache walk. They're now tracked as distinct phases (witness_phase) with
their own monotonic progress and labels ("Setting witnesses" vs "Rebuilding
witnesses"), so neither resets/bounces and the long phase shows real movement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The daemon's post-rescan witness rebuild ("Building Witnesses for block ...")
is a distinct, often-long phase that previously showed only as an indeterminate
"Rescanning..." with no progress. Parse the daemon's witness-build log lines and
surface a dedicated progress indicator.
- Parse "Building Witnesses for block <h> <frac> complete, <n> remaining" (and the
earlier "Setting Initial Sapling Witness for tx ..., <i> of <m>") from daemon
output, extracting a 0..1 fraction and remaining-block count.
- New SyncInfo fields building_witnesses / witness_progress / witness_remaining,
cleared at every rescan-completion site (warmup-end, getrescaninfo poll, runtime
rescan callback, daemon-log "finished").
- Status bar shows "Rebuilding witnesses NN%" (priority over the generic rescan
text); the loading overlay (shown during -rescan warmup) gets a labelled witness
progress bar with the remaining-block count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A wallet bootstrapped from a snapshot keeps its wallet.dat but never rescans, so
its spent-state is stale and the first send tries to spend already-spent notes and
is rejected. The startup -rescan flag can't fix it either: the snapshot lacks the
pre-snapshot block history -rescan needs, so it errors. The working fix is a runtime
rescanblockchain RPC from a height the snapshot actually has.
- Add App::runtimeRescan(startHeight): runs rescanblockchain via the worker, drives
the rescanning UI state, and owns completion via the RPC callback (getrescaninfo
is unavailable on this daemon). Suppresses the per-second mining/rescan pollers
and the Core/balance/tx refreshes while the daemon holds cs_main for the scan.
- Add App::detectLowestAvailableBlockHeight(): async binary search via getblock for
the lowest height whose block data is on disk → the snapshot base, and whether the
node still has full history.
- Auto-reconcile after bootstrap: both completion sites (wizard + Settings download
dialog) mark a pending rescan; once the daemon is back up and the tip is known,
detect the base and runtimeRescan() from it (or -rescan restart on a full node).
- Settings "Rescan Blockchain" now probes first: full-history nodes get the existing
-rescan restart; bootstrapped/pruned nodes get a prompt pre-filled with the
detected base height that runs the runtime rescan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Delete Blockchain and Repair Wallet now sit on one line, paired with the same
uniform button width as Test Connection / Rescan Blockchain (a clean 2-column grid;
all maintenance buttons share one rowBtnW sized across the four labels).
- The Daemon binary panel lays Installed and Bundled side by side across the node
column (version + size·date | version + size) instead of stacked narrow lines, with
the status line spanning underneath and Install bundled / Refresh paired below.
- Shorten the install button label to "Install bundled" so it fits the shared width;
the tooltip still explains the full action. Date shown as YYYY-MM-DD.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously the wallet re-extracted the bundled dragonxd on startup whenever the
installed binary's size differed from the bundle ("stale" overwrite), which could
replace a node a user had deliberately placed in dragonx/.
Now dragonx binaries (dragonxd/cli/tx) are auto-placed ONLY when missing — never
auto-overwritten on a size mismatch (needsParamsExtraction + extractEmbeddedResources).
Params/asmap keep their size-based refresh; a daemon dropped next to the wallet exe
still takes priority and is never touched.
Replacing the daemon is now an explicit action: Settings → "Daemon binary" reports the
installed binary's version (scanned from the file), size and modified date, compares it
to the version bundled in this build, and offers an "Install bundled daemon" button.
That stops the node, overwrites dragonxd/cli/tx with the bundled copies (waiting for the
process to release the file lock), and restarts — wallet/keys/chain data untouched.
Adds resources::{getInstalledDaemonInfo,getBundledDaemonInfo,reextractBundledDaemon}
(+ a version-string scanner) and App::reinstallBundledDaemon().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a note's stored record is corrupt or its tx isn't in the canonical chain,
z_sendmany fails to build a valid sapling spend proof even after a full -rescan,
because a plain rescan replays witnesses but keeps the existing tx/note records.
The zcashd repair for this is -zapwallettxes=2, which deletes all wallet tx/note
records and rebuilds them from the chain (keys/addresses preserved).
Adds a RepairWallet lifecycle operation that mirrors the existing -rescan plumbing
(one-shot zapOnNextStart flag on the embedded daemon; -zapwallettxes=2 implies and
supersedes -rescan), an App::repairWallet() that reuses the rescan status UI (so the
status bar + warmup-end completion detection apply), and a confirmed "Repair Wallet"
button + dialog in Settings → node maintenance (embedded daemon only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two related fixes for the post-bootstrap "send fails / rescan stuck at 99%" trap:
1) Rescan completion now keys off warmup-end. A -rescan runs entirely inside daemon
warmup (every RPC returns -28 until it finishes), so warmup completing IS the rescan
completing. The old detectors relied on getrescaninfo (which some daemons answer with
"Method not found") or a "Done rescanning"/bench log line the daemon may never print,
leaving the status bar stuck at 99% — so users killed the rescan before it finished.
When warmup ends and a rescan was confirmed active, clear the rescan state, flip to
100%, refresh history/balance, and toast completion.
2) z_sendmany failures that mean stale shielded note data (shielded-requirements-not-met,
missing sapling anchor, invalid sapling spend proof, bad-txns-sapling-*) now append a
plain-language hint telling the user to run a full rescan, instead of surfacing only the
raw daemon string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On first run the lite welcome screen's "Restore from seed" button only showed a
hint toast and bounced the user to Settings, dismissing the welcome with no
wallet open — it never prompted for a seed. Add a real restore step to the
welcome wizard: a seed-phrase field + optional birthday height, which calls
beginRestoreWalletAsync() (same server failover as create/open), shows
"Restoring…" progress, then completes (wallet syncs) or surfaces the error to
retry. The seed buffer is wiped on success/Back and in finish().
(The Settings -> Lite -> Restore path already prompted for a seed; this fixes
the first-run welcome path.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lite SilentDragonXLite backend stores its wallet in its own directory
(dirs::data_dir()/silentdragonxlite — %APPDATA%\silentdragonxlite on Windows,
~/.silentdragonxlite on Linux, ~/Library/Application Support/silentdragonxlite on
macOS), NOT the full-node getDragonXDataDir() (…/Hush/DRAGONX). The newly added
lite "Open data folder" button opened the wrong (full-node) directory.
Add Platform::getLiteWalletDataDir() mirroring the backend's get_zcash_data_path
for the "main" chain, and point the lite button at it. The full-node button is
unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an explicit button in Settings that opens the wallet/blockchain data
directory (getDragonXDataDir()) in the OS file manager via the existing
Platform::openFolder(). Placed in the full-node connection section (next to the
data-dir path, which was only a subtle clickable link) and in the lite section
(always available). i18n strings added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a maintenance option for the lite wallet to re-download and re-scan every
block from the lite server — useful when balances or history look wrong.
- LiteWalletController::startRescan() runs the backend `rescan` command (which
clears the wallet's synced block cache and re-syncs from its birthday) on a
detached thread, reusing the existing sync progress/refresh machinery: it
resets syncDone_ so refreshModel() shows progress again and refreshes data on
completion. No-op if no wallet is open or a scan is already running.
- scanInProgress() exposes the initial-sync-or-rescan state.
- Settings (lite, open wallet) gains a "Redownload blocks" button behind a
confirmation modal, disabled while a scan is running. i18n strings added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The console mixed RPC traces, daemon output, and the wallet's own "[app] ..."
log lines with no way to hide the latter. Add an "App" checkbox alongside the
existing Daemon/Errors/RPC toggles. Since [app] lines share COLOR_INFO with
other info text, the filter matches them by their "[app] " prefix rather than by
color. Default on; unit test + i18n added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a fuzzy mode to the explorer search: a non-numeric, non-full-hash query now
filters the list to cached blocks whose hash (or height text) contains the query
substring, live as you type. Backed by a new ExplorerBlockCache::searchBlocks()
(SQLite LIKE with escaped wildcards), memoized per query so it doesn't hit the DB
every frame. Exact queries still navigate precisely: a block height re-anchors
the list, and a full 64-char hash is resolved via RPC. Row clicks still open the
detail modal. Empty results show "No matching cached blocks".
Note: fuzzy matching covers cached (browsed/prefetched) blocks only — the daemon
has no partial-hash index — while exact height/hash lookups reach any block.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Rescan blockchain and Restart daemon buttons fired immediately on click —
both are disruptive (long offline rescan / connection drop) and easy to hit by
accident. Route them through confirmation modals, matching the existing
delete-blockchain / clear-ztx confirmations: the button now sets a confirm flag
and an overlay dialog performs the action only on explicit confirm. New i18n
strings added with English defaults.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a sort selector next to the type filter with four modes: Newest first
(default), Oldest first, Largest amount, Smallest amount. The mode folds into
the merged-list memoization cache key (so the list re-sorts only when the mode
changes) and the comparator branches on it, keeping txid as a deterministic
tiebreak. Changing the sort resets to page 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
History streams in over many refresh cycles (the incremental shielded scan
walks every z-address), so the first batch appears long before the list is
complete — with no indication more is still coming. The existing loading banner
deliberately goes quiet once any rows are on screen.
Track whether the first full shielded scan has finished
(initial_history_scan_complete_) and, until it has, surface a progress percentage
(fraction of z-addresses scanned) in transactionRefreshProgressText() — which the
History tab already renders as its pulsing loading indicator. Goes quiet once the
first scan completes; routine per-block re-scans don't re-trigger it. Reset on a
full history invalidation (rescan / session reset) so it shows again on reload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Auto-clear: the Copy button now routes through App::copySecretToClipboard, so a copied
private/viewing key is wiped from the clipboard after ~45s (same protection as the seed) with
a "auto-clears" notice — instead of the raw SetClipboardText that left it indefinitely.
- QR: once the key is revealed, a Show/Hide QR toggle renders the key's QR inline (via the same
GenerateQRTexture/RenderQRCode widget the Receive tab uses) for scanning into another wallet.
The QR texture is cached, regenerated on key change, and freed on hide/close/dismiss; hiding the
key also hides its QR.
- Actions row tightened to Show/Hide · Copy · QR, and the key + QR texture are now cleared on any
dismissal (Close button, scrim click, Esc), not just the Close button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The address list supported two drag gestures that collided: dragging a row onto another
transferred funds, dragging into a gap reordered. Since rows are contiguous, a reorder-drag was
almost always over another row, so it triggered a fund transfer instead of reordering.
Disambiguate by WHERE on the target row the drag is released (user's suggestion): the top/bottom
~30% edge bands = reorder (an insertion line is shown), the centre = transfer (the row highlights).
A zero-balance row or an off-row drop always reorders. Tooltip and i18n hint updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- rpc_client::callRaw: a daemon error object is no longer assumed to carry a string
"message" — a malformed error now yields a clean "RPC error: <dump>" instead of throwing
a json type-exception from .get<std::string>().
- sendTransaction (full-node): add a single-flight guard so a rapid double-click can't issue
two z_sendmany before the first returns its opid. The lite path already guarded this; the
send form guards it in the UI, but the controller entry point now does too.
(#9 from the audit was mostly false positives on verification — all popen sites already
null-check and the xmrig download FILE* path has no throwing calls. The payment-URI
checksum idea was dropped: the send flow already checksum-validates the recipient before
broadcasting, and tightening the parser would reject the placeholder addresses the existing
test relies on; added a comment noting this is format-only by design.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-frame hot paths in the immediate-mode UI were allocating needlessly:
- Address filtering in the balance tab rebuilt a std::string filter per address AND
containsIgnoreCase() lower-cased two fresh copies per call — ~6×N allocations/frame
on large wallets. New util::containsIgnoreCase(string_view, string_view) is
allocation-free, and the filter is now built once outside the loop.
- Four duplicated "time ago" implementations (balance_tab_helpers, balance_recent_tx,
send_tab, transactions_tab) are consolidated into util::formatTimeAgo (localized long
form) + util::formatTimeAgoShort (compact "5s ago"), preserving each call site's exact
display style. Both use snprintf, no per-row string concatenation.
- The send-tab address-suggestion scan (a walk over the whole tx list) is memoized on the
typed text + tx count, so it no longer recomputes every frame while the user pauses.
New src/util/text_format.{h,cpp}; the two existing containsIgnoreCase/timeAgo definitions
now delegate to it. Added to both the app and test targets (test target also gains i18n.cpp,
which text_format's localized path needs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two failure modes left the wallet stuck on a silent "connecting / Starting
dragonxd…" spinner with no path forward:
1. Stale external-daemon latch. EmbeddedDaemon::start() sets
external_daemon_detected_ whenever the RPC port was busy at a prior attempt
and never re-checks it, so tryConnect's no-config branch trusted that latch
and waited forever for a config the phantom would never write — even after a
stale/half-dead process freed the port. Now the port is re-evaluated LIVE
(EmbeddedDaemon::isRpcPortInUse()) each attempt: if it's genuinely busy we
keep waiting (and, after a bounded ~20s with no config, warn that whatever
owns the port isn't a usable DragonX node and how to fix it); if it's free we
fall through and start our own daemon.
2. Silent start failure. When startEmbeddedDaemon() failed (binary not found,
Sapling params missing, spawn failure) the status stayed on "Starting
dragonxd…" with the real reason only in a VERBOSE log. Now the reason
(daemon_controller_->lastError()) is surfaced once as a sticky error
notification, with a short "Couldn't start dragonxd" status.
Both counters reset on a successful connect so the messages re-arm for the next
disconnect. Lite is unaffected (tryConnect returns early for lite builds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lite backend's litelib_execute() is the same command interface as
silentdragonxlite-cli (balance, info, height, list, notes, addresses, sync,
syncstatus, new, send, shield, encrypt, …), so the lite Console can be a real
interactive console — like the full-node RPC console — instead of a read-only
diagnostics log.
Controller: add an async arbitrary-command runner mirroring the broadcast
pattern — runConsoleCommand() splits "<command> [args]" (the first token is the
command, the remainder is passed as the single arg string litelib_execute
expects, since it does NOT whitespace-split), runs the bridge call on a detached
thread that captures the shared bridge (never `this`), and delivers the result
to a main-thread slot drained by takeConsoleResult(). Results are NEVER routed
through LiteDiagnostics (seed/export can return secrets).
Console tab: a command input (Enter to run, Up/Down history via the shared
console_input_model helpers) over a unified scroll buffer that interleaves the
automatic diagnostics events with user command I/O, colour-coded, with the live
status header preserved. The input is disabled while a command runs.
Two backend footguns are intercepted at the UI layer before forwarding:
`clear` (the backend command WIPES wallet tx history — re-bound to clearing the
view, what the user expects) and `quit`/`exit` (would only save; the embedded
backend must stay running with the app).
Test: runConsoleCommand drives the fake backend (info -> raw response; "new zs"
-> exercises the command/arg split; blank line rejected).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Console could look empty if the wallet produced few events. Make it useful
in every state and remove a cross-platform footgun:
- Add a live status header read straight from the controller (connected /
connecting / disconnected, sync %, and the last open error) — independent of the
diagnostics event log, so the Console always shows the current connection +
wallet-open state even when the log is sparse.
- Move LiteDiagnostics::instance() into a single .cpp so there is exactly one
instance across the binary, rather than relying on the linker folding an
inline-function static across translation units (a known fragility, especially
on mingw/Windows — the most likely cause of a stuck-empty event log there).
Verified the writer and reader share one instance on Linux; builds clean for
full-node, lite, and Windows cross-compile; tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lite variant had no visibility into why a wallet failed to open — just a
"disconnected" spinner. Add a lite-only Console tab (full-node keeps its RPC
console) that shows a live diagnostic log.
- LiteDiagnostics: a small thread-safe, bounded ring buffer (header-only). The
controller writes to it from its background threads: each failover server
attempt and result, wallet open/create/restore outcomes, sync start, and
blocked-open reasons. The App logs controller (re)builds with the preferred
server.
- lite_console_tab: a terminal-styled, read-only view of the log (newest at the
bottom, error/success lines coloured) with Clear / Copy / Auto-scroll. Reachable
even when the wallet is locked (it's diagnostics, no secrets). Registered as
NavPage::LiteConsole, gated lite-only via WalletUiSurface::LiteConsole.
A unit test drives an open-with-failover and asserts the log records the
connection attempt and the successful open. Built clean for full-node, lite, and
Windows cross-compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both variants hardcoded "ObsidianDragon" as the per-user config folder
(settings.json, themes, the lite_rollout cache), so the lite app and the
full-node app shared one settings.json. That cross-variant pollution can leave
the lite server selection in a bad state — and since openWallet() contacts the
selected lightwalletd server, a wrong/empty server URL there makes an existing
wallet fail to open (a silent "disconnected" spinner).
Use DRAGONX_APP_NAME (already "ObsidianDragon" / "ObsidianDragonLite" per variant)
for the config-dir name in Settings::getDefaultPath, Platform::getConfigDir and
getObsidianDragonDir (and the theme-setup exe-name probe). Full-node is unchanged;
lite now reads/writes %APPDATA%\ObsidianDragonLite (and ~/.config/ObsidianDragonLite),
so it starts from a clean, isolated config and uses default servers.
Note: the lite wallet file itself lives in the litelib backend's own data dir
(unaffected); this isolates the GUI config only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The send screen labelled any prefix+length match as a "Valid" address, so a
mistyped address that still matched the pattern passed the gate. Add pure,
offline checksum validation — Base58Check (transparent R-addresses) and Bech32
(Sapling zs-addresses) — and require it in the validity check. Both verifiers are
version-byte/HRP agnostic (the HRP is taken from the string, the Base58 checksum
is chain-independent), so a correct implementation never rejects a genuine
address while catching transcription errors. Works for both build variants
(no daemon round-trip), unit-tested against standard BIP173 / Base58Check vectors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
removeVault() overwrote vault.dat with zeros then unlinked it, but never flushed
to stable storage, so the zeros could stay in the OS cache and never reach disk.
flush + fsync before unlink on POSIX (still best-effort on CoW/SSD, but now does
what it claims).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Many strings are used directly as printf/ImGui format strings, and translations
are loaded from user/installer-modifiable JSON with no validation. A translated
value that drops or changes a conversion specifier would be passed to printf with
mismatched varargs (undefined behavior) on a wallet screen.
overlayTranslations() now compares each translated value's argument signature
against the English source and keeps English on mismatch. Also adds the
send_status_unconfirmed string used by the deferred-send-result path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Write vault.dat atomically and 0600 (it holds the PIN-encrypted passphrase, so
a world-readable copy enables an offline brute-force of the short PIN), and
chmod the tx-history SQLite + its WAL/SHM sidecars to 0600 on open.
- The tx-history snapshot and key-salt rows are keyed on a hash of the full
address set, which changes whenever a new address is generated — orphaning the
prior hash's full-history blob and salt forever. pruneOtherWallets() now drops
rows for every non-live wallet hash on each save, bounding the database.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
settings.json and addressbook.json were written in place with a bare ofstream —
a crash or power loss mid-write truncated the file, and on the next launch the
parse failure silently reset every preference (hidden/favorite addresses, labels,
pool workers, language, theme, lite-server list) because the next save overwrote
the corrupt file with defaults.
Add Platform::writeFileAtomically() (temp file -> fsync -> atomic rename; dir
fsync on POSIX, MoveFileEx on Windows; optional owner-only 0600) and route both
saves through it. On a parse failure, quarantine the unreadable settings file to
settings.json.corrupt-<ts> instead of clobbering it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a status panel at the top of the Network tab driven by the live WalletState:
- Connection: a colored dot + Connected / Syncing / Not connected, with the in-use server host
(or "Random server") and its latency on the right.
- Sync: "<pct>% · <walletHeight> / <chainHeight>" while syncing (with a thin progress bar),
"Synced · block N" when complete, or "No wallet open" when disconnected.
Reads app->state().sync (populated by the lite refresh: progress / wallet+chain height / complete)
and state().connected (= walletOpen). Advances with a Dummy so the bounds grow correctly.
Both variants build; suite passes; hygiene clean; lite GUI smoke OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>