Commit Graph
93 Commits
Author SHA1 Message Date
DanSandClaude Opus 4.8 59b8c4da81 docs(lite): record end-of-session implementation status
Summarize the 2026-06-05 session in the v2 plan: M1–M5a + encryption complete,
GUI wired with lite wording, ~3.2k lines cleanup, Linux+Windows packaging
verified, both variants build clean, runtime-verified on Linux. Notes the
remaining M5b infra (macOS/CI/signing/rollout) and the push plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:32:50 -05:00
DanSandClaude Opus 4.8 950d7ace50 feat(lite): startup unlock prompt + real-backend encryption verification
Startup lock screen (soft): once the first refresh reveals the auto-opened wallet
is encrypted+locked, show the unlock modal on launch (reusing renderLiteUnlockPrompt,
one-shot per session). Soft by design — balances stay viewable via viewing keys
while locked, so the user may dismiss and browse read-only; only spending needs
the passphrase.

Real-backend verification: add `lite_smoke --encrypt` (create -> encryptionstatus
-> encrypt -> lock -> unlock, checking flags; passphrase never printed). Running it
against the real SDXL backend showed encrypt LOCKS immediately
(after encrypt: encrypted=1, locked=1) — the backend removes spending keys right
after encrypting. The controller already relays encryptionstatus faithfully (UI is
state-driven, so unaffected), but the fake modeled encrypt->unlocked; corrected the
fake (encrypt -> encrypted+locked) and the test sequence (encrypt -> unlock -> lock
-> decrypt) to match real behavior.

Builds clean, tests pass, hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 18:53:35 -05:00
DanSandClaude Opus 4.8 d52d3d1b7f feat(lite): send-time unlock prompt for locked encrypted wallets
When the user confirms a send on a locked encrypted lite wallet, show an unlock
modal (passphrase -> unlockWallet) instead of letting the backend reject it with
"Wallet is locked". After unlocking, the user re-confirms the send (the form is
preserved). Balances remain viewable while locked; only spending needs unlock.

- send_tab: the Confirm-and-send button routes to App::requestLiteUnlock() when
  getWalletState().isLocked(), else sends as before.
- App::renderLiteUnlockPrompt(): centered modal, passphrase (Enter submits),
  Unlock/Cancel; the passphrase buffer is sodium-zeroed after every path.

Full-node unaffected (gated on liteWallet()/isLocked()). Builds clean, launches
clean, tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 18:22:15 -05:00
DanSandClaude Opus 4.8 9569b0ba43 feat(lite): encryption UI — encrypt/unlock/lock/decrypt in Settings
Add a "Security" subsection to Settings → Backup & keys (open wallet only) that
wires the encryption controller methods to the UI:

- Unencrypted wallet: passphrase field + "Encrypt wallet".
- Encrypted + locked: "Unlock" (passphrase) ; Encrypted + unlocked: "Lock now".
- Encrypted: passphrase + "Remove encryption" (decrypt).
- Status line reflects the result; state shown from WalletState.isEncrypted()/
  isLocked() (kept current by the controller's encryptionstatus refresh poll).

Secret hygiene: the passphrase inputs (lite_enc_pass / lite_dec_pass) are
sodium-zeroed immediately after each action and when the wallet closes while the
section was open.

Runtime-checked: app auto-opens a wallet and the new encryptionstatus worker poll
runs clean (no errors); tests pass; hygiene clean.

Follow-ups (not yet): a send-time unlock prompt and a startup lock-screen overlay
for an encrypted+locked wallet (today: unlock via Settings; balances remain
viewable while locked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:55:09 -05:00
DanSandClaude Opus 4.8 50b0419dfe feat(lite): wallet encryption controller layer (encrypt/unlock/lock/decrypt)
Wire the backend passphrase-encryption commands into LiteWalletController:

- encryptWallet / decryptWallet (take passphrase by value, securely wipe it,
  save after), unlockWallet / lockWallet (bring spending keys into/out of
  memory), and encryptionStatus() -> {encrypted, locked}. All return
  failure-safe results; errors arrive as {"error":..} or "Error:" (handled).
- Fold encryptionstatus into refreshModel() (polled every cycle, available even
  mid-sync since it reads local wallet state) and apply it in
  applyLiteRefreshModelToWalletState, so WalletState.isEncrypted()/isLocked()
  track the backend — which gates the existing locked/auto-lock UI.

Backend contracts verified against the SDXL source: encrypt/unlock/decrypt take
the passphrase as the single arg; lock takes none; encryptionstatus returns
{"encrypted","locked"}; ops return {"result":"success"} / {"error":..}.

Tests: testLiteWalletControllerEncryption drives encrypt -> lock -> unlock ->
decrypt via encryptionStatus(), checks empty-passphrase + closed-wallet rejection,
and that the status folds into WalletState. Fake models the state machine.

GUI wiring (encrypt in Settings, unlock prompt / lock action) is the follow-up;
the backend create flow remains unencrypted by default until encrypt is run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:50:53 -05:00
DanSandClaude Opus 4.8 4f7a4fb38e feat(lite): first-run welcome prompt (create / restore)
Replace the bare "land on main UI with a No-wallet overlay" first-run with a
lite welcome modal, shown when no wallet file exists yet (lite_wallet_ present,
not open, walletExists() false):

- "Create new wallet" — one-click createWallet({}); on success, notifies the user
  to back up their recovery phrase and navigates to Settings (Backup & keys),
  where the seed can be revealed/copied via the existing backup UI.
- "Restore from seed" — navigates to Settings (Lite wallet request → Restore).
- "Later" — dismiss for the session.

Routes to the already-built + verified create/restore/backup flows rather than
re-implementing seed display in the modal (no new secret-handling surface).
Dismissed once an action is chosen; never shown again once a wallet exists.
Full-node is unaffected (renderLiteFirstRunPrompt() returns early when
lite_wallet_ is null). English i18n built-ins added.

Verified: fresh-HOME lite launch shows the prompt, clean run + shutdown, no
crash/RPC noise; tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:38:32 -05:00
DanSandClaude Opus 4.8 f511c0d509 fix(lite): skip the full-node first-run wizard in lite builds
isFirstRun() keys off the full-node `blocks/` data dir, which never exists in
lite — so the daemon/blockchain setup wizard (download node, extract blockchain,
daemon status) fired in lite, where none of it applies and it has zero
lite-awareness. Gate the wizard on !isLiteBuild(); lite goes straight to the main
UI, where the "No wallet open — create or open one in Settings" prompt guides new
users to the lite create/open flow. Full-node behavior is unchanged
(isFirstRun() && !isLiteBuild() == isFirstRun() there).

Completes the lite daemon-wording sweep: the other full-node surfaces are already
lite-gated — daemon settings via supportsFullNodeLifecycleActions(), RPC settings
in the isLiteBuild() else-branch, and Console/Peers/Explorer hidden via
isUiSurfaceAvailable.

Verified: true first-run in lite (fresh HOME) no longer starts the wizard; clean
launch + shutdown, no daemon noise. tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:31:27 -05:00
DanSandClaude Opus 4.8 235504657d polish(lite): lite-appropriate wording for no-wallet/connection states
In lite builds there is no daemon, and isConnected() now tracks the lite wallet,
so the full-node "not connected / waiting for daemon" wording was misleading when
no wallet is open. Add two strings (lite_no_wallet, lite_no_wallet_short; English
built-ins, so other languages fall back until translated) and use them in lite:

- receive/send address preview + receive empty-state overlay + send "can't send"
  tooltip + transactions empty state -> "No wallet open [— create or open one in
  Settings]" instead of daemon wording.
- Status bar: the red indicator shows "No wallet open" (not "Disconnected") in
  lite; the P2P peer count is skipped (lite has no peers); and the redundant
  full-node connection-detail line is suppressed (connection_status_ set to
  "Connected"/"" from the lite wallet state).

Full-node wording unchanged (all gated on isLiteBuild()). Build + run clean
(no RPC noise), tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:11:41 -05:00
DanSandClaude Opus 4.8 76c2ac5db8 feat(lite): auto-open existing wallet on startup + gate full-node RPC refreshes
Auto-open: on the first update() tick (kept off init() so a slow
initialize_existing network call can't freeze startup before the window), if a
wallet file exists, open it. initialize_existing needs no passphrase — it loads
the file; a previously-synced + saved wallet resumes from its height (fast)
instead of rescanning from the checkpoint. Adds LiteWalletController::walletExists()
(bridge.walletExists on the connection's chain) + a chainName_ member.

RPC-refresh gating: the earlier connected=walletOpen() fix (so the wallet UI is
enabled in lite) had a side effect — the full-node periodic + per-page RPC
refreshes (mining/balance/peers/txs, and setCurrentPage's immediate refresh)
gate on state_.connected, so they began firing in lite and failing
("X error: Not connected"). Re-gate those on ACTUAL RPC connectivity
(rpc_ && rpc_->isConnected()) instead of the lite proxy. Full-node is unchanged
(state_.connected ⟺ rpc connected there); lite no longer issues any RPC.

Runtime-verified in WSLg with a pre-seeded wallet: app auto-opens (Starting
Mempool + sync begins), and "Not connected" / getMiningInfo / RPC-connect noise
all drop to 0 — a fully clean lite run. tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:29:27 -05:00
DanSandClaude Opus 4.8 89bd21018a fix(lite): don't run the full-node RPC loop in lite; drive isConnected() from the wallet
Runtime monitoring of ObsidianDragonLite (WSLg) showed the full-node RPC connect
state machine running in the lite build — `tryConnect()` fired every ~5s and
failed ("Couldn't connect to server / no daemon"). It's called unconditionally
from the main loop with no lite guard.

Worse than noise: `state_.connected` (App::isConnected()) was therefore ALWAYS
false in lite, and it gates the wallet UI — receive_tab disables the new-address
button + shows "not connected", send_tab disables send, transactions_tab shows
not-connected. So the M3/M4 GUI wiring was effectively unreachable: a lite user
could never generate an address or send, even with an open, synced wallet.

Fix:
- tryConnect() no-ops in lite builds (isLiteBuild()), so no RPC attempts.
- App::update() derives state_.connected from lite_wallet_->walletOpen() each
  frame — a non-blocking proxy for "lite backend operational" (a wallet opens
  only after a successful backend init against the lite server). This enables the
  wallet UI once a wallet is open.

Full-node is unaffected (both branches are runtime-gated: isLiteBuild() is false
and lite_wallet_ is null there).

Verified by re-running the app: RPC connection attempts dropped from 7/30s to 0;
clean launch (GL 4.2) + clean shutdown; tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:11:58 -05:00
DanSandClaude Opus 4.8 9d7054b245 build(lite): enable Windows lite-backend cross-compile (.exe verified)
`build.sh --lite-backend --win-release` now cross-compiles a working
ObsidianDragonLite.exe with the real SDXL backend:

- Artifact platform follows the cross target: when only --win-release is
  requested, auto-select build/lite-backend/windows/ (previously always the host
  artifact, which would link a Linux .a into a Windows .exe).
- Link the Win32 system libs a Rust x86_64-pc-windows-gnu staticlib pulls in
  (rustls/schannel, ring, dirs, std) via DRAGONX_LITE_BACKEND_EXTRA_LIBS. The set
  is rustc's `--print native-static-libs` for the backend (winapi_* shims mapped
  to real mingw import libs); all 21 exist in mingw-w64.

Verified end to end on Linux:
- scripts/build-lite-backend-artifact.sh --platform windows cross-builds the
  backend to x86_64-pc-windows-gnu (~105 MB .a); rustls/ring cross-compile clean
  (no openssl blocker); all required litelib_* symbols present.
- build.sh --lite-backend --win-release -> release/windows/ObsidianDragonLite-
  <ver>.exe (PE32+ GUI x86-64, INCBIN-embedded, ~170 MB) + zip, with the same
  full-node-asset exclusion as Linux.

Not yet done: running the .exe on real Windows (cross-compiled only). Plan
updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:45:48 -05:00
DanSandClaude Opus 4.8 70608fcb7a docs(lite): record verified Linux lite release packaging (M5b)
`./build.sh --lite-backend --linux-release` produces a working
ObsidianDragonLite zip + AppImage (SDXL backend linked statically). Verified the
lite bundle excludes all full-node assets (dragonxd, dragonx-cli, sapling
params, asmap.dat) and includes res/ + xmrig (pool mining works in lite). CMake
falls back to FetchContent SDL3 when system SDL3 is absent, so the release build
has no system-SDL3 prerequisite. release/ is gitignored.

Remaining M5b (Windows/macOS packaging, CI artifact build + signing,
kill-switch/rollout) is infra/CI, not locally verifiable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:23:51 -05:00
DanSandClaude Opus 4.8 0c819afdd4 test(lite): smoke-check M4/M5 command shapes against the real backend
Add `lite_smoke --keys`: create a fresh wallet and exercise the M4/M5
spend/backup commands (new-address, export, seed, save) against the real linked
SDXL backend, verifying each response's JSON shape with nlohmann. SECRET-SAFE:
seed and private-key VALUES are never printed — only field presence/shape and
counts (no send/shield, which would broadcast).

Verified live (isolated HOME, throwaway wallet shredded after):
  new z      shape_ok=1            new t      shape_ok=1
  seed       has_seed=1 has_birthday=1 (REDACTED)
  export     is_array=1 count=4 has_private_key=1 (REDACTED)
  save       result_success=1

Confirms the controller's newAddress / exportSeed / exportPrivateKeys / save
parsing matches real backend output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:00:05 -05:00
DanSandClaude Opus 4.8 db4778e6a7 feat(lite): backup & keys UI — export seed/keys + import (Settings)
Add a "Backup & keys" section to the lite Settings page, shown only for an open
wallet, wiring the M4 controller backup/import surface into the GUI:

- "Show seed" / "Show private keys" -> exportSeed() / exportPrivateKeys();
  the revealed secret is displayed read-only (TextWrapped, no extra copies) with
  Copy and "Hide & wipe" controls.
- "Import key" (password input) -> importKey() (auto-detects WIF vs shielded);
  do_import_sk just records the key + saves (no synchronous rescan), so this is
  safe on the UI thread — history appears after the next sync.

Secret hygiene: the revealed-backup buffer is sodium-wiped via
secureWipeLiteSecret on hide, on a new export (overwrite), and if the wallet
closes while revealed; each export also wipes the controller's result copy; the
import input buffer is zeroed immediately after submission.

Lite app + full-node variant build/link clean; controller methods already
covered by testLiteWalletControllerM4; hygiene clean. GUI behavior itself isn't
auto-verifiable here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:40:17 -05:00
DanSandClaude Opus 4.8 eb6114ee19 feat(lite): wire send + new-address GUI to the lite controller (M3/M4)
Route the existing receive/balance/send UI to the lite controller in lite builds,
with no per-tab UI changes — the existing buttons just work:

- App::createNewZAddress / createNewTAddress: lite branch calls
  lite_wallet_->newAddress() (synchronous local key derivation), injects the new
  address into WalletState so the UI selects it next frame, and invokes the
  receive-tab callback. Placed before the full-node !connected guard.
- App::sendTransaction: lite branch builds a LiteSendRequest (DRGX -> zatoshis,
  memo; `from`/`fee` ignored since the backend selects inputs and adds the fee),
  fires the controller's async broadcast, and stashes the send_tab callback.
- App::update: drains takeBroadcastResult() and delivers txid/error to the stored
  callback, so the send_tab's existing "sending.../sent" flow works unchanged.

All branches guard on lite_wallet_ (null in full-node). Verified: lite app +
test suite + full-node variant all build/link clean; hygiene clean.

Backup/import UI (export seed/keys, import) is deferred — it needs new
secret-display UI rather than an existing button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:10:56 -05:00
DanSandClaude Opus 4.8 5d317f6be3 feat(lite): M5a — wallet persistence after sync/send/shield
Verified against the SDXL Rust source that the backend auto-saves only on
new-address / import / rescan; it does NOT save after sync, send, or shield, and
litelib_shutdown merely sets a flag. So without intervention a first sync
(~30 min) and any sent transaction are lost on restart.

The controller now triggers the backend `save` at exactly the right points:
- after the detached `sync` completes — and BEFORE syncDone_ is set, so a
  syncComplete() observer always sees a fully persisted wallet;
- after a successful send / shield (the doSend/doShield cores; skipped on
  failure so a failed broadcast doesn't write);
- a guarded best-effort flush in the destructor, only when syncDone_ and no
  broadcast is in flight, so shutdown never blocks on the wallet lock held by an
  uninterruptible scan or in-progress proving;
- plus a public saveWallet() for explicit/periodic saves.

Wallet-file crash recovery (.dat / .dat.bak rotation) is already handled inside
the backend.

Tests: testLiteWalletControllerM5Persistence proves saves fire after
sync/send/shield and explicit saveWallet(), and do NOT fire on a failed send or
with no wallet open (fake gains a save counter). Plan doc updated; M5b
(packaging/CI/signing/rollout) remains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:48:44 -05:00
DanSandClaude Opus 4.8 a677c09984 refactor(lite): drop 4 unused OOP wrapper classes over free functions
Each of these classes wrapped an existing free function with a one-line
delegating method and was never instantiated anywhere (verified: no references
outside their own translation unit, not even within their own .cpp beyond the
definition) — the redundant "wrapper layer" pattern CLAUDE.md warns against:

- LiteWalletLifecycleUiExecutionAdapter      -> executeLiteWalletLifecycleUiRequest
- LiteWalletServerSelectionUiExecutionAdapter -> executeLiteWalletServerSelectionUi
- LiteWalletServerLifecycleReadinessPlanner   -> evaluateLiteWalletServerLifecycleReadiness
- LiteBackendActivationReadinessAdapter       -> evaluateLiteBackendActivationReadiness

The live free functions (the actual entry points used by the UI/runtime) are
unchanged. Both targets build, test suite passes, source-hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:40:26 -05:00
DanSandClaude Opus 4.8 6611d57147 refactor(lite): remove dead parallel refresh/readiness scaffolding (~3.1k lines)
The lite-wallet tree carried a second, unused refresh+readiness architecture
that never reached the shipping binary — exactly the churn CLAUDE.md warns
against. The live refresh path is controller -> gateway.refresh ->
mapLiteWalletRefreshResult -> applyLiteRefreshModelToWalletState; this parallel
stack was dead weight.

Verified unused (their public types/functions are referenced only within the
cluster), then deleted (8 files / 16 incl. headers):
- lite_wallet_refresh_service            (LiteWalletRefreshService + gateway adapters)
- lite_wallet_app_refresh_coordinator
- lite_wallet_app_refresh_orchestrator
- lite_wallet_refresh_readiness_policy
- lite_wallet_state_apply_plan
- lite_wallet_state_apply_executor
- lite_wallet_sync_app_refresh_integration
- lite_wallet_sync_execution_readiness

Severed three thin couplings into the cluster from live files:
- state_mapper: dropped the dead mapLiteWalletRefreshServiceResult and switched
  its include from refresh_service.h to gateway.h (where the live
  LiteWalletRefreshResult/Bundle DTOs actually live).
- server_lifecycle_readiness: dropped the unused syncLifecycleInput member +
  converter and the sync_app_refresh_integration include.
- artifact_resolver: relocated the three LIVE artifact-input structs
  (LiteWalletSdxlArtifact{Symbols,}Input, LiteWalletLinkedBackendReadinessInput)
  out of sync_execution_readiness.h — their only real consumers — into
  artifact_resolver.h, then dropped the include.

Also removed the dead DRAGONX_LONG_LITE_BATCH CMake machinery (its source var
was empty; on Windows it generated a broken lite_batch90_receipt_plan.cpp that
#included an empty path) and the stale .cpp/.h entries in CMakeLists.

Lite source files: 44 -> 30. Lite + full-node configure, both targets build,
test suite passes, source-hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:25:02 -05:00
DanSandClaude Opus 4.8 6a4e98b7ed feat(lite): M4 — send/shield/import/export/seed via controller + bridge
Add the spend & backup surface to LiteWalletController, with the real SDXL
backend contracts verified against the Rust source:

- send / shield: ASYNC (detached broadcast thread + takeBroadcastResult() slot,
  mirroring the sync thread's shared-lifetime pattern, since sapling proving can
  take seconds), plus synchronous *Blocking cores for tests. send uses the
  JSON-array form ([{address,amount,memo}]) because litelib_execute passes the
  whole args string as ONE argument (no whitespace split) — the space-separated
  CLI form would never parse. send/shield report failure via {"error":..} in the
  body (NOT an "Error:" prefix), so the result is derived from the parsed JSON.
- importKey: auto-detects transparent WIF (U/5/K/L -> timport) vs shielded key
  (-> import); takes the key by value and securely wipes it before returning.
- exportPrivateKeys / exportSeed: synchronous local reads returning SECRET
  material (flagged: no logging; caller wipes after the user saves the backup).
- broadcast thread is detached in the dtor (captures shared bridge + flag + slot,
  never `this`), so it is safe to outlive the controller.

Tests: testLiteWalletControllerM4 drives send (success / no-recipients /
{"error":..} / async-slot delivery / pre-open rejection), shield, export, seed,
and import (shielded + WIF + pre-open). Fake backend returns the real command
shapes + a g_liteFakeSendFails error toggle.

GUI wiring (send_tab button, backup/import UI) is deferred like the M3 UI hop
(GUI-unverifiable here). Plan doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:06:19 -05:00
DanSandClaude Opus 4.8 4b9d6f7db5 fix(lite): rebuild controller on lite-server change (stale-settings audit HIGH)
The LiteWalletController was constructed once at App::init() with the lite
connection settings known at startup; changing the lite server in Settings
persisted to disk but never reached the live controller, so the new server had
no effect until the next launch.

Factor the construction into App::rebuildLiteWallet() and call it after a
successful server-selection save. The rebuild deliberately preserves a live
session: if a wallet is already open (and possibly mid-sync), it no-ops and the
new selection applies on the next controller build, rather than discarding the
open wallet and its uninterruptible in-flight sync.

Closes the last remaining HIGH from the session audit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 11:32:54 -05:00
DanSandClaude Opus 4.8 043cdc7128 fix(lite): address adversarial audit findings in session's lite work
Re-audited this session's lite-wallet changes (originally written at medium
effort) and fixed the genuine issues found:

- walletReady (open path): litelib_initialize_existing returns the bare string
  "OK", which is NOT valid JSON, so the previous `json::accept(value)` check
  marked a *successful* open as not-ready. Key off a non-empty success response
  instead (the bridge already maps "Error:"/null to failure). Drops the now
  unused nlohmann include.
- sync progress: while the detached sync thread is still running, syncDone_ is
  authoritative — don't surface the backend's transient idle syncstatus
  ({"syncing":"false"} -> parser progress=1.0/complete=true) as a misleading
  100%/done. Force complete=false and zero the bogus 1.0 in the progress model.
- per-address balance: also exclude `pending` outputs (notes/utxos from an
  unconfirmed received tx) so per-address figures match confirmed/available.
- secret wiping: the settings page left the page-local request copies
  (input.request.*Request.{passphrase,seedPhrase}) unwiped, and the
  validation-only fallback path wiped nothing. Replace the single-path memzero
  with an RAII scrubber that wipes both the UI char buffers and the request
  string copies on every return path.
- concurrency: document that concurrent bridge->execute() is intentionally
  unguarded — litelib serializes wallet access internally via
  Arc<RwLock<LightWallet>>, so a C++ mutex is unnecessary and would defeat the
  sync/syncstatus concurrency the design relies on. syncLaunched_ -> atomic.

Tests: fake backend now returns the real init shapes (seed object for
create/restore, bare "OK" for open) and a new open-path case guards the
walletReady regression. Removed an unreliable alloc==freed leak assert from the
thread-bearing controller test (kept in the thread-free bridge test). Also fixed
a stray CMake indent and removed ~220MB of untracked build/debug scratch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 11:28:37 -05:00
DanSandClaude Opus 4.8 2daea67a1e feat(lite): M3 — new-address generation + sync-indicator confirmation
- LiteWalletController::newAddress(shielded) runs the backend "new" command ("zs"/"R" ->
  do_new_address), parses the ["addr"] response, and returns the new address; the next
  refresh lists it. Fast (local derivation), safe on the UI thread.
- fake_lite_backend returns ["zs1fakenew"]/["R1fakenew"] for "new" by args.
- testLiteWalletControllerNewAddress covers shielded/transparent + no-wallet error.

Also confirmed (no code needed): the sync-progress indicator already works for lite —
balance_tab reads state.sync.* which M2b-3 populates. Per-address balances landed in M2.

Remaining M3 is pure UI wiring (receive_tab button -> newAddress, loading/empty states),
which isn't verifiable without a GUI session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:36:00 -05:00
DanSandClaude Opus 4.8 e6b91ca661 feat(lite): per-address balances from unspent notes/utxos
applyLiteRefreshModelToWalletState now derives each address's balance by summing its unspent
notes/utxos (excluding spent and unconfirmed-spent outputs) instead of the aggregate-only
zeros, so the Receive/Balance UI shows per-address amounts. The notes parser shape is
confirmed against do_list_notes in the backend source.

testLitePerAddressBalances covers the summing + spent-exclusion. Completes M2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:24:36 -05:00
DanSandClaude Opus 4.8 c6e28fc4da test(lite): real-backend shape verification of refresh parsers
lite_smoke: add --restore-recent (restore a throwaway wallet at birthday≈tip) and factor
the data-shape checks (non-blocking commands first). Finding: the backend downloads from a
fixed checkpoint regardless of birthday, so first sync is ~30 min and balance/list block
until synced — a full live data run is impractical.

Verified all refresh parsers against the real backend without a full sync:
- live run: info/addresses/syncstatus parse_ok=1 (addresses z=1/t=6 on a restored wallet).
- via the authoritative Rust source (commands.rs / lightclient.rs):
  - balance do_balance fields match parseLiteBalanceResponse.
  - list do_list_transactions: sends use outgoing_metadata (no top-level address), receives
    use address+amount; parseTransactionRecord already branches correctly.
  - syncstatus was the only mismatch (fixed in the prior commit).

No parser changes needed beyond syncstatus. M2 refresh path verified end-to-end at the
shape level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:13:54 -05:00
DanSandClaude Opus 4.8 268eba6321 fix(lite): gateway refresh degrades gracefully on a failed command
LiteWalletGateway::refresh() aborted the entire refresh on the first command whose bridge
call or parse failed — which turned a single real-backend shape mismatch (e.g. syncstatus)
into a total, empty-everything refresh. Since the balance/addresses/list real shapes are
still unverified and we've already hit shape drift twice, make refresh resilient:

- Run every planned command; assembleLiteWalletRefreshBundle already skips failed results.
- result.ok = any usable data came back (bundle.complete still reflects all-succeeded).
- One command's failure now degrades gracefully — the other sections still populate.

testLiteWalletGatewayRefreshSkipsFailedCommand (fake balance returns invalid JSON) asserts
the refresh still succeeds with addresses/transactions/info populated and balance skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 06:57:10 -05:00
DanSandClaude Opus 4.8 3119440cd9 fix(lite): non-blocking, non-hanging sync (Finding B)
The backend `sync` command is a blocking, uninterruptible full chain scan (do_sync(true);
does not honor the shutdown flag), and balance/list block until synced. Previously
startSync() ran on the main thread (would freeze wallet creation) and the worker could
block, making the destructor join() hang at shutdown.

Redesign:
- bridge is now std::shared_ptr<LiteClientBridge>, shared with a detached sync thread so
  detaching is safe and litelib_shutdown isn't called while a running sync still holds the
  bridge; the controller's own ref prevents premature shutdown during normal operation.
- startSync() launches the blocking `sync` on a detached thread (non-blocking; never joined).
- refreshModel() gates on syncDone_: while syncing it publishes syncstatus progress only;
  once synced it does the full balance/addresses/list refresh (now fast).
- destructor joins only the fast poll worker and detaches the sync thread -> no hang.
- syncComplete() accessor added.

Tests (deterministic, via a blocking-sync fake; counters made atomic for the detached
thread): testLiteWalletControllerShutdownDoesNotHangDuringSync (destructor returns <1.5s
with sync blocked); refresh/worker tests wait for syncComplete()/a balance-bearing model.
Stable across repeated runs; lite+backend and full-node apps build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 06:35:26 -05:00
DanSandClaude Opus 4.8 59c55e33f8 fix(lite): parse real syncstatus shapes (idle vs in-progress)
The real backend returns syncstatus as idle {"syncing":"false"} (string) or in-progress
{"syncing":"true","synced_blocks":N,"total_blocks":M} (commands.rs:83-87), but
parseLiteSyncStatusResponse hard-required the block fields and failed whenever the wallet
wasn't actively syncing — so sync/progress never updated in the real app.

- Read "syncing" as a string; require synced_blocks/total_blocks only when syncing=true;
  idle => complete, synced/total 0.
- fake_lite_backend syncstatus now uses the real "syncing":"true" shape.
- testLiteSyncStatusParserRealShapes covers idle, in-progress, and missing-counts-while-syncing.
- Verified against the live backend via lite_smoke --refresh (syncstatus parse_ok=1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:52:42 -05:00
DanSandClaude Opus 4.8 a8b5d2f6a3 feat(lite): M2b-3 — background refresh worker + App::update hook
- LiteWalletController owns a background std::thread worker that, once a wallet is ready,
  refreshes every ~4s and publishes a copyable LiteWalletAppRefreshModel under a mutex.
  Worker auto-starts on lifecycle-ready and is stopped+joined in the destructor. status_
  is written only on the main thread; walletOpen_/syncStarted_ are atomic.
- App::update() calls takeRefreshedModel() and applies it into state_ on the main thread
  (WalletState is non-copyable, so the model crosses the thread boundary, not the state),
  so the existing Balance/Receive/Transactions tabs populate from lite data.
- refreshWalletState() refactored onto refreshModel() (pure, worker-safe).
- testLiteWalletControllerWorkerProducesModel verifies the worker publishes a populated
  model (stable across repeated runs). Builds clean in all configs.

Real-backend smoke (lite_smoke --refresh now runs real output through the parsers) found
two integration bugs, documented in the plan for follow-up:
- syncstatus parser requires synced_blocks/total_blocks but the real idle response is
  {"syncing":"false"} (string), so it fails to parse when not actively syncing.
- the first data query (balance/list) blocks on a full chain sync, which would hang the
  worker's shutdown join — needs a cancel/timeout path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:38:34 -05:00
DanSandClaude Opus 4.8 012341b1a4 feat(lite): M2b-1/2 — shared-bridge refactor + sync/refresh into WalletState
Shared-bridge refactor (litelib is a global singleton; every LiteClientBridge calls
litelib_shutdown() on destruction, so services must not each own one):
- LiteWalletLifecycleService, LiteWalletGateway, LiteSyncService now take a non-owning
  LiteClientBridge*; LiteWalletController owns the single bridge and passes &bridge_.

Sync + controller refresh:
- LiteSyncService::startSync executes the real "sync" command (was a stub).
- LiteWalletController: startSync() (auto-fires when a wallet becomes ready) and
  refreshWalletState(WalletState&) — polls syncstatus, runs gateway.refresh(), maps the
  bundle, applies balances/addresses/transactions/sync into WalletState.

Tests:
- fake_lite_backend.h returns command-shaped JSON (per tests/fixtures/lite/result_parsers.json).
- testLiteWalletControllerRefreshPopulatesState drives the full path against the fake.
- Surfaced + worked around a real integration issue: parseLiteInfoResponse requires
  latest_block_height and the gateway aborts the whole refresh on the first command's
  parse failure (fragile vs partial backend responses; hardening tracked for M2b-3).

Verified: ctest green; lite+backend, full-node, lite-no-backend apps + lite_smoke build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:24:18 -05:00
DanSandClaude Opus 4.8 5586f334a4 feat(lite): real backend integration — controller, M0-M2a wiring, smoke tool, tests
- LiteWalletController (src/wallet/lite_wallet_controller.*): App-owned; runs real
  create/open/restore via the linked SDXL bridge with allowBridgeCalls=true; wipes
  seed/passphrase with sodium_memzero; persists on a ready wallet. M2a:
  applyLiteRefreshModelToWalletState maps a parsed refresh bundle into WalletState
  (zatoshi->DRGX, z/t split, tx typing + confirmations, sync progress).
- App wiring: liteWallet() accessor + init() construction when supportsLiteBackend();
  persist -> settings save.
- settings_page: "Validate" reroutes to the controller for real execution (validation-
  only fallback otherwise); wipes UI secret buffers after submit.
- chain name default -> "main" with load-time migration of legacy "DRAGONX"
  (settings.cpp), preventing the backend "Unknown chain" panic.
- M0: build.sh --lite-backend flag; lite_smoke real-backend tool + CMake targets;
  tests/fake_lite_backend.h deterministic harness.
- Tests (test_phase4): injectable-fake bridge, controller lifecycle, chain-name
  migration, refresh->WalletState mapping; plus the lite test-suite churn-cleanup rewrite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:44 -05:00
DanSandClaude Opus 4.8 863d015628 feat(lite): lite wallet foundation (inherited working-tree state)
Preserve the previously-uncommitted lite wallet implementation and related dev WIP
under version control:
- src/wallet/ lite services: client bridge, bridge runtime, connection, lifecycle,
  sync, gateway, result parsers, state mapper, artifact contract/resolver, refresh
  services, UI adapters, wallet_backend/capabilities. (Includes two small M1 fixes:
  lifecycle walletReady now parses the response; default chain name -> "main".)
- src/chat/ chat protocol; tests/fixtures/ (lite + hushchat); tools/hushchat_fixture_check.cpp;
  scripts/build-lite-backend-artifact.sh.
- Pre-existing modified app_network/security/wizard, network_refresh_service, sidebar,
  mining_tab, bootstrap dialog, and version headers captured as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:28 -05:00
DanSandClaude Opus 4.8 a78a13edf3 docs(lite): add v2 implementation plan, source-hygiene guard, and CLAUDE.md
- docs/lite-wallet-implementation-plan-v2-2026-06-04.md: vertical-slice plan that
  supersedes the v1 plan (now banner-marked); carries over the inherited artifact/
  signing/phase-2 design docs for reference.
- scripts/check-source-hygiene.sh: pre-commit/CI guard rejecting >80-char filenames
  and chained churn-token names, to stop the deleted "_plan"/"_batch" scaffolding
  from regrowing.
- CLAUDE.md: repository guidance for future sessions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:11 -05:00
DanS e95ad50e41 feat(lite): add ObsidianDragonLite build mode and gate full-node features
Add --lite build flow and ObsidianDragonLite target naming, hide full-node pages/features in lite mode, enforce pool-only mining in lite, and include chat port feasibility audit documentation.
2026-05-06 03:42:05 -05:00
DanS 975743f754 feat(wallet): persist history and surface pending sends
Add an encrypted SQLite transaction history cache with cached tip metadata and
per-address shielded scan progress so startup and full refreshes avoid
re-scanning every z-address while still invalidating on wallet/address/rescan
changes.

Improve wallet history loading by paging transparent transactions, preserving
cached shielded and sent rows, keeping recent/unconfirmed activity visible, and
classifying mining-address receives. Show z_sendmany opid sends immediately in
History and Overview, pin pending rows through refreshes, and apply optimistic
address/balance debits until opids resolve.

Add timestamped RPC console tracing by source/method without logging params or
results, reduce redundant refresh/RPC calls, and cache Explorer recent block
summaries in SQLite.

Expand focused tests for transaction cache encryption, scan-progress
persistence/invalidation, history preservation, operation-status parsing,
pending send visibility, and Explorer/RPC refresh behavior.
2026-05-05 03:22:14 -05:00
DanS 948ef419ac fix(history): keep wallet-created sends visible
Replay cached outgoing viewtransaction entries during transaction refresh so shielded sends created from the wallet remain in the History tab after send tracking is cleared.

Keep incomplete tracked sends retryable, preserve cached send timestamp/confirmation metadata, and emit a send placeholder from gettransaction metadata when viewtransaction enrichment is not yet available.

Add regression coverage for cached sends, retryable empty entries, placeholder sends, and send txid cleanup behavior.
2026-04-30 14:57:37 -05:00
DanS 9edab31728 Refactor app services and stabilize refresh/UI flows
- Add refresh scheduler and network refresh service boundaries for typed
  refresh results, ordered RPC collectors, applicators, and price parsing.
- Add daemon lifecycle and wallet security workflow helpers while preserving
  App-owned command RPC, decrypt, cancellation, and UI handoff behavior.
- Split balance, console, mining, amount formatting, and async task logic into
  focused modules with expanded Phase 4 test coverage.
- Fix market price loading by triggering price refresh immediately, avoiding
  queue-pressure drops, tracking loading/error state, and adding translations.
- Polish send, explorer, peers, settings, theme/schema, and related tab UI.
- Replace checked-in generated language headers with build-generated resources.
- Document the cleanup audit, UI static-state guidance, and architecture updates.
2026-04-29 12:47:57 -05:00
DanS ee8a08e569 feat(addresses): improve address labeling and view-only handling
- 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
2026-04-27 13:54:28 -05:00
DanS ddb810e2f3 fix: drag-to-transfer drop not triggering transfer dialog
s_dropTargetIdx was reset to -1 unconditionally each frame, including
the release frame. Since drop target detection runs in PASS 2 (after
the drop handler), the target was always -1 when checked. Only reset
while mouse button is held so the previous frame's value is preserved.

Also bump version to 1.2.0-rc1 and add release notes.
2026-04-12 19:07:41 -05:00
DanS aa26ab5fbd fix: sidebar nav text overflow for long translations
- Add text scaling for section labels (TOOLS, ADVANCED) in sidebar
- Separate explorer_section key from explorer nav label to fix ALL CAPS
- Shorten long sidebar translations: es/pt settings, pt overview, ru tools/advanced
- Fix explorer translations from ALL CAPS to proper case in all languages
2026-04-12 18:45:48 -05:00
DanS 40cec14ebf Add bootstrap download dialog and fix 100 missing translation keys
- New BootstrapDownloadDialog accessible from Settings page
  - Stops daemon before download, prevents auto-restart during bootstrap
  - Confirm/Downloading/Done/Failed states with progress display
  - Mirror support (bootstrap2.dragonx.is)
- Add bootstrap_downloading_ flag to prevent tryConnect() auto-reconnect
- Right-align Download Bootstrap + Setup Wizard buttons in settings
- Add 100 missing i18n keys to all 8 language files (de/es/fr/ja/ko/pt/ru/zh)
  - Includes bootstrap, explorer, mining benchmark, transfer, delete blockchain,
    force quit, address label, and settings section translations
- Update add_missing_translations.py with new translation batch
2026-04-12 18:19:01 -05:00
DanS 88d30c1612 feat: modernize address list with drag-transfer, labels, and UX polish
- Rewrite RenderSharedAddressList with two-pass layout architecture
- Add drag-to-transfer: drag address onto another to open transfer dialog
- Add AddressLabelDialog with custom label text and 20-icon picker
- Add AddressTransferDialog with amount input, fee, and balance preview
- Add AddressMeta persistence (label, icon, sortOrder) in settings.json
- Gold favorite border inset 2dp from container edge
- Show hide button on all addresses, not just zero-balance
- Smaller star/hide buttons to clear favorite border
- Semi-transparent dragged row with context-aware tooltip
- Copy-to-clipboard deferred to mouse-up (no copy on drag)
- Themed colors via resolveColor() with CSS variable fallbacks
- Keyboard nav (Up/Down/J/K, Enter to copy, F2 to edit label)
- Add i18n keys for all new UI strings
2026-04-12 17:29:56 -05:00
DanS dbe6546f9f refactor: rewrite sidebar layout with two-pass architecture
Replace fragile Dummy()-based cursor flow with a deterministic two-pass
layout system:
- Pass 1: compute exact Y positions for all elements (pure math)
- Pass 2: render at computed positions using SetCursorScreenPos + draw list

Eliminates the dual-coordinate mismatch that caused persistent centering
and overflow bugs. Height is computed once, not estimated then measured.

Also tune sidebar spacing via ui.toml:
- button-spacing: 4 → 6
- section-gap: 4 → 8
- Add section-label-pad-bottom (4px) below category labels
- bottom-padding: 0 → 4
2026-04-12 16:34:31 -05:00
DanS 6e2db50675 fix: accurate sync speed display, add missing i18n keys, native language names
- 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 (中文, Русский, 日本語, 한국어)
2026-04-12 15:12:36 -05:00
DanS d2dccbac05 feat: non-blocking warmup — connect during daemon initialization
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.
2026-04-12 14:32:57 -05:00
DanS 1860e9b277 fix: auto-refresh peers list, show warmup status during daemon startup
- Fix peer timer calling refreshEncryptionState() instead of
  refreshPeerInfo(), so the Network tab now auto-updates every 5s
- Reorder RPC error handling so warmup messages (Loading block index,
  Verifying blocks, etc.) display in the status bar instead of being
  masked by the generic "Waiting for dragonxd" message
2026-04-12 13:43:45 -05:00
DanS 648a6c29e0 feat: use DragonX DNS seed nodes, pass -maxconnections to daemon, show sync speed
- Replace hardcoded IP addnodes with node.dragonx.is, node1–4.dragonx.is
  in both daemon launch params and auto-generated DRAGONX.conf
- Add max_connections setting (persisted, default 0 = daemon default);
  passed as -maxconnections= flag to dragonxd on startup
- Show blocks/sec in status bar during sync with exponential smoothing
  (e.g. "Syncing 45.2% (12340 left, 85 blk/s)")
2026-04-12 13:22:22 -05:00
DanS c013038ef7 feat: CJK font rendering, force quit confirmation, settings i18n
- Rebuild CJK font subset (1421 glyphs) and convert CFF→TTF for
  stb_truetype compatibility, fixing Chinese/Japanese/Korean rendering
- Add force quit confirmation dialog with cancel/confirm actions
- Show force quit tooltip immediately on hover (no delay)
- Translate hardcoded English strings in settings dropdowns
  (auto-lock timeouts, slider "Off" labels)
- Fix mojibake en-dashes in 7 translation JSON files
- Add helper scripts: build_cjk_subset, convert_cjk_to_ttf,
  check_font_coverage, fix_mojibake
2026-04-12 10:32:58 -05:00
DanS 20cbad687d Redesign benchmark to measure sustained (thermally throttled) hashrate
instead of initial burst performance. Previously the benchmark used a
fixed 20s warmup + 10s peak measurement, which reported inflated
results on thermally constrained hardware (e.g. 179 H/s vs actual
sustained 117 H/s on a MacBook Pro).

- Adaptive warmup with stability detection: mine for at least 90s,
  then compare rolling 10s hashrate windows. Require 3 consecutive
  windows within 5% before declaring thermal equilibrium (cap 300s)
- Average-based measurement: record mean hashrate over 30s instead
  of peak, reflecting real sustained throughput
- Start candidates at half the system cores — lower thread counts
  are rarely optimal and waste time warming up
- Add CoolingDown phase: 5s idle pause between tests so each starts
  from a similar thermal baseline
- Adaptive time estimates: use observed warmup durations from
  completed tests to predict remaining time
- UI shows Stabilizing when waiting for thermal equilibrium past
  the minimum warmup, Cooling during idle pauses"
2026-04-06 13:51:56 -05:00
DanS ddca8b2e43 v1.2.0: UX audit — security fixes, accessibility, and polish
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
2026-04-04 19:10:58 -05:00
DanS 50e9e7d75e refactor: tab-aware prioritized refresh system
Split monolithic refreshData() into independent sub-functions
(refreshCoreData, refreshAddressData, refreshTransactionData,
refreshEncryptionState) each with its own timer and atomic guard.

Per-category timers replace the single 5s refresh_timer_:
- core_timer_: balance + blockchain info (5s default)
- transaction_timer_: tx list + enrichment (10s default)
- address_timer_: z/t address lists (15s default)
- peer_timer_: encryption state (10s default)

Tab-switching via setCurrentPage() adjusts active intervals so
the current tab's data refreshes faster (e.g. 3s core on Overview,
5s transactions on History) while background categories slow down.

Use fast_worker_ for core data on Overview tab to avoid blocking
behind the main refresh batch.

Bump version to 1.1.2.
2026-04-04 13:05:00 -05:00
DanS d755f6816b refactor: extract AI/agent files into separate repo
ObsidianDragon-agent/ is now a standalone git repo (future submodule)
so AI configuration files are not pushed to the main repository.

- Remove copilot-instructions.md and ARCHITECTURE.md from main tracking
- Remove symlinks from .github/ and docs/
- Add ObsidianDragon-agent/ and .github/ to .gitignore
2026-04-04 11:36:04 -05:00
DanS 84d2b9c39d refactor: move AI/agent files into ObsidianDragon-agent/
- copilot-instructions.md → ObsidianDragon-agent/copilot-instructions.md
- ARCHITECTURE.md → ObsidianDragon-agent/ARCHITECTURE.md
- Symlinks at original locations preserve Copilot auto-discovery
2026-04-04 11:29:12 -05:00
DanS 27e9a8df26 docs: add ARCHITECTURE.md with project overview
Covers directory layout, threading model, RPC architecture,
connection lifecycle, UI system, build system, and key conventions.
2026-04-04 11:17:21 -05:00
DanS 8d51f374cd docs: add copilot-instructions.md and file-level comments
- Create .github/copilot-instructions.md with project coding standards,
  architecture overview, threading model, and key rules for AI sessions
- Add module description comments to app.cpp, rpc_client.cpp, rpc_worker.cpp,
  embedded_daemon.cpp, xmrig_manager.cpp, console_tab.cpp, settings.cpp
- Add ASCII connection state diagram to app_network.cpp
- Remove /.github/ from .gitignore so instructions file is tracked
2026-04-04 11:14:31 -05:00
DanS 7ab8f5d82c fix: console not connected when fast-lane RPC still connecting
The console tab was passed fast_rpc_ even before its async connection
completed, causing 'Not connected to daemon' errors despite the main
RPC being connected and sync data flowing. Fall back to the main
rpc_/worker_ until fast_rpc_ reports isConnected().
2026-04-03 11:34:32 -05:00
DanS e4b1b644b3 build: macOS universal binary (arm64+x86_64) with deployment target 11.0
- Set CMAKE_OSX_DEPLOYMENT_TARGET and CMAKE_OSX_ARCHITECTURES before
  project() so they propagate to all FetchContent dependencies (SDL3, etc.)
- build.sh: native mac release builds universal binary, detects and
  rebuilds single-arch libsodium, verifies with lipo, exports
  MACOSX_DEPLOYMENT_TARGET; dev build uses correct build/mac directory
- fetch-libsodium.sh: build arm64 and x86_64 separately then merge with
  lipo on native macOS; fix sha256sum unavailable on macOS (use shasum)
2026-04-03 10:55:07 -05:00
DanS 8ef8abeb37 feat: thread benchmark, GPU-aware idle mining, thread scaling fix
- 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
2026-04-01 17:06:05 -05:00
DanS 09f876eb60 update build output filenames to include version info 2026-03-25 11:24:21 -05:00
DanS aa3bd4e304 update hardcoded version for mac dmg build 2026-03-25 11:18:03 -05:00
DanS 801fa2b96b feat: track shielded send txids via z_viewtransaction
Extract txids from completed z_sendmany operations and store in
send_txids_ so pure shielded sends are discoverable. The network
thread includes them in the enrichment set, calls z_viewtransaction,
caches results in viewtx_cache_, and removes them from send_txids_.
2026-03-25 11:06:09 -05:00
DanS e0bfeb2f29 fix: macOS block index corruption, dbcache auto-sizing, import key rescan height
- Shutdown: 3-phase stop (wait for RPC stop → SIGTERM → SIGKILL) prevents
  LevelDB flush interruption on macOS/APFS that caused full re-sync on restart
- dbcache: auto-detect RAM and set -dbcache to 12.5% (clamped 450-4096 MB)
  on macOS (sysctl), Linux (sysconf), and Windows (GlobalMemoryStatusEx)
- Import key: pass user-entered start height to z_importkey and trigger
  rescanblockchain from that height for t-key imports
- Bump version to 1.1.1
2026-03-25 11:00:14 -05:00
DanS 9ed4fbc476 update mac icons 2026-03-19 14:46:33 -05:00
DanS 2b3277529e macOS port: build, rendering, daemon, and mining fixes
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)
2026-03-19 14:26:04 -05:00
DanS d79d013060 feat: sync thread grid during idle scaling, skip lock screen while pool mining, add paste preview to import key dialog
- Mining tab: sync s_selected_threads with actual thread count when idle
  thread scaling adjusts threads (solo via genproclimit, pool via
  threads_active), skipping sync during user drag
- Auto-lock: bypass lock screen overlay when xmrig pool mining is active
  so the mining UI remains accessible
- Import key dialog: add clipboard hover preview with transparent overlay
  on the input field, inline key type validation next to title (matching
  send tab paste button pattern), configurable via ui.toml
2026-03-19 06:10:46 -05:00
DanS 474250bb50 v1.1.0: explorer tab, bootstrap fixes, full theme overlay merge
Explorer tab:
- New block explorer tab with search, chain stats, mempool info,
  recent blocks table, block detail modal with tx expansion
- Sidebar nav entry, i18n strings, ui.toml layout values

Bootstrap fixes:
- Move wizard Done handler into render() — was dead code, preventing
  startEmbeddedDaemon() and tryConnect() from firing post-wizard
- Stop deleting BDB database/ dir during cleanup — caused LSN mismatch
  that salvaged wallet.dat into wallet.{timestamp}.bak
- Add banlist.dat, db.log, .lock to cleanup file list
- Fatal extraction failure for blocks/ and chainstate/ files
- Verification progress: split SHA-256 (0-50%) and MD5 (50-100%)

Theme system:
- Expand overlay merge to apply ALL sections (tabs, dialogs, components,
  screens, flat sections), not just theme+backdrop+effects
- Add screens and security section parsing to UISchema
- Build-time theme expansion via expand_themes.py (CMake + build.sh)

Other:
- Version bump to 1.1.0
- WalletState::clear() resets all fields (sync, daemon info, etc.)
- Sidebar item-height 42 → 36
2026-03-17 18:49:46 -05:00
DanS 40dd6d45b2 daemon version check, idle mining control, bootstrap mirror, import key paste, and cleanup
- Add startup binary version checking for dragonxd/xmrig
- Display daemon version in UI
- Add idle mining thread count adjustment
- Add bootstrap mirror option (bootstrap2.dragonx.is) in setup wizard
- Add paste button to import private key dialog with address validation
- Add z-address generation UI feedback (loading indicator)
- Add option to delete blockchain data while preserving wallet.dat
- Add font scale slider hotkey tooltip (Ctrl+Plus/Ctrl+Minus)
- Fix Windows RPC auth: trim \r from config values, add .cookie fallback
- Fix connection status message during block index loading
- Improve application shutdown to prevent lingering background process
2026-03-17 14:57:12 -05:00
DanS 1b97476a54 update version to v1.0.2 2026-03-12 02:29:08 -05:00
DanS d7bc5c638a fix: remove D3D11 debug layer flag that prevented startup on user machines
DRAGONX_DEBUG was defined unconditionally, causing D3D11CreateDevice() to
request the debug layer via D3D11_CREATE_DEVICE_DEBUG. This layer is only
available on machines with the Windows SDK or Graphics Tools installed,
so the call fails with DXGI_ERROR_SDK_COMPONENT_MISSING on regular user
machines — causing the app to silently exit.
2026-03-12 00:13:27 -05:00
DanS 39f193a264 fix xmrig bundling issues 2026-03-11 21:14:03 -05:00
DanS 4023af9466 fix scrolling bug 2026-03-11 03:15:31 -05:00
DanS 853e3e0f17 build: Linux release outputs binaries zip + AppImage, bundle sapling params
- Linux --linux-release now produces both ObsidianDragon-Linux-x64.zip
  (raw binaries) and ObsidianDragon.AppImage (single-file)
- Windows --win-release keeps standalone exe alongside zip with binaries
- Bundle sapling-spend.params and sapling-output.params in Linux release
2026-03-11 01:38:59 -05:00
DanS 1bcfcd1c73 ui: add idle delay combo to mining tab
Add inline combo box (30s/1m/2m/5m/10m) next to the idle mining
toggle so users can choose how long to wait before idle mining starts.
2026-03-11 01:38:48 -05:00
DanS 6bb35bd3e1 ui: reorganize settings page with collapsible sections
- Rename APPEARANCE section to THEME & LANGUAGE
- Move font scale slider out of effects into main section
- Collapse visual effects into "Advanced Effects..." toggle
- Collapse wallet tools into "Tools & Actions..." toggle
- Remove redundant Tools & Actions divider/section from wallet card
- Add i18n strings: theme_language, advanced_effects, tools_actions
2026-03-11 01:38:40 -05:00
DanS 2c5a658ea5 feat: Full UI internationalization, pool hashrate stats, and layout caching
- 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
2026-03-11 00:40:50 -05:00
DanS f416ff3d09 Add mine-when-idle, default banlist, and console parsing improvements
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
2026-03-07 13:42:31 -06:00
DanS b3a0ce29ed fix: Windows identity, async address creation, mining UI, and chart artifacts
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
2026-03-05 22:43:27 -06:00
DanS e2265b0bdf improve diagnostics, security UX, and network tab refresh
Diagnostics & logging:
- add verbose logging system (VERBOSE_LOGF) with toggle in Settings
- forward app-level log messages to Console tab for in-UI visibility
- add detailed connection attempt logging (attempt #, daemon state,
  config paths, auth failures, port owner identification)
- detect HTTP 401 auth failures and show actionable error messages
- identify port owner process (PID + name) on both Linux and Windows
- demote noisy acrylic/shader traces from DEBUG_LOGF to VERBOSE_LOGF
- persist verbose_logging preference in settings.json
- link iphlpapi on Windows for GetExtendedTcpTable

Security & encryption:
- update local encryption state immediately after encryptwallet RPC
  so Settings reflects the change before daemon restarts
- show notifications for encrypt success/failure and PIN skip
- use dedicated RPC client for z_importwallet during decrypt flow
  to avoid blocking main rpc_ curl_mutex (which starved peer/tx refresh)
- force full state refresh (addresses, transactions, peers) after
  successful wallet import

Network tab:
- redesign peers refresh button as glass-panel with icon + label,
  matching the mining button style
- add spinning arc animation while peer data is loading
  (peer_refresh_in_progress_ atomic flag set/cleared in refreshPeerInfo)
- prevent double-click spam during refresh
- add refresh-button size to ui.toml

Other:
- use fast_rpc_ for rescan polling to avoid blocking on main rpc_
- enable DRAGONX_DEBUG in all build configs (was debug-only)
- setup.sh: pull latest xmrig-hac when repo already exists
2026-03-05 05:26:04 -06:00
DanS 9368b945e0 fix text shifting in status bar from font scale changes 2026-03-05 01:29:03 -06:00
DanS eda19ef6d5 improved font scaling text and window adjustment, added ctrl + scroll hotkey for font scaling 2026-03-05 01:22:20 -06:00
DanS 7d48936f30 refresh network info instantly when switching to network tab 2026-03-04 15:16:32 -06:00
DanS bc2ef4ffaf feat: RPC caching, background decrypt import, fast-lane peers, mining fix
RPC client:
- Add call() overload with per-call timeout parameter
- z_exportwallet uses 300s, z_importwallet uses 1200s timeout

Decrypt wallet (app_security.cpp, app.cpp):
- Show per-step and overall elapsed timers during decrypt flow
- Reduce dialog to 5 steps; close before key import begins
- Run z_importwallet on detached background thread
- Add pulsing "Importing keys..." status bar indicator
- Report success/failure via notifications instead of dialog

RPC caching (app_network.cpp, app.h):
- Cache z_viewtransaction results in viewtx_cache_ across refresh cycles
- Skip RPC calls for already-cached txids (biggest perf win)
- Build confirmed_tx_cache_ for deeply-confirmed transactions
- Clear all caches on disconnect
- Remove unused refreshTransactions() dead code

Peers (app_network.cpp, peers_tab.cpp):
- Route refreshPeerInfo() through fast_worker_ to avoid head-of-line blocking
- Replace footer "Refresh Peers" button with ICON_MD_REFRESH in toggle header
- Refresh button triggers both peer list and full blockchain data refresh

Mining (mining_tab.cpp):
- Allow pool mining toggle when blockchain is not synced
- Pool mining only needs xmrig, not local daemon sync
2026-03-04 15:12:24 -06:00
DanS 0da1657b12 Rename hush→dragonx across wallet codebase
- Rename RESOURCE_HUSHD/HUSH_CLI/HUSH_TX to RESOURCE_DRAGONXD/DRAGONX_CLI/DRAGONX_TX
- Remove unused .bat resource constants (DRAGONXD_BAT, DRAGONX_CLI_BAT)
- Update INCBIN symbols: g_hushd_exe → g_dragonxd_exe, etc.
- Update daemon search paths, removing hush-arrakis-chain fallbacks
- Update process detection (Windows findProcessByName, Linux /proc/comm, macOS pgrep)
- Update build.sh: embed dragonxd.exe/dragonx-cli.exe/dragonx-tx.exe
- Overhaul setup.sh: fix binary names, release paths, add -j passthrough
- Update getDaemonPath/needsDaemonExtraction/hasDaemonAvailable for new names
2026-03-04 03:17:32 -06:00
DanS 0d30ebc8e8 setup script improvements, automatically clone xmrig-hac and build for multiple platforms 2026-03-03 01:47:44 -06:00
DanS c778e03f37 update links 2026-03-03 01:20:03 -06:00
DanS 0a3f68f1eb change release output names 2026-02-28 15:28:40 -06:00
DanS 1a5c4e8744 feat: blockchain rescan via daemon restart + status bar progress
- 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
2026-02-28 15:06:35 -06:00
DanS f5378a55ed UX improvements 2026-02-28 00:58:43 -06:00
DanS 6607bae2b5 Update README.md 2026-02-28 00:53:41 -06:00
DanS eebfd5947e console and mining tab visual improvements 2026-02-27 13:30:06 -06:00
DanS 48ce983966 Reduce redundant RPC calls in periodic refresh cycle
- Remove getinfo from refreshMiningInfo slow path; daemon_version,
  protocol_version, and p2p_port are static per connection (set in
  onConnected). Move longestchain/notarized into refreshBalance's
  existing getblockchaininfo callback.

- Remove refreshMiningInfo from refreshData(); it already runs on
  the 1-second fast_refresh_timer_ independently.

- Make refreshAddresses demand-driven via addresses_dirty_ flag;
  only re-fetch when a new address is created or a send completes,
  not unconditionally every 5 seconds.

- Gate refreshPeerInfo to only run when the Peers tab is active.

- Skip duplicate getwalletinfo on connect; onConnected() already
  prefetches it for immediate lock-screen display, so suppress the
  redundant call in the first refreshData() cycle.

Steady-state savings: ~8 fewer RPC calls per 5-second cycle
(from ~12+ down to ~4-5 in the common case).
2026-02-27 02:28:29 -06:00
DanS 721fb292c4 added visual feedback to mining button to show miner is in process of startup/shutdown 2026-02-27 00:48:13 -06:00
DanS f3fcda1a5e update README link 2026-02-27 00:29:35 -06:00
DanS 3aee55b49c ObsidianDragon - DragonX ImGui Wallet
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)
2026-02-27 00:26:01 -06:00