Skip to content

fix: round-2 full-repo architecture audit (21 waves + shielded-screen consolidation)#873

Merged
lklimek merged 67 commits into
docs/platform-wallet-migration-designfrom
fix/round-2-architecture-audit
Jul 9, 2026
Merged

fix: round-2 full-repo architecture audit (21 waves + shielded-screen consolidation)#873
lklimek merged 67 commits into
docs/platform-wallet-migration-designfrom
fix/round-2-architecture-audit

Conversation

@Claudius-Maginificent

@Claudius-Maginificent Claudius-Maginificent commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Why this PR exists

  • Problem: PR feat: platform-wallet backend rewrite (spec + implementation) #860 (the platform-wallet backend rewrite) is a large migration. Once it landed, a second, broader pass reviewed the whole repository — not just that PR's diff — for structural debt: duplication, dead code, stringly-typed error handling, and violations of the project's own module-placement rules (CLAUDE.md's DET Module Placement Policy).
  • What breaks without it: nothing user-visible breaks without this PR — the codebase works today. What accumulates without it is maintenance risk: triplicated error-mapping logic that drifts out of sync, String-typed errors that bypass the type system, dead code that misleads future readers, and a few latent bugs that only surface under specific conditions (see "What was done" below for the concrete ones this PR actually fixes).
  • Blocking relationship: stacked atop PR feat: platform-wallet backend rewrite (spec + implementation) #860 — this PR's base is docs/platform-wallet-migration-design (PR feat: platform-wallet backend rewrite (spec + implementation) #860's head branch). It is meant to merge into that branch, not directly into v1.0-dev.

What was done

131 findings from a full-repo architecture audit were triaged; ~120 were fixed across 21 independently implemented and merged waves plus one funds-adjacent follow-up (the shielded-screen consolidation), each built and verified in its own isolated git worktree before integration into this branch. The remainder were deferred with self-contained TODO comments or accepted as documented, low-risk exceptions.

The two highest-risk waves — the secret/signing-surface changes (Wave 1) and the shielded-screen consolidation (CODE-098) — each went through an independent QA and/or security review pass in addition to the standard build/clippy/test gate, given their proximity to secret and fund handling.

This work is overwhelmingly internal and has no user-visible effect: type-safety improvements (stringified errors replaced with typed TaskError/WalletError variants), deduplication of near-identical UI screens and backend helpers, dead-code removal, and module reorganization to match the project's documented layering rules.

A small number of findings did surface genuine, user-observable fixes and one deliberate UI consolidation:

  • Shield, Send Privately, and Unshield are now one screen. The three previously separate screens were folded into the existing unified Send screen, each opened pre-configured for its flow. Same steps and options, including raw-hex shielded-address entry — just fewer screens to navigate. The independent QA pass on this fold also caught and fixed a fee-reservation regression (the Shield-from-Platform Max button would have under-reserved the network fee by ~6x) before it ever reached a merged state.
  • DashPay profile no longer requires a display name — only the length limits on name/bio/avatar URL are still enforced.
  • Two identity-funding UX bugs fixed: switching wallets on the identity registration/top-up screen no longer silently discards an explicitly chosen funding method, and the top-up screen's automatic wallet selection no longer pre-selects a wallet whose balance is too small or locked to actually cover the top-up.
  • The "My Tokens" tab no longer gets stuck on a loading spinner for a wallet with no identities — a latent bug in error-type comparison meant the old string-based check never actually matched, so it hung until dismissed.
  • A rare settings-save race that could drop one of two near-simultaneous setting changes is now closed (settings are saved as a single atomic read-modify-write step).

All of the above are also reflected in CHANGELOG.md. Full per-finding detail lives in the 22 merge commit messages on this branch (Merge branch 'fix/r2-w1''fix/r2-w21', 'fix/r2-code098'). The design rationale behind Wave 1's secret-decrypt dedup is in-repo at docs/ai-design/2026-07-08-secret-decrypt-dedup/.

Testing

  • cargo +nightly fmt --all -- --check — clean
  • cargo clippy --all-features --all-targets -- -D warnings — clean
  • cargo test --all-features --workspace — all green:
    • lib: 1375 passed, 0 failed, 1 ignored
    • kittest: 187 passed, 0 failed
    • e2e: 10 passed, 0 failed
    • legacy-table-surface: 3 passed, 0 failed
    • doctests: 3 run + 12 correctly ignored/compile-fail, 0 failed
    • backend-e2e: 1 passed (network-dependent tests correctly #[ignore]d in this environment)
  • One flaky test identified and confirmed pre-existing/unrelated: ui::components::identity_selector::tests::with_app_default_inert_when_global_id_not_in_candidate_list fails intermittently under parallel --lib execution (shared global secret-store lock) but passes clean single-threaded and on repeated full-suite runs — not a regression introduced by this round.

Breaking changes

None for end users. Internal API surface changed extensively (typed errors replacing stringified ones, several modules moved/renamed per the placement policy) — anything depending on this crate's internals rather than its UI would need to update call sites, but there are no external consumers of this crate.

Checklist

  • All 21 audit waves + the CODE-098 follow-up merged with individual build/clippy/test verification
  • Independent security review (Wave 1, secret-handling surface)
  • Independent QA review (CODE-098, funds-adjacent shielded-screen consolidation) — 3 findings raised and fixed before merge
  • Full workspace test suite green
  • CHANGELOG.md updated with user-facing changes
  • Human review

Attribution

🤖 Co-authored by Claudius the Magnificent AI Agent


Follow-up remediation — independent full-repo review (round 3)

After the waves above, an independent full-codebase review (single reviewer, structural/consistency/quality focus) surfaced 18 additional findings. All 18 were triaged fix and remediated across 6 sequential waves plus an adversarial QA pass, each committed on this branch:

  • Typed errors — converted the residual detail: String / reason: String cluster (21 platform_info sites + others) to #[source]-carrying variants; relocated key-format validation to model/key_input.rs with a typed error; deleted dead DashPayError variants; DPNS contract not found substring-match TODOs now reference tracked issue Replace 'contract not found' error-string matching in contested-name queries with a structural SDK variant #875 (the SDK exposes no structural variant client-side).
  • Concurrency / secrets — standardized lock-poison recovery for rebuildable state via wallet_backend/poison.rs traits + Database::locked_conn() (fail-loud preserved for the fund-safety send-index); routed single_key.rs raw vault I/O through the SecretSeam doorway; zeroized the encryption module's derived keys and typed its errors.
  • Correctness — centralized address↔network validation in model/address.rs and closed a funds-adjacent gap: the identity_credits_to_address MCP tool now rejects a destination whose network HRP mismatches the active network (previously unchecked).
  • RobustnessAppContext::wallet_arc() replaces a 15× copy-paste; corrupt address rows now log instead of silently dropping; eliminated all 263 panicking unwrap()/expect() in production paths (error-propagation where fallible, graceful MessageBanner degradation in egui frame code).
  • Hygiene — removed ephemeral review-IDs, tombstone comments, speculative dead_code, and a redundant predicate; split the 4305-line context/wallet_lifecycle.rs god-file into responsibility-scoped submodules (verified a pure zero-behavior code-move).
  • UI — the Identity Hub hero card is now a flat theme-surface card (the earlier gradient read as a misplaced blue block; wireframes govern placement, the app theme governs colors).

Verification: an adversarial QA pass independently confirmed the god-file split is a byte-for-byte pure move and the funds gate is sound, and caught two blockers (a relocated-test allow-list path + a missed repaint-loop lock) which were fixed. Final gate on the merged tree: clippy --all-features --all-targets -D warnings clean, cargo test --all-features --workspace 1596 passed / 0 failed.

Deferred follow-ups (tracked, out of scope here): typing the dashpay ECDH TaskError::EncryptionError cascade; the full SingleKeyWallet/WalletSeed String-API type-through; stretch god-file splits (send_screen, tokens_screen, the 2859-LOC tests.rs); making resolve::validate_address network-aware for Core addresses.

lklimek and others added 30 commits July 8, 2026 13:53
show_wallet_balance() on both the Create-Identity and Top-Up
"use wallet balance" screens read DetWalletBalance.total, while the
insufficient-funds banner, gate, and picker on the same screens all
read .spendable(). With immature or CoinJoin-locked funds present,
the user saw a positive "Wallet Balance" directly contradicted by a
"not enough Dash" banner one line below. Switch both to .spendable()
so all four surfaces agree.

Raised on PR #869 review (thepastaclaw); verified still live post-merge.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Full-repo audit CODE-005 (untriaged) found that handle_wallet_unlocked's
None-passphrase early return skips drive_unlock_registration, so a
non-remember unlock never re-registers the wallet with the upstream SPV
backend until next launch. User decision 2026-07-08: defer rather than
fix now, to keep the adjacent CODE-024 cleanup (Wave 11) unconstrained.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wave 9 of the wallet-backend architecture audit — caches & nits.

- Avatar cache eviction now orders entries by a sibling `det:avatar_ts:`
  timestamp index (small i64 reads) instead of deserializing every cached
  image's full bytes on each put. put/invalidate/clear/evict keep the index
  in lock-step with the byte entries.
- Trim test-only / over-public wallet-backend surface: gate `wallet_count`
  and `AuthPubkeyCacheView::delete` behind `#[cfg(test)]`; make the
  `AVATAR_TTL_MS`, `MAX_AVATAR_ENTRIES`, and `DASHPAY_REQUEST_EXPIRY_DAYS`
  constants private; delete the unused `shielded_activity`/`shielded_notes`
  stubs; correct the stale `ensure_wallets_registered` migration-engine doc.
- Name the Devnet/Regtest SPV P2P ports and collapse `spv_primary_peer_socket`
  into one match with an early `_ => None`.
- Unify the two SPV error-snippet truncations under one
  `SPV_ERROR_SNIPPET_MAX` constant and drop the shadowing `use` lines in
  `on_platform_address_sync_completed`.
- Drop the dead `TokenBalanceSnapshot` re-export.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-013/017/047/049 (round-2 audit): avatar-cache eviction stops
deserializing every image's bytes (sibling timestamp index), test-only
accessors gated, dead constants privatized, shielded stubs removed,
devnet/regtest port match collapsed, dead TokenBalanceSnapshot
re-export dropped.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…e helpers

Wave 6 of the architecture audit — context/identity_db.rs plus the shared
kv-access surface it touches.

- CODE-001: replace the `format!("{:?}", identity_type)` Debug-repr
  discriminator (four writers) and the hardcoded string filters with a
  stable `IdentityType::as_tag()` / `from_tag()` mapping. Writer and filter
  now share one source of truth, immune to a variant rename. Adds a
  round-trip test.
- CODE-007: extract `hydrate_stored_identity()`, shared by the bulk-load
  and single-get paths so both reconstruct an identity identically.
- CODE-014: introduce per-domain `err`-style free fns (`identity_err`,
  `scheduled_vote_err`, `top_up_err`, `contract_err`, `token_err`,
  `contest_err`) replacing the verbose `.map_err(|source| …)` closures and
  the fully-qualified error paths; collapse the three `*_kv()` accessors
  into one `AppContext::det_kv()`.
- CODE-035: add `scheduled_vote_keys()` and `remove_vote_voter_from_index()`
  helpers; the three hand-rolled list/delete/prune loops shrink to a few
  lines.
- CODE-041: delete the dead `load_local_qualified_identities_in_wallets`
  and its speculative `#[allow(dead_code)]`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-001/007/014/035/041 (round-2 audit): stable IdentityType tag
replaces Debug-repr discriminator; shared hydrate_stored_identity()
for bulk/single load; AppContext::det_kv() collapses three per-domain
kv accessors, per-domain error-mapper free fns replace closures;
scheduled-vote index helpers extracted; dead
load_local_qualified_identities_in_wallets removed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wave 20 dead-UI-surface cleanup:

- Delete the unused `left_wallet_panel.rs` (no opener anywhere) and its
  module declaration.
- Consolidate the three copied `Assets`/`load_icon`/`load_svg_icon`
  RustEmbed icon loaders into one shared `components/icons.rs`; drop the
  dead copy in `top_panel.rs` and the duplicate in `left_panel.rs`.
- Remove dead styled widgets `GlassCard`, `HeroSection`, `AnimatedIcon`,
  `AnimatedGradientCard` and `styled_text_edit_multiline`, plus their
  catalog rows in `components/README.md`.
- Collapse `StyledButton` to its single reachable configuration (drop the
  never-constructed `ButtonVariant`/`ButtonSize` enums and arms); remove
  the commented-out `StyledCard` builders and dead `title` field/branch;
  remove the inert `GradientButton::glow` field/builder and its no-op
  call site in `left_panel.rs`.
- Drop the six blanket `#[allow(dead_code)]` blocks in `theme.rs`. This is
  a library crate, so `dead_code` never fires on `pub` items; clippy
  `--all-features --all-targets -D warnings` is clean without them, so no
  per-item `#[expect(dead_code)]` is warranted (it would be unfulfilled).
- Delete five zero-caller `helpers.rs` items (`BUTTON_ADJUSTMENT_PADDING_TOP`,
  `render_key_selector`, `is_platform_address`, `PLATFORM_ADDRESS_HINT`,
  `PLATFORM_ADDRESS_EXAMPLES`); the `is_platform_address_string` re-export
  stays (four live callers).
- Delete `OptionBannerExt::replace` (unused alias + its essay); drop the
  duplicate inherent `AmountInputResponse::{is_valid,has_changed}` (the
  `ComponentResponse` impl provides them); `add_connection_indicator`
  returns `()`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-119/120/118/114/111/121/122 (round-2 audit): delete
left_wallet_panel.rs and dead styled-component widgets (GlassCard,
HeroSection, AnimatedIcon, AnimatedGradientCard,
styled_text_edit_multiline) plus their README catalog rows; dedup
icon loaders into components/icons.rs; drop unreachable
ButtonVariant/ButtonSize and inert GradientButton::glow; remove
blanket dead_code allows (verified clean without them, this is a lib
crate so #[expect(dead_code)] doesn't fire on pub items); delete
zero-caller helpers.rs items and OptionBannerExt::replace.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…Wave 2)

Wave 2 of the architecture audit — wallet-backend funds paths and error
integrity. Behavior-preserving throughout; funds/secret-adjacent code kept
conservative.

- CODE-004: extract `WalletBackend::seed_wallet` for the repeated
  `from_seed_bytes` construction; replace fabricated
  `PlatformWalletError::{WalletCreation,AssetLockTransaction,InvalidIdentityData}`
  string wraps with typed `SeedWalletBuildFailed` /
  `IdentityFundingAccountProvisionFailed` variants carrying the real
  `#[source]` (`key_wallet::Error`).
- CODE-011: replace `unreachable!("handled by pre-flight")` in
  `map_shielded_op_error` with a defensive `WalletBackend` fallthrough so an
  ambiguous shielded-spend result can never panic a funds path.
- CODE-012: rewrite the DashPay adapter module doc in present tense and fix
  the inverted sidecar-scope comment (2 Global / 4 Identity families).
- CODE-015: delete dead `flush_persister` (+ orphaned
  `WalletPersistenceFlushFailed`) and `broadcast_transaction`; keep the
  documented single-key signing chokepoint `sign_single_key` (intentional,
  test-exercised infra awaiting single-key send) but fix its
  source-discarding `map_err` via a typed
  `SingleKeySignFailed { #[source] DetSignerError }`.
- CODE-025: parse identity funding intent once into a local `Funding` enum —
  the repeated `AccountType` matches, `unreachable!` arms and now-impossible
  `UnsupportedIdentityFundingAccount` disappear; fix the semantically-wrong
  `WalletRegistrationXpubMismatch` reuse in `upstream_identity_from_seed`
  (-> `WalletStateInconsistent`).
- CODE-029: collapse `record_sent`/`record_incoming_contact_request` into one
  `record_contact_request(..., direction)` over a 2-variant enum; the public
  names stay thin wrappers.
- CODE-034: extract a `with_managed` closure helper for the 4x DashPay view
  find-wallet -> kv -> state -> managed_identity preamble.
- CODE-046: collapse the byte-identical `contact_sidecar_key` into
  `sidecar_key`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… integrity

CODE-004/011/012/015/025/029/034/046 (round-2 audit): seed_wallet
helper replaces 4x repeated from_seed_bytes construction; fabricated
error string-wraps replaced with typed #[source]-carrying variants;
unreachable! shielded-op fallthrough replaced with a safe error path;
dashpay.rs doc corrected to present tense and right Global/Identity
scope count; funding-account parsing collapsed into one Funding enum;
record_contact_request unifies sent/incoming; with_managed closure
dedups 4 DashPay view preambles; sidecar_key dedup. Dead
flush_persister/broadcast_transaction deleted (verified zero callers);
sign_single_key kept per its documented not-yet-ungated-send-flow
intent, with a typed SingleKeySignFailed replacing the source-discard.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
`SingleKeyView::sign_with`, `raw_key_bytes`, and the `sign_message_with_raw_key`
free fn were production-visible but reachable only from unit tests — production
JIT single-key signing goes through `SecretAccess` + `DetSigner`, which signs
inline. Move all three behind `#[cfg(test)]` (next to the tests module) and gate
their now-test-only `Message`/`Signature` imports. Delete the dead
`has_passphrase` method (zero callers) and its stale doc.

Audit R2 CODE-010.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…& key mapping

Wave 1 of the R2 architecture audit — secret access & signing surface dedup.
No behavior change: error mappings and length validation are preserved exactly.

- CODE-003: extract one `decrypt_message` + two-variant `DecryptError` in
  model/wallet/encryption.rs; route all three AES-GCM legacy readers
  (decrypt_hd_seed, SingleKeyEntry::decrypt, ClosedKeyItem::decrypt_seed)
  through it. Those readers are retained deliberately — they decode secrets
  already written to users' disks pending the lazy Tier-2 re-wrap, so this is
  the safe local dedup, NOT the upstream per-secret migration. See
  docs/ai-design/2026-07-08-secret-decrypt-dedup. Wave-16 CODE-087 still applies
  unchanged (encryption.rs is kept).
- CODE-031: make `identity_key_from_bytes` and `identity_flavored` pub(crate);
  route IdentityKeyView::get/get_protected through the shared length check
  (standardizing get's mapping onto IdentityKeyMalformed); drop the hand-rolled
  SecretSeam->IdentityKeyVault map in seal_new_identity_key_with_password.
- CODE-038: extract `versioned_bincode::{encode_tagged, decode_tagged_or}`;
  wire wallet_seed_store and SingleKeyEntry encode/decode through it, per-format
  fallback staying a closure.
- CODE-039: maybe_remember takes the plaintext by value and moves it into the
  cache box (copied once, not twice); the duplicated per-variant boxing match
  is gone.
- CODE-050: merge the identical WalletPromptMeta/IdentityPromptMeta into one
  PromptMeta.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Consolidate the machinery behind the network-scoped KV sidecars and
offline caches so five near-identical patterns live in one place.

- Generic `SidecarView<V>` (new `sidecar.rs`): the wallet-meta,
  identity-meta, and auth-pubkey-cache views become thin typed wrappers
  parameterised by infix, value type, scope, and error mapper. Domains
  needing a legacy-format fallback override `SidecarValue::read`
  (WalletMeta's dual-format migration + one-shot re-store).
- One `network_prefix` in `kv.rs`, imported at 7 sites (SPV storage
  paths, migration sentinels, the four sidecar keys). The MCP
  `network_display_name` keeps its distinct Regtest->"local" mapping.
- `kv_get_logged` / `kv_get_or_default` in `kv.rs` replace seven
  read-then-default/None call sites (selected wallet/identity, DashPay
  timestamps, avatar + contact-profile caches, auth-pubkey cache via the
  generic view).
- One `map_kv_storage_error` funnel behind all five sidecar error
  mappers; adds `TaskError::ContactProfileCacheStorage` so contact-profile
  failures stop misreporting as `AvatarCacheStorage`.
- One `bip44_account0_xpub` helper replaces four copies of the
  fund-routing gate's BIP44 account-0 predicate (2 production + 2 test).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mechanical typed-error and placement pass across the DashPay backend
subdomain, plus supporting cleanups.

- CODE-068 + CODE-051: move the pure DIP-14/DIP-15 derivation files to
  `model/dashpay_derivation/{dip14,hd}.rs` and introduce a typed
  `DerivationError` (`#[from]` bip32/secp256k1), replacing ~stringly
  `Result<_, String>` derivation signatures. Wire it into `DashPayError`.
- CODE-051: replace the four `DashPayError::Internal { message }` boundary
  wraps with typed `TaskError` at their source functions
  (`payments::load_payment_history`, `register_dashpay_addresses_for_identity`,
  `generate_auto_accept_proof`); add a typed `KeyVerificationError` for the
  three identity voting/owner/payout key checks; carry the per-vote
  `DPNSVoteResults` error as `Arc<TaskError>` instead of a pre-formatted
  String.
- CODE-053: carry DashPay payment amounts as `u64` duffs end-to-end
  (task field, backend fn, `DashPayPaymentSent` success variant); the UI
  resolves duffs at its edge — no f64 crosses the backend boundary.
- CODE-055: drop the redundant `encryption_tests.rs` (its three checks are
  already covered by superior `#[cfg(test)]` tests in `encryption.rs`);
  delete the module decl and the dead task builders.
- CODE-059: migrate the two UI callers to `Display`; delete `user_message()`.
- CODE-060: delete `ckd_pub_256`, the legacy `send_payment_to_contact`
  wrapper (rename `_impl`), the dead `profile::send_payment` /
  `identity_to_child_number`, `From<String>`, the `ToDashPayError` trait,
  the `DashPayResult` alias, the unused error-factory helpers, and the
  now-unconstructed `Internal` + dead taxonomy variants, with their
  speculative allows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…urface

Wave 4 architecture-audit cleanup. Consolidates duplicated fee, amount, and
DPNS logic onto single sources of truth and removes a dead send dialog.

- CODE-090: replace the 9-arg estimate_contract_create_detailed with a
  ContractComponents struct; delete the argument-ignoring from_platform_version
  (no callers); /1000 -> /CREDITS_PER_DUFF; derive CREDITS_PER_DASH from
  DASH_DECIMAL_PLACES.
- CODE-095: move estimate_platform_fee, both transition-based estimators,
  AddressAllocationResult and allocate_platform_addresses[_with_fee] out of
  send_screen.rs into model/fee_estimation.rs; hoist ESTIMATED_BYTES_PER_INPUT
  to one module constant. Add unit tests for the convergence, shortfall and
  fee-payer/destination paths.
- CODE-093: consolidate all credits/duffs->DASH display formatting on the model
  formatters (add format_duffs_as_dash); delete the private format_dash/
  format_credits copies, the local CREDITS_PER_DUFF re-declaration and the
  hand-rolled float conversions; one trimmed-Amount precision policy.
- CODE-077: Amount::partial_cmp returns None unless is_same_token, keeping
  ordering consistent with equality.
- CODE-092: strip_dash_suffix branches on has_dash_suffix and slices once;
  validate_dpns_input returns a fieldless NonDashDomainError.
- CODE-103: move account_summary into ui/state (non-widget view state).
- CODE-099: delete the unreachable SendDialogState, render_send_dialog,
  prepare_send_action, the field/init/render call and orphaned imports.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…dead editor

Wave 17 — DashPay UI cleanup (three findings).

PROJ-003 + CODE-097 — single avatar pipeline. Add a rendering `Avatar`
component (`ui/components/avatar.rs`) backed by an `AvatarCache` fetch state
(`ui/state/avatar_cache.rs`) that dispatches through the App Task System
(`DashPayTask::FetchAvatar` → `BackendTaskSuccessResult::DashPayAvatar`).
Disk-cache consult/populate now happens once, in the backend task
(`avatar_processing::fetch_avatar_cached`). The three screens
(contact_profile_viewer, contacts_list, profile_screen) delegate to the
component instead of each running a raw `tokio::spawn` with its own texture
map and decode/stash copies. No orphaned frame-loop spawns remain.

CODE-094 — one profile-field validator. Add `validate_profile_fields` plus
`MAX_*_CHARS` constants and `ProfileFieldError` to `model/dashpay.rs`
(char-count, matching the protocol). The backend size check and both editors
(profile_screen, identity settings) delegate to it, collapsing four divergent
copies — including the avatar-URL cap, unified on the DIP-0015 value of 2048.
Empty display name is decided legal (matches the backend and DIP-0015).

CODE-100 — delete the dead `contact_info_editor.rs` (no opener anywhere) and
its `ui/mod.rs` wiring. Extract the shared nickname/note/hidden save into
`persist_contact_private_info`, used by the three live inline editors
(contact_profile_viewer, contact_details, contacts_list).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-002/006/008/022/026 (round-2 audit): generic SidecarView<V>
replaces four near-identical sidecar wrappers (wallet_meta,
identity_meta, auth_pubkey_cache); one network_prefix helper replaces
8 duplicated inline copies; map_kv_storage_error funnels 5 error
mappers, fixing contact_profile_cache misreporting as
AvatarCacheStorage; kv_get_logged/kv_get_or_default collapse 7 KV
read call sites; bip44_account0_xpub dedups all 4 BIP44 xpub-finder
copies (prod + test).

Resolved a real conflict in upstream_identity_from_seed: this wave's
base predates Wave 2's seed_wallet() helper and WalletStateInconsistent
fix (CODE-004/CODE-025), so it independently reconstructed the wallet
and kept the old WalletRegistrationXpubMismatch variant. Combined both:
seed_wallet() for construction, bip44_account0_xpub() for the account
lookup, WalletStateInconsistent for the absence case.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…t params

Structural fee honesty and token-op deduplication across the token backend:

- FeeResult.actual_fee is now Option<u64> with a FeeResult::estimated_only
  constructor; the 22 sites that faked an actual fee by passing the estimate
  twice now report an estimate only, so the UI/MCP no longer claim a settled
  fee the platform never returned.
- Extract AppContext::execute_token_op: a single executor owning the SDK-call
  error mapping, post-broadcast side effects, and the fee tail. All 11 token
  state-transition ops shrink to their builder setup plus a delegation.
- Introduce TokenContractParams, replacing the 26-field RegisterTokenContract
  variant and the 26-argument build_data_contract_v1_with_one_token with one
  grouped struct; delete the always-NotTradeable marketplace_trade_mode field
  and its dead selector (its match arms were identical).
- run_token_task now matches the owned task, moving fields into handlers
  instead of cloning, aligning with document.rs/dashpay.rs.

Consumers (send screen, MCP identity/masternode outputs, e2e assertions)
updated for the Option-typed actual fee.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove fragile string parsing and duplication from the contract/document/
contested-names recovery paths:

- Delete extract_contract_id_from_error (parsed the contract id out of an
  error message string). register/update now use data_contract.id(), already
  in scope before broadcast, and share one AppContext::recover_contract_after_
  proof_error helper built on log_drive_proof_error with named refetch delays.
- Add a shared log_contested_proof_error for the GroveDB proof-failure shape
  the three contested-resource queries surface, collapsing three identical
  logging blocks; run_contested_resource_task now matches the owned task.
- Extract DocumentTask::fetch_document_for_mutation (fetch-by-id + bump
  revision) used by transfer/purchase/set-price, and convert the six
  multi-field DocumentTask tuple variants to named-field struct variants for
  readable construction at the UI call sites.
- Document/contract fee tails switch to FeeResult::estimated_only.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-010/031/038/039/050 (round-2 audit): test-only single-key signing
helpers scoped to #[cfg(test)]; identity-key length check + corrupt-key
error mapping unified (SecretDecryptFailed -> IdentityKeyMalformed,
more accurate for a corrupt identity key); versioned_bincode helper
dedups wallet-seed/single-key-entry framing; maybe_remember takes
plaintext by value, eliminating a double-copy; WalletPromptMeta and
IdentityPromptMeta merged into one PromptMeta.

CODE-003: adopted route (a), a shared decrypt_message/DecryptError
next to encrypt_message, after investigation showed the three
"duplicated" decrypt sites are the legacy-format reader needed for
secrets not yet re-wrapped to Tier-2 encryption (which already exists
and already lazy-migrates on unlock) -- deleting them would lock out
any user who hasn't unlocked since Tier-2 shipped. This supersedes my
earlier direction to attempt the full migration; see
docs/ai-design/2026-07-08-secret-decrypt-dedup/README.md for the
route-(b) migration playbook if it's ever scheduled. Wave-16 CODE-087
still applies unchanged.

Independently security-reviewed (Smythe, opus) before merge: all 6
changes verdict SAFE, error classification preserved exactly at all
three CODE-003 call sites, zero production callers of the now-test-only
signing helpers confirmed by tracing the JIT path, no production
match on the changed SecretDecryptFailed variant, zeroization holds on
every exit path. One LOW/non-blocking finding: the resolve loop now
unconditionally copies plaintext before checking the remember policy
(zeroized immediately, no leak, just an avoidable transient copy when
policy is None) -- left as a minor follow-up, not merge-blocking.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wave 15 of the architecture audit — strip upward edges out of `model/`,
type the MCP tool errors, and consolidate the tool helpers.

- PROJ-005: move `RootScreenType`/`ThemeMode` into `model::settings`
  (re-exported from `ui`); invert the `model → backend_task` error edges in
  `passphrase.rs` and `wallet/mod.rs` via model-local error enums wired into
  `TaskError` with manual `From` impls. `qualified_identity` keeps `TaskError`
  because the `SecretAccess::with_secret` chokepoint contract requires it
  (documented).
- CODE-070: relocate `FeatureGate` + predicate to `context/` (dead
  `FeatureGateUiExt` deleted); move token `From` conversions next to their UI
  types; `IdentityStatus → Color32` mapping to `ui::theme`; model-local
  `MasternodeInputError` converted to `McpToolError` at the tool boundary.
- CODE-072: move GroveSTARK proof generation/verification into
  `backend_task::grovestark` (data types + serialization stay in `model`),
  drop 59 info-level hex dumps, type `GroveSTARKError` with `#[source]` fields.
- CODE-073: `qualified_identity_public_key` extracts `find_wallet_path` and
  warn-skips malformed network-supplied key data instead of panicking.
- CODE-080/081/082/083: delete dead items (gate test-only ones with
  `#[cfg(test)]`), collapse the auth-key accessors behind
  `authentication_keys_matching`, drop dead `RequestType` u8 conversions and
  rename the module to `request_type`, delegate `borrow_decode` to `decode`.
- CODE-079: `tool_ctx()` returns `McpToolError` natively — kills the lossy
  `McpError → String` round-trip at all 26 tool invocations.
- CODE-085: fold the blank-`network` check into `resolve::require_network`;
  merge `validate_amount`/`validate_credits` into `validate_positive_amount`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-054/052/057/056/058/063/064 (round-2 audit): AppContext::execute_token_op
consolidates 11 token state-transition ops (builder-setup + delegation
only, per-op files shrink accordingly); FeeResult.actual_fee becomes
Option<u64> with estimated_only() so estimate-vs-actual is structurally
honest (22 sites converted, 6 genuine-actual sites keep new());
extract_contract_id_from_error string-parsing deleted in favor of
data_contract.id(); recover_contract_after_proof_error /
log_contested_proof_error share contract/contested-names proof-error
recovery; fetch_document_for_mutation extracted, DocumentTask tuple
variants become named-field structs; TokenContractParams struct
replaces a 26-field/26-arg construction, dead marketplace_trade_mode
selector deleted; both token/contested-resource dispatchers match by
value instead of cloning.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… identity-hub flag

Wave 12 of the full-repo architecture audit — app.rs boot path, feature
flags, and the context provider.

PROJ-001: collapse the four duplicated eager wallet-backend init shapes
(boot, network switch, post-onboarding auto-start, manual Connect) into one
`AppState::spawn_backend_init` parameterized by a `BackendInitReason`. Extract
the per-frame reconcilers accreted on `AppState` into small `update()`-style
structs under `src/app/reconcilers.rs` — `AccessibilityActivator`,
`SpvBlockReconciler`, `ConnectionBanner`, and `MigrationReconciler` (the latter
two return a `BackendTask` for `AppState` to dispatch, keeping the channel
chokepoint in one place). Reduces `AppState` from ~13 flat reconciler fields to
four cohesive members; no behavior change.

PROJ-004: move scheduled-vote casting off the UI thread into a new
`ContestedResourceTask::CastDueScheduledVotes` backend task. The 60s tick only
dispatches; the DB query, identity load, and casting run in the task; the DPNS
Scheduled Votes screen learns progress via `display_task_result`
(`ScheduledVotesInProgress` then per-vote `CastScheduledVote`). Removes the two
frame-aborting `return`s and names the 2-minute lateness window
(`SCHEDULED_VOTE_MAX_LATENESS_MS`).

PROJ-006: delete the `identity-hub` and `identity-hub-activity-feed` Cargo
features and every `cfg` fallback branch — the hub is now always built and
registered; the Activity tab renders its gated "coming soon" message
unconditionally.

PROJ-008: fold `context_provider_spv.rs` into `context_provider.rs`. Drop the
unused `db` field and `_db` params, make `SpvProvider::new` infallible, and
replace the `Mutex` with an `Arc<ArcSwapOption>` so the manual `Clone` impl and
lock-poison plumbing disappear. Keeps the `SYSTEM_CONTRACT_COUNT` compile check.

PROJ-009: give dir/env/logger boot setup a single owner,
`boot::prepare_environment()`, called from both `main` and
`AppState::boot_inputs`; the logger's `Once` guard keeps the second call a
no-op. Preserves the `testing`-cfg `boot_inputs` split.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rcing

CODE-095/093/090/077/092/103/099 (round-2 audit): fee estimation
helpers moved into model/fee_estimation.rs with one
ESTIMATED_BYTES_PER_INPUT constant and new convergence/shortfall/
fee-payer tests; all credits/duffs-to-DASH display formatting
consolidated on the model formatters; ContractComponents struct
replaces a 9-arg fee estimator; Amount::partial_cmp returns None
across different tokens; strip_dash_suffix branches once; dead
SendDialogState/render_send_dialog/prepare_send_action deleted;
account_summary.rs moved to ui/state/.

CODE-098 (folding the three shielded screens into the unified send
screen) deliberately deferred to its own follow-up wave.

Resolved a conflict in send_screen.rs: Wave 3 (merged first) added
format_fee_info for the new Option<u64> actual_fee; Wave 4 deleted the
now-dead format_dash/format_credits (consolidated onto model
formatters, zero remaining callers confirmed) from the same region.
Kept format_fee_info, removed the dead pair.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…erivation move

CODE-051/068/053/055/059/060 (round-2 audit): dip14_derivation.rs and
hd_derivation.rs moved to model/dashpay_derivation/ with a new typed
DerivationError wired into DashPayError; four stringly Internal{message}
boundary wraps replaced with typed TaskError returns; new
KeyVerificationError for the three identity voting/owner/payout key
checks; DPNSVoteResults per-vote error becomes Arc<TaskError>; payment
amounts carried as u64/duffs end-to-end instead of f64; dead
encryption_tests.rs deleted (already covered by better in-file
#[cfg(test)] tests); two UI error callers migrated off user_message()
to Display; 12 dead/never-constructed error items removed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PROJ-003 + CODE-097 (one task), CODE-094, CODE-100 (round-2 audit):
single avatar pipeline -- rendering Avatar component backed by a
pure-state AvatarCache dispatching fetch through a new
DashPayTask::FetchAvatar backend task; validate_profile_fields +
char-limit constants added to model/dashpay.rs; avatar-URL cap unified
on 2048; dead contact_info_editor.rs deleted with its ui/mod.rs wiring.

Behavior change worth flagging for the changelog: empty display name
is now legal (matches the backend/DIP-0015), so profile_screen no
longer blocks Save on a blank display name.

Resolved two conflicts: ui/state/mod.rs needed both this wave's
avatar_cache module and Wave 4's account_summary module declarations
(purely additive). backend_task/mod.rs's DashPayPaymentSent variant --
this wave's base predated Wave 13's CODE-053 fix (amount f64 -> u64
duffs); kept Wave 13's u64 signature and added this wave's new
DashPayAvatar variant alongside it.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PROJ-005/CODE-070/072/073/080/081/082/083/079/085 (round-2 audit):
RootScreenType/ThemeMode moved into model/settings; model-local
PassphraseError/PaymentValidationError/WalletCreationError wired into
TaskError via manual From impls; FeatureGate + predicate moved to
context/, dead FeatureGateUiExt deleted; GroveSTARK proving moved to
backend_task/, 59 info-level hex dumps removed; find_wallet_path
extracted, three network-supplied-key .expect() panics converted to
warn-skip; dead items deleted; authentication_keys_matching(predicate)
replaces two near-duplicate methods; proof_log_item renamed to
request_type; MCP tool_ctx() returns McpToolError natively at all 26
invoke sites; blank-network/amount validation consolidated.

Resolved 3 merge conflicts (all Wave-3/Wave-4 base-drift, mechanical):
register_contract.rs and update_data_contract.rs each had a stale
request_type::RequestType import left over from before Wave 3's
CODE-056 refactor removed the code that used it -- dropped as dead.
wallets_screen/mod.rs needed the NEW context::feature_gate::FeatureGate
path (post-move) plus Wave 4's format_duffs_as_dash, dropped an unused
model::amount::Amount import. Also fixed one non-conflicting stale
import this rename missed: backend_task/contested_names/mod.rs's
log_contested_proof_error (added by Wave 3, same rename collision).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ename

Missed in the Wave 15 merge commit -- contested_names/mod.rs's
log_contested_proof_error (added by Wave 3) imported RequestType from
the old model::proof_log_item path, which Wave 15 renamed to
model::request_type. Same collision class as the two request_type
import conflicts resolved in that merge, just not flagged by git
since the lines didn't overlap.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…t_provider

PROJ-001/004/006/008/009 (round-2 audit): four duplicated eager
wallet-backend init shapes collapsed into one
AppState::spawn_backend_init + BackendInitReason; accreted per-frame
reconcilers extracted into app/reconcilers.rs; scheduled-vote casting
moved into a ContestedResourceTask backend task; identity-hub /
identity-hub-activity-feed features deleted entirely;
context_provider_spv.rs folded into context_provider.rs, db field/params
dropped, SpvProvider::new infallible, Arc<ArcSwapOption> replaces
Mutex; single boot::prepare_environment() owns dir/env/logger setup.

Resolved one conflict in context/mod.rs: needed both Wave 15's
context::feature_gate::FeatureGate import (post feature_gate move) and
this wave's context_provider::SpvProvider import (post
context_provider_spv.rs fold).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…, param & fn moves

Wave 7 of the round-2 architecture audit on context/contract_token_db.rs:

- CODE-018: propagate TokenConfiguration encode failures through a new typed
  TaskError::TokenConfigSerialization variant instead of silently no-oping;
  drop the pre-C7 tombstone comment.
- CODE-019: extract load_sorted_tokens(); both token listers map its output,
  killing the warn-vs-silent drift on unparseable token keys.
- CODE-030: drop three dead parameters — get_contracts pagination
  (limit/offset, all callers passed None,None), ConnectionStatus::tooltip_text
  app_context, and request_to_det_contact's unused ContactRequest.
- CODE-033: move remove_wallet to context/wallet_lifecycle.rs, next to
  register_wallet and its existing tests.
- CODE-044: iterate the system contracts for the five get_contracts insertion
  blocks; fold the five system-contract load blocks in context/mod.rs into one
  local closure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
lklimek and others added 13 commits July 8, 2026 17:23
…ontext nits

CODE-016/023/027/040/042/045 (round-2 audit): write-only Core-RPC health
plumbing deleted (rpc_online/rpc_last_error + dead ChainLocks handling,
zero readers); settings updaters consolidated into a closure-based
update_app_settings() that holds the cached_settings write guard across
the full read-modify-write, fixing the lost-write race under concurrent
updates (two new tests: sequential RMW + a 6-thread concurrent-distinct
-field-flip proving no lost writes); seed_platform_address_entries()
extracted, both platform-address seeding callers now thin adapters over
it; contest-duration magic numbers named with spec refs; four small
dedups (default_platform_version arms, spv_connected inlined,
scheduled-vote fns take borrowed slices, identity dead map_kv_error
wrapper removed).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-107: Extract one generic add_subscreen_chooser_panel + nav_button
(subscreen_chooser_panel.rs); the four dashpay/dpns/tokens/tools panels
now build item lists and delegate. Move ToolsSubscreen next to the tools
screens (ui/tools/mod.rs), matching its siblings.

CODE-109: Extract modal_chrome (backdrop + centered bordered Window) from
passphrase_modal; rebase passphrase_modal and the confirmation/selection/
info dialogs onto it. Dedupe the NOTHING sentinel into modal_chrome. Drop
ConfirmationDialog's write-only `status` field (current_value never read it).

CODE-110: Rewrite ScreenType::eq as explicit payload arms + discriminant
equality (no `_ => false`). Collapse the seven ScreenLike delegation methods
onto one exhaustive delegate_to_screen! macro — a missing variant is now a
compile error. (Macro, not a dyn accessor, to preserve inherent-method
resolution for screens with an inherent refresh.)

CODE-113: Extract key_eligibility, render_key_combo, render_no_eligible_key_group
and render_info_section in ui/helpers.rs; the two key choosers and two success
screens delegate. Unify the divergent no-eligible-key wording.

CODE-115: Wire the contract-chooser right-click via Response::context_menu on
the header row (Copy Hex / Copy JSON), removing the unreachable manual Window
and its three dead state fields; derive Default; flatten the font-size ladder.

CODE-117: Extract stage_progress + window_fraction in network_chooser_screen;
the five progress calculators shrink to thin wrappers.

Also folds in a stray cargo-fmt import-order fix in
backend_task/contested_names/mod.rs (Wave 15 rename residue).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Model-layer cleanups across the wallet crate. All error paths in
model/wallet now carry typed WalletError up to the TaskError boundary;
the platform-payment registration, coin-type mapping, and AES-GCM
envelope shapes are single-sourced.

- CODE-071: model/wallet/mod.rs derivation/registration functions return
  Result<_, WalletError> instead of stringly errors (~17 KeyDerivation
  erasures + 5 bare erasures gone). New typed WalletError variants:
  PublicKeyParse, AccountDerivationPath, PlatformAddressConversion,
  AddressNetworkMismatch. TaskError::WalletAddressProviderSetupFailed
  now holds a typed #[source] instead of a String. database/utxo.rs
  get_utxos_by_address returns rusqlite::Result.
- CODE-076: delete Wallet::coin_type + four inline network matches; call
  the canonical coin_type_for_network everywhere.
- CODE-078: extract Wallet::register_platform_payment_entry; the five
  duplicated known/watched insert blocks collapse to one call. The
  vestigial post-T-W-01 `register` param on
  generate_platform_receive_address_with_seed is dropped.
- CODE-087: replace the (Vec<u8>,Vec<u8>,Vec<u8>) crypto triple (and its
  type_complexity allows) with a named EncryptedEnvelope struct.
- CODE-088: one summary log per platform-address sync; the per-key dumps
  in WalletAddressProvider::on_address_found and QualifiedIdentity::sign
  move behind their failure paths, off the hot loop.
- CODE-089: delete the unused DASH_SECRET_MESSAGE constant.
- CODE-091: cross-link + status-mark the three single-key modules (LIVE
  imported-key sidecar vs the LEGACY Decision-#7 runtime/DB pair).

cargo clippy --all-features --all-targets -D warnings clean; full lib
suite (1364 tests) green.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-107/109/110/113/115/117 (round-2 audit): generic
add_subscreen_chooser_panel + nav_button replace 4 near-identical
dashpay/dpns/tokens/tools panels (~500 dup lines gone); modal_chrome.rs
extracted from passphrase_modal, rebasing confirmation/selection/info
dialogs onto it; ScreenType::eq rewritten as explicit payload arms +
discriminant catch-all, seven ScreenLike delegation methods collapsed
onto one delegate_to_screen! macro (preserves inherent-method
resolution, verified behavior-identical against every non-default
override); helpers.rs key-eligibility rendering deduped across 3
screens; contract-chooser context menu made actually reachable via
right-click (was a dead fixed-pos Window nothing ever opened); five
progress-stage calculators reduced to thin wrappers over two shared
functions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-071/076/078/087/088/089/091 (round-2 audit): derivation/
registration fns return typed Result<_, WalletError> instead of
stringifying errors (new PublicKeyParse/AccountDerivationPath/
PlatformAddressConversion/AddressNetworkMismatch variants;
TaskError::WalletAddressProviderSetupFailed's detail field typed);
Wallet::coin_type + 4 inline match-network copies collapsed onto
coin_type_for_network; register_platform_payment_entry extracted,
collapsing 5 duplicated known/watched insert blocks; the
(Vec<u8>,Vec<u8>,Vec<u8>) triple replaced by a named EncryptedEnvelope
struct (CODE-003 resolved as route (a), local dedup, so encryption.rs
stayed and this applies as planned); per-key debug dumps moved behind
their failure paths, one summary log per platform-address sync;
dead DASH_SECRET_MESSAGE deleted; the three single-key modules
cross-linked and status-marked (LIVE sidecar vs LEGACY runtime/DB
pair) rather than renamed, for zero-risk traceability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…E-098)

The three standalone shielded screens (shield / shielded-send / unshield)
duplicated flow logic the unified WalletSendScreen already owns end to end
(dispatch + result handling for every shield/unshield/private-send path).
Consolidate them into routes on the one canonical send screen.

- Add `SendFlow` preset {General, Shield, ShieldedSend, Unshield}. A preset
  locks the source — and, for Shield, auto-targets the wallet's own pool with
  a sentinel shielded destination (the shield dispatch ignores the destination
  address) — so the screen shows only the controls that flow needs while
  reusing the unified validation, fee/amount limits, and dispatch.
- Extract shared shielded-recipient parsing into
  `model::address::parse_shielded_recipient` (Bech32m or 43-byte hex) so the
  send dispatch and any validation path cannot diverge.
- Route the Shielded tab's Shield / Send (Private) / Unshield buttons to open
  the unified send screen pre-configured for the flow.
- `ScreenType::WalletSendScreen` now carries the `SendFlow`; remove the three
  `ScreenType`/`Screen` shielded variants and delete the standalone screen
  files.

Behavioral parity preserved for all five shielded dispatches. One delta:
raw-hex shielded recipient entry is dropped from the UI (AddressInput accepts
canonical Bech32m only); the parser still accepts hex at dispatch.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…renderers

Collapse the seven near-identical single-shot token action screens (pause,
resume, freeze, unfreeze, destroy-frozen-funds, burn, mint) onto one
parameterised scaffold, and merge the two duplicated control-rule renderers.

- Add `TokenActionScreen<A: TokenAction>` (ui/tokens/token_action_screen.rs):
  the shared shell — authorization resolution, wallet unlock, advanced key
  chooser, per-action form slot, public note, fee estimate, confirmation
  dialog, success screen and status. Each action supplies only its diffs
  (labels, rules accessor, form, task builder, success variant) via the
  `TokenAction` trait; the seven screens become thin `type` aliases + a small
  action struct. Net ~3,900 lines removed.
- Fold the token authorization check into the shared scaffold's one call site
  via the previously zero-caller `check_token_authorization`; also route
  `update_token_config`'s inline resolver through it.
- Merge `render_mint_control_change_rules_ui` into
  `render_control_change_rules_ui` via an optional `MintRecipientSection`,
  deleting the ~180-line duplicate; update all call sites.
- `change_context` now rebinds token-screen context through `.common` via a
  `set_app_context` setter and a `common_set` macro arm.

Deferred (TODO markers in-file): set_token_price migration onto the scaffold
(large pricing form; verb-based auth message template does not fit "set price"
grammatically) and the TokensScreen god-struct per-subscreen split.

cargo fmt + clippy (all-features, all-targets) clean; lib unit tests and the
kittest suite pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CODE-104/105/108 (round-2 audit): new parameterized
TokenActionScreen<A: TokenAction> is the shared shell (auth resolution,
wallet unlock, advanced key chooser, form slot, public note, fee
estimate, confirmation dialog, success screen, status/refresh/
display_task_result) for 7 of the 8 token action screens (pause/
resume/freeze/unfreeze/destroy_frozen_funds/burn/mint), each now a type
alias + small Action struct; the scaffold's constructor is the single
call site of the previously zero-caller check_token_authorization,
subsuming 7 inline copies; render_mint_control_change_rules_ui merged
into render_control_change_rules_ui via an optional bundle, deleting a
~180-line duplicate across 12 call sites. Net ~3900 lines removed.

set_token_price (8th screen) and the TokensScreen god-struct split are
deliberately left as TODOs -- the former needs a UX decision on an
ungrammatical verb template, the latter is a separate large refactor
that would rewire thousands of field references; both flagged for
their own future dedicated work rather than riding along in this wave.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- QA-002: Shield-from-Platform "Max" now reserves the two-action shielded-fee
  headroom (>50M credits) instead of the plain platform-transfer estimate
  (~8M). ShieldFromBalance pays the shield fee from the same balance as the
  amount, so the old reserve under-shot ~6x and a Max attempt was rejected
  upstream. New model helper `shield_from_balance_fee_headroom` keeps the fee
  math in model/fee_estimation.rs. Shield-from-Core parity was already correct.
- QA-001: restore raw-hex shielded-recipient entry. `AddressKind::detect` and
  `AddressInput::validate_shielded` now accept the 43-byte (86-hex-char) form
  via the shared `parse_shielded_recipient`, matching what the old private-send
  screen advertised.
- QA-003: on a network switch, `WalletSendScreen` now drops the wallet seed
  hash, source, destination, and amount (`reset_for_network_switch`) so a
  preset flow can no longer resurrect stale cross-network state / balance.

Tests: model coverage for the fee headroom (asserts the shielded-fee reserve,
not the transfer estimate) and for hex shielded detection at both the model
and AddressInput layers.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…unified send

Round-2 audit finding CODE-098 ("unified send screen is the way to
go, make it reusable and reuse"): shield_screen.rs, shielded_send_screen.rs,
and unshield_credits_screen.rs deleted; WalletSendScreen gains a
SendFlow preset enum {General, Shield, ShieldedSend, Unshield} +
with_flow() builder, locking source/destination appropriately per
flow (Shield's destination is dispatch-ignored, satisfied by a
discriminant-only sentinel). shielded_tab.rs routes through
open_send_flow() instead of three standalone screens. Shared shielded-
recipient parsing (bech32m + 43-byte hex) extracted to
model::address::parse_shielded_recipient. Net -178 lines from the fold
itself.

Independent QA pass (funds-adjacent surface, given this touches
shield/unshield/shielded-send dispatch) found and this includes fixes
for:
- Shield-from-Platform's Max button under-reserved the shield fee by
  ~6x versus the old screen (new shield_from_balance_fee_headroom()
  in model/fee_estimation.rs restores the correct >50M-credit reserve).
- Raw-hex shielded-recipient entry was dropped from the UI even though
  the model parser still accepted it (AddressInput/AddressKind now
  detect and validate the 43-byte hex form too).
- A network switch didn't clear a preset flow's stale wallet/seed-hash
  state, risking a stale cross-network balance display (send stayed
  blocked, but misleading); WalletSendScreen::reset_for_network_switch()
  fixes it.

Sentinel-destination handling, dispatch routing for both Shield
sources, and the unlock gate were independently verified correct with
no changes needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Strip development-history narration and ephemeral review IDs from comments;
keep present-state invariants only. No executable logic changed.

- CODE-021: rewrite archaeological rationale (det_platform_signer, snapshot,
  wallet_backend/mod, secret_seam, wallet_seed_store, wallet_lifecycle
  stop_spv); dedup the AlreadyOpen restart-in-place rationale to one copy.
- CODE-067: present-state the Phase B/D/E + Post-D4c narration in
  backend_task/shielded/mod.rs and dashpay.rs; stale "until Phase-E lands"
  reworded now the push writer exists.
- CODE-084: drop RUST-001 / 6a2818cd IDs from fee_estimation.rs and
  wallet/single_key.rs comments; keep the durable TS-DBG-01 test-spec ID.
- CODE-102: delete the shielded_tab tombstone (keep "Fund-moving results
  only."); present-state the "replaces the dropped/legacy…" docs in
  wallets_screen, contacts_list, contact_details.
- CODE-106: drop INTENTIONAL(CMT-010/RUST-003/CODE-003) prefixes and FIX N
  markers in theme, message_banner, address_input, add_new_identity_screen,
  key_info_screen.
- PROJ-007: remove the removed-subsystem ZMQ sentence and the "RPC, ZMQ"
  ConnectionStatus residue from CLAUDE.md; protoc v25.2+ verified against CI.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…(final)

CODE-021/067/084/102/106, PROJ-007 (round-2 audit, run last since it
touches files across every prior wave): comment-only sweep, zero
executable logic changed. Development-history/archaeology narration
("Phase B/D/E", "Post-D4c", "used to read", ephemeral commit-hash/
finding-ID references) rewritten to present-state descriptions or
concrete TODOs across det_platform_signer.rs, snapshot.rs,
wallet_backend/mod.rs, secret_seam.rs, wallet_seed_store.rs,
wallet_lifecycle.rs, backend_task/shielded/mod.rs, dashpay.rs,
model/fee_estimation.rs, model/wallet/single_key.rs, shielded_tab.rs,
wallets_screen/mod.rs, contacts_list.rs, contact_details.rs, theme.rs,
message_banner.rs, address_input.rs, add_new_identity_screen/mod.rs,
key_info_screen.rs; durable test-spec IDs (TS-DBG-01) kept. CLAUDE.md
corrected: removed the stale per-platform ZMQ mention and narrowed the
ConnectionStatus health list to SPV+DAPI (RPC/ZMQ subsystems were
retired in the platform-wallet migration); protoc v25.2+ claim
verified accurate against CI config.

This is the last of 21 planned waves plus the CODE-098 follow-up --
round 2 of the full-repo architecture audit is now fully merged.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds Changed/Fixed entries for the genuinely user-observable outcomes
of the round-2 architecture audit (21 waves + CODE-098): the shielded
screens' fold into the unified Send screen, DashPay's now-optional
display name, two identity-funding UX fixes, the My Tokens
loading-spinner hang, and a settings-save race. Internal refactors,
dedup, and error-typing from the same round are intentionally omitted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/round-2-architecture-audit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

lklimek and others added 12 commits July 9, 2026 09:57
The Identity Hub hero card painted its 14%-opacity gradient band using
`ui.max_rect()` — the available space, not the card's content bounds — so
on the Home tab the band bled downward through every sibling widget below
the card (quick actions, onboarding checklist, recent activity).

Reserve a shape slot before laying out content, then fill it afterward
from `ui.min_rect()`, confining the band to the card's actual bounds. Add
unit tests asserting the strips stay within the given rect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…urces

Convert the largest cluster of `detail: String` TaskError variants to typed
`#[source]`/`#[from]` variants and delete the dead ones, tightening the
Display/Debug separation and reducing `result_large_err` pressure.

- platform_info: introduce a `WithdrawalParseError` source enum (platform-value
  field errors, missing/invalid timestamps, unrecognized status, boxed
  ProtocolError for the daily limit). `WithdrawalDocumentParsingError` now wraps
  it via `#[from]`; the two duplicated per-document formatting blocks are
  extracted into `format_withdrawal_line` / `format_completed_withdrawal_line`.
  `ShieldedSyncFailed` now carries `Box<SdkError>`.
- key input: move `verify_key_input` out of the backend into `model/key_input.rs`
  as the stateless single source of truth, returning a typed `KeyInputError`
  (NotHex / BadWif / UnsupportedLength) with complete, i18n-ready sentences.
  `KeyInputValidationFailed` becomes a transparent `#[from]` wrap, removing the
  double-naming and fragment concatenation; callers use `?`. Unit-tested.
- dashpay: delete the unconstructed `reason: String` variants BroadcastFailed,
  QueryFailed, PlatformError, RateLimited and their dead retry-classification
  clone arms; make the sole remaining recoverable variant `NetworkError`
  fieldless.
- delete unconstructed TaskError variants (WalletUtxoReloadFailed,
  WalletBalanceRecalculationFailed, WalletPaymentFailed, UtxoUpdateFailed,
  SerializationError, RpcProviderCreationFailed, AssetLockTransactionBuildFailed,
  ShieldedTreeUpdateFailed, ShieldedNullifierSyncFailed,
  ShieldedMerkleWitnessUnavailable); make InvalidPrivateKey and
  NetworkContextCreationFailed fieldless (their strings were hardcoded, not
  upstream errors).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The 'contract not found when querying from value with contract info'
substring match in the three contested-name query retry loops is a
deliberate workaround: the condition originates server-side as
QuerySyntaxError::DataContractNotFound and reaches the client only as a
gRPC Internal status with message text, so no client-side structural SDK
variant exists to match on yet. Reference the tracking issue at all three
sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… the secret seam, and type encryption key hygiene

Three wallet-backend hardening changes.

- Lock-poison policy: add `wallet_backend/poison.rs` with `read_recover` /
  `write_recover`, which recover a poisoned `RwLock` guard instead of erroring.
  The single-key in-memory index and the secret session cache guard derived,
  rebuildable state, so recovery is correct and self-healing. Route the
  single-key index (imports, alias, forget, rehydrate, list) and the
  `SecretAccess` session cache (lookup, eviction, remember) through it, deleting
  the two lying poison mappings — `ImportedKeyNotFound` (told the user to
  re-import) and `SecretDecryptFailed` (claimed a decrypt failure).

- Secret seam: route every raw vault access in `single_key.rs` through
  `SecretSeam::{put_secret, get_secret, put_secret_protected, delete_secret}`
  instead of hand-rolled `SecretStore` calls, so imported keys honor the same
  chokepoint and failure variant (`TaskError::SecretSeam`) as their siblings.
  The verify-passphrase wrong-password signal is preserved by matching the
  seam's typed `SecretSeam` source structurally.

- Encryption hygiene: `derive_password_key` returns `Zeroizing<Vec<u8>>` and
  `ClosedKeyItem::decrypt_seed` returns `Zeroizing<[u8; 64]>` (copied straight
  into the zeroizing buffer, no bare stack copy), so the derived AES key and the
  decrypted seed wipe on drop. Introduce a typed `EncryptionError`
  (WrongPassword / Malformed / KeyDerivation / Encryption, Everyday-User
  messages) and return it from the encryption primitives and the two
  `ClosedSingleKey` crypto methods, replacing their `Result<_, String>`. The
  broad `SingleKeyData::open` / `WalletSeed::open` / `SingleKeyWallet::new`
  String APIs (pre-existing model/wallet debt that mixes crypto with key-parse
  errors) render the typed error through `Display` at the boundary; a full
  type-through of those APIs is deferred.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… the credits-to-address MCP destination

Two coupled changes around address↔network validation.

- Single source of truth: add `validate_platform_address_for_network` and its
  Orchard twin `validate_orchard_address_for_network` to `model/address.rs`,
  returning a typed `AddressNetworkMismatch` (Everyday-User message). They parse
  the bech32m HRP (`dash1…` mainnet, `tdash1…` testnet, case-insensitive) and
  reject a cross-network address. `address_input.rs` delegates its hand-rolled
  `validate_platform` / `validate_shielded` prefix checks to these, keeping the
  same banner copy; the GUI's format/length/case checks stay put.

- Close the MCP network gap (funds-adjacent): `identity_credits_to_address`
  decoded the destination with `PlatformAddress::from_bech32m_string` and no
  network check, so a mainnet `dash1…` address could be paid on testnet (or
  vice-versa). Gate the parsed destination against `ctx.network()` via the new
  model validator, returning `InvalidParam` on mismatch — mirroring the withdraw
  tool's Core `require_network` guard. The required `network` param only pins the
  active network; it never validated the destination, which was the gap.

The `resolve::validate_address` first-Base58-char Core heuristic is left as-is:
it is a network-agnostic format sanity check with no network parameter, and Core
network validation is already performed via `Address::require_network` at each
tool. Adding a model Core validator would duplicate that with no clean
delegation target, so it is deferred rather than widened here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…upt address rows; drop per-item logging loops

Three wallet-layer cleanups from PR review.

- Dedup wallet lookup: add `AppContext::wallet_arc(&self, &WalletSeedHash) ->
  Result<Arc<RwLock<Wallet>>, TaskError>` as the single source of truth for the
  "look up a wallet arc or `WalletNotFound`" pattern, replacing 15 copy-paste
  blocks across backend_task/{wallet,identity,shielded} and context. The helper
  recovers a poisoned lock via `wallet_backend::poison::read_recover` rather than
  erroring: the in-memory wallet map is rebuildable, so recovery matches the
  Wave-2 poison discipline for rebuildable state. This is a behavior change on
  the rare poison path — the replaced sites previously surfaced `LockPoisoned`
  via `.read()?`; they now self-heal, consistent with `mcp::resolve::wallet_arc`
  (which now delegates to this helper and re-wraps the id-bearing MCP error).

- Loud failure on corrupt rows: `database/wallet.rs` `get_wallets` skipped
  unparseable address rows silently. Silently dropping an address could hide a
  missing key and lead to lost funds, so log a `warn!` (row index + error) and
  continue, matching the wallet_backend hydration skip-logging discipline.

- Drop per-item logging loops: `fetch_platform_address_balances` logged every
  address/balance/nonce and `transfer_platform_credits` logged every input
  address/amount at `info!`. Both keep their aggregate summary line; the
  per-address financial detail is removed — it violates the no-loop logging rule
  and should not sit in plaintext logs at the default level.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ropagation and graceful UI degradation

Eliminate every bare `.unwrap()` on the non-test production paths (~150 sites)
so a broken invariant degrades instead of aborting the process. Applied by
category:

- Lock poison recovery: extend `wallet_backend/poison.rs` with a `Mutex`
  `lock_recover` free fn plus ergonomic `RwLockRecover` (`read_recover` /
  `write_recover`) and `MutexRecover` (`lock_recover`) extension traits. Route
  the wallet/step/settings `RwLock` and the DPNS/identity/token-search `Mutex`
  screen-state locks — all rebuildable in-memory state — through them, so a
  thread panicking mid-update never wedges the UI. Unify the coordinator gate's
  action mutex on the same recovery policy (its `should_fire` already tolerated
  poison via `is_ok_and`, while `arm`/`try_fire`/`reset` panicked). The
  `Mutex<Connection>` gains a `Database::locked_conn()` helper that recovers the
  guard: a `rusqlite::Connection` carries no invariant a panic can break, so
  recovering avoids cascading one unrelated panic into every later DB call.

- Graceful UI fallback: egui frame paths return `AppAction`/`BackendTask` and
  cannot `?`. The `Option::unwrap()` selection reads now guard with `let-else` /
  `if let` and degrade — an actionable `MessageBanner`, an early return, or a
  logged skip — instead of panicking. The document-action builders share a new
  `require_selections()` helper that returns `BackendTask::None` with a banner
  when a selection is missing.

- Invariant expects: unwraps that are provably infallible after a preceding
  guard, on constants, or on a construction invariant are upgraded to
  `.expect("invariant: …")` so a future regression fails loudly with context
  rather than a bare panic. BIP32 child indices are documented as `< 2^31`
  (hardened constants, small coin type, and hash indices masked with
  `& 0x7FFFFFFF`).

The `dashpay_increment_send_index` mutex keeps its documented fail-loud
poison contract: a panic mid-increment can leave the address-index counter
inconsistent, and surfacing that is safer than handing out a duplicate index.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, and a redundant predicate

Four small cleanups; every deletion grep-verified against both the lib and the
integration-test crates first.

- Ephemeral review-IDs: strip the per-run tags from committed source — the
  `RUST-002` tag on the app.rs banner-flash TODO (the substance and the #660
  issue link stay) and the `CODE-027` prefixes on two settings_db doc comments
  (replaced with a plain statement of the read-modify-write guarantee). The
  standards-body `RUSTSEC-2025-0141` reference is deliberately kept.

- Tombstones: drop the two deleted-code tombstones in `database/wallet.rs`
  `get_wallets` (git is the record) and renumber the trailing "step 8" to
  "step 4" so the step comments read coherently.

- Dead code: delete genuinely-unused speculative items and the code they guard
  — three unconstructed `BackendTaskSuccessResult` variants, the `CoreTask::
  GetBestChainLock` variant plus its handler / PartialEq arm / classification
  test (and the now-orphaned `AppContext::rpc_error_with_url` its handler was
  the sole caller of), a duplicate `decrypt_private_data`, an unused avatar
  `to_grayscale`, three unused `KeyStorage` accessors, an unused token-info
  constructor, a write-only `core_address` field, and a dead visualizer search
  field. Items that turned out to be live are handled correctly instead of
  deleted: `AppAction::Refresh`, `IdentityTask::SearchIdentityFromWallet`,
  `KeyStorage::keys_set`, and `send_screen`'s `selected_wallet_seed_hash` had
  stale `#[allow(dead_code)]` (the attribute is simply removed); `CORE_APPLICATION`
  is non-Linux-only, so it becomes `#[cfg(not(target_os = "linux"))]` and is only
  compiled where used; and `MessageBanner::{has_global, set_auto_dismiss}` plus
  `build_identity_registration` are exercised by the kittest / backend-e2e
  integration-test crates (a separate compilation the lib does not see), so they
  keep `#[allow(dead_code)]` with a reason comment rather than `#[expect]`, which
  would be unfulfilled under the `--all-targets` gate.

- Redundant predicate: inline `is_distinct_change_candidate` (a one-line
  `candidate != destination` wrapped in a documented fn with two dedicated
  tests) at its single call site and delete the fn and its tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Present-state architectural rationale about a module boundary — Platform
per-address balances and the sync cursor are owned by the upstream coordinator,
so DET keeps no at-rest copy and warm-starts from the coordinator's first push.
This is an external-constraint explanation, not a git-history tombstone, and
was removed in error during the tombstone cleanup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y-scoped submodules

Pure code-move: converts the 4305-line `src/context/wallet_lifecycle.rs` (the
repo's largest file) into a module directory whose `impl AppContext` methods are
grouped by responsibility across submodules — the same multi-impl-of-one-struct
layout `wallet_backend/` already uses. No logic, signature, or behavior change;
the only signature edits are two visibility widenings noted below.

New structure (`src/context/wallet_lifecycle/`):
- `mod.rs` — module docs, shared imports/constants, the three free helpers
  (`spv_storage_dir`, `clear_spv_chain_storage`, `cleanup_legacy_shielded_files`),
  the `AppContext::wallet_arc` lookup, and the submodule + test declarations.
- `spv.rs` — backend wiring and chain-storage lifecycle: `clear_spv_data`,
  `clear_network_database`, `ensure_wallet_backend_and_start_spv`,
  `mark_spv_error`, `stop_spv`.
- `registration.rs` — single-key import and HD-wallet registration/persistence:
  `import_single_key_wif`, `verify_single_key_passphrase`, `register_wallet`,
  `register_wallet_upstream`, `write_seed_envelope`, `write_wallet_meta`,
  `promote_seed_to_session`.
- `removal.rs` — `remove_wallet`.
- `bootstrap.rs` — address derivation and post-unlock warmup: the
  `bootstrap_*`/`wallet_needs_bootstrap` methods, managed-identity reconciliation
  and contact-account/auth-key warming, and the identity-discovery queue.
- `unlock.rs` — lock/unlock handling and the open-wallet snapshot:
  `handle_wallet_unlocked`, `drive_unlock_registration`, `handle_wallet_locked`,
  `open_wallets`, `unregistered_open_wallet_count`.
- `tests.rs` — the `#[cfg(test)]` module, unchanged.

Submodules access `AppContext`'s private fields as descendants of `context`
(no field-visibility change) and share `mod.rs`'s imports via `use super::*`.
The only forced visibility widenings: `open_wallets` and
`reconcile_managed_identities` go from private to `pub(super)` because they are
now called from a sibling submodule (and, for both, from the test module).

Verified move-integrity: the set of `impl AppContext` method names is identical
before/after, the test-function count is unchanged (56), clippy
`--all-features --all-targets -D warnings` is clean, and the full `--lib` suite
passes 1393/1393.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Identity Hub hero card rendered a 14%-opacity blue→purple wash that
read as a heavy blue block rather than the subtle diagonal accent the
wireframe implied. Per the design rule that wireframes govern placement
while colors follow the app theme, drop the gradient entirely: the card
is now a flat `DashColors::surface` fill with the existing border and
elevation.

This supersedes the earlier gradient-containment commit — with no
gradient, the reserved-slot machinery and its geometry tests are removed
along with the now-unused colour-lerp helper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sts; recover remaining identities-screen lock

Resolves the QA blockers on the branch.

- Merge gate (workspace test) was red: the Wave-6 god-file split moved the
  wallet_lifecycle test fixtures — which construct legacy-schema `wallet` /
  `single_key_wallet` rows — from the monolithic file into the split module's
  `tests.rs`, but `tests/legacy_table_surface.rs`'s allow-list still pointed at
  the old path, so TC-DEV-001/003 fired on the moved (previously-allowed)
  fixtures. Repoint the single allow-list entry to
  `src/context/wallet_lifecycle/tests.rs`. Verified the guard patterns
  (`FROM wallet`, `INTO single_key_wallet`) appear only in that test file — no
  production submodule contains legacy-table SQL — so the exemption is unchanged,
  only its path.

- The unwrap sweep missed one `self.identities.lock().unwrap()` in
  `identities_screen.rs::ui()` (it was split across lines, so the single-line
  pass skipped it): on a poisoned identities mutex that screen would panic on
  every repaint instead of self-healing. Convert it to `.lock_recover()` like its
  ten siblings. A multi-line re-scan of the sweep-touched files found the same
  line-wrapped gap in four more production wallet/single-key `RwLock` reads
  (add_existing / add_new identity screens, wallets screen); convert those to
  `.read_recover()` too. Remaining line-wrapped matches are all test-only code
  (`#[cfg(test)]` modules, `kv_test_support`) and are left as-is.

- Refresh the `poison.rs` module doc to describe its present usage surface — the
  loaded-wallet map, per-screen view state and settings cache, the Identities /
  DPNS / token-search screen caches, the coordinator-gate slot, and the SQLite
  connection mutex — rather than only the two wallet-backend caches it originally
  named, and note the DashPay address-index mutex's deliberate fail-loud
  exception.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@lklimek lklimek marked this pull request as ready for review July 9, 2026 14:01
@lklimek lklimek merged commit 61df418 into docs/platform-wallet-migration-design Jul 9, 2026
6 checks passed
@lklimek lklimek deleted the fix/round-2-architecture-audit branch July 9, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants