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>
The exchange candle APIs return full OHLC but we only kept the close (a line).
Now the per-exchange chart draws real candlesticks; the CoinGecko aggregate stays
a line (it's close-only).
- Adapter keeps OHLC: parseExchangeOHLC() returns open/high/low/close candles
(parseExchangeCandles is now a close-only wrapper over it). New data/candle.h
holds the dependency-free Candle + bucketOHLC (5-min -> hourly for the 1D view).
- Model stores exchange_ohlc_intraday/daily alongside the close series;
refreshExchangeChart populates both. market_series::chartCandles() returns the
bucketed OHLC for the range, empty unless the per-exchange series is active.
- The chart renders wick (low..high) + body (open..close), green up / red down,
with a low..high y-range; the line-only bits (fill, hi/lo labels, hover tooltip)
are gated off for candles. Falls back to the line for the aggregate / Live view.
Verified: OHLC parsers + bucketOHLC + chartCandles unit-tested; a forced-state
render shows correct candlesticks; the aggregate still draws a clean line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The market chart was hardwired to CoinGecko's cross-exchange USD aggregate
(vs_currency=usd), so selecting a trading pair only changed the "Trade" link —
never the chart or price. Now the SELECTED exchange drives both.
CoinGecko gives us which venues list DRGX (the ticker `market.identifier`) but
not their APIs, so add data/exchange_candles.h: a hand-maintained map from that
identifier to each venue's public candle endpoint, with two adapters verified
against the live APIs — Ourbit (MEXC-style /api/v3/klines, array format) and
NonKYC (TradingView-UDF /market/candles, `bars`). Unmapped venues return no URL,
so the chart falls back to the CoinGecko aggregate and nothing regresses.
- App::refreshExchangeChart() resolves the selected pair, fetches its intraday
(5-min) + daily candles via the TLS-verified httpGetString on the worker, and
stores them on MarketInfo (exchange_chart_intraday/daily + exchange_chart_active).
Re-fetches on pair change; self-throttled to ~30 min otherwise; falls back to
aggregate on any failure.
- chartSeries() draws the per-exchange series when active (portfolio sparklines
stay on the aggregate). The hero shows the selected venue's real price
(converted_last) — Ourbit vs NonKYC differ ~7%. parseCoinGeckoTickers now
captures the identifier + per-exchange USD price (previously discarded).
Adapters unit-tested against real captured responses (URL builder + both parsers
+ the chartSeries switch) and validated against the full live feeds (Ourbit 20d,
NonKYC 370d, ascending, correct closes). Build + ctest + hygiene green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Portfolio-group sparklines previously resampled the ~24-minute in-session price
buffer, so the hour/day/week/month intervals could never fill. Fetch real
historical USD series from CoinGecko /market_chart and back each interval with
appropriately-grained data.
- MarketInfo gains two timestamped series: price_chart_intraday (days=1, 5-min)
and price_chart_daily (days=365, daily), plus a fetch timestamp.
- App::refreshMarketChart() fetches both on the RPC worker via the TLS-verifying
util::httpGetString helper, self-throttled to ~30 min (historical data moves
slowly). Gated by getFetchPrices(); triggered on connect and each price tick.
- Pure NetworkRefreshService::parseCoinGeckoMarketChart() parses {"prices":
[[ms,price],...]} into (unix-seconds, price); malformed rows skipped. Unit-tested.
- market_tab pfSparklineSeries() maps interval -> series+bucket: minute = live
buffer; hour = intraday bucketed to 1h; day/week/month = daily bucketed to
1d/7d/30d. Falls back to the in-session buffer until the fetch populates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fetch /coins/dragonx-2/tickers once per session (App::refreshExchanges, reusing the
price-fetch worker path) and build the exchange registry at runtime into
state.market.exchanges; parseCoinGeckoTickers groups tickers by venue. The Market tab
sources from this live list and falls back to the compiled-in registry (now including
OurBit + Nonkyc.io) when offline. Fixes the missing OurBit pair and auto-tracks future
listings.
- Fix stale selection defaults (TradeOgre / DRGX-BTC -> Nonkyc.io / DRGX/USDT) that never
matched the registry, and make the attribution generic ("Price data from CoinGecko").
- Reframe the single-asset portfolio card as a DRGX holdings dashboard: relabel to
"MY DRGX" and show the 24h change on the holdings value (colored by direction).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- app: periodic weighted-random auto-balance of the pool while pool-mining
(~30-min cadence; restarts the miner only when the pick actually changes),
plus snapshot + refresh accessors for the UI.
- app_network: resolve the xmrig algo per pool at start (pool.dragonx.cc =
rx/dragonx, pool.dragonx.is = rx/hush; custom hosts keep the setting).
- xmrig_manager: schema-aware pool-side hashrate readout (fixes a silent 0 for
Miningcore pools); expose the running miner's version from its HTTP API and a
cached `xmrig --version` detection so the UI can show it before mining.
- wallet_state: carry the running miner's version.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified by running the app against a live node and watching a real rescan. Three
issues that only surfaced at runtime:
- Wrong RPC name: this daemon (hush/komodo) exposes the runtime rescan as
"rescan <height>", not bitcoin's "rescanblockchain". runtimeRescan() and
RPCClient::rescanBlockchain() used the bitcoin name and failed with "Method not
found" on every node. Corrected to "rescan".
- Witness/rescan progress never surfaced during a rescan: the daemon-output parser
that drives it was gated behind rpcConnected, but a heavy rescan holds cs_main so
getinfo times out and the RPC reads disconnected — silencing the parser exactly
when it's needed. The parser reads the daemon's stdout pipe (no RPC), so it now
runs whenever the daemon process is alive. It also now parses INLINE on the main
thread instead of via fast_worker_, so it can't be starved when the worker is
blocked on a getrescaninfo call (which waits on cs_main during a witness rebuild).
- Witness rebuild has TWO sub-phases with different scales — the initial-witness
pass ("Setting Initial Sapling Witness for tx <hash>, <i> of <N>") and the cache
walk ("Building Witnesses for block <h> <frac> complete, <n> remaining"). Tracking
them with one monotonic value pinned the bar at the initial pass's ~100% through
the whole cache walk. They're now tracked as distinct phases (witness_phase) with
their own monotonic progress and labels ("Setting witnesses" vs "Rebuilding
witnesses"), so neither resets/bounces and the long phase shows real movement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The daemon's post-rescan witness rebuild ("Building Witnesses for block ...")
is a distinct, often-long phase that previously showed only as an indeterminate
"Rescanning..." with no progress. Parse the daemon's witness-build log lines and
surface a dedicated progress indicator.
- Parse "Building Witnesses for block <h> <frac> complete, <n> remaining" (and the
earlier "Setting Initial Sapling Witness for tx ..., <i> of <m>") from daemon
output, extracting a 0..1 fraction and remaining-block count.
- New SyncInfo fields building_witnesses / witness_progress / witness_remaining,
cleared at every rescan-completion site (warmup-end, getrescaninfo poll, runtime
rescan callback, daemon-log "finished").
- Status bar shows "Rebuilding witnesses NN%" (priority over the generic rescan
text); the loading overlay (shown during -rescan warmup) gets a labelled witness
progress bar with the remaining-block count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the full-node connect probe (getinfo) times out, the daemon is reachable
at the TCP level but busy initializing (loading the block index, verifying,
activating best chain, …) and won't answer RPC. The wallet only recognized the
JSON-RPC -28 warmup reply, so a raw socket timeout fell through to a bare,
alarming "Connection failed" retry with no indication of what the user was
waiting on.
Add a daemon-initializing UI state that drives the existing loading overlay:
- WalletState::daemon_initializing — daemon up/launching but not serving yet
(distinct from warming_up, which needs a -28 reply).
- App::applyDaemonInitStatus() infers the current phase from the daemon's own
console output (scanning recent lines for Loading/Verifying/Activating/
Rescanning/Rewinding/Pruning) and the latest block height, producing a
friendly title + description, e.g. "Processing blocks… (Block 123456)".
- The connect loop calls it from the daemon-starting and external-detected
branches: a timeout -> "reachable but initializing", a connect refusal ->
"launching, waiting to come online". Cleared on a real connect.
- The loading overlay now shows the description for daemon_initializing too,
and the status-bar amber indicator covers it (so Peers/Console tabs without
the overlay still explain the wait).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add expanded address icon picker with search, bottom-aligned actions, and improved modal sizing
- Embed a pickaxe icon font subset and wire it into typography/address icon rendering
- Track view-only shielded addresses and prevent sends from non-spendable z-addresses
- Improve address transfer dialog sizing, max amount handling, and text clipping
- Tune main header layout values in ui.toml
- Update README, codebase overview, and third-party license documentation
- Fix blk/s calculation that was inflated ~10x due to resetting the
time baseline every frame instead of only when blocks advanced
- Add decay when no new blocks arrive for 10s so rate doesn't stay stale
- Add 7 missing translation keys (timeout_off/1min/5min/15min/30min/1hour,
slider_off) to all 8 language files so settings dropdowns translate
- Show language names in native script (中文, Русский, 日本語, 한국어)
Instead of blocking the entire UI with "Activating best chain..." until
the daemon finishes warmup, treat warmup responses as a successful
connection. The wallet now:
- Sets connected=true + warming_up=true when daemon returns RPC -28
- Shows warmup status with block progress in the loading overlay
- Polls getinfo every few seconds to detect warmup completion
- Allows Console, Peers, Settings tabs during warmup
- Shows orange status indicator with warmup message in status bar
- Skips balance/tx/address refresh until warmup completes
- Triggers full data refresh once daemon is ready
Also: fix curl handle/header leak on reconnect, fill in empty
externalDetected error branch, bump version to v1.2.0 in build scripts.
Security (P0):
- Fix sidebar remaining interactive behind lock screen
- Extend auto-lock idle detection to include active widget interactions
- Distinguish missing PIN vault from wrong PIN; auto-switch to passphrase
Blocking UX (P1):
- Add 15s timeout for encryption state check to prevent indefinite loading
- Show restart reason in loading overlay after wallet encryption
- Add Force Quit button on shutdown screen after 10s
- Warn user if embedded daemon fails to start during wizard completion
Polish (P2):
- Use configured explorer URL in Receive tab instead of hardcoded URL
- Increase request memo buffer from 256 to 512 bytes to match Send tab
- Extend notification duration to 5s for critical operations (tx sent,
wallet encrypted, key import, backup, export)
- Add Reduce Motion accessibility setting (disables page fade + balance lerp)
- Show estimated remaining time during mining thread benchmark
- Add staleness indicator to market price data (warning after 5 min)
New i18n keys: incorrect_pin, incorrect_passphrase, pin_not_set,
restarting_after_encryption, force_quit, reduce_motion, tt_reduce_motion,
ago, wizard_daemon_start_failed
- Replace all hardcoded English strings with TR() translation keys across
every tab, dialog, and component (~20 UI files)
- Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with
complete translations (~37k lines added)
- Improve i18n loader with exe-relative path fallback and English base
fallback for missing keys
- Add pool-side hashrate polling via pool stats API in xmrig_manager
- Introduce Layout::beginFrame() per-frame caching and refresh balance
layout config only on schema generation change
- Offload daemon output parsing to worker thread
- Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
Windows identity:
- Add VERSIONINFO resource (.rc) with ObsidianDragon file description
- Embed application manifest for DPI awareness and shell identity
- Patch libwinpthread/libpthread to remove competing VERSIONINFO
- Set AppUserModelID and HWND property store to override Task Manager cache
- Link patched pthread libs to eliminate "POSIX WinThreads" description
Address creation (+New button):
- Move z_getnewaddress/getnewaddress off UI thread to async worker
- Inject new address into state immediately for instant UI selection
- Trigger background refresh for balance updates
Mining tab:
- Add pool mining dropdown with saved URLs/workers and bookmarks
- Add solo mining log panel from daemon output with chart/log toggle
- Fix toggle button cursor (render after InputTextMultiline)
- Auto-restart miner on pool config change
- Migrate default pool URL to include stratum port
Transactions:
- Sort pending (0-conf) transactions to top of history
- Fall back to timereceived when timestamp is missing
Shutdown:
- Replace blocking sleep_for calls with 100ms polling loops
- Check shutting_down_ flag throughout daemon restart/bootstrap flows
- Reduce daemon stop timeout from 30s to 10s
Other:
- Fix market chart fill artifact (single concave polygon vs per-segment quads)
- Add bootstrap checksum verification state display
- Rename daemon client identifier to ObsidianDragon
- Fix z_importwallet to use full path instead of filename only
- Add rescanBlockchain() method that restarts daemon with -rescan flag
- Track rescan progress via daemon output parsing and getrescaninfo RPC
- Display rescan progress in status bar with animated indicator when starting
- Improve dark theme card contrast: lighter surface-variant, tinted borders, stronger rim-light
Full-node GUI wallet for DragonX cryptocurrency.
Built with Dear ImGui, SDL3, and OpenGL3/DX11.
Features:
- Send/receive shielded and transparent transactions
- Autoshield with merged transaction display
- Built-in CPU mining (xmrig)
- Peer management and network monitoring
- Wallet encryption with PIN lock
- QR code generation for receive addresses
- Transaction history with pagination
- Console for direct RPC commands
- Cross-platform (Linux, Windows)