Fund-correctness audit, send path:
- Flagship wrong-send: s_send_max was only cleared on a successful send, so after
Max → typing a smaller amount → changing the fee tier in the confirm dialog,
the amount was silently overwritten back to the maximum and Confirm sent far
more than the user typed. Clear s_send_max on any manual amount edit (DRGX and
USD inputs), so a later fee bump never rewrites a hand-entered amount.
- A valid exact-maximum send was blocked by 1 ulp because amount+fee<=available
was computed in IEEE doubles. New util::toSats/canAfford compare in int64
satoshis; wired into the Review gate, the confirm-time re-check, the Send
tooltip and the amount-error banner. This only unblocks a correct send; it
changes no sent amount.
- Added util::coinsToZatoshis (reject non-finite / <=0 / rounds-to-0 / over-supply)
and regression tests for formatAmountFixed, toSats/canAfford and coinsToZatoshis
(incl. the exact failing inputs the audit found).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Defense-in-depth against a local attacker / core dump reading un-zeroed heap
copies of secrets (threat model is local, not remote). Best-effort by nature —
std::string SSO/reallocation and ImGui buffer growth can still strand copies —
so these shrink the exposure window rather than guarantee absence:
- rpc_client performCall: a scope guard now sodium_memzeroes the request body +
payload tree (walletpassphrase / *_importkey args) after the call and detaches
CURLOPT_POSTFIELDS. Request content sent to curl is unchanged.
- New ui::scrubImGuiInputTextState() zeroes ImGui's retained InputText buffers
(live/revert/callback/deactivated) — the app memzeroed its own char[]s but
ImGui kept copies. Called from wipeSecrets()/wipeDisplayedSecrets(). PINNED to
ImGui 1.92.6 via a #error guard so a vendored bump forces a re-audit (the stb
undo buffer is an incomplete type in the header, so it's left as a noted gap).
- Lite exportSeed/exportPrivateKeys wipe result.value + the parsed JSON tree.
- Seed-word grid (SplitSeedWords/RenderSeedWordGrid) zeroes its per-frame word
and cell copies; the wizard word/chip vectors are zeroed before clearing.
- ChatStore::clear() + ResetChatTab() zero decrypted message bodies on
lock/switch/teardown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Six bounded defensive fixes:
- Payment-URI amount is now strictly validated (reject NaN/inf/sign/garbage,
<=8 decimals, 0..21,000,000) at parse time, so a crafted QR/URI can't inject a
bogus amount into the Send form. The z_sendmany call the user confirms is
unchanged; this is input validation only.
- export-all-keys filename is sanitized (basename-only, no traversal) via a
shared util::sanitizeExportFilename (factored out of the CSV export dialog).
- Chat append()/upsert() wrap the strict serialize in try/catch so a malformed
(invalid-UTF-8, attacker-influenceable) body is skipped, not crashed on —
matching appendBatch's existing drop behavior.
- http_download (used by the updaters + pool stats) restricts transfers AND
redirects to HTTPS and bounds the download path's redirects, so a redirect
can't downgrade to http:// or another scheme.
- The default DRGX.conf (rpcuser/rpcpassword) is written 0600 from the start via
writeFileAtomically, closing the brief world-readable ofstream+chmod window.
- Auto-lock is suppressed only for SOLO mining (needs the wallet), not pool
mining (pays an external address), which was holding the wallet open forever.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The console redacted secret command ARGS but showed the OUTPUT of key-revealing
commands (dumpprivkey / z_exportkey / z_exportviewingkey / z_exportmnemonic /
dumpwallet / z_exportwallet, and the lite seed/export verbs) in full — visible
in the scrollback, Copy All, Ctrl+C and the filter. Now, when the submitted
command is key-revealing, its result is held ONLY in a member buffer (never in
the scrollback model, so Copy-All etc. can't leak it) and a masked placeholder
is shown. An explicit "Copy secret" button routes through the auto-clearing
clipboard; the buffer auto-wipes after 60s, on Clear, and on wallet lock
(via wipeDisplayedSecrets). The command string passed to the executor is
byte-identical. New pure helper ConsoleCommandRevealsSecret is unit-testable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- writeFileAtomically honored restrictPermissions only on POSIX (0600); on
Windows it was a no-op, so exported keys / seed backups / the vault / chat
exports inherited the parent dir's ACL. It now builds a PROTECTED owner-only
DACL (current-user SID, SE_DACL_PROTECTED) on the temp file before the atomic
rename; best-effort, falling back to default ACLs on failure. (Verified it
compiles under mingw-w64; the POSIX path is unchanged.)
- The key-export dialog's Copy wrote the private key to the plain, permanent
clipboard; it now goes through copySecretToClipboard() (45s armed auto-wipe +
its own "copied, auto-clears" notice), like the seed backup copy.
- Bootstrap archive verification dropped the collision-prone MD5 fallback: it now
requires a published SHA-256 and fails closed otherwise. (Authenticity vs a
malicious server still needs a detached ed25519 signature like the updaters —
a separate release-coordinated change.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Locking the wallet (manual / auto / lite) now calls wipeDisplayedSecrets(),
which scrubs + closes any revealed seed phrase, migration seed, or exported
key — previously a locked wallet could leave the phrase/key on screen and
resident. Runs synchronously, before the async lock round-trip.
- The console secret-arg redaction list only had full-node RPC names, so lite
verbs (import / timport / encrypt / decrypt / unlock) echoed keys and
passphrases into the scrollback and up-arrow history; added them.
- The offline wallet-rebuild helper was invoked via a shell string
(popen "sh -c") built from paths including the wallet filename, so a crafted
name could inject a command; it now refuses any path with a shell
metacharacter (rc stays -1 and the existing verify-before-swap fails safe).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- lite_owned_string: the destructor and move-assignment no longer copy the raw
value before freeing (nothing reads the copy there, and a std::string copy can
throw bad_alloc — from the noexcept destructor that meant std::terminate, and
it could also double-attempt the copy after a throw). They now free only; the
copy happens solely in intoResult(), which also moves (not copies) the value
into its result so no extra plaintext lingers.
- lite_result_parsers: jsonToUnsigned rejected nothing on the string path, so
std::stoull accepted "-1" and wrapped it to a huge balance; now requires a
leading digit. jsonToSigned rejects leading whitespace/'+'. The outgoing-
metadata sum rejects negative values and guards against signed overflow (UB),
so a hostile/buggy backend can't forge or overflow a transaction amount.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finishes the feedback-conventions hygiene thread: ~30 hardcoded ImVec4 green /
red / amber literals used for success / error / warning text now use
material::Success/Error/WarningVec4(), so semantic status colors follow the
active skin and stay legible in every theme (several hardcoded greens/reds read
poorly on the light/pastel skins). Covers validate-address, transaction-details,
key-export, export-all-keys, about, the Settings confirmations/lock state, and
the encryption/decryption dialogs. Brand/logo colors and muted white/grey
(disabled) text are left as-is; dynamic-alpha animated indicators are untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The import-key sweep and migrate-to-seed flows set several user-visible
status/error strings (shown via sweep_status_ / seed_migration_status_ /
rescan_status) as bare English; routed them through TR() with additive keys.
- Extended scripts/check-source-hygiene.sh with two low-false-positive content
guards so this debt can't regrow: a bare-English migrate/sweep status
assignment, and a Tactile button sized with a raw (unscaled) pixel literal.
Passes clean on the tree (362 files); the dev sweep harness is allowlisted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feedback-convention + HiDPI cleanup (from the audit's living-hygiene threads):
- Copy actions that were silent now toast a confirmation: transaction
address/txid (context menu, click-to-copy, detail buttons), peer and banned-
peer addresses, the Settings data-dir copy, the chat message "Copy", and the
key-export result copy.
- The first-run wizard's Skip/Back buttons used raw ImVec2(120/80,0) widths that
rendered tiny at HiDPI; they now multiply by ui::Layout::dpiScale().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finishes the transactions-export partial. The dialog now offers three scopes:
- All transactions (unchanged default; exported with no copy).
- Current History filter — the Transactions tab publishes a TxFilterSnapshot of
the visible legs (a merged autoshield row contributes both its send+receive
legs, so the CSV stays a per-leg record like an unfiltered export); the radio
is disabled with an explanation when no filter is active, and a snapshot that
predates the current tx list (revision mismatch) is refused rather than
silently exporting everything.
- Date range (YYYY-MM-DD, inclusive end-of-day; pending/dateless rows excluded
with a note; invalid/reversed dates blocked inline).
The live count reflects what will actually be exported. formatTransactionsCsv
and the atomic write are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The transactions CSV export now sanitizes the typed filename (basename only,
illegal chars → '_', no leading dots/trailing dots-or-spaces, length-capped,
always .csv) so it can't be pointed outside the config dir, and the success
screen gets an "Open folder" button. (All-vs-filter / date-range export scope is
left for a follow-up — it needs tx-tab filter-snapshot plumbing.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Chat: a dismissible first-run intro card (how chat works — encrypted, sent as
shielded txs with a small fee, tied to the seed) with a "Copy my chat address"
button, shown in the empty thread pane; dismissal persisted. The copy logic is
shared with the existing sidebar button (moved to a helper, unchanged).
- Toasts: clicking a toast now opens the existing notification-history panel, and
a "+N more" badge appears when several live toasts are queued (render only ever
showed the newest). Badge opacity is a ui.toml token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Explorer: a hash lookup now tells a transport/RPC failure apart from a genuine
not-found (RPC -5/-8), showing "Lookup failed: <friendly error>" instead of
"no block or transaction found"; block-detail errors routed through
friendlyRpcError. getblock/getblockhash/getrawtransaction calls unchanged.
- Key-export: when the wallet is locked, the dialog now says "Unlock your wallet
first" with an Unlock button (lite → requestLiteUnlock; full-node → close to
reveal the lock screen) instead of a raw error, and the fetching state animates
with LoadingDots. The export RPC/args are unchanged.
(i18n.cpp carries the additive keys for these and the chat/CSV commits below.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gaps in the destructive-command confirm:
- Regression: a command confirmed via the command-reference dialog's "Run" was
replayed through submitConsoleCommand, which re-armed the destructive gate and
silently did nothing until the user retyped it. The dialog now pre-arms the
exact-line confirm so the replay executes.
- z_exportmnemonic (full-node seed-phrase reveal) wasn't in the reference
catalog, so a pasted z_exportmnemonic ran instantly. Added a small
secret-revealing fallback set (z_exportmnemonic, z_exportviewingkey) to the
destructive check. Command execution is otherwise unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The code changes intended with the settings_window removal in a90aa29 (a bad
git-add pathspec aborted staging them there, leaving that commit with only the
file deletions and CMakeLists still referencing them):
- First-run handoff info toast at the completion point.
- Daemon-updater "restart pending" reminder (Settings → Daemon Binary) + the
Done screen's warning-level "restart required" headline.
- Debug restart-note raised to a readable color with the restart prompt beneath
it; DEBUG OPTIONS header shows a lock + explanatory caption (auth gate
unchanged); a few hardcoded literals → material tokens / dpiScale.
- Drop the legacy settings_window from the build (CMakeLists + the dev-only
references) to match the file deletions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The header key-verify lock was an unexplained hover-only icon. It's now
clickable, opening a dialog that shows the contact's identity-key fingerprint
and explains comparing it out-of-band, with a Copy-key button and a clearer
tooltip. The crypto/verification logic is unchanged (the wording is careful that
the lock means "encrypted to this key", since there is no trusted/verified flag).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Peer ban actions only toasted on failure; success was silent. Added success
toasts for ban/unban/clear-all, and the bulk "Clear All Bans" now takes a
confirming second click (per-row unban stays one-click). The setban/clearbanned
RPC calls are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- First-run handoff: one info toast ("node starting, will keep syncing in the
background") at the completion point, instead of dropping the user onto a busy
blank window with no explanation.
- Daemon updater: a newer installed binary now sets a persistent "restart
pending" reminder (surfaced in Settings → Daemon Binary with a Restart button)
and the updater's Done screen leads with a warning-level "restart required"
headline — easy to miss before.
- Settings: the debug-categories "restart required" note is raised from ~35%
alpha to a readable color with the restart prompt moved directly beneath it;
the DEBUG OPTIONS header shows a lock + explanatory caption so it's clearly a
gated diagnostics section (the auth gate itself is unchanged); a couple of
hardcoded color/size literals now use material tokens / dpiScale.
- Removed the legacy duplicate settings_window.cpp/.h (only reachable via a dev
flag, never in production) so edits can't silently miss one of two settings UIs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The failed-unlock error message now stays on screen until the next attempt /
mode switch / success, instead of fading after ~3s (the reason was often gone
before it could be read).
- The escalating failed-attempt lockout counter + expiry are now persisted
(keyed to the active wallet file) and restored once per lock-screen session,
so killing and relaunching the app can no longer reset the lockout curve — a
brute-force window. A forward clock change is capped; the PIN/passphrase is
never stored. The verification (walletpassphrase/decrypt) path is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrate-to-seed is irreversible and fund-moving, so its gates are strengthened
(UI only — the sweep/adopt/wipe logic, z_mergetoaddress args, amounts and
destination are byte-identical):
- Continue now requires a second, consequence-stating acknowledgement checkbox
(funds move and are recoverable only via the seed phrase), not just "I wrote
it down".
- Discard, Sweep-all and Sweep-remaining each take a second confirming click
(stating the amount being moved) instead of firing on one click; the confirm
body runs verbatim only after confirmation, and arming never survives a
close/reopen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chat header's export button wrote a decrypted plain-text file immediately on
click, warning only via a hover tooltip. It now opens a confirm dialog that
states the file is unencrypted and shows the destination path before writing.
The export format, path and write call are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The pool payout-address rule is no longer hover-only: an invalid payout
address now outlines the field in red and shows a short reason below it (a row
is reserved so it doesn't overlap the card beneath). The longer tooltip stays.
- The solo "Mining Addr" row gains a hover explainer and a "None yet" empty-state
(with a tooltip pointing to Receive → New mining address) instead of a blank.
- Replaced hardcoded English in the earnings/stats cards ("(%d txn)", uptime
"N/A"/"%lldm %llds"/"%lldh %lldm") with TR() keys.
Also carries the chat-export confirm strings used by the next commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Request-Payment no longer dead-ends with no shielded address: it shows a hint
and a "Create a shielded address" button (auto-selecting the new address), and
labels address entries with their contact/wallet names.
- The balance "Recent transactions" empty area uses a proper empty-state and now
distinguishes "still loading / syncing" from "no transactions yet" instead of a
dim one-liner that also showed mid-sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Send-tab UX pass (presentation/validation only — the z_sendmany submit path, its
amounts, fee, recipients and memo bytes are unchanged, and the Review button plus
the confirm-time live re-check still block any send until the wallet is synced):
- The form is now editable while syncing (so the recipient/amount/memo can be
pre-filled); only Review/Send stays gated, with an "available once synced" note.
- The recipient's red "Invalid address" no longer flashes from the first
keystroke — it waits until the field loses focus.
- The memo field stays visible (text preserved) with a "requires a shielded
recipient" explainer for transparent recipients, instead of vanishing + silently
dropping what was typed.
- "Cancel" (which only cleared the form) is relabeled "Clear form", disabled when
empty, and drops its redundant confirm (the timed Undo already covers it).
- The source picker shows address labels, and warns when the chosen source is
transparent (non-private on this chain).
Also adds the string keys used here and by the next commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The Shielded balance card gains the explanatory hover tooltip the mining card
already has (suppressed while the pending-chip tooltip is showing).
- Toasts get a severity-tinted background fill and a slightly thicker accent
border, so Success/Warning/Error/Info differ by more than hue alone (the
per-severity leading icon already existed). Tint opacity is a ui.toml token.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First-run bootstrap polish:
- Failed card wraps the (often long) error instead of clipping it, and offers a
"Try mirror" button; Retry now keeps whichever source (main/mirror) was used.
- Bootstrap offer shows an honest size/time hint and relabels "Mirror" →
"Use mirror" with a clearer tooltip.
- Daemon status dot/label is keyed off the EmbeddedDaemon::State enum instead of
matching the English substring "Stopping" (which broke in every other
language), and the faint ~55%-alpha label is raised to ~86%.
- Bootstrap Done screen: for the embedded daemon it now offers "Start Daemon"
(and says the node will start) instead of telling the user to start it by hand.
Also carries the new string-table keys for these (and the shielded-card tooltip
used by the next commit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The conversation list now marks a pending stranger request with a small
"Request" pill (bottom-right of the row), so it's distinguishable before you
open it; the preview text is clipped to the pill's left so they don't overlap.
Completes the request-affordance pair with the in-thread Accept/Block card.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the labeled "Pending +X" chip (with its awaiting-confirmations tooltip)
to three more layouts via a shared DrawPendingChip() helper. It draws on the
ticker line (the USD value sits on the line below in these layouts, so there's no
overlap) and is skipped if it would overflow the card. The remaining draw-list
layouts with right-aligned content (Consolidated, Minimal, the grid variants)
are left for a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An unsolicited first message from a stranger (an incoming ContactRequest, with
nothing sent by us and the peer not in contacts) now shows an Accept / Block
card above the thread instead of looking like a normal conversation. Accept adds
the peer to the address book (which clears the request state); Block deletes and
blocks the conversation (reversible from the Blocked manager). ConvSummary gains
a derived pendingRequest flag. (The list-row "Request" pill and a header Block
button were left out to avoid the fragile hand-drawn header-toolbar geometry.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shield-coinbase replaced its free-text "*"-wildcard address box with a dropdown
of the wallet's mining (transparent) addresses and their balances ("All mining
addresses" default), and the confirm step now previews "Shield up to X DRGX from
N mining address(es)… to <label>? Network fee: Y" instead of only relabeling the
button. Also blocks submitting from an empty source or one with no funds. The
z_shieldcoinbase call (args/fee/limit/destination) is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The unconfirmed balance was shown in only one layout as an unlabeled "+X" that
read as a mystery. It's now a labeled "Pending +X" chip with an "awaiting
confirmations" tooltip — the Classic card's badge is relabeled, and the shared
compact hero (Dashboard + Two-row layouts) now draws it too. (The remaining
hand-drawn layouts can reuse the same pattern as a follow-up.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bootstrap: Retry now reuses the source the user actually started (was always
the default), and a "Try mirror" button offers the alternate server on
failure (s_lastUrl remembered on start, cleared on a fresh Confirm).
- Validate Address: classification + checksum are local, so the dialog no
longer requires a node connection — offline/lite now validate and show the
type, with ownership reported as "Unknown — node offline / not checkable in
the lite wallet" instead of a dead "not connected" screen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Sync ETA: a new pure util::SyncRateEstimator (App-owned) replaces the
function-local statics that went stale after a rescan/restart (producing the
"412h" ETA). It self-resets on a height drop and on connect/disconnect/rescan/
restart (via an atomic flag, since onConnected can run off-thread), formats
with days, and uses the Warning() token.
- Settings: the Explorer-tab URL fields + custom-fees/fetch-prices checkboxes
now persist on edit — they were the only controls still relying on the
About-only Save button that a user on this tab never sees.
- Export-all-keys: hidden in the lite build (which has its own key export),
instead of dead-ending on "Not connected to the daemon".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- A mining start/stop/pool failure now sets a persistent App::mining_last_error_
(cleared on the next attempt), rendered as a red caption with "Press Mine to
try again" below the Mine button (plus a Dismiss, and a Windows-Defender
"How to fix" on an AV block), so a crashed/blocked/failed miner is no longer
just a button that "did nothing" once its toast faded. Two more hardcoded
English strings (pool-payout-invalid, AV-blocked) moved to i18n.
- Pool connection status is now a persistent "Status" row in the pool stats
card (PoolMiningState.running_since / last_error): it flips from "Connecting…"
to "Can't reach pool — check the URL" after a 30s timeout instead of sitting
on red forever, and no longer vanishes once logs arrive. The old transient
banner in mining_tab.cpp was removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shield/merge status line was amber only while in-flight and uncoloured for
every terminal state (success, failure, uncertain). It now tracks an OpOutcome
(display-only) and colours the terminal line Success/Error/Warning accordingly,
shows a LoadingDots spinner while the 120s scan runs, and maps the raw daemon
error through friendlyRpcError. The actual shield/merge submit logic is
unchanged (no "Try again" that could double-submit a fund move).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the node rejects the wallet's RPC login (bad rpcuser/rpcpassword), the
status bar showed developer jargon ("Auth failed — check rpcuser/rpcpassword")
and the only action was Reconnect, which just repeats the failure. Now a
dedicated AuthFailed banner reason surfaces "Wallet can't sign in to the node"
with a plain-language detail and a real fix: Restart node for the embedded
daemon (regenerates its cookie creds) or Open config folder for an external one
(to fix DRAGONX.conf). Driven by a new rpc_auth_failed_ flag (set on the auth
path, cleared on each connect attempt), with evaluator unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The address-transfer dialog (own-address moves) enabled Confirm on amount alone,
so a disconnected / syncing / locked wallet failed only at submit. It now
mirrors the Send tab's guards (strictly more restrictive — the transfer logic is
unchanged) and re-checks live state before broadcast, shows an inline reason
when blocked (enter amount / not connected / syncing / locked), and labels the
result as a "Transaction ID" (not "Operation ID") with a copy button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Warmup/daemon status no longer appends a hardcoded English " (Block N)" — it
uses a translated warmup_block_fmt key.
- Recent-transaction type labels (Sent/Received/Mined) are now translated in the
UI consumer (recentTypeLabel in balance_recent_tx.cpp) with a generic
"Transaction" fallback, instead of the data layer's raw English getTypeDisplay().
- Mining: a disconnected solo miner hovering the dead Mine button now gets a
"not connected to the node" tooltip instead of a misleading "Start Mining"
(the tooltip chain had no branch for the solo !isConnected() disable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HiDPI geometry debt from the audit: 15 security-dialog buttons used a raw 40px
height (undersized vs the scaled label at 1.5x), the market "Updated" caption
used a raw SameLine(0,12), and the QR popup's 28px overlay-inset budget wasn't
scaled (wrapping the address at HiDPI). All now go through ui::Layout::dpiScale().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Contacts: a "Message" context-menu item for shielded (z) contacts hands the
address to the Chat tab (new OpenChatNewConversation entry point) and switches
there with the New-conversation dialog prefilled; disabled with a reason for
transparent-only contacts. (Kept to the safe context-menu + picker path, not
the hand-drawn 4-slot row action cluster, to avoid mis-wiring the destructive
Delete slot.)
- Chat: the New-conversation picker no longer silently hides transparent
contacts — they're shown disabled with a "needs a shielded address to chat"
tooltip instead of vanishing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The conversation header only copies a PEER's address, so a user had no way to
hand out their own chat address for others to message them. Add a "Copy my chat
address" link in the left pane (public App::chatMyAddress() forwards the stable
seed-derived reply z-address; copied on click only, with an "address copied"
toast and a warning if none exists yet).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The aggregate price is deliberately kept on a failed or old refresh (it's never
cleared), so the hero number could be hours out of date while still drawn in the
"live" green. It now turns amber with an "as of N min/h ago" caption and a hover
tooltip carrying the friendly fetch error, when the last refresh failed or the
aggregate is >=5 min old (only when the hero is the aggregate, not an exchange
pair with its own feed). The hardcoded "Price fetch failed" default is now i18n.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three next-batch UX-audit fixes:
- Chat delivery: the sending/sent glyph has higher contrast and a tooltip
("Sent to the network — arrives after it confirms", so a check isn't mistaken
for "delivered"); a failed send now carries the real reason (ChatMessage::
fail_reason, set from onChatBroadcastResult via friendlyRpcError, in-memory
only) in the "not sent" hover tooltip.
- Explorer: a block height beyond the tip says so ("Block N is beyond the chain
tip (M)") instead of silently snapping to the top; the empty block list shows
Not connected / Syncing (N behind) / Loading instead of an endless "Loading".
- Settings: the misleading "Reset to Defaults" button (which only reloads the
last-saved values) is relabeled "Discard unsaved changes" with accurate copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The network fee and running total are now shown on the compose form (once an
amount is entered), instead of only being revealed in the confirm popup.
- When the pre-broadcast re-validation fails (balance dropped, sync restarted,
fee pushed total over available), the confirm popup now closes and surfaces a
warning toast — previously the reason was drawn on the form behind the still-
open popup, so the user was left re-clicking a button that did nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Seed-backup dialog: a failed "Save to file" was drawn in the success (green)
colour; it's now red on failure, green on success (flag reset on close).
- Encrypt-wallet dialog: the disabled Encrypt button now says why once the user
starts typing ("Use at least 8 characters." / "The passphrases don't match."),
and a "Show passphrase" checkbox reveals both fields.
- Key-export dialog: the raw RPC exception (e.g. "...walletpassphrase first")
is routed through util::friendlyRpcError(), so a locked wallet reads
"Unlock your wallet first, then try again." instead of daemon jargon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New-user mining cluster from the UX audit:
- Solo/Pool toggle: hover tooltips on each half explaining the trade-off
(solo = no fees but long waits on a small CPU; pool = frequent small payouts,
needs the miner + a shielded payout address).
- Payout field: when it's empty (new wallets have no z-address, and a pool
payout must be shielded) the bookmark button is replaced by a one-click
"Generate a shielded payout address" button (createNewZAddress → fills the
field, persists, toasts), instead of only a hover hint to the Receive tab.
- Miner updater pill: when no miner is installed it becomes a prominent accent
"Download miner" call-to-action with a "pool mining needs DRG-XMRig" tooltip,
instead of a subtle grey "Update" pill that's easy to miss.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A coinbase/transparent→shielded move (often auto-shield) was drawn with a red
"Sent" label and send arrow yet a green "+X" amount — an internally
contradictory row that reads as "money leaving" to anyone who auto-shielded. It
now renders as a neutral "Shielded" row (shield icon + the shielded accent
colour for label and amount), and since a shield is your own funds going
private — not a spend — it's excluded from the Sent card total and the Sent
filter (it merges into one row visible under "All").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finish the error-surfacing foundation across app_network.cpp (+ one in
app_wizard.cpp): ~21 toasts that were hardcoded English — or raw daemon/RPC
error text concatenated after an English prefix — now go through TR() keys, and
every raw-error one maps the detail through util::friendlyRpcError() (warming
up / locked / method-not-found / insufficient / unreachable → plain guidance,
raw text as fallback). Covers mining start/stop, pool start, peer ban/unban/
clear, key export, rescan, wallet-switch guards, and the lite-unavailable
notices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renderNodeStatusBanner suppresses itself during a UI sweep (the forced demo
state would otherwise paint a banner over every tab). Add a narrow opt-in
(sweep_show_node_banner_) so ONLY the overview-no-peers target shows it; every
other capture is unchanged. The target also clears connection_in_progress_ so
the evaluator classifies the state as no-peers rather than "connecting".
Verified in a full-node + DRAGONX_ENABLE_UI_SWEEP build: the banner renders as
"Connected, but no network peers — the node can't sync · Check your internet
connection and firewall, then restart the node to find peers." (Restart button
only appears with an embedded daemon, which the sweep has none of.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>