cd83038de5f89abcfc86bd1ff87b7fec212c5d35
974
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d0bd55b9c1 |
feat(ui): settings polish — button retune, daemon card, RPC 2-row, chat preview
Settings tabs brought closer to the approved mockup: - ActionButton/renderCardButton retune (settings-scoped): 7px radius, 9px padX, Primary → accent-outline chip, Secondary/card buttons more defined. - Daemon-binary card: compact status right-aligned on the DAEMON BINARY heading (Up to date / Version differs / Not installed), filled/rounded status box, neutral danger divider (was alarming red), roomier spacing. - RPC Connection: two-row column-aligned layout (Host | Port, then Username | Password) so the password no longer clips off the card edge. - Chat settings tab: live conversation preview below the Appearance / Messaging cards; "Focus input on open" checkbox reflowed onto the console color-toggle row. - Debug Options: "Current theme only" toggle restricts either screenshot sweep to the active theme instead of cycling every skin. - Tabs fill the full content width (content-max-width cap disabled) and the sidebar nav panel centers within the true visible area. - i18n: new keys for the above (untranslated keys fall back to English). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b37d3d97b6 |
fix(send/receive): unify card width, justify receive footer, fix recipient-row button height/clip/glyph
Send and Receive are now consistent in layout, and the Send recipient row's buttons
render correctly.
Card envelope (Send ⇄ Receive consistency):
- Add Layout::mainComposeCardBox(availW) — a single shared source for the compose card's
width + centering (fill the available column up to content-max-width, then center). Both
tabs derive their card from it, so they can't drift again. Previously Send capped at
760dp and Receive at 860dp, so the Send card rendered ~150px narrower on any window wider
than ~860dp; now they fill available width identically.
Receive:
- Justify the footer buttons edge-to-edge (equal shares over the live count) instead of
left-clustering with dead space, matching Send's full-width footer rhythm.
- Build the address-dropdown preview to the combo's real pixel width so the trailing
balance ("— 12.00000000 DRGX") no longer hard-clips at 150% (was char-count truncation).
Send recipient row (input | Paste | contacts-icon):
- Pin the contacts icon button to the frame height so the larger iconMed font doesn't
auto-size it taller than Paste/the input.
- Reserve the real ItemSpacing.x gaps (not the smaller spacingSm token) so the row no
longer overshoots the card and clips the icon's right border.
draw_helpers (root cause, app-wide):
- TactileButton's icon path measured/drew the label INCLUDING the "##id" suffix (which
CalcTextSizeA/AddText don't strip the way ImGui's text render does), shoving the glyph
off-center-left. Strip at "##" before measuring/drawing. Corrects any icon button that
passes an explicit size and a "##id" label; no-op for labels without "##".
Verified via headless sweeps at 1.0x and 1.5x, plus a real 3800px-wide render (both cards
byte-identical at L=1174/R=2773). ctest 1/1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8778398d31 |
feat(ui): layout polish — fill dead space, pair/box/collapse, warning weight, dialog glass
Implements the layout-improvement suggestions from the layout audit (visual arrangement only; no functionality added or removed): - Send/Receive: the recent-activity list now grows to fill the space below the fixed compose/receive card (more history visible), with a centered empty-state when there is none — instead of leaving dead canvas. - Shield/Merge: pair the Fee and UTXO-Limit fields on one row to tighten vertical rhythm. - Market: extend + frame the portfolio group-list as one contained panel (with a bottom edge) and center its empty-state, closing the previously un-anchored gap. - Overlay dialogs: raise the card glass fill/border alpha (35/50 -> 60/90 of 255) so the dialog card reads as a distinct surface over busy backdrops (global, all overlays). - Wallets: size the list height to the actual wallet count instead of always reserving 7 rows, removing the large gap before the scan/create prompts (still scrolls when many). - Contacts: width-aware address truncation shows more of the address on wide rows. - Transfer Funds: give the "sends the full balance" warning a warning icon + color so the stakes stand out from the neutral result-preview lines. - First-run wizard: collapse a completed Step 1 (Appearance) to the compact pill like Step 2, so a finished step is no longer taller than the active one. - Explorer: distribute the Chain card's two stats to match the density of the sibling metrics grid. - Validate Address: a "Results will appear here" caption fills the pre-interaction blank. - Change Passphrase: add the warning banner its sibling security dialogs have. - Migration ShowSeed: box the 24-word mnemonic grid (a GlassSectionScope behind the existing RenderSeedWordGrid) so the critical secret reads as a distinct artifact — purely visual, no seed/logic/state change. Verified at 1280 across full-node + Lite + Windows (ctest green) and an adversarial diff review (clean). New i18n key backfilled into all 8 languages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d5c30237d2 |
refactor(ui): consolidate dialog footers + shared empty-state, unify buttons/rounding
Addresses the cross-screen inconsistencies from the layout audit by routing screens onto the design system's own (previously under-used) shared helpers: - Dialog footers: migrate ~9 overlay dialogs off hand-rolled placement onto the shared helpers — DialogActionFooter (primary+Close), DialogConfirmFooter, or BeginOverlayDialogFooter for custom/multi-button rows — so footers share one centered treatment. All footer/action buttons now use TactileButton (glass press) instead of the bare StyledButton some dialogs used. - Empty states: add a shared material::DrawEmptyState(icon, title, hint) (centered icon + title + wrapped hint) and adopt it in Peers, Transactions, and Market-portfolio, which previously showed a bare left-aligned caption. - Security dialogs: add the missing Cancel to Change Passphrase and Set PIN so the whole security family shares a two-button footer (Cancel dismisses without applying). - Transactions pager: shared TactileButton helpers (matching Explorer). - Frosted-pane rounding: Contacts/Chat use Layout::glassRounding() instead of hardcoded 12/10/8px literals, matching Peers. - "Set Label..." title loses its stray trailing ellipsis. Preserves every button's label and action; the transfer footer's order becomes [Confirm][Cancel] to match the shared helper's primary-first convention. Verified at 1280 across full-node + Lite + Windows (ctest green) and an adversarial diff review (clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a598217975 |
fix(ui): cut-off/clipping — recent-tx collisions, updater note wrap, request-payment URI, console filter, icon-grid scroll
Fixes the cut-off/clipping bugs from the layout audit (all visible at the default
1280/1024 window sizes):
- Receive "Recent Received" rows: the amount collided with the relative-time
("+15.7500 DRGX14 days ago") and the type label touched the address at narrow
widths. Use the shared short time format (formatTimeAgoShort, matching Overview),
chain the amount's right edge off the measured time width, and start the address
after the measured type-label width — so neither pair can collide.
- Daemon & xmrig updater verify-note: drawn unwrapped and clipped at the card's right
edge; wrap it (PushTextWrapPos) within the already-reserved height.
- Request Payment: the three footer buttons shared one fixed width (clipping "Copy
Full Address"); size each to its own label. The Payment URI overflowed a plain
field; render it in a bordered read-only box (bounded, un-chunked).
- Console: the filter input shrank below its own placeholder (gone entirely at 1024);
give it a min width >= the placeholder and drop the "N lines" count when the row
can't fit both.
- Address-label "Choose Icon" grid: had NoScrollbar hiding most of the catalog with
no cue; give it a real scrollbar.
- Overview "Recent Transactions": drop the 4th row at 1024 (it clipped off-screen) by
capping to rows that fully fit the reserved height.
- Sidebar: reserve the unread-badge width in the nav-label centering so History/Chat
labels no longer collide with their badge.
Verified at 1024 and 1280 across full-node + Lite + Windows (ctest green) and an
adversarial diff review (clean). Skipped the legacy settings_window overlay footer
(dead code / removal candidate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9205addf55 |
feat(ui): width-responsiveness — content max-width cap + per-surface form/input clamps
Wide/ultrawide (1440-3440px) responsiveness was unhealthy: no page/content-level max-width cap existed, and every card/form/table derived width from raw GetContentRegionAvail().x with floor-only clamps, so surfaces stretched edge-to-edge (2000-3000px inputs, ballooning cards, giant grid cells, 2000px+ dead row-voids). Root cause: adopt the (previously dead-code) clamp helpers. - New Layout::kContentMaxWidth() (~1600dp, tunable via ui.toml [layout] content-max-width; <=0 disables). Cap ##ContentArea to it and center the column in wider windows — every tab derives from this child, so one change tames the app at wide widths. No-op below the cap (fills as before), so 1080p/1440p are unaffected. Per-surface upper-clamps (std::min(cap*dp, expr), floors preserved) where a single element is still too wide even within the capped column: - Settings: Theme/Layout/Language combos, the font-scale slider (~3000px -> 360dp), the effect sliders, Explorer URL and RPC credential fields. - Send / Receive: cap the compose / receive cards to a readable form width and center them (Indent(pad+offset) so the auto-layout fields align with the hand-drawn card); the recent-tx lists below keep the full column width. - Chat message bubbles + composer, mining pool URL/payout inputs + stats left/right split, contacts search, and the lite-network add-server row / server cards / status panel (capped + centered). - Wizard: vertically center the cards when they fit (was top-anchored, leaving a void on tall monitors), compensating the content-height measurement so it can't oscillate. The 1600 cap also subsumes the fixed-4-column balance grids (~400px cards) and the right-anchored row dead-gaps (voids shrink from ~2700px to ~800px), so those are left to the cap rather than blind column/row redesigns. Verified at 1024/1280 (and via a temporary 900dp cap to exercise the cap+center path, since the test display clamps to 1280) across full-node + Lite + Windows (ctest green) and an adversarial diff review (clean). The true wide/ultrawide look and the 1600dp cap value still want eyes on a real wide monitor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ce8c7696d4 |
fix(ui): finish HiDPI pass — cosmetic ×dpiScale, narrow-width reflow, recent-list reserves
The tail of the DPI/font-scale/responsiveness audit — ~26 remaining findings.
Container / recent-list (Theme-1 leftovers):
- Send: drop NoScrollbar|NoScrollWithMouse on ##SendFormScroll so Recent Sends is
reachable at font_scale 1.5 (parity with receive).
- Receive: cap the QR/form card via std::min(mainCardTargetH, availH - recentReserve)
so RECENT RECEIVED stays on-screen (identity at 1.0x).
- Wallets dialog: size the capped-mode list to whole rows so it no longer clips a
partial row / crowds "Create a new wallet".
Narrow-width (1024px) reflow:
- Console toolbar reserves space for ALL trailing controls (both icon toggles + zoom
buttons) so the +/- zoom no longer runs off-window.
- History sort combo sized to its measured widest localized label ("Newest first").
- Settings Theme/Layout/Language row: scale the wide→stacked breakpoint by dpiScale so
it drops to full-width stacked combos at 1.5x (Consolidated Card no longer clips).
- Recent-tx type label: derive the address column X from the measured label width so it
can't collide at narrow widths.
- Mining Recent Pool Payouts: floor the panel height to fit the empty-state caption.
Cosmetic ×dpiScale() on absolute geometry (no-ops at 1.0x): mining SOLO|POOL toggle &
idle combos, market pair-chips, password/PIN strength bars, receive/send currency
toggles, explorer search bar/rows/rounding, About-card logo, chat empty-state wrap,
recent-list address/time offsets, address-toolbar & two-row action buttons, console
line-gap/status-dot/pane rounding.
Verified at font_scale 1.5 and at 1024px across full-node + Lite + Windows (ctest
green) and an adversarial diff review (one over-reserve regression fixed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
99d1e73676 |
fix(ui): scale unscaled geometry at HiDPI — inputs, send progress cards, chat/pool overlaps, peer rows
At font_scale 1.5 ~13 sites read absolute geometry (schema .size/.width) straight into ImGui without ×dpiScale(), so they stayed native-size while the font grew and overlapped/clipped real text or money: - Shield/Merge fee + UTXO inputs, Request Payment amount, Block Info height input: ×dpiScale() so the value no longer clips (e.g. 0.00010000 -> 0.00010). - Send: the confirm-popup Amount Details divider (floored the row step at the scaled caption height so it no longer strikes the Fee row), the tx-progress error and sending/success cards, and the zero-balance CTA button — all ×dp. - Mining pool row: ellipsis-truncate the hostname so it can't collide with the right-aligned hashrate. - Chat conversation list: scale the pane-width clamp AND clip the peer name to the column left of the timestamp (measure-then-clip) so name and time never overlap. - Explorer block-detail label column, Peers row offsets, and DialogConfirmFooter button height — ×dp. transaction_details keeps its negative fill-sentinel widths unscaled (a content margin, not raw px). Verified at font_scale 1.5 across full-node + Lite + Windows (ctest green) and an adversarial diff review (three wrong-scale regressions fixed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
755cf22ad0 |
fix(ui): make HiDPI-overflowing containers scrollable — wizard, overlay dialogs, balance recent-tx
At font_scale 1.5 (dpiScale 1.5) three fixed, non-scrolling containers clipped content off the bottom with no scroll escape: - First-run wizard: the hand-drawn cards grow ~1.5x past the fixed window, pushing Continue / Encrypt & Continue / Skip off-screen (a setup blocker). Inject a wheel-driven scroll offset into the layout seed + a scroll indicator; gate the wheel on !IsPopupOpen + NoPopupHierarchy so an open combo popup does not scroll the wizard behind it. No-op at 1.0x. - Overlay dialogs (BeginOverlayDialog): auto-height cards taller than the viewport (About, Request Payment) ran their footer off the bottom. Add a sticky per-open overflow flag that clamps the card to the viewport and makes the content child scrollable; short dialogs still center unchanged. Give the nested settings clear-history confirm its own idSuffix so it can't inherit the parent dialog's overflow state or collide on the child window id. - Balance Recent Transactions: the dp-scaled address card evicted the recent-tx list off the non-scrolling tab host. Cap the card inside RenderSharedAddressList against the space that actually remains (minus a caller-provided reserve) so the section below stays on-screen — covers all 10 balance layouts. Verified at font_scale 1.5 across full-node + Lite + Windows (ctest green) and an adversarial diff review (two low-severity regressions found + fixed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e24ca015d1 |
feat(ui): implement UI/UX audit — i18n, HiDPI, theme, destructive-action & overflow fixes
Implements all 26 confirmed UI/UX audit findings plus the 4 dashboard/timeline tile labels. Verified across full-node + Lite + Windows builds (ctest green) and an adversarial diff review (one market-tab delete regression caught + fixed). i18n coverage: - Balance hero/quick-actions/toasts + dashboard & timeline tiles (Total Balance, Shielded, Transparent, Quick Send, Quick Receive, Click to open, Market) - Send: Review Send / Cancel / Paste / view-only tooltip / memo byte counter - Settings: Lite lifecycle errors, plaintext-RPC security warning, 8 toasts - Mining pool-payout tooltip - Resolve daemon_update_title double-assignment collision (new daemon_update_prompt_title) - 23 new keys translated into all 8 locales; CJK subset font rebuilt HiDPI: DPI-scale the Send amount bar, mining thread-tile clamp bounds, receive loading skeleton, and sidebar notification-badge insets. Light theme: theme-aware material::SurfaceOverlay() for balance bar tracks and row-hover highlights; contacts active-pill foreground uses OnPrimary(). Destructive actions: arm/confirm for portfolio-group delete, saved pool/worker remove, and avatar-image delete. Interaction/overflow: route balance star/eye buttons and the console fold-toggle through popup-safe guards; clip peer addr/subver; truncate the balance custom label; measure Settings tool-button widths; mining stepper disabled-state feedback; +/- stepper buttons restyled to match the thread tiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0de44569b7 |
feat(mining): thread-count stepper, adaptive tiles, dropdown click-through fix, xmrig version state
Mining-tab UI improvements: - Thread selector: a centered [-] N [+] stepper (top-aligned in the header) to pick an exact thread count — number centered, -/+ step by one (clamped to [1, cores]), still typeable (commits on Enter so it doesn't restart the miner mid-typing). - Thread tiles now render at an adaptive step (1/2/4/8 by core count) plus 1 and the max, so a high-core CPU (e.g. a 192-thread EPYC) shows ~25 tiles instead of one-per-thread and no longer overflows the card. Unchanged for <=24-core machines. - Fix: clicking the X (or a row) in an open saved-pools / payout-address dropdown no longer bleeds through to the thread tiles / Mine button — the custom drawlist hit-tests now gate on IsPopupOpen(AnyPopup). - xmrig update button: shows "xmrig releases" when installed >= latest (numeric version compare), else "Update <latest>"; the "Current: <ver>" text and the button are subtle green when up to date and subtle orange when an update is available (neutral when either version is unknown). New i18n keys back-filled across all 8 languages; CJK subset font rebuilt for the new glyphs. Verified across full-node, lite, and Windows builds; the stepper layout confirmed via the UI sweep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
06afbee4f8 |
fix(mining): remediate mining-tab audit (22 findings) — crash-safety, async control, validation, math
Fixes all 22 confirmed findings from the mining-tab audit (10 Medium, 12 Low; 0 Critical/High), adversarially reviewed (6 follow-ups found + fixed, incl. the review-caught idle-auto-start bypass and a wrong benchmark-restore condition). Crash-safety & lifecycle: - M-04: join a stale/finished monitor thread in XmrigManager::start() and ~XmrigManager so an xmrig crash-then-restart (or quit) no longer std::terminate()s the wallet. - L-03/L-10: surface an unexpected miner exit once and clear the stale running flag. UI never blocks (M-03/L-06/L-08/L-09/L-13): pool start/stop now run on a dedicated serialized FIFO mining-control thread (joined before teardown), so the ~13 call sites don't block the render thread on stop()'s SIGTERM->SIGKILL->join; the spawn result marshals back to the UI. Miner-process / pool trust boundary: - M-01: validate the payout address (util::isValidRecipientAddress) at EVERY start path — the UI gate AND App::startPoolMining() (idle auto-start / thread scaling) — so a stale/wrong-chain address can't silently lose rewards. - M-09: SSRF guard skips the background pool-stats GET for loopback/private/link-local/single-label hosts. - M-02/L-02: cap the pool-stats + xmrig-API HTTP response bodies. - L-01: write the xmrig config 0600 at creation (POSIX open with mode) — no world/group-readable window. - M-10: reject shell-metacharacter binary paths before the version popen (excluding '()' so Program Files (x86) still works). Solo mining: M-06/M-08 clamp thread count to [1, cores] at the setgenerate/xmrig boundary; M-07 notify + don't lie on stop failure. Correctness: L-05 block-time constant 75->150s (chainparams); M-05 discloses pool-mode "Est. Daily" as a rough solo-equivalent; L-04/L-11/L-12 benchmark lifecycle (cancel on nav-away / mode-switch with restore, skip rebalance mid-benchmark); L-07 honor cancel mid-extract in both the xmrig and daemon updaters. Two new i18n keys back-filled across all 8 languages; CJK subset font rebuilt for the new glyphs. Verified across full-node, lite, and Windows builds; tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6ee81a5abe |
fix: security-audit remediation (15 findings), empty-wallet warning, and send/chat/console/shutdown UX
Security audit remediation (15 confirmed findings from the codebase audit): - H-02: scrub+delete the decrypt-flow plaintext key export on ALL exit paths (RAII guard) and purge stale obsidiandecryptexport* files at startup. - M-01/L-03/L-04/L-05/L-07: sodium_memzero the Set-PIN and encrypt-PIN worker passphrase/PIN copies, the RPC Basic-auth string (auth_), the exported/imported key buffers (App::wipeSecrets, called from ~App and before main's _Exit), and the first-run wizard "Skip" buffers. - M-03/M-04/M-05/L-06: return locked COPIES from XmrigManager/EmbeddedDaemon getters (dedicated error_mutex_; DaemonController::lastError now by value), route xmrig last_error_ writes through a locked setter, and wrap shutdown_status_/wizard_stop_status_ in a locking GuardedStatus (wizard_stopping_external_ -> std::atomic). - M-02: persist after a console send/shield/import in the lite backend. - L-01: require the confirm click for z_shieldcoinbase/z_mergetoaddress. - L-02: quote/escape each Windows daemon argv per the MSDN CommandLineToArgvW rules. - L-08: pin json/tomlplusplus/libwebp FetchContent to immutable commit SHAs. - I-01: extract updater archives from the already-verified in-memory buffer (no disk re-read TOCTOU). Feature: warn once (full-node) when the active wallet loads empty while a sibling wallet file in the datadir holds keys. A funded salvage wallet.<ts>.bak routes to the recovery/Restore flow; a funded sibling .dat routes to the wallet manager. Per-wallet-file dismissal; gated on synced + address-list-loaded to avoid false positives on warm reconnect / spent-down wallets. UX fixes: - send: show the TOTAL balance (with a spendable "available" note) in the source dropdown and keep pending-change addresses visible. - chat: insert emoji at the cursor position; restrict new-chat recipients to shielded (z) addresses. - console: optional auto-focus of the command input on tab open (off by default). - shutdown: when "stop external daemon" is on, keep the shutdown screen up until the external node actually exits, showing live status. Adversarially reviewed; verified across full-node, lite, and Windows builds; tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ea26c0cbbb |
fix(balance): stop the displayed balance cratering during a pending shielded send
Sending a small amount from an address holding a large balance made the displayed balance collapse to ~0 until the tx confirmed. A shielded spend consumes the whole source note; the change returns as a 0-confirmation note, and every balance query used the default minconf=1 — so the spent note dropped out and the change wasn't counted yet. Split every balance into two views: - DISPLAY (balance / privateBalance / transparentBalance / totalBalance) — now queried at minconf=0, so it INCLUDES the user's own pending change and no longer craters. This is what the Overview, balance tab, market portfolio and receive tab show. unconfirmedBalance is now populated (= total - spendable). - SPENDABLE (new spendableBalance / spendable*Balance) — confirmed (minconf>=1), what z_sendmany (run at minconf=1) can actually spend. The Send form's available/ Max/validation, the from-address selection, the drag-to-transfer dialog cap, the chat pay-from and the auto-shield gate all size off these, so they never offer 0-conf change the daemon would reject. Implementation: a single z_listunspent(0)/listunspent(0), partitioned per-note by "confirmations">=1; z_gettotalbalance called at minconf 0 (display) and 1 (spendable); the z_getbalance fallback queries both. applyPendingSendDelta (the optimistic post-send debit) now touches ONLY the spendable fields — debiting the display too would re-crater it on top of the honest minconf=0 RPC. Lite mirrors spendableBalance = balance (its per-address balance is already confirmed) so lite sends aren't zeroed. The confirmed-only gates (seed-migration/sweep z_gettotalbalance, sweep z_getbalance(addr,1), z_sendmany's minconf arg) are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
13b225d8f5 |
fix(send): prefix shielded-send memos with "utf8:" so the daemon accepts them
A shielded send with a memo failed: "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix." The Send-tab path (and its fee-gap retry) put the user's plain-text memo straight into the z_sendmany recipient, which the daemon now rejects — it wants the memo hex-encoded or with a "utf8:" prefix. The chat path already prefixes with "utf8:"; do the same for user memos. Only the RPC recipient["memo"] is prefixed; the raw memo is still what's stored for the transaction-history display, and the daemon returns the decoded memoStr to receivers, so it round-trips as plain text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
08aed34bbe |
fix(daemon): actually stop an external daemon on quit when the setting is on
"Stop external daemon" silently left an external dragonxd running. beginShutdown() calls rpc_->requestAbort() (a sticky abort flag, cleared only by connect()) to unblock in-flight requests; the shutdown thread's stopEmbeddedDaemon() then sent the graceful "stop" over that same connection, so curl self-aborted it (CURLE_ABORTED_BY_CALLBACK). doRPC swallowed the error but stop_sent was set true anyway, skipping the temp-connection fallback that would have worked — so the daemon never received "stop" and only died via the 20s by-name force-kill (which collides with the 8s "Force Quit / may corrupt chain data" prompt, so it read as "doesn't work"). Clear the abort before the shutdown stop and send it synchronously via sendStopCommandSafely so real delivery success is surfaced (and the fallback can still run on failure). The graceful stop now reaches the daemon, it exits in a second or two, and the 20s stall / Force-Quit prompt no longer appears. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5edbe8a276 |
feat(recovery): redesign the wallet auto-recovery flow
Turns the "Daemon Error + raw log dump" moment into one calm, honest recovery
dialog plus a recovery-aware rescan screen. Presentation + orchestration only —
the file-safety logic in rebuildWalletDatabase()/restoreOriginalWallet() (source
selection, verify-before-swap, copy/rename-never-delete, .bak) is unchanged.
- One authoritative dialog with a phase machine Offer -> Working -> Done/Failed.
The duplicate in-overlay recovery card, the untranslated red "Daemon Error"
heading, and the raw daemon-log dump are gone for the recovery case (they stay
for genuine, unrelated crashes).
- Offer is a choice-cards layout: "Repair automatically" (recommended, accent-
tinted) vs "Restore original", side by side; the rare actions ("Show me the
files", "Decide later") and a plain-language "What happens to my files?" sit in
a quiet footer. When the rebuild helper is missing, it collapses to a single
Restore card — never a dead end.
- Post-repair rescan shows a calm "Finishing your wallet repair" screen with
elapsed time + the growing wallet size, instead of "RPC timeout / taking longer
than expected / restart daemon"; the daemon-crash toast is suppressed and the
detection toast is downgraded from red to info.
- Fixes a confirmed dead-end: if a repair succeeds but the restarted daemon then
crashes for a *different* reason (block index, disk, OOM), the recovery flags
now clear (in tryConnect + onConnected) so it surfaces as a normal daemon
failure instead of freezing forever on a reassuring "don't restart" screen.
- Clickable "Wallet repair available" status-bar chip for re-entry.
The same app.cpp changes HiDPI-harden the surfaces the recovery flow lives on:
the status-bar and loading-overlay hand-drawn geometry are multiplied by dpiScale
(they rendered native-size and clipped at HiDPI / font_scale>1), the loading-
overlay status text wraps instead of running off both edges, and the node-status
banner floors its height to its DPI-baked font so the title can't clip off the top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
001e85ac1a |
fix(ui): prevent text/button cutoff and scale hand-drawn geometry at HiDPI
Findings from a UI-cutoff audit — each is a spot where an in-tree helper (truncateMiddle / TruncateToWidth / measured button width / the *dpiScale/*hs factors) was bypassed: - notifications: the toast-pill height/padding/icon-gap were raw logical px while the icon/text drawn inside are DPI-baked, so they clipped the pill at HiDPI. Scale the geometry by dpiScale (not the already-scaled glyph metrics). - settings: in the two-column NODE & SECURITY layout the data-directory path could overrun into the Daemon-binary column (shared draw list, no clip rect between them). Middle-ellipsize it to the column width; the full path stays in the tooltip + click-to-open + copy. - send: the "Confirm & Send" button width came straight from the schema and was never measured against the label, clipping the Russian translation on the pre-broadcast dialog. Size to max(schema width, measured label + padding). - balance: the recent-tx address-column offset missed the `* hs` DPI factor its sibling (amount-right-margin) uses, overlapping the type label at HiDPI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
393f3d147e |
feat(recovery): bundle & embed the offline wallet-rebuild helper in every release
The dragonx-wallet-rebuild helper is the only thing that repairs a genuinely BDB-inconsistent wallet.dat — plain "Restore" just re-triggers the daemon's salvage cascade — yet it was silently dropped from every packaged build: - Linux zip/AppImage copied a hand-picked file list that omitted it. - Windows bundled it only behind a soft `[[ -f ]]` guard (silent skip). - macOS never wired Berkeley DB, never built it, never bundled it. build.sh now HARD-REQUIRES the helper for full-node releases (fails the build if the vendored Berkeley DB depends are missing, rather than shipping recovery-less), and ships it in the Linux zip + AppImage, the Windows zip, and the macOS .app. It also compiles the helper standalone for Windows and INCBINs it, and embedded_resources gains ensureWalletRebuildHelperExtracted() to extract it on demand — so a self-contained ObsidianDragon.exe carries recovery exactly like the embedded daemon, even on a machine where first-run param extraction already ran. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ac49f44f84 |
fix(node): show recovery actions in the daemon-error overlay (not a separate dialog)
Reported with a screenshot: on a salvage-then-abort, the status correctly read "Wallet needs recovery — see the prompt" but no prompt appeared — the separate BeginOverlayDialog is occluded by the full-frame loading/daemon-error overlay that's drawn every frame while the node is down. Render the recovery actions directly IN the daemon-error overlay when a salvage is detected: a concise message + prominent one-click "Rebuild wallet database" / "Restore original" / "Open data folder" buttons (same handlers as the dialog), placed right after the title and skipping the verbose daemon-output dump so they stay on-screen. The verbose diagnostics + crash-count hint still show for non-recovery errors. Build clean, suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3216debc7d |
fix(node): detect a wallet salvage at startup, not only on connect
Reported: loading a BDB-inconsistent wallet silently renamed it and created a new
one — no recovery dialog. Two causes, both fixed:
1) Detection ran only in onConnected(). The salvage happens at STARTUP, and the
node may never connect (block-index abort, long sync, crash) — or a long sync
trims the salvage line out of the rolling output buffer before connect. Extract
detectWalletAutoRecovery() and run it every tryConnect() tick (every ~5s during
startup), so the salvage is caught the instant it appears, regardless of whether
the node connects. Also hold the crash-restart loop while a salvage is pending,
so the wallet can't be re-salvaged/shrunk while the Rebuild/Restore dialog is up.
2) walletAutoRecovered() only matched the SUCCESSFUL-salvage strings. A
BDB-inconsistent file makes aggressive salvage FAIL ("found no records"), which
prints different lines. Broaden the detector to the signals that fire in every
case: "CDBEnv::Salvage", the "Renamed <wallet> to wallet.<ts>.bak" rename, and
"found no records in wallet" — while still not matching normal startup or a
block-DB abort.
Adds the exact failed-salvage sequence to the detector test. Build clean, suite
green (1/1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8ffcd9cc8c |
build(node): bundle dragonx-wallet-rebuild in Linux + Windows releases
Wires the wallet-rebuild recovery helper into the release pipeline so a shipped
build actually carries it (the app locates it next to dragonxd).
- CMakeLists: link the vendored STATIC Berkeley DB for the helper — add
Threads::Threads + dl (Linux) / ws2_32 (Windows) that the static libdb-6.2 needs
(the system shared lib pulled those in transitively; the static one doesn't).
- build.sh (Linux + Windows): pass BDB_INCLUDE_DIR/BDB_LIBRARY explicitly at
configure, pointing at external/dragonx/depends/<triple>/{include,lib/libdb-6.2.a}
— the same libdb the daemon links, so the helper's output is a v6.2 btree the
bundled dragonxd reads. Explicit paths bypass find_library (and the mingw
toolchain's sysroot-only find restriction). Guarded: no depends → helper simply
not built/bundled. Strip + copy the helper next to dragonxd(.exe) in both bundles.
Verified: Linux links the vendored libdb-6.2.a statically (no dynamic libdb) and
rebuilds the real broken wallet correctly; the helper cross-compiles cleanly with
mingw against the vendored Windows libdb-6.2.a to a PE32+ x64 exe. Full app +
helper build, suite green (1/1), build.sh syntax OK.
Remaining: macOS Berkeley DB (no in-tree depends artifact) + resource-embedding as
an alternative to side-by-side bundling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
bc183257b7 |
feat(node): in-app "Rebuild wallet database" recovery for a BDB-inconsistent wallet
Automates the manual recovery that fixed a wallet.dat with stale Berkeley DB extent metadata (the "main" subdb metapage records a low last_pgno while its live data spans thousands of pages beyond it). A tolerant page-walk reads every record, but the daemon's BDB verify rejects the file and auto-salvages it — finding nothing and shrinking the wallet to empty on each restart (the salvage cascade that looks like fund loss). Plain "Restore original" can't fix it (hands the same broken file back → re-salvage); a rebuild must produce a fresh, consistent DB. Pieces (Approach A from the design workflow — out-of-process helper keeps AGPL Berkeley DB out of the GPLv3 GUI): - util/wallet_file_probe.h: extractWalletBtreeRecords() — sibling to parseWalletBtree that collects raw (key,value) bytes (same bounds-checked, subdb-aware walk). Records copied verbatim → encrypted key material passes through as opaque ciphertext (no passphrase). Overflow-page values (only large tx history) are skipped + counted; a rescan rebuilds history — funds unaffected. - tools/wallet_rebuild/main.cpp: dragonx-wallet-rebuild CLI — reads via the tolerant reader, writes the records into a fresh BDB "main" btree via libdb (DB_EXCL, never overwrites), prints a JSON summary. New BDB-guarded CMake target. - App::rebuildWalletDatabase(): picks the largest readable wallet/.bak as source, stops the daemon, runs the helper, VERIFIES the output (readable BDB with keys) before swapping, moves the current wallet aside (kept, timestamped), installs the rebuilt one, clears the stale BDB env, sets -rescan, restarts. Copy/rename only — never deletes. Result surfaced via the existing pumpWalletRestore channel. - Wired as the preferred action on the existing wallet-auto-recovery dialog (shown only when the helper is present). Full-node only; lite-safe. Verified end-to-end against the real broken wallet: helper reads 3,808 t-keys + 1 z-key + HD seed and the daemon LOADS the rebuilt output with no salvage. Adds extractWalletBtreeRecords coverage. Build clean, suite green (1/1). Remaining (follow-up): release packaging — build.sh bundling the helper built against the vendored per-platform static libdb (DRAGONX_BDB_ROOT), and a macOS Berkeley DB port (no in-tree artifact). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b2e037bb67 |
chore(gitignore): never track wallet.dat (holds private keys)
A wallet.dat placed in the repo root for recovery was untracked but NOT ignored, so a stray 'git add .' could commit private keys. *.bak already covered the salvage backups; add wallet.dat / wallet-*.dat / wallet.dat.* explicitly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
975650f11b |
fix(node): restore the LARGEST salvage backup, not the newest (salvage cascade)
The "Restore original wallet" action picked the newest wallet.<ts>.bak — but the daemon auto-salvages on every failed BDB verify, and each round SHRINKS the wallet (salvage keeps only readable records + drops the dead-page bloat). In a cascade the newest .bak is the most-degraded (seen in the wild as "Salvage found no records") while the original is the oldest and by far the largest. Pick by file SIZE instead: add largestWalletSalvageBak((name,size) pairs) — the largest wallet.<digits>.bak is the least-salvaged, i.e. the pristine original (an emptied salvage is tiny; a real wallet is large); ties break to the newest ts. Factor the shared parse into parseWalletSalvageBakTs(). restoreOriginalWallet() now gathers file sizes and uses it (still verifies the pick is a valid BDB before swapping). newestWalletSalvageBak kept for reference. Adds a cascade regression test (a 40KB emptied newest .bak must NOT win over the 194MB original). Suite green (1/1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
384d64ea5d |
feat(node): one-click "Restore original wallet" after a daemon auto-recovery
Adds the restore action to the wallet-auto-recovery warning: undo the daemon's salvage by swapping the untouched original (wallet.<ts>.bak) back over the salvaged copy and clearing the stale BDB env that triggered the false recovery, then restarting. Modeled on beginAdoptSeedWallet (stop daemon → file ops → restart on a worker; result pumped to the main thread for notifications). Safety (fund-adjacent file ops on a real wallet — copy/rename only, never delete user data): - picks the newest wallet.<unixtime>.bak via the pure, unit-tested newestWalletSalvageBak(); aborts if none. - verifies the .bak is a real Berkeley DB (probeWalletFile) before touching anything — won't overwrite a working wallet with a bad backup. - stops the daemon first (stopDaemonForWalletSwitch) so wallet.dat is released. - moves the salvaged copy aside to wallet.dat.salvaged-<ts>.dat (kept), COPIES the .bak into place (the .bak stays), moves database/ aside to database.pre-restore-<ts>.bak (kept), and drops only the transient __db.* BDB region files. Rolls back the move if the copy fails. - relaunches the node even on failure so it's never left down. The warning dialog now offers Restore original wallet / Open data folder / Keep salvaged copy. Full-node only; lite-safe. Build clean, suite green (1/1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3b7423f3a1 |
feat(node): warn when the daemon auto-recovers (salvages) wallet.dat
dragonxd auto-recovers a wallet.dat that fails BDB verification on startup — no flag needed (CWallet::Verify → CDBEnv::Verify(walletFile, CWalletDB::Recover)): it moves the original to wallet.<timestamp>.bak, salvages readable keys into a fresh wallet.dat, and keeps running. The salvage can be incomplete (or the whole thing a FALSE POSITIVE from stale/cross-platform BDB env state — __db.* / the database/ dir carried between machines), so the node silently comes up on a possibly-empty wallet. To the user that reads as fund loss, with no warning. Detect it and warn loudly instead: - daemon/daemon_startup_diagnosis.h: pure walletAutoRecovered() (the salvage / "Original wallet.dat saved as wallet.<ts>.bak" markers) + newestWalletSalvageBak() (picks the wallet.<unixtime>.bak the recovery just made). - onConnected() scans the node's captured output once per session; on a match it shows a warning dialog + notification: the ORIGINAL is safe in wallet.<ts>.bak, the shown balance may be incomplete, and here are the exact steps to restore it (rename the .bak back + delete the stale database/ + __db.* env). One-click "Open data folder" jumps straight there. Full-node only; lite-safe. Deliberately does NOT auto-swap the wallet files (untested per-platform file manipulation on a real wallet is not worth the risk) — it informs + guides. Adds walletAutoRecovered / newestWalletSalvageBak coverage to testBlockDbOutputDiagnosis. Suite green (1/1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d136916e80 |
feat(node): detect an unreadable block DB on startup and offer a one-click reindex
When a daemon update changes the block-index on-disk format (or the index is
corrupt), dragonxd aborts at startup — "non-canonical optional discriminant" →
"Error loading block database. Aborted." — and the wallet silently shows a zero
balance. Previously the connect loop just crash-restarted into the same abort up
to 3x and then reported a bare "Daemon crashed N times", with no path forward.
Now:
- daemon/daemon_startup_diagnosis.h: pure blockDbOutputLooksBroken() classifies
the crashed node's captured console output (the fatal block-DB markers).
- The connect loop detects it on the FIRST abort, STOPS crash-restarting into the
same failure (each retry reloads the whole index — wasteful), and offers a fix.
- A one-shot -reindex flag (EmbeddedDaemon::setReindexOnNextStart → DaemonController
forwarder → args) rebuilds the block index + chainstate from the intact raw
blocks; App::reindexBlockDatabase() arms it and un-gates the loop to restart.
- An auto-shown dialog (renderBlockDbReindexDialog) + a notification explain the
situation ("your coins are safe; the node just can't load the chain") and offer
a one-click "Rebuild block database". Full-node only (gated), lite-safe.
This is the exact trap behind a real "big wallet shows no funds" report: a
post-format-change daemon over pre-change chaindata. Reindex also fixes a plain
corrupt index.
Adds testBlockDbOutputDiagnosis (the abort sequence + individual markers trip it;
normal startup / wallet-corruption / asmap errors do not). Suite green (1/1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f88304fed2 |
fix(wallets): stop a legacy wallet showing as a seed-phrase wallet when linked
The wallets list badged the active/linked row from the runtime seed status (activeWalletSeedBadge → wallet_seed_status_, reset only on disconnect) in preference to the offline on-disk probe. Two issues let a genuinely legacy wallet render as "seed phrase": - The offline probe's budget-fallback branch dropped the fMnemonicSeed flag: res.mnemonic was set only in the (parsed && complete) branch. The probe shares a 768 MB budget across all wallet files, so a large wallet (e.g. a 194 MB one) probed after the budget is spent falls into the fallback, loses its seed/legacy classification (mnemonic → 0), and the row defers to the runtime badge. - With mnemonic == 0, the code used the runtime badge, which can still carry a HasMnemonic from a previously-active mnemonic wallet — mislabelling the legacy wallet. Fix: - Carry the definitive positives (fMnemonicSeed/hdSeed/mkey) from a cap-truncated btree walk — a found marker is authoritative even when the scan didn't finish. - Make the on-disk fMnemonicSeed read take precedence: it's the SAME flag the daemon's IsMnemonicSeed()/z_exportmnemonic consult, so a definitive read wins; the runtime badge is used only when the probe genuinely couldn't decide, and never overrides a definitive on-disk classification. Verified the wallet in question is truly legacy (fMnemonicSeed=false on disk, matching the daemon's CHDChain serialization + IsMnemonicSeed). The flag reader (hdChainMnemonicFlag) is already unit-tested; suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5296dd7ae5 |
fix(parsers): harden RPC/price/updater parsing against valid-but-unhandled input
Audit of the full-node RPC response parsers and updater release-body parsers (find -> adversarially-verify workflow) surfaced three worth fixing; three others guard formats the project doesn't emit and are backstopped by signature verification, so they're documented rather than churned. - Price (Medium): parseCoinGeckoPriceResponse used .value(key, 0.0), which throws type_error on a PRESENT null. CoinGecko emits null for usd_24h_change/ usd_24h_vol on illiquid tokens (DRGX is one) while still returning a valid spot price; the outer catch turned that into no price update at all. Read null-tolerantly so the valid usd/btc survives. - Daemon updater (Medium): parseDaemonChecksums blanked '|'/backtick but not markdown emphasis, so a bolded **archive.zip** checksum row was dropped and a valid, correctly-signed release would be refused. Also blank '*'/'_' (cannot cause a wrong-asset match; the 64-hex + .zip-suffix tests are unchanged). - Opid poll (Low, severe failure mode): parseOperationStatusPoll read id/status via .value() (throws on a present non-string) and the call site parsed OUTSIDE its try/catch, so a throw left opid_poll_in_progress_ stuck true and wedged all z-operation polling for the session. Type-check the reads and parse inside the guard. (dragonxd can't emit non-string id/status; this is defense-in-depth.) Regression tests: CoinGecko null field; opid non-string id/status; **bold** checksum row. Suite green (1/1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a1d3964e34 |
fix(lite): require exactly 24 words on first-run restore (crash on valid seed)
The lite first-run restore wizard enabled Restore for {12,15,18,21,24}-word
phrases, but the SDXL backend only accepts 24-word / 32-byte-entropy seeds:
LightWallet::new does copy_from_slice(&phrase.entropy()) into a [u8;32]
(lightwallet.rs:231), which panics on 16/20/24/28-byte entropy. Mnemonic::
from_phrase accepts the shorter valid phrase, and the restore FFI
litelib_initialize_new_from_phrase (lib.rs:127) has no catch_unwind (unlike
litelib_execute), so the panic unwinds across extern "C" -> process abort
(UB on the pinned rustc 1.63). A user restoring a legitimate 12-word seed
from another wallet crashed the app.
The Settings restore gate was already tightened to == 24 (
|
||
|
|
5b6ba5094b |
docs(lite): clarify SDXL viewing-key HRP differs from the full node
Audit of the lite import path found no false-rejection defect (no client-side gate; the two-command fallback in importKey makes the U/5/K/L prefix guess non-binding; lite send reuses the now-P2SH-fixed send_tab helpers). But the "zxview" viewing-key comment — which was WRONG in the full node (fixed earlier) — is genuinely CORRECT here: SDXL's import takes an extended full viewing key (zxviews…, hrp_sapling_viewing_key), whereas the full node's z_importviewingkey takes an incoming viewing key (zivks…). The two are not interchangeable. Add a note so nobody "harmonizes" the two gates and reintroduces the full-node bug. Comment-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c3e81a5fa6 |
fix(send): accept P2SH/multisig recipients in the send + URI address gates
Same defect class as the import-key fix: a hardcoded prefix/length pre-filter
layered over the checksum validators rejected valid addresses before the daemon
saw them. The send-screen recipient gate required a[0]=='R', and the payment-URI
parser accepted only 'R'/'t' with rigid length bands — so every valid P2SH /
multisig address (DragonX SCRIPT_ADDRESS=85 → 'b…') was silently refused, leaving
the Send button disabled with no usable recipient.
Centralize recipient recognition in util/address_validation:
- isTransparentAddress: Base58Check with a 21-byte version+hash160 payload —
covers P2PKH ('R…', v60) AND P2SH ('b…', v85) on every network, rejects WIF
keys / typos by real checksum.
- isShieldedAddress: Bech32 + a Sapling payment-address HRP (zs / ztestsapling /
zregtestsapling), distinguishing a payment address from a viewing key.
- isValidRecipientAddress: either of the above.
send_tab's two validity helpers (the single choke point for all 5 call sites) and
the payment-URI format check now route through these. The URI parser now
checksum-validates the recipient (fail-fast on transcription errors) rather than
being prefix/length-only.
Tests use real checksummed vectors (P2PKH/P2SH/shielded, WIF- and typo-rejection);
testPaymentUri updated off its old fake fixed-char addresses. Suite green (1/1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
d603a54618 |
fix(import): recognize real DragonX key formats in the import gate
The client-side pre-check rejected legitimate keys before the daemon ever
saw them, surfacing "Unrecognized key format" / a cryptic daemon "Invalid"
error. Two concrete defects plus the brittle heuristic behind them:
- Viewing keys: isViewingKey looked for Zcash's "zxview" extended-FVK
prefix, but DragonX's z_exportviewingkey emits a Sapling *incoming*
viewing key (HRP "zivks"), which z_importviewingkey is the only form the
daemon decodes. Every real DragonX viewing key was refused. (F1)
- Uncompressed transparent WIF: the length+first-char heuristic accepted
{5,K,L,U} only, but a version-188 uncompressed key starts with '7'. (F2)
Replace the heuristic with structural validation using the existing
checksum validators (F3): add util::decodeBase58Check (checksum-stripped
payload) and util::bech32Hrp (HRP of a valid Bech32 string). Transparent
keys are now accepted by decoding Base58Check and checking the payload is a
33/34-byte secret key with a DragonX SECRET_KEY version byte (188 main/
regtest, 128 testnet) — covering compressed and uncompressed, rejecting
addresses/typos by real checksum. Viewing keys are matched by the real
incoming-VK HRPs (zivks / zivktestsapling / zivkregtestsapling).
The Sweep gate and the dialog's live type indicator run off the same
predicates, so they are fixed too (F4). Messaging now names the likely
cause and appends a wrong-coin/network hint to the daemon's raw "Invalid"
error (F5).
Adds testPrivateKeyImportRecognition plus decodeBase58Check/bech32Hrp
coverage; suite green (1/1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c61c211dfe |
fix(ui): add vertical padding above and below the recent-alerts popup content
The alert-history popup content sat flush against the popup's top and bottom edges. Add a padY spacer above the header and below the content (on both the empty and populated paths). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
16244d84a0 |
chore(release): bump version to 2.0.1
Full-node ObsidianDragon 2.0.0 -> 2.0.1 (single source of truth: the project() VERSION in CMakeLists.txt). Verified the generated header renders "2.0.1 (ObsidianDragon)". The Lite variant is versioned independently (DRAGONX_LITE_VERSION, unchanged at 1.0.0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
32be868dbc |
feat(migrate): persist the sweep opid so a mid-sweep interruption can resume (W3-3)
Migrate-to-seed submits z_mergetoaddress -> an async opid, then only persists the resolved txid once the op completes. An app-close during Sweeping (opid submitted, txid not yet resolved) dropped the opid and resumed at the re-sweep gate, silently losing the tx. Now the opid is persisted and re-tracked on resume. If the daemon forgot it (restart — its op queue is in-memory only), the existing poller flags it stale and the callback falls back to the dismissable Sweep gate; it can never hang (a thrown RPC aborts the poll, so a stale classification only comes from a *successful* poll that omits the opid). - New seed_migration_sweep_opid setting; adopted atomically with clearing any prior txid in the SAME settings.save(), and only once the submit succeeds — so a failed "Sweep remaining" re-sweep keeps the already-mined first sweep's Confirming context, and txid/opid are never both authoritative (resume checks txid first; torn-write safe). - Resume routing extracted to a pure, unit-tested helper (data/seed_migration_resume.h::decideSeedMigrationResume): txid -> Confirming; opid AND connected -> re-track (Sweeping); else -> the dismissable Sweep gate. The connectivity gate keeps a disconnected resume out of the buttonless Sweeping spinner. - Shared makeSweepCompletionCallback(resumed): success -> Confirming; resumed-stale -> Sweep gate (re-fetch balance + "may have already completed" copy); fresh-fail -> Error. Fund safety unchanged: adopt still gated on legacy balance ~0 AND sweep tx mined; legacy wallet.dat only ever moved to a never-deleted timestamped .bak. Reviewed in two adversarial rounds (design + implementation) per the migration-code mandate; both safety facts (no fund loss, no hang) held, and the resume-UX traps they surfaced are fixed. Build-clean; ctest 1/1 (adds testSeedMigrationResume). See docs/wallet-hardening.md. *** Still requires a live mainnet interrupted-sweep run before release (human gate). *** Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8bb3198562 |
fix(diagnostics): address adversarial review of the QoL UI (popup, staleness, DPI, i18n)
Follow-up to the node-banner / staleness-badge / alert-history features — a 5-dimension finder->verify review surfaced 4 real issues (the ImGui-stack-balance finder found none): - Alert popup grew off the right edge: pivot (0,1) pinned the panel's LEFT edge at the bell, which sits near the window's right edge, so a 320px panel overflowed rightward (an explicit SetNextWindowPos pivot skips ImGui's on-screen clamp). Anchor the bottom-RIGHT corner at the bell instead (pivot (1,1) at bellMax.x) so it grows left. - Staleness badge could flash red on reconnect: WalletState::clear() reset everything except the four last_*_update stamps, so the pre-outage timestamp survived and the badge briefly showed "Updated Nm ago" the same frame the node banner cleared. Zero the stamps in clear() (all readers treat 0 as "never"; app_network.cpp:1473 guards != 0). - Banner min-height floor wasn't DPI-scaled: std::max(minH, baseH*vScale()) now uses minH * dpiScale() so both operands are in scaled px. - New i18n keys weren't in res/lang/: back-filled all 16 diagnostics/QoL keys into the 8 language files, additively (128 insertions, 0 deletions). zh/ja/ko reworded around 2 glyphs missing from the CJK subset and hard-asserted tofu-free against the subset font. Build-clean both variants; ctest 1/1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4b3f0fa92b |
feat(diagnostics): persistent alert history with a status-bar bell (Foundation QoL)
Toasts fade in 1-4s, so anything that scrolled past was gone. Notifications now retains every pushed alert in a capped (100) ring buffer with a wall-clock epoch (AlertRecord) — separate from the 5-item live-toast deque — plus a monotonic total_pushed_ counter. A bell in the status-bar right cluster opens an upward popup listing recent alerts newest-first: severity icon + colour (reusing the toast palette), the message, and a relative age (formatTimeAgoShort), with a Clear-all action. An unread dot on the bell, coloured by the most-severe unseen alert, marks alerts that arrived since the panel was last opened — driven by totalPushed() deltas so it survives capping/clearing. Thread note: every push is on the UI thread (RPC results run as main-thread MainCb callbacks), matching this class's existing lock-free model; documented as a no-raw-worker-thread invariant. New i18n keys (alerts_*). Build-clean; ctest 1/1 (adds testNotificationHistory: retention, order, cap, monotonic counter, clear). Closes the QoL bundle and the Foundation tier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c7c3440a7b |
feat(diagnostics): refresh-staleness badge on the Total Balance card (W6-2)
When the wallet is connected but the balance has quietly stopped refreshing — a busy
daemon can fail z_gettotalbalance without dropping the whole connection (only *both*
core RPCs failing 3x triggers a disconnect) — the old number sits on screen while the
node-status banner stays hidden. The Total Balance card now shows a small pill on its
status line ("Updated 2m ago", amber, escalating to red past 3 min) so the stale value
isn't silently trusted; hovering explains it and points at the node connection.
No refresh-path changes: WalletState::last_balance_update is already stamped only on a
successful fetch (network_refresh_service.cpp), so the badge reads it and computes age
against the same std::time clock via util::formatTimeAgoShort. The decision is a pure,
unit-tested helper (ui/staleness_badge.h::evaluateStalenessBadge, 45s/180s thresholds)
gated on connected so it never contradicts the banner.
Closes P2 (5/5). Build-clean; ctest 1/1 (adds testStalenessBadge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
e779ded2e8 |
feat(diagnostics): persistent node/RPC error banner at top of content (Foundation QoL)
A persistent horizontal strip now appears at the top of the content column whenever the
wallet can't reach its node — unlike the transient toasts it stays up for as long as the
fault persists, so an offline wallet is never silently mistaken for a working one.
The show/severity/action decision is a pure, unit-tested function
(ui/node_status_banner.h::evaluateNodeStatusBanner) fed a state snapshot by the new
App::renderNodeStatusBanner(). Three cases:
- full-node offline -> amber, "Reconnect" (App::tryConnect)
- embedded daemon crashed
& auto-restart gave up -> red, "Restart node" (App::restartDaemon)
- lite wallet open failed -> red, message-only
Suppressed during the wizard / wallet-switch / daemon-restart / screenshot-sweep / shutdown,
and while an expected startup phase (warmup / init / connect-in-progress) already owns the
screen. Banner height lives in res/themes/ui.toml (banners.node-status); colours come from the
material semantic palette; the detail text is ellipsis-clipped so it can't push the action
button off-screen. Drawn before the content edge-fade vertex capture so it stays fully opaque.
New i18n keys (node_banner_*). Build-clean both variants; ctest 1/1 (adds testNodeStatusBanner).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
940dd21464 |
feat(diagnostics): add "Copy diagnostics" + "Open log folder" actions (Foundation QoL)
Settings (logging section) gains two support-friendly actions, now that the logging foundation actually produces logs (W7-2): - Open log folder: opens the config dir (Platform::openFolder) so users can find dragonx-debug.log / dragonx-crash.log. - Copy diagnostics: copies a plaintext support snapshot to the clipboard via the new App::buildDiagnosticsReport() — version, build variant, platform, connection status, active wallet path + existence + size, encryption/lock state, sync heights, and (full- node) daemon status/running/crash-count/lastError, plus the log paths. No secrets. Build-clean; ctest 1/1. Remaining QoL: persistent alert history, a daemon/RPC error banner, and the W6-2 refresh-staleness badge. See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
207f9074db |
feat(diagnostics): make the logging + crash infrastructure actually work (W7-2, W7-3, W7-4)
The Foundation tier — answers the original "make it easier to diagnose problems" ask. - W7-2 (keystone): the app-level Logger file sink was never initialized, so LOG/LOGF/ VERBOSE_LOGF went nowhere and dragonx-debug.log didn't exist on Linux/macOS at all. main() now calls Logger::init(<config>/dragonx-debug.log) on every platform. Fixed a latent deadlock this exposed: init() wrote its banner via write(), which re-locks the non-recursive mutex_ it already holds — now written directly. On Windows the raw stdout/stderr freopen moved to a separate dragonx-stdout.log so the two writers don't contend on one file. Added testLoggerFileSink (also a deadlock guard — it would hang if the fix regressed). - W7-3: no crash handler existed on Linux/macOS. Added an async-signal-safe sigaction handler (SIGSEGV/ABRT/BUS/FPE/ILL) that writes the signal id + a backtrace_symbols_fd backtrace to dragonx-crash.log, then re-raises the default disposition for a core dump — the POSIX counterpart of the Windows SEH filter. - W7-4: Logger::init now rotates the log to a single .1 backup past 10 MB, so a long or verbose session can't grow it unbounded. Build-clean; ctest 1/1. Remaining Foundation: the QoL bundle (mostly UI). See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
05b00b158b |
fix(wallet): surface silent save failures + stale-state cleanups (W5-1, W5-2, W6-1, W6-3)
P2 robustness batch (localized): - W5-1 (Med, lite): persistAfterBroadcast returned false on a persistent post-send/shield save failure, but both callers discarded it and it never logged — completely silent. It now liteLogs the failure (the spent note re-derives on the next sync, so it's a robustness gap, not fund loss). - W5-2 (Med, lite): the post-sync and post-rescan save results (in the detached scan threads) were ignored; both now liteLog on failure. LiteDiagnostics::log is mutex-guarded, so it's safe from those threads. - W6-1 (Med): WalletState::clear() didn't reset mining/pool_mining, so a wallet switch could briefly show the previous wallet's hashrate/blocks. Now reset in clear() (the daemon restarts on switch, so mining genuinely stops). - W6-3 (Low): AddressBook::load() cleared entries_ then threw on the first non-object array element — discarding EVERY contact. It now guards is_object() + per-entry try/catch, skipping and counting malformed entries. Build-clean; ctest 1/1. Remaining P2: W6-2 (refresh-staleness badge — needs UI, overlaps the diagnostics Foundation bundle). See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f9ddab059e |
fix(wallet): stamp syncedHere only after identity verified + guard the startup wallet file (W1-3)
- W1-3 (Med): updateWalletIndexForActiveWallet stamped syncedHere in the markOpened block at bare connect (idHash still empty), letting a freshly-restored wallet skip its needed rescan. syncedHere is now stamped only once the wallet's identity is verified (idHash non-empty), so it takes effect at the post-address-refresh index update; lastOpenedEpoch still records at open. - Startup guard (the W1-1 launch counterpart): App::init now exists()-checks the recorded active wallet before the daemon is configured. A non-default active wallet moved/deleted between sessions falls back to the default wallet.dat with a warning, instead of the daemon silently auto-creating an empty wallet under the missing name. Runs before the PIN vault init so the vault is scoped to the wallet actually opened. Completes P1-B. Remaining P1: W3-3 (sweep opid persistence) deferred for careful adversarially-reviewed work — re-tracking a stale opid could hang the migration if the op poller doesn't time out; the existing balance/mined gates already prevent fund loss. See docs/wallet-hardening.md. Build-clean; ctest 1/1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
de1ae736de |
fix(wallet): guard against opening a missing/wrong wallet file (W1-1, W1-2, W1-4)
- W1-1 (High): switchToWallet never verified the target wallet file exists before switching. dragonxd auto-creates a fresh empty wallet for a missing -wallet=<name>, so a moved/deleted wallet file silently "opened" as a brand-new empty wallet with a zero balance — looking exactly like fund loss. It now std::filesystem::exists-checks datadir/<walletFile> before switching (ahead of the daemon-stop prompt) and blocks with a "not found (moved or deleted?)" warning. Because the check runs regardless of how switchToWallet is invoked, it also closes W1-4 (the stale switcher-row TOCTOU). - W1-2 (Med): walletOutputLooksCorrupt matched the generic "Error loading wallet" string, which dragonxd also prints for DB_TOO_NEW (a newer-version wallet) — so a version mismatch was offered a -salvagewallet repair that cannot fix it. The generic match is now excluded when the output also contains "newer version". Build-clean; ctest 1/1. Remaining P1-B: W1-3 (syncedHere timing) + the startup-path existence check. See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
03c1b63e03 |
fix(migrate): correct fund-adjacent migrate-to-seed bugs (W3-1, W3-2, W3-4)
Migrate-to-seed (legacy -> mnemonic wallet) moves real funds; three correctness fixes: - W3-1 (High): beginAdoptSeedWallet swapped a hardcoded datadir/wallet.dat instead of the ACTIVE wallet file. With a non-default active wallet (e.g. wallet-2.dat) it installed the swept seed wallet into an unloaded wallet.dat and left the daemon reloading the emptied legacy wallet — swept funds only recoverable via the seed phrase. Now swaps datadir + "/" + getActiveWalletFile(), captured on the main thread (switching is blocked during migration, so no race). - W3-2 (High): SeedWalletCreator::create() ran remove_all(<config>/seed-migrate) unconditionally at the start, so a prior migration that swept funds into the temp wallet but was abandoned/crashed before adopting would have that fund-bearing wallet destroyed. It now refuses (with a clear message) when DRAGONX/wallet.dat already exists — a completed migration removes the dir on adopt, so a leftover means an unfinished one. - W3-4 (Med): switchToWallet blocked switching only while the migration dialog was open; closing it via "Later" mid-migration dropped the guard. Now also blocks while getSeedMigrationPending(). Build-clean; ctest 1/1. Remaining P1-A: W3-3 (persist the sweep opid). See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8c12b27c0a |
fix(security): don't silently leave a wallet unencrypted or unlocked (W2-2, W2-4)
P0-B encryption-integrity cluster.
W2-2: the first-run wizard's "encrypt" stored the passphrase only in memory and let the
user into the app immediately, so a quit/crash or a failed daemon connect before the
deferred encryption applied left the wallet unencrypted with NO record encryption was
ever requested — the user believing it was encrypted. A persisted encryption_pending
settings flag is now set the moment encryption is requested (never the passphrase, only
the fact). refreshWalletEncryptionState() reconciles it on every connect: wallet observed
encrypted -> clear the flag; wallet NOT encrypted while the flag is set and no deferred
encryption is pending/in-flight -> a once-per-session "your wallet is NOT encrypted — open
Settings to finish" warning (the flag stays set, so it recurs each launch until resolved).
The passphrase is deliberately never persisted to auto-complete — surfacing it is the
secure choice.
W2-4: lockWallet()'s continuation only handled success — a failed walletlock RPC silently
left the wallet UNLOCKED (an unfulfilled auto-lock). It now logs and warns once (reset on
the next successful lock) so a failing auto-lock is visible instead of leaving the wallet
exposed.
Touches settings.{h,cpp}, app_wizard.cpp, app_security.cpp, app.h. Not unit-testable at
this layer (RPC/connect-driven state). Build-clean; ctest 1/1. See docs/wallet-hardening.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
c7d163f44a |
feat(lite): wire the create-time passphrase into the lite encrypt/unlock flow (W5-3)
The lite create/open/restore requests carried a passphrase field that the UI collected (a labeled, masked "passphrase" input) but the backend initialize* calls never used — so a user could believe their lite wallet was passphrase-protected at creation when it did nothing. It now has a real meaning, wired in LiteWalletController: - create / restore -> encryptWallet(passphrase): the backend encrypts + locks + saves the brand-new wallet. - open -> unlockWallet(passphrase), but only when encryptionStatus() reports the existing wallet is actually encrypted + locked (no spurious unlock on an unencrypted wallet). encryptWallet/unlockWallet take their own copy of the passphrase and wipe it; the request copy is still wiped as before. A post-create encrypt failure is liteLog'd (the wallet still exists, so the create is not failed). Six existing lite-controller tests carried an incidental "hunter2" create passphrase from when the field was dead; removed (they exercise non-encryption flows and want an unencrypted wallet), and added testLiteWalletControllerCreateEncryptsWithPassphrase to prove the new behavior. Completes the wallet-hardening P0-A cluster (7/7). ctest 1/1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7e4822c021 |
fix(security): warn that the seed-backup file is unencrypted plaintext (W4-5)
The seed-phrase "Save" already wrote the file 0600 and zeroed the in-memory buffer, but the success message was a bare "Saved to <path>" — no hint that it's a permanent UNENCRYPTED copy of the seed at a predictable location. The message now reads "Saved an UNENCRYPTED seed file — move it to secure offline storage and delete this copy: <path>". English source updated; the res/lang back-fill of this changed key is deferred to the batch i18n pass. Also documents W5-3 (lite create-time passphrase) as a product decision rather than a speculative change: the field is already wiped on every path (minimal security risk), but the labeled masked "passphrase" input at lite create/open/restore is never consumed by the backend — so either remove the dead UI or wire it into the lite encrypt flow. Finishes the actionable part of the wallet-hardening P0-A cluster (docs/wallet-hardening.md). Build-clean; ctest 1/1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |