Four confirmed findings from the review pass:
SECURITY — B7 was incomplete:
- The single-key Export dialog (key_export_dialog) still used plain call() for
z_exportkey/dumpprivkey/z_exportviewingkey — a live spending-key leak on the
most common per-address export path, missed by the B7 commit.
- callSecret() zeros the raw body but the parsed json holds its OWN heap copy of
the secret; several callers did .get<string>() on a temporary json and freed
that copy un-wiped.
Fix: add RPCClient::callSecretString() — returns the bare-string result with
BOTH the raw body AND the json node zeroed, so callers can't forget. Route
key_export_dialog (×2), exportPrivateKey, and export_all_keys (×2) through it;
scrub the z_exportmnemonic json node in seed_wallet_creator (object result);
also wipe the transient key copies, the displayed s_key on reset, and the
aggregated export-all `keys` buffer.
CHAT:
- Jump-to-latest pill: SetCursorScreenPos moved the parent cursor and never
restored it, so the composer footer rendered ~8px too high while scrolled up.
Save + restore the cursor around the pill.
- New-message toast: gating on a chatUnreadCount() watermark delta could be
swallowed when an outgoing echo (wall-clock) pushed the seen-watermark past a
later reply's block time. ingest() now reports the cids it appended; the toast
fires when any is a non-muted conversation — skew-proof, still mute-aware.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The secret exports (z_exportmnemonic / z_exportkey / dumpprivkey) already scrub
the parsed value at the call site, but the raw HTTP response string those RPCs
build — the curl write buffer, which holds the same secret in the clear — was
freed without zeroing. That's the "fuller fix belongs in the RPC layer" the
identity-fetch comment flagged.
Add RPCClient::callSecret(), a call() variant that sodium_memzeros the raw
response body after parsing (on success and on throw). NRVO makes the returned
string the very buffer curl wrote into, so one wipe covers it. Route every
secret-bearing export through it: chat identity (mnemonic + z_exportkey
fallback), Settings seed-phrase + single-key export, Export-all-keys, and the
migrate-to-seed isolated-node mnemonic export. Purely additive — the parsed
result is byte-identical, so no behavior change (safe for the fund-critical
migrate path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the Windows migrate-to-seed failure (the old bundled daemon lacked
z_exportmnemonic) and rounds out the flow:
- seed_wallet_creator: actionable "daemon too old" message instead of the
raw "Method not found" RPC error.
- Startup daemon-update prompt: if the wallet bundles a newer node than the
installed one, offer to update it (once per version, keyed on a new
daemon_update_prompted_size setting; never silently clobbers a custom
node). "Install bundled" now also works when attached to an external /
leftover daemon (RPC-stops it, then swaps + restarts).
- Migrate-to-seed Intro pre-flight: detect an already-mnemonic wallet
(offer backup instead), an old daemon, or a locked/disconnected wallet
before offering to create anything.
- No-funds path: a zero-balance wallet can adopt the new seed wallet
directly (behind an acknowledgement checkbox) instead of a dead sweep.
- Loading spinners on the Working/Sweeping/Adopting steps; sweep coverage
for the new intro / no-funds surfaces.
- Translations for all 18 new strings across 8 languages (+ rebuilt CJK
subset font).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The seed-wallet creator forced TLS on its RPC client, but the DRAGONX RPC is
plaintext HTTP on localhost (-tls=only applies to P2P, not the RPC; the main
GUI's conf has no rpctls key and connects over http too). Forcing https made
every connect fail with "SSL connect error", so the isolated create never
completed. Connect without TLS, matching the main daemon path.
Also add an env-gated (DRAGONX_SEED_CREATE_SMOKE=1) headless smoke in main()
that runs SeedWalletCreator::create() before GUI init and prints
ok/word-count/address (never the seed words) — used to validate the full C++
creator path without the GUI. Harmless in normal runs.
Validated end-to-end against the hd-transparent-keys (v1.0.3-dc45e7d9)
daemon: ok=1, words=24, a shielded z-address returned, temp datadir cleaned up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First, fund-safe step of "migrate a legacy wallet to a seed wallet": mint a
brand-new BIP39 mnemonic wallet in an ISOLATED throwaway datadir so the user
can later sweep funds into it. No funds move and the real wallet.dat / main
daemon are never touched — the isolated node runs concurrently on its own port.
- EmbeddedDaemon: one-shot isolated-datadir override (setNextStartOverride)
consumed on the next start(); a skip-default-port-check for an isolated
instance beside the main daemon; and a tcpPortInUse() probe. The datadir's
basename must be the assetchain name (DRAGONX) and the daemon reads
<datadir>/DRAGONX.conf itself, so no -conf is passed (both learned from
live testing against the hd-transparent-keys daemon).
- SeedWalletCreator (src/daemon): picks a free port, writes a throwaway conf,
starts an isolated dragonxd with -usemnemonic=1 -connect=0, waits for RPC,
calls z_exportmnemonic + z_getnewaddress (the sweep target), stops it, and
keeps/scrubs the temp datadir. Off-thread; the seed is wiped after use.
- App: beginCreateSeedWallet (background) -> pumpSeedMigration (main-thread
handoff) -> renderSeedMigrationDialog (Intro -> Working -> Show seed ->
Error), reusing the seed_display grid + clipboard auto-clear.
- Settings: "Migrate to seed…" button (full-node gated) + persisted pending
migration state (dest address + temp datadir) for Phase 2 to adopt.
Validated end-to-end against the hd-transparent-keys daemon: the isolated
node comes up in ~4s and returns a 24-word mnemonic + a shielded z-address.
Requires that daemon (the bundled Jun-28 build lacks z_exportmnemonic). The
migration modal uses English literals for now (as renderBackupDialog does);
the whole flow gets translated once Phase 2 finalizes it. Phase 2 (sweep +
adopt) is next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>