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>
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>
- 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>
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>
- 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>
Two of the systemic error-surfacing foundations from the UX audit:
- util::friendlyRpcError() maps the common raw daemon/JSON-RPC errors
(warming up / -28, locked / -13, method-not-found / -32601, insufficient
funds, unreachable node) to short translated guidance with a next step,
falling back to the trimmed raw text. Wired into the send (money) path: the
toast shows the friendly cause, the overlay keeps the raw text below it for
diagnostics. The helper is reusable for the remaining raw-error sites.
- Toasts now pause their auto-dismiss while hovered and ellipsize over-long
text with the full message in a hover tooltip, instead of hard-clipping it
mid-word (durations were already lengthened earlier).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A syncing node skips the balance RPC, so the balance card paints a bare
0.00000000 / $-.-- that reads as lost funds after an update (a documented
support trap). Add one shared notice at the top of the Balance tab — above
every layout — shown only for the normal catching-up case (connected, network
tip known, still verifying, not a rescan/witness rebuild). Offline, no-peers,
a busy node and rescans stay the node-status banner's job, so the two never
stack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Seed backup: the one-time 12s toast that marked itself "reminded" the instant
it fired (so a funded, unbacked wallet was never nudged again) is replaced by
an actionable alert with a "Back up now" link that opens the backup dialog and
persists in the bell panel. "Reminded" now means the user actually viewed the
phrase (set from the backup dialog), so the nudge re-fires each session until
they do, and escalates to a warning once a balance exists.
- Console: fund-moving / key-revealing commands (dumpprivkey, z_exportkey,
z_sendmany, z_mergetoaddress, …) now require a confirming re-entry when typed
or PASTED, honoring the command reference's `destructive` flag that was
previously enforced only by the reference dialog's Run button. (Output
masking of key dumps in the scrollback is still a follow-up.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Chat tab no longer shows "Unlock your wallet" for every missing-identity
case. A new App::chatIdentityStatus() distinguishes Locked (real),
Preparing (unlocked, deriving the identity) and Unavailable (view-only or a
legacy wallet with no exportable secret — correctly latched). Each gets its
own icon + message, and the Unavailable prompt points at Back up / Migrate
instead of telling the user to unlock an already-unlocked wallet.
- A chat message that sits queued ("Sending") because the wallet is offline or
has no shielded funds for the fee now surfaces the reason (throttled, after a
short dwell), wiring the ready-made chat_toast_not_connected /
chat_toast_need_funds strings that were never shown. The send stays queued
and goes out when the condition clears.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The no-peers banner headline ("Connected, but no network peers") was followed
by a detail line that just repeated connection_status ("Connected"/"Peers: 0"),
reading as a contradiction, and it offered no action at the exact moment the
wallet looks synced but is silently stalled at a stale height.
- NoPeers now clears the passthrough detail; App renders a translated,
actionable hint (node_banner_no_peers_detail) for this reason instead.
- When the embedded daemon is the node, offer a Restart button (re-triggers
peer discovery via the -addnode seeds); an external node stays action-less
since it is the user's own to restart.
- Update + extend the evaluator unit tests (external = no action/empty detail;
embedded = RestartNode).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch of safe, isolated fixes from the 10-surface UX audit:
- settings: Auto-shield / Save-z-transactions toggles now persist on change
(they discarded CB()'s return and relied on a Save button that only renders
on the About tab, so the choice was silently lost on relaunch).
- exports: show the resolved Platform::getConfigDir() instead of a hardcoded
~/.config path (wrong on Windows/macOS) in both the all-keys and CSV
dialogs; all-keys also reveals the file path on a partial export.
- notifications: error/warning toasts last 9s/7s (were 4s/3.5s) so failures
are readable.
- qr popup / tx-details: copy-to-clipboard now confirms with a toast.
- about: Escape-to-close + readable heading contrast via OnSurfaceMedium().
- sidebar: suppress single-key nav (J/K/arrows/[) while an overlay dialog or
ImGui popup is open, so it no longer switches the tab behind a modal.
- i18n: strip the stale hardcoded path out of file_save_location in i18n.cpp
and all eight res/lang/*.json locales (additive, one key each).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surfaces the period-earnings aggregator in the Balance tab:
- Toolbar (shared address list): interval pills (1H/1D/1M/1Y), a received-
vs-net mode toggle, and a global show-all toggle, placed right of "Hide 0
Balances". The New Z-Address / New mining address buttons move to a footer
row across from "Showing X of Y" to make room.
- Per address: the row draws its earnings on line 2, left of the $USD value
(green positive, red negative in net mode), shown when the global toggle
is on or the address is opted in via a new "Show/Hide earnings" right-click
item. Works in every layout via the shared list.
- Total: FormatTotalEarnings (gated on the global toggle, so the default
appearance is unchanged) is wired into the compact hero (dashboard/minimal)
and the classic layout, left of / beside their USD figure. The remaining
layouts' totals are not wired yet.
- Settings > Appearance: an "Earnings Display Format" selector (Coin /
Coin + fiat / Fiat).
New i18n keys are translated into all eight locales; the CJK subset font is
rebuilt for the new glyphs. Builds + test suites pass on both variants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mining "Est. Daily" was a solo expected-value (your hashrate share x
blocks/day x a fixed block reward) rendered in the same green as the real
Today / Yesterday / All-Time earnings, off an instantaneous hashrate and
gross of pool fees. It read like a dependable daily income when it is a
long-run statistical average with huge (Poisson) variance.
Framing:
- The estimate is now drawn in a neutral colour, not the green "money
earned" colour (the actual mined totals stay green).
- A sub-label in BOTH modes marks it as an average ("avg", or "avg, net"
in pool mode when the fee is applied), replacing the pool-only
"before pool fee" note.
- A hover tooltip over the column explains the variance ("often nothing
for long stretches, then a lump - NOT a steady daily income") and the
fee status (after the N% fee / before the fee when unknown / nothing for
solo and 0%-fee pools).
Accuracy:
- Uses a trailing-average hashrate instead of the instantaneous / 10s
snapshot: pool prefers hashrate_15m (then 60s, then 10s); solo uses the
mean of hashrate_history once there are >=10 samples, else the instant
rate. This stops a spike or a momentary dip from swinging the figure.
- Subtracts the known pool fee so pool-mode figures are net; synthetic or
unmatched pools stay gross and the tooltip says the fee is unknown.
The static block reward and true pool-reported earnings are intentionally
out of scope (the latter needs a new pool-API field). New i18n keys are
translated into all eight locales; the CJK subset font is rebuilt for the
three new glyphs. Builds + test suites pass on both variants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "View transactions" action that opens the History tab pre-filtered
to a chosen address. SetTransactionsAddressFilter() stores a pending filter
that RenderTransactionsTab consumes on its next frame — pre-filling the
search box and resetting the type filter to All — then the caller navigates
to NavPage::History. This reuses the existing "prefill + navigate" pattern
(cf. SetSendFromAddress) and all of History's rendering, detail and export.
Triggers:
- Contacts: a context-menu item and a 4th per-row action icon (copy /
view-transactions / edit / delete; the trailing width is widened to fit).
- Balance address list: a per-address context-menu item (own z/t addresses).
- Receive tab: the recent-received rows now carry their address into the
filter instead of jumping to History unfiltered.
The History search also now matches from_address, so filtering by one of your
own addresses catches sends FROM it, not only receives INTO it.
Scoping is honest, not exhaustive: transparent is well covered, but shielded
counterparties are private and the transparent sender of received funds is
never recorded on-chain, so this shows transactions *involving* the address.
On Lite, from_address is thin, so own-address send filtering is weaker.
New i18n keys view_transactions / tt_view_transactions translated into all
eight locales (no new glyphs). Builds + test suites pass on both variants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "Favorite Highlight Opacity" slider (15-100%, default 100%) to
Settings > Appearance > Scale & Effects. It scales the alpha of the four
favorite gold tokens (fill/border/icon/outline) at their resolve site in
the Balance address list via material::ScaleAlpha, so the gold row outline
and the star button fade together. The multiplier is skin-agnostic (it
scales whatever alpha the theme resolved) and touches only favorited rows;
non-favorite rows and the white selection highlight are unchanged, and the
100% default is byte-identical to the previous look.
The preference lives in Settings (favorite_opacity, clamped 0.15-1.0 in both
the setter and on load), persists across restarts, updates live, and is not
low-spec gated (it's a pure color-alpha preference, independent of the
transparency sliders). New i18n keys favorite_opacity / tt_favorite_opacity
are translated into all eight locales; the CJK subset font is rebuilt to add
the two new glyphs the zh string needs.
Verified: full + lite builds clean, full + lite test suites pass, hygiene OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A failed wallet unlock was always treated as a wrong guess: the lockout
counter was bumped and the back-off curve armed no matter why the RPC
failed. A busy or warming-up node, a transport timeout, or a wallet that
is not actually encrypted would all eat an attempt and could push a user
into a lockout without a single mistyped passphrase.
The failed call is now classified before it is acted on:
- Only the daemon's "passphrase incorrect" error counts as a wrong
guess and advances the lockout.
- A warming-up or unreachable node, a reply timeout, or a full queue is
reported as a transient busy-node condition with no attempt charged.
- A "wallet is not encrypted" reply refreshes the local encryption state
instead of penalising the user.
- Any unknown error surfaces its message without touching the counter.
The PIN path got the same treatment: when the vault decrypts correctly
but the unlock RPC then fails for a non-credential reason, the attempt
is no longer counted.
Tests cover the classification of each failure kind.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Use Tor" checkbox was saved but never read: no build routed any
traffic (node, RPC, price/update/bootstrap/pool HTTP or Lite gRPC)
through Tor, so users who ticked it believed they were anonymous.
- The checkbox is disabled and always shown unticked, on the Settings
page and in the legacy Settings window. Its tooltip says it is not
available and gives the alternatives: for the node, proxy=127.0.0.1:9050
in DRAGONX.conf with Tor running; otherwise a system-wide VPN/Tor (Lite
builds get the short version). The "Use Tor" label is kept.
- Behaviour change: a stored use_tor=true is cleared once at startup
(Settings::consumeLegacyUseTor + a synchronous save before the async
writer and any Settings page state), with a 20 s warning in the user's
language. The cleared key is the "already told" flag, so the notice
never repeats. No settings format change.
- 3 new strings (tt_tor_unavailable, tt_tor_unavailable_lite,
tor_never_applied_notice) in all 8 languages; the tooltip wraps at
tor-tooltip-wrap-width from ui.toml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rescan or key import holds the node's cs_main for the whole wallet
scan. On the shared clients it hit the 30 s curl timeout, reported a
failure that wasn't one, abandoned a daemon RPC slot and flipped the app
into "node busy".
- One long wallet RPC at a time (rescan, importprivkey, importaddress,
importwallet, z_importkey, z_importviewingkey, z_importwallet) runs on
its own ungated client with no client timeout (rpc/long_call_lane.h).
It can be aborted from any thread, and an abort is never lost, even
one that lands before the client opens.
- The GUI rescan, key import and sweep use it. The sweep's fund-moving
body is unchanged; only the client it calls differs. runtimeRescan()
returns bool, and a refused post-bootstrap reconcile stays armed.
- The console routes rescan and the import commands through the lane;
its input stays usable meanwhile, and a refusal is printed like any
other result. RPCClient gains callRaw(.., timeoutSec).
- Restart, wallet switch, delete-chain, bootstrap (now also from
Settings) and the other node-stopping flows are refused while it runs,
with a message saying a key import or rescan is still running; quit
asks for confirmation. The lane is aborted at quit.
- A second import or rescan is refused before the key is copied anywhere.
An import cut off after the request reached the node (a lost reply,
or a proxy's 502/504 while the node still imports) says the key may
already be in the wallet and to run Settings -> Rescan. Only the
node's own HTTP-layer refusals (401, 503, work queue full, unhandled
request) say that the key was not imported. A pure
classifyLongCallCutOff() makes that choice.
- New strings in all 8 languages; console results stay English.
Tests: the long-method list and the console's routing; one lane at a
time; an abort lands whether it comes before the client opens or during
the call; a call with no client timeout outlasts a 3 s scan, where a 1 s
gated client times out and marks the node busy; classifyLongCallCutOff()
for every failure kind with and without a sent request, and from raw
HTTP replies (502, 504, a plain 500 -> cut off; 401, 503, queue full,
unhandled -> refused).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A passphrase change left the PIN vault sealing the old passphrase. The
next PIN unlock opened the vault, walletpassphrase answered RPC -14, and
the generic catch ran the lockout curve on a correct PIN, while Settings
kept showing the PIN as active.
- Passphrase change: when the active wallet's vault exists at completion
(same vault path as at dispatch, passphrase actually changed), the
vault is marked out of date for the session and kept on disk: PIN entry
is no longer offered, a warning toast says so, and Settings shows
"PIN out of date" with a tooltip on how to fix it. The Change
Passphrase dialog notes beforehand that a PIN will go out of date.
- PIN unlock: RPC -14 after the vault opened is treated as "PIN out of
date": passphrase entry, a message, no attempt counted, no lockout, and
the vault is not deleted (-14 is only the daemon's answer; the vault may
hold the only copy of a passphrase that opens an older backup). After a
restart Settings shows the PIN as active until the first PIN attempt.
- A malformed vault (zero-filled by a removeVault() whose unlink failed,
truncated, unknown version) is treated as missing: passphrase entry,
"PIN file unreadable", no penalty. New SecureVault::isWellFormed().
- Race guards: Set/Change/Remove PIN are disabled while a passphrase
change runs, and Change Passphrase while a PIN operation runs.
- Setting or removing the PIN, or switching wallets, clears the mark;
setting a PIN offers PIN entry again. Change PIN keeps the mark (the
vault still seals the same passphrase).
- Pure decisions in data/pin_vault_policy.h, unit-tested. New strings in
all 8 languages.
No vault, wallet or settings format change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full UI sweep (dev builds only) ran its demo surfaces against the
real profile. Its teardown deleted wallet-{cold,trading,mining,donations,
2023,payroll}.dat from the real datadir, it created zero-filled wallet
files and picker-demo/extra-wallets folders, its demo wallet-index upsert
re-minted the active wallet's scopeId (hiding "w:"-scoped contacts), the
Contacts tab could re-attach legacy-scoped contacts to such a throwaway
id in addressbook.json, and it persisted the demo active wallet file and
Mining pool worker.
- Preflight: a full sweep is refused (toast + log line) while a real
workflow is in flight: a pending seed_migration_* setting or open
migrate dialog, a wallet switch, an encryption/import workflow, an
encrypt/PIN/lock operation, the seed-backup dialog, or a bootstrap.
The DRAGONX_FULL_SWEEP hook then exits as for an empty catalog.
- Persistence latches: AddressBook (snapshot on the first sweepBegin,
save/load latched while a demo-contacts surface is shown), WalletIndex
and Settings (active wallet file, contacts view mode, pool URL/worker)
for the whole sweep. Settings::sweepEnd() is the last statement of
clearDemoWalletData and flushes once only if a save was suppressed; a
quit mid-sweep writes nothing to the profile (the 0700 sandbox, with
zero-filled demo files only, may stay in the temp dir).
- Scope ids: during a full sweep activeWalletScopeId() returns the
persisted id or "" and never mints one, so the Contacts tab cannot
move legacy-scoped contacts to a throwaway id. The only address-book
write a sweep can still cause is the one-time legacy re-attach to the
wallet's persisted id, which opening Contacts does anyway.
- Sandbox: demo wallet files and folders go to a fresh random 0700 dir
under the temp dir (exclusive create, a pre-existing path or symlink
is refused), removed at sweep end. The teardown that deleted those six
wallet files is gone.
- Cleanup: the migrate-to-seed state, wizard phase, demo chat
conversations and a fabricated demo chat identity are reset at sweep
end; the seed-backup surface always shows the demo phrase and a real
z_exportmnemonic result is dropped while the sweep runs.
- i18n: one new key (screenshot_sweep_refused) in i18n.cpp and the 8
language files.
Tests: the address book snapshots only on the first sweepBegin, keeps
addressbook.json byte-identical while latched and restores the real
book; the wallet index keeps wallets.json and the real scope id, also
with the background writer attached; Settings restores the latched
values and flushes once only when a save was suppressed; the sandbox
helper refuses a pre-existing path or symlink and removes only a
prefixed dir, never a symlink's target.
Release note: v2.0.x UI sweeps could delete wallet-{cold,trading,mining,
donations,2023,payroll}.dat from the data directory and re-scope
contacts; this cannot be undone automatically.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Lite "Encrypt wallet" field and the Create/Restore passphrase had no
confirmation and no minimum length; only an empty passphrase was
refused. Encrypting locks the wallet at once, and a locked wallet cannot
reveal its seed, so a typo there could cost a user without a seed backup
their funds. The full-node encrypt dialog already requires 8 characters
and a confirmation.
This applies to new passphrases only. Opening and unlocking are
unchanged: an existing wallet with a shorter passphrase still opens and
unlocks.
- A pure liteEncryptPassphraseValid(pass, confirm): at least 8 bytes and
equal to the confirmation. It takes string_views, so the page's secret
buffers are never copied.
- liteLifecyclePassphraseAcceptable(op, pass, confirm) gates the Settings
create/open/restore request. Open is never checked, because its
passphrase field also unlocks an existing wallet. Create and Restore
are checked only when a passphrase was entered; an empty one still
creates an unencrypted wallet.
- Encrypt gets a Confirm field. The button stays disabled until the
passphrase is valid, and a hint says why (too short, with the count,
or not matching).
- Create/Restore show a Confirm row once a passphrase is typed, with the
same hint. Validate stays disabled while the passphrase is invalid, so
a click never wipes a typed seed over a passphrase typo. The request
builder re-checks the rule.
- The confirmation buffers are wiped with their passphrase: after a run
or an encrypt, and when the Confirm row is hidden.
- Once the wallet is closed, the Settings page wipes the Encrypt,
Confirm and Decrypt passphrase buffers whole every time, also when no
secret was revealed; before, closing the wallet wiped them only while
clearing a revealed backup or secret. They are zeroed in full because
an emptied field can still hold the old tail past its first NUL.
- New copy under Encrypt: "Anyone with the wallet file can try
passphrases offline. Back up your seed phrase first and use a long
passphrase."
- i18n: three new keys (lite_confirm_label, tt_lite_confirm_pass,
lite_encrypt_offline_warning) in i18n.cpp and the 8 language files,
added additively. The hints reuse the wizard's wiz_pass_too_short and
wiz_pass_mismatch. No new CJK glyphs, so the subset font is unchanged.
Tests: the validator rejects empty, short (7) and mismatched or
unconfirmed passphrases, and accepts 8 or more with a match. The request
gate always accepts Open, even "abc" with no confirmation, accepts
Create/Restore without a passphrase, and applies the rule to them
otherwise. An encrypted, locked wallet still opens and unlocks through
the controller with the 3-character passphrase "abc".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CMake turned every res/lang/*.json into an xxd header that i18n.cpp
compiled in as a fallback, on every platform. Only the Windows
single-file exe needs it: the Linux zip and AppImage and the macOS .app
always ship res/lang, and loadLanguage() reads the loose file first. On
Linux the embedded copy added about 1.5 MB to the binary, and every
translation edit recompiled i18n.cpp.
A new option, DRAGONX_EMBED_LANG, defaults to ON for WIN32 and OFF
elsewhere:
- ON without xxd is now a configure error (it used to warn, then fail to
compile i18n.cpp on the missing headers);
- i18n.cpp compiles the embedded includes and the fallback only when ON;
- outside Windows, loadLanguage() also tries
<exe>/../share/<app>/res/lang (the cmake --install layout and the
full-node AppImage) and <exe>/../Resources/res/lang (the macOS .app
binary run directly), so those layouts find the loose files whatever
the working directory; before, only the embedded copy covered that.
Measured:
- full-node Linux binary: .data 1,699,696 -> 168,592 bytes; stripped
19,983,136 -> 18,456,128 bytes (-1.53 MB);
- with the ImGui demo stubs, the Linux zips and AppImages are
0.50-0.55 MB smaller, with identical file lists including all 8
language files;
- a Lite build started from an unrelated working directory loads the
ja.json next to the binary (2422 strings), and the packaged Lite zip
and AppImage load ja.json with no new not-found or failed lines;
- Windows keeps the embedded copy: the 8 language headers are generated
and the ja.json bytes are in the exe.
A new test loads "de" from the loose files and checks a translated
string; with bin/res/lang moved away exactly its two assertions fail.
Behaviour changes on Linux and macOS: a missing or corrupt res/lang now
falls back to English instead of the embedded copy, and a translation
edit needs only the copy_langs step instead of a recompile.
Not verifiable here: the macOS .app in a non-English language, and the
Windows single-file exe at runtime.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a first run or after an upgrade, Windows release builds wrote the
bundled Sapling params, asmap, node and miner (about 88 MB) in
App::init, before the first frame: 0.5-3 s of frozen blank window, 5-30 s
on a slow disk or under antivirus. Zip, AppImage and .app builds copied
the bundled files into the daemon dir and hashed the 51.5 MB of params on
the UI thread at the first node start, a 0.3-1.5 s freeze in the wizard
or the connect loop.
A stat-only check at startup decides whether anything is missing, stale
or unverified; a normal launch starts no thread and shows nothing.
Otherwise one background job extracts (Windows) or copies (Linux, macOS)
the files, then verifies the params once:
- The first frame paints at once. If the job takes longer than 0.4 s, a
chip at the bottom shows "Preparing wallet files... N%", then
"Verifying wallet files...".
- A node start on the UI thread while the job runs is deferred and
replayed when it ends (never into a bootstrap download, a restart or a
live connection); worker threads wait, and give up on quit. After an
upgrade that ships a new miner, the node starts after the miner is
written instead of before. Pool-miner starts and the miner version
check wait too, so the old miner is never run or reported while it is
being replaced.
- Each file goes to "<name>.partial", is flushed and renamed into place
under one lock shared with the daemon start, the bundled-node
reinstall and the miner extraction, and the job stops between 4 MB
chunks at shutdown. An interrupted write used to leave a truncated
binary that was trusted forever; a leftover .partial is now removed.
Other behaviour changes:
- Linux and macOS full-node builds copy the bundled node files (about
77 MB) into the daemon dir at first launch, even if the wallet ends up
on an external node; before, this happened at the first embedded start.
- Lite no longer writes the 53 MB of params and asmap it never reads.
- A disk-full or permission failure that leaves the node files unusable
shows a warning; a miner blocked by antivirus alone does not.
- A corrupt or truncated param in a zip, AppImage or .app install is
replaced from the bundle and verified again instead of trusted forever.
- Reinstalling the bundled node holds off the connect loop and a deferred
start for its whole run (either could launch the old binary while the
new one was written), and is refused while another restart is running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every open of the Wallets dialog, Refresh, folder add or remove, and Open
on a stale row walked each user-added folder recursively on the UI thread
before the dialog drew: up to 40,000 entries per folder, roughly
50-150 ms warm on Linux and seconds on a cold disk, a network share or
Windows.
The datadir listing (a few top-level files) stays synchronous, so a
wallet just created shows at once. The recursive walk runs on a detached
worker with the same depth, hit and entry limits; its rows are merged at
the start of a later frame and only the new rows are probed.
- The dialog opens at once with the datadir rows. The external rows
arrive when the walk ends, with "Scanning folders..." beside the
scanned-folders caption, and the card grows once.
- Reopening within 60 s with the same folders reuses the last walk
without touching the disk, so its rows and sizes can be up to a minute
stale: a wallet copied into a folder in that minute needs Refresh.
Refresh and Open on a stale row always walk again.
- While a walk or a wallet probe runs (up to 30 s) the dialog keeps
frames coming, so rows and badges appear without moving the mouse.
- The 200-row cap now holds across all folders; each extra folder could
add one row past it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend's error for a key it doesn't recognize repeats the key
("'<key>' was not recognized as either a spending key or a viewing
key"). Import passed that text on as its error, so a mistyped private
key was written to the lite log (shown in the Console tab) and to the
Settings status line. A failed import now carries a fixed reason
(unrecognized format, wallet locked, key already exists, could not be
decoded, or rejected), which is all the log records. Settings shows
"Couldn't import the key — check it, unlock the wallet (if encrypted)
and try again." instead, translated in all eight languages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finding the lowest block a node still has (Settings > Rescan, and the
automatic reconcile after a bootstrap) ran a binary search of about 42
synchronous getblockhash/getblock calls on the UI thread, each waiting
for the RPC connection behind any wallet scan (10-20 s on a big wallet)
and each with a 30 s timeout. The window froze for that long.
It now runs as one task on the wallet RPC worker, bounded to 90 s, and
the answer comes back on the UI thread; meanwhile the dialog shows
"Checking which blocks..." with Rescan disabled. An answer from an
earlier connection counts as "could not tell", and one for an earlier
opening of the dialog is dropped. The search probes block 1 first, so a
full-history node answers in one probe, and it answers "could not tell"
at once while a runtime rescan holds the lane.
Fixed along the way: any probe error (timeout, busy node) read as "block
missing", so a busy node could get a bogus "bootstrapped, rescan from
the tip"; and a non-string getblockhash reply never called back, leaving
the dialog on "Checking..." for good. Only the daemon's "can't read
block" error now counts as missing; anything else means "could not
tell".
User-visible:
- after a bootstrap, a probe that cannot tell is tried up to 6 times,
30 s apart and only while the node is not busy, then a toast points
to Settings > Rescan, instead of falling back to the -rescan
restart, which fails on a bootstrapped node;
- the Settings Rescan dialog warns when the probe could not tell.
Two new strings, translated into all eight languages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Keep in each address" posted one task that called z_mergetoaddress for
every batch of every address back to back; each call is a whole-wallet
scan under cs_main, and the daemon runs the ops one at a time anyway.
It is now an App-owned services::ConsolidationRun:
- one RPC at a time; the next merge only after the previous batch left the
queue and a gap as long as the last call took (2-120 s);
- pauses while the node syncs, the wallet is locked, a send of ours is in
progress, or another wallet scan is running;
- failures classified: nothing that may have queued is ever resent; a
breaker stops after 3 dead addresses or 3 failed ops in a row;
- each merge is charged to the scan budget; one re-read and one forced
balance at the end;
- it keeps running when the dialog closes: a status-bar chip, a run view
with progress, the wait reason and a Stop button.
The "Into one" merge can no longer be confirmed twice while its call is
in flight. fake_dragonxd gains FAKE_SCAN_SECS, FAKE_MERGE_SERIAL and
FAKE_MERGE_FAIL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Connect gate: after the node starts answering, wallet scans wait until the
lock state is known and the node knows its tip (or the wallet has fewer
than 500 transactions), at most 20 s. A node already behind latches the
sync profile from the connect's getinfo. Meanwhile a list-only pass keeps
the address list and the cached history up, and the session's first
balance and per-address balances are read once even while catching up,
so the wallet shows its last-synced values instead of 0.
- Background: minimized, hidden, or covered without focus (3 s debounce,
any input cancels) polls only the chain state every 15 s. Wallet scans,
the mining poll, price, nudges and auto-lock interaction tracking pause;
auto-lock now fires while minimized. Leaving a held profile marks every
timer due again.
- Tab entry marks the timers due instead of forcing scans; a stale list or
a never-loaded history still loads on entry.
- The scan entry points own the hard gates, failed enumerations keep the
previous lists behind a retry hold, and failed balance calls never
overwrite known balances with 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Core poll ran z_gettotalbalance twice (minconf 0 and 1), each a whole-
wallet scan under dragonxd's cs_main. The spendable (minconf=1) call now
runs only after the display call succeeded, and only when something is
unconfirmed or pending or the exact total moved: about 95% of polls make
one scan instead of two. A forced post-send balance can no longer be lost
to a refused enqueue, and pending-send debits aren't re-applied each tick.
z_getoperationstatus is filtered to our opids with its real argument form
([[id,...]]; the old ["id"] was rejected), falling back to the unfiltered
call if a daemon refuses the filter. The Shield/Merge dialog's own polls
are filtered and session-guarded too, and finally reach Done; the dead
RPCClient wrappers are deleted.
The Merge/Consolidate scope scan becomes a tested state machine: one scan
at a time, failures latch behind a Retry button, only "node not ready"
retries by itself (10/30/60 s, at most 3), Lite never scans. Its 120 s
calls are maintenance calls: every main-thread disconnect aborts one in
flight first, so a lifecycle action no longer freezes the UI behind it.
An aborted merge that was already sent says it may still go through.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users who keep funds apart under different labelled addresses can now
shed notes (smaller wallet, faster scans/witness rebuilds) without the
funds being merged into a single address.
- Merge/Consolidate dialog: "Where should the funds go?" - Into one
address (unchanged) or Keep in each address. The latter lists shielded
addresses with 2+ spendable notes (label, note count, amount;
too-small ones disabled) and merges each address's notes back into
that address with z_mergetoaddress [addr] addr. Addresses with more
notes than the batch size get several batches (the daemon locks a
batch's notes before returning, so calls take disjoint notes); the
dialog suggests a second run after those confirm. Per-address
progress; ops keep running if the dialog is closed. The Consolidate
(wallet-size) preset opens in this mode; Merge keeps "Into one".
Shielded only: a transparent address can't take a merge's output.
- services/note_consolidation: groupSpendableNotes, worthConsolidating,
estimateBatches, consolidateInPlace (pure; driven by a merge-call
function), with unit tests against a stand-in following dragonxd's
rules.
- fake_dragonxd: optional note wallet (FAKE_WALLET_NOTES,
FAKE_MERGE_SECS) with z_mergetoaddress and its operations.
- i18n: 17 new keys in all 8 languages; CJK subset rebuilt.
- UI sweep: modal-consolidate target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up to 5246898. A transparent address only ever receives mining
rewards on DragonX (private chain), so:
- One explainer ("Receives solo-mining rewards only - the network rejects
payments to it. Rewards are shielded automatically once they mature.")
on the Overview row hover, the Receive picker hover, the Receive card
and the Validate Address result (now typed "Mining address
(transparent)").
- Receive: a selected mining address shows the explainer and a "Use a
shielded address" button (creates one if none exists) instead of the
payment request, and a "Not for payments" panel instead of a QR code.
The default selection prefers a shielded address; the "All" filter
hides empty mining addresses unless solo mining or marked as a mining
address; the filter reads All / Shielded / Mining.
- Request Payment: no transparent section, never prefilled with one.
- Overview: no "Mining - Mining" tag on a marked transparent row.
- FAQ: address answers rewritten; the send and memo answers no longer
imply you can pay a transparent address. All 8 languages.
i18n: 4 new keys, 6 FAQ answers updated in place, 2 dead keys removed,
CJK subset rebuilt. UI sweep: receive-mining-address and
modal-validate-mining targets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DragonX is a private chain (-ac_private=1): consensus rejects any
non-coinbase output to a transparent address, so a t-address can only
ever receive mining rewards, and its coins can only move to a shielded
address. The wallet treated them like ordinary recipients.
Block paying one:
- Send: a transparent recipient shows "Mining address - can't be paid"
and disables Review Send (tooltip explains; ask for a zs... address).
Typing, paste, contacts and payment URIs all go through this field.
- Overview drag-to-transfer: a mining-address row is never a transfer
target (it only reorders).
- Pool payout must be shielded (pools pay with ordinary transactions):
field tooltip, disabled Start button and the start check say why.
- isValidRecipientAddress() is now shielded-only; new isDragonXAddress()
and classifyRecipient() (Shielded / MiningOnly / Invalid).
Call them mining addresses:
- Overview: "Mining rewards" card (amber "Not yet shielded" when there
is a balance; hover explains; click opens the Shield dialog), "New
mining address" button with a tooltip, rows typed "Mining".
- QR / label dialogs, portfolio filter, contact badge ("Mining only"),
Receive notifications.
i18n: 17 new keys in all 8 languages, 12 dead keys removed, CJK subset
rebuilt. Tests: recipient classification, translation completeness.
UI sweep: new "send-mining-recipient" target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every operation that stops the node (restart, rescan, repair, delete
chain data, reinstall, bootstrap, wallet switch/adopt/restore/rebuild,
wizard restart, encrypt, decrypt) now:
- refuses up front while the node is rebuilding witnesses or has not
answered for 30s, with a "try again when it's done" warning, and sends
no stop;
- goes ahead only once the node's PROCESS has exited, not its RPC port.
A stop that doesn't finish within 120s cancels the operation (warning;
a cancelled rescan/repair disarms its flags); nothing is started over
the old node, chain data isn't deleted, wallet.dat isn't swapped.
Specifics:
- stopDaemonForWalletSwitch() no longer reports "stopped" when the app
is quitting (adopt could swap wallet.dat under a live node).
- Decrypt waited a fixed 3s after "stop" before renaming wallet.dat; it
now waits for the process to exit, and scrubs the key export if not.
- Bootstrap waits for the process before cleanChainData and stops the
node through the stop routine instead of the shared RPC client.
- StopScope::Connected lets switch/reinstall/decrypt stop an adopted or
external node through the same never-kill routine.
- A bounded stop that can't reach the node (a foreign node refusing our
credentials) gives up after 5s instead of 120s.
- macOS: isDaemonProcessRunning() checks processes (libproc), not the
RPC port.
- rpcBusyWhileAlive() also reads the busy gate: since the gate, the
first timeout usually lands on another lane and the 30s clock never
started (also weakened the quit prompt for a silent rebuild).
- Notifications render during the first-run wizard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A node rebuilding note witnesses for an hour was shown as "Starting
DragonX node... Launching dragonxd", which invited closing it.
- Overlay, a new NodeBusy banner and the status bar now say what the node
is doing (rebuilding witnesses / catching up on blocks / up but not
answering), with the elapsed time and Phase-1 block progress. A rebuild
during startup warm-up replaces the stale "-28 Activating best chain"
text, and the overlay re-applies its text once a second instead of only
on a connect attempt (30s apart while the node is busy).
- Quit prompt: elapsed time, "Keep node running & quit" is the default,
"Stop anyway & quit" becomes "Stop node & wait" (it no longer kills).
- A reinstall skipped because the node is still running now notifies.
- Status bar: don't repeat the warm-up status on the right, and don't
print "Connected" there in non-English locales.
- i18n: 31 new keys in all 8 languages; 10 dead force-quit/old-modal keys
removed; CJK subset rebuilt for the new glyphs.
- Tests: chooseDaemonStatus, NodeBusy banner priority, and every new key
present in all 8 JSONs with matching printf conversions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closing the wallet sent "stop" at most twice, gated on a getinfo probe
that needs the daemon's cs_main, then killed the node after 20 s (Windows)
or 30 s (POSIX) whether or not stop had arrived. A node busy with an
hour-long witness rebuild therefore never got stop, was killed mid-
rebuild, and replayed everything since its last flush on every start.
Delivery (src/daemon/stop_delivery.h, RPCClient::open/trySendStop):
- stop goes over a dedicated client with no probe, so it never waits on
rpc_'s lock or a cs_main-bound getinfo
- every attempt is classified, and StopDeliveryPolicy decides the next:
Accepted / 503 = delivered; Queued (reply timeout) is resent at most
every 30 s; QueueFull / not listening are retried every 1.5 s; a
refusal after we were heard means the node is shutting down; a 401 on
our own node tries the next credential source
- POSIX: a group SIGTERM goes out as soon as an attempt fails, which
bypasses the HTTP queue entirely
Waiting (DaemonController::executeDaemonStop, pure, virtual-clock
tested):
- never kills on its own. Quitting waits as long as the node needs; the
shutdown screen offers "Close wallet — leave node running" after 3 s,
and "Kill node (unsafe)" (confirmed) only once stop was delivered and
the node showed no CPU/I-O progress for ~10 min outside a rebuild
- other callers (restart, rescan, reinstall, switch, ...) wait up to
120 s, let go once the app starts quitting, and keep watching a node
that is still running; wallet switch refuses to go ahead then
- EmbeddedDaemon gains stop-session primitives (beginStop,
pollOwnedProcess, signalGracefulStop, forceKillOwnedProcess,
ownedResourceUsage over the Linux process group / Windows handle,
endStop) and only one stop session runs at a time
- the Force Quit button and its stall heuristic are gone; "Leave
running" aborts a pending stop attempt and closes at once
Verified against the fake daemon with the real GUI: closing mid-rebuild
now delivers stop (or SIGTERM 2 ms after a queue-full refusal) and the
node exits cleanly after its rebuild, where it was SIGKILLed before. The
shutdown screen, its "Leave running" button and the idle Kill offer
(after ~11 min) were checked by screenshot. New strings are English-
only for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bug-report fixes plus adversarial-audit follow-ups. Bumps full 2.0.1->2.0.2 and
lite 1.1.0->1.1.1.
i18n / fonts:
- Rebuild the NotoSansCJK subset with the glyphs the FAQ back-fill introduced
(什/么/门/做); Chinese FAQ titles rendered as "??" on zh (and ja/ko).
Transaction history (enumeration gaps):
- Surface outgoing shielded z->z sends via z_listsentbyaddress (listtransactions
and z_listreceivedbyaddress never report them).
- Surface z->t deshields by parsing z_listsentbyaddress transparentSends (which
z_viewtransaction does not expose); dedupe t->t against listtransactions.
- Key send-row dedup on address so equal-value multi-output sends aren't dropped.
Lite wallet (key safety):
- The async create/restore/open path the UI uses now applies the passphrase:
encrypt a new/restored wallet, unlock an existing one. It previously discarded
the passphrase, storing the seed/keys in PLAINTEXT (create/restore) or leaving
an encrypted wallet locked (open).
Migrate-to-seed (fund safety):
- Persist a sweep-submitted marker at broadcast so a resumed migration can't treat
a ~0 balance (an unconfirmed in-flight sweep) as an empty wallet and adopt (swap
wallet.dat) before the sweep confirms; best-effort locate routes to the confirm gate.
Sync / threading:
- Gate isSynced() on a peer-derived tip (tip_known) so a peerless node isn't reported
synced (Send against a stale balance); add a "no peers" status banner.
- Guard daemon_status_ with a mutex (monitor-thread write vs UI-thread read).
Tests: extends tests/test_phase4.cpp across all of the above; suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwycPDQSfEKtrTS2uC4JeT