ui.toml and the default banlist were embedded by a CMake function that
read each file and emitted a C array header one byte at a time in a
configure-time loop. That is slow to configure, and because the result
is a header, editing either resource recompiled every translation unit
that included it.
- Both files are now embedded with INCBIN from a small generated
translation unit, the same pattern already used for the fonts. There
is no generated header, so editing a resource re-assembles one tiny
object file instead of triggering a wide recompile, and configure does
no per-byte work.
- Consumers include a hand-written header declaring the symbols and
always use the embedded data (the __has_include fallback is gone). The
data is not NUL-terminated, so every consumer passes an explicit size.
- The old EmbedResources.cmake script is removed, and the generated unit
is added to the app and test builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two secure-delete paths overwrote a sensitive file and then unlinked it,
but neither reliably forced the overwrite to stable storage. The
plaintext key export used a buffered stream with only a flush, and the
PIN vault removal opened a second descriptor to fsync only on non-
Windows. On a crash, or on Windows, the zeroing write could stay in the
OS cache and the original bytes could survive on disk.
Both now go through one shared best-effort secure-delete helper:
- It overwrites the file in place in chunks, never truncating first, so
the original blocks are the ones rewritten.
- It flushes the data to the device before unlinking (full fsync on
macOS, write-through on Windows), then removes the file.
- It is safe on a missing file and reports whether the file is gone.
This is still best-effort: SSD wear-levelling and copy-on-write or
journaling filesystems may retain copies.
Tests exercise the overwrite-and-remove helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On Linux and macOS openUrl/openFolder spawned xdg-open/open and waited
for it on the UI thread: 50-300 ms per click where the opener forks and
exits, and on generic xdg-open sessions (sway, i3, WSLg, ...), which run
the browser or file manager as xdg-open's foreground child, the window
froze until the user closed that app. The opener is now left running
and a detached thread reaps it; the click returns at once.
Its stdin, stdout and stderr go to /dev/null again. That redirect was
lost in the switch to posix_spawnp, so a text-mode browser fallback
could take over the wallet's terminal.
The result now means "launched" rather than "exited 0" (no caller reads
it), and a non-zero opener exit is reported on stderr instead of in the
debug log. Windows (ShellExecute, which never waited) is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows writeFileAtomically wrote the temp file through std::ofstream,
whose CRT handle is inheritable. xmrig and the hidden helper commands
(where, nvidia-smi, xmrig --version) are started with handle inheritance
on, so one started during a save kept "<file>.tmp" open without delete
sharing, and every later replace of that file failed for as long as the
child ran: hours while mining, with settings saves quietly not landing.
The flush then reopened the file with share mode 0, so it was silently
skipped whenever AV or the search indexer had the file open, and a
replace refused by them just failed.
- Windows: one CreateFileA handle, not inheritable, writes and flushes
the temp file; the replace is retried up to 4 times (20+40+80+160 ms)
on access-denied, sharing or lock violations, where it used to fail.
- Linux/macOS: the temp file and directory fds are opened O_CLOEXEC, so
a dragonxd or xmrig fork+exec doesn't inherit them.
A later commit moves settings writes to a background thread, which makes
every one of them concurrent with those spawns. The vault, the exports
and DRAGONX.conf use the same function and get the same fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_popen/_popen-style shell-outs flash a cmd.exe console window on Windows. Add
Platform::runHiddenCapture() — CreateProcess + CREATE_NO_WINDOW capturing stdout on
Windows, popen on POSIX — and route the remaining shell-outs through it:
- GPU-aware idle detection (getGpuUtilization: "where nvidia-smi" / "nvidia-smi --query-gpu")
- xmrig discovery + version (findXmrigBinary "where xmrig.exe"; "<bin> --version", stderr merged)
- wallet-rebuild helper (app_network) — keeps its exit-code check via the new exitCode out-param
None of these are on the launch path (that was the daemon spawn, fixed in a2f84be); each
would flash a console only when it ran (idle-GPU mining, mining tab, wallet recovery).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three verified daemon-startup edge-case fixes centered on the config/params
filesystem path:
- F7: new non-throwing Platform::ensureDirectory(dir, outError) with one
consistent "Cannot create <dir>: <reason>. Check permissions / free space."
message. Replaces the unchecked/throwing create_directories sites at main.cpp
(pre-init: log + Windows MessageBox + return 1), connection.cpp's
autoDetectConfig (was the *throwing* overload -- could raise an uncaught
filesystem_error through its callers; now sets the new
ConnectionConfig::dir_error), and both app.cpp daemon-dir sites (surface via
daemon_status_ + return false). The primary connect path (app_network.cpp)
checks dir_error and shows it instead of mislabelling it "waiting for config".
embedded_resources.cpp already checked its error_code, so it is left as-is.
- F6: verifySaplingParams() now hash-verifies each param against its pinned
canonical SHA-256 (source of truth: scripts/build-lite-backend-artifact.sh)
instead of only checking existence, so a truncated / corrupt-but-present param
is rejected up front rather than failing later on a shielded operation. A
<params_dir>/.sapling_verified marker keyed on size:mtime avoids re-hashing
~48MB on every startup. Logic extracted to the injectable, unit-testable
verifySaplingParamsIn(dir, digests); reuses util::sha256Hex (no new hash impl).
- F5: startEmbeddedDaemon() now checks extractEmbeddedResources()'s return and
the previously-dropped copy_file error_code in the daemon-binary fallback loop,
aborting with a clear status (sb_daemon_extract_failed / sb_daemon_files_failed)
instead of failing opaquely at spawn. An absent source file stays non-fatal.
Adds testPlatformEnsureDirectory and testVerifySaplingParams to test_phase4.cpp.
i18n keys added to i18n.cpp (English source of truth); the res/lang/*.json
back-fill via add_missing_translations.py is deferred to a single run at the end
of the batch. Progress tracked in docs/daemon-startup-hardening.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Settings::getDefaultPath and AddressBook::getDefaultPath each hand-rolled the
same per-platform #ifdef (Win SHGetFolderPath / macOS Application Support /
XDG ~/.config) with the DRAGONX_APP_NAME variant suffix. Route both through
util::Platform::getConfigDir() instead — one place owns the split — and add
the missing __APPLE__ branch to getConfigDir so it too returns the
macOS-native ~/Library/Application Support/<AppName>.
This also fixes a latent macOS inconsistency: transaction_history_cache
already resolves via getConfigDir(), which lacked the __APPLE__ branch, so on
macOS the tx cache landed in ~/.config while settings/addressbook went to
~/Library/Application Support. Now all three agree. No migration needed:
Linux/Windows paths are unchanged, and macOS packaging is not yet shipped.
Removes ~50 lines of duplicated #ifdef.
Full-node + Lite build clean; ctest 1/1; hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lite SilentDragonXLite backend stores its wallet in its own directory
(dirs::data_dir()/silentdragonxlite — %APPDATA%\silentdragonxlite on Windows,
~/.silentdragonxlite on Linux, ~/Library/Application Support/silentdragonxlite on
macOS), NOT the full-node getDragonXDataDir() (…/Hush/DRAGONX). The newly added
lite "Open data folder" button opened the wrong (full-node) directory.
Add Platform::getLiteWalletDataDir() mirroring the backend's get_zcash_data_path
for the "main" chain, and point the lite button at it. The full-node button is
unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both variants hardcoded "ObsidianDragon" as the per-user config folder
(settings.json, themes, the lite_rollout cache), so the lite app and the
full-node app shared one settings.json. That cross-variant pollution can leave
the lite server selection in a bad state — and since openWallet() contacts the
selected lightwalletd server, a wrong/empty server URL there makes an existing
wallet fail to open (a silent "disconnected" spinner).
Use DRAGONX_APP_NAME (already "ObsidianDragon" / "ObsidianDragonLite" per variant)
for the config-dir name in Settings::getDefaultPath, Platform::getConfigDir and
getObsidianDragonDir (and the theme-setup exe-name probe). Full-node is unchanged;
lite now reads/writes %APPDATA%\ObsidianDragonLite (and ~/.config/ObsidianDragonLite),
so it starts from a clean, isolated config and uses default servers.
Note: the lite wallet file itself lives in the litelib backend's own data dir
(unaffected); this isolates the GUI config only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
settings.json and addressbook.json were written in place with a bare ofstream —
a crash or power loss mid-write truncated the file, and on the next launch the
parse failure silently reset every preference (hidden/favorite addresses, labels,
pool workers, language, theme, lite-server list) because the next save overwrote
the corrupt file with defaults.
Add Platform::writeFileAtomically() (temp file -> fsync -> atomic rename; dir
fsync on POSIX, MoveFileEx on Windows; optional owner-only 0600) and route both
saves through it. On a parse failure, quarantine the unreadable settings file to
settings.json.corrupt-<ts> instead of clobbering it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add pool mining thread benchmark: cycles through thread counts with
20s warmup + 10s measurement to find optimal setting for CPU
- Add GPU-aware idle detection: GPU utilization >= 10% (video, games)
treats system as active; toggle in mining tab header (default: on)
Supports AMD sysfs, NVIDIA nvidia-smi, Intel freq ratio; -1 on macOS
- Fix idle thread scaling: use getRequestedThreads() for immediate
thread count instead of xmrig API threads_active which lags on restart
- Apply active thread count on initial mining start when user is active
- Skip idle mining adjustments while benchmark is running
- Disable thread grid drag-to-select during benchmark
- Add idle_gpu_aware setting with JSON persistence (default: true)
- Add 7 i18n English strings for benchmark and GPU-aware tooltips
Build & setup:
- Fix setup.sh and build.sh for macOS (bundle daemon, xmrig, sapling params, asmap.dat into .app)
- Fix CMakeLists.txt libsodium linking for macOS
- Fix incbin.h to use __DATA,__const section on macOS
- Remove vendored libsodium-1.0.18 source tree (use fetch script instead)
- Remove prebuilt-binaries/xmrig (replaced by xmrig-hac)
- Add .DS_Store to .gitignore
Rendering & UI:
- Use GLSL #version 150 and OpenGL 3.2 Core Profile on macOS
- Force dpiScale=1.0 on macOS to fix Retina double-scaling
- Set default window/UI opacity to 100% on Mac/Linux
- Add scroll fade shader guard for macOS GL compatibility
- Add ImGui error recovery around render loop and mining tab
Daemon & bootstrap:
- Fix getDragonXDataDir() to return ~/Library/Application Support/Hush/DRAGONX/ on macOS
- Fix isPortInUse() with connect() fallback (no /proc/net/tcp on macOS)
- Increase daemon watchdog timeout from 3s to 15s
- Add daemon status indicator (colored dot + label) in wizard bootstrap phases
Mining tab:
- Fix EmbeddedDaemon::getMemoryUsageMB() crash on macOS (was using Linux /proc)
- Fix XmrigManager::getMemoryUsageMB() to use ps on macOS instead of /proc
- Restructure RenderMiningTab with wrapper pattern for exception safety
- Fix default pool URL to include port (pool.dragonx.is:3433)
Mine-when-idle:
- Auto-start/stop mining based on system idle time detection
- Platform::getSystemIdleSeconds() via XScreenSaver (Linux) / GetLastInputInfo (Win)
- Settings: mine_when_idle toggle + configurable delay (30s–10m)
- Settings page UI with checkbox and delay combo
Console tab:
- Shell-like argument parsing with quote and JSON bracket support
- Pass JSON objects/arrays directly as RPC params
- Fix selection indices when lines are evicted from buffer
Connection & status bar:
- Reduce RPC connect timeout to 1s for localhost fast-fail
- Fast retry timer on daemon startup and external daemon detection
- Show pool mining hashrate in status bar; sidebar badge reflects pool state
UI polish:
- Add logo to About card in settings; expose logo dimensions on App
- Header title offset-y support; adjust content-area margins
- Fix banned peers row cursor position (rawRowPosB.x)
Branding:
- Update copyright to "DragonX Developers" in RC and About section
- Replace logo/icon assets with updated versions
Misc:
- setup.sh: checkout dragonx branch before pulling
- Remove stale prebuilt-binaries/xmrig/.gitkeep
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)