Conversation
…627) During SPV reconciliation, per_address_sum only contains addresses with current UTXOs. Addresses whose funds were fully spent never had their balance reset to zero, causing the address table to display stale non-zero balances even though UTXO count correctly showed 0. Now explicitly zeroes address_balances for any known address absent from the refreshed UTXO map before applying current sums. Closes #571 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…613) * fix: handle malformed YAML gracefully in load_testnet_nodes_from_yml Replace .expect() with match expression to avoid panicking when .testnet_nodes.yml contains malformed YAML. Instead, logs the error with tracing::error and returns None, allowing the application to continue without crashing. Closes #557 Co-authored-by: lklimek <lklimek@users.noreply.github.com> * fix: propagate YAML parse errors to UI and remove unwrap calls - Change load_testnet_nodes_from_yml to return Result<Option<TestnetNodes>, String> so parse errors display in the UI error banner instead of only logging - Use explicit type annotation on serde_yaml_ng::from_str::<TestnetNodes> - Replace .unwrap() with .and_then() in fill_random_hpmn/fill_random_masternode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: lklimek <lklimek@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: move doc/ contents into docs/ and update references Consolidate documentation under a single docs/ directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: CLAUDE.md should write manual test scenarios for PRs --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…tion (#603) * build(flatpak): use only-arches for dynamic protoc architecture selection Replace the fragile sed-based CI patching of the Flatpak manifest with Flatpak's native `only-arches` source selector. The protoc module now declares both x86_64 and aarch64 sources inline, and build-commands use a glob pattern (`protoc-*.zip`) so no per-arch fixup is needed. Changes: - flatpak manifest: add aarch64 protoc source with `only-arches`, use glob in unzip commands, remove stale CI-patching comment - CI workflow: remove `protoc-zip`/`protoc-sha256` matrix variables and the "Patch manifest for architecture" step https://claude.ai/code/session_015AD2pCWoJdV2VDydcqFHPG * fix(flatpak): use dest-filename for deterministic protoc extraction Use dest-filename to normalize both arch-specific protoc zips to a common name, avoiding glob expansion in build-commands. This ensures the unzip target is deterministic regardless of shell behavior in the Flatpak sandbox. https://claude.ai/code/session_015AD2pCWoJdV2VDydcqFHPG * build: update platform to b445b6f0 and remove rust-dashcore patches Update dashpay/platform dependency from d6f4eb9a to b445b6f0e0bd4863 (3.0.1 → 3.1.0-dev.1). Remove the [patch] section that pinned rust-dashcore crates to a separate rev, as the new platform commit resolves them correctly on its own. https://claude.ai/code/session_015AD2pCWoJdV2VDydcqFHPG --------- Co-authored-by: Claude <noreply@anthropic.com>
Resolve conflicts preserving shielded pool features from HEAD while adopting v1.0-dev improvements: refactored asset lock fee calculation, mine dialog hardening, network validation, and API signature updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…xtract/all-merged
The [patch] section referenced ../platform local paths that don't exist in CI. Since dash-sdk already depends on the feat/zk branch, all transitive platform crates resolve correctly without patches. Also fixes a formatting issue in address_table.rs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The remove_utxos tests were failing because `register_test_address` inserts into `wallet_addresses` which has a foreign key constraint on `wallet(seed_hash)`. Added the missing `store_wallet` call so the parent row exists before inserting child address rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rged # Conflicts: # Cargo.lock # Cargo.toml # src/backend_task/core/mod.rs # src/backend_task/wallet/fetch_platform_address_balances.rs # src/context/wallet_lifecycle.rs # src/database/wallet.rs # src/model/wallet/mod.rs # src/ui/wallets/wallets_screen/mod.rs
The `pub mod shielded;` declaration was accidentally removed from `src/model/wallet/mod.rs` during the v1.0-dev merge, causing build failures since the shielded.rs file still exists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Initial plan * fix(test): restore store_wallet calls lost in merge Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>
…rged Resolve conflicts in Cargo.toml (keep feat/zk branch), Cargo.lock (regenerate with pinned platform rev 4d7b9be5), and backend_task/mod.rs (combine TaskError wrapping with ShieldedTask). Fix post-merge integration issues: - SPV manager: remove stale .await on subscribe methods, add command_receiver channel for updated DashSpvClient::run() API - send_screen: update SendStatus::WaitingForResult to unit variant - network_chooser_screen: handle new SyncState::Initializing variant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rged # Conflicts: # .github/workflows/clippy.yml # .github/workflows/tests.yml # CLAUDE.md # Cargo.lock # Cargo.toml # src/app.rs # src/backend_task/core/mod.rs # src/backend_task/error.rs # src/backend_task/mod.rs # src/context/mod.rs # src/database/initialization.rs # src/spv/manager.rs # src/ui/components/top_panel.rs # src/ui/components/wallet_unlock.rs # src/ui/contracts_documents/add_contracts_screen.rs # src/ui/contracts_documents/document_action_screen.rs # src/ui/contracts_documents/group_actions_screen.rs # src/ui/contracts_documents/register_contract_screen.rs # src/ui/contracts_documents/update_contract_screen.rs # src/ui/dashpay/contact_requests.rs # src/ui/dashpay/profile_screen.rs # src/ui/dashpay/profile_search.rs # src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs # src/ui/identities/identities_screen.rs # src/ui/identities/keys/key_info_screen.rs # src/ui/mod.rs # src/ui/tokens/destroy_frozen_funds_screen.rs # src/ui/tokens/direct_token_purchase_screen.rs # src/ui/tokens/freeze_tokens_screen.rs # src/ui/tokens/mint_tokens_screen.rs # src/ui/tokens/pause_tokens_screen.rs # src/ui/tokens/resume_tokens_screen.rs # src/ui/tokens/set_token_price_screen.rs # src/ui/tokens/tokens_screen/my_tokens.rs # src/ui/tokens/unfreeze_tokens_screen.rs # src/ui/tokens/update_token_config.rs # src/ui/tools/masternode_list_diff_screen.rs # src/ui/wallets/asset_lock_detail_screen.rs # src/ui/wallets/create_asset_lock_screen.rs # src/ui/wallets/single_key_send_screen.rs # src/ui/wallets/wallets_screen/mod.rs
Migrate from feat/zk to 3.1-dev branch of dashpay/platform, adapting to breaking API changes in dash-spv, key-wallet, and dpp: - FeeLevel removed; use FeeRate::normal() directly - DashSpvClientInterface/Command removed; use DashSpvClient directly - SyncState::Initializing removed; replaced with WaitForEvents - NetworkExt trait inlined into Network impl - OrchardProver now requires wrapper struct around ProvingKey - OrchardAddress::from_raw_bytes now returns Result - Builder functions gain fee/platform_version params - NullifierSyncConfig API uses NullifierSyncCheckpoint - WalletManager.create_unsigned_payment_transaction removed; use TransactionBuilder directly - Work around Send lifetime issues with spawn_blocking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…kError Replace all Result<T, String> error patterns in the shielded pool module with typed TaskError variants, aligning with the codebase-wide typed error migration (PR #739). New TaskError variants: ShieldedNoUnspentNotes, ShieldedInsufficientBalance, PlatformAddressNotFound, ShieldedMerkleWitnessUnavailable, ShieldedTransitionBuildFailed, ShieldedBroadcastFailed, ShieldedInvalidRecipientAddress, ShieldedAssetLockTimeout, ShieldedSyncFailed, ShieldedTreeUpdateFailed, ShieldedNullifierSyncFailed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge origin/zk branch which adapts to dashpay/platform v3.1-dev API: - OrchardProver trait wrapper (CachedProver) replaces raw ProvingKey refs - OrchardAddress::from_raw_bytes now returns Result - FeeLevel removed; use FeeRate::normal() directly - WalletManager.create_unsigned_payment_transaction removed; use TransactionBuilder - SyncState::Initializing removed; replaced with WaitForEvents - SpvClient ownership model changed (no longer Arc-wrapped) Resolved conflicts preserving typed TaskError variants from our branch while adopting origin/zk's v3.1-dev API adaptations. Fixed branch name typo (3.1-dev → v3.1-dev) in Cargo.toml. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
backport: merge v1.0-dev (including extracted non-ZK fixes) back into zk
Reserve width for Save and Auto Update buttons so the password input doesn't consume all available space, pushing buttons off-screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nto zk-fixes # Conflicts: # Cargo.lock # Cargo.toml # src/app.rs # src/backend_task/core/mod.rs # src/backend_task/error.rs # src/database/initialization.rs # src/spv/manager.rs # src/ui/wallets/wallets_screen/mod.rs
…how details Add CoreRpcConnectionFailed variant to TaskError that includes the configured address in the user-facing message. Connection-refused errors are now detected via is_rpc_connection_error() and enriched with host:port at every RPC call site where the URL is known (AppContext::rpc_error_with_url helper). Details panel is now shown for all RPC-related errors regardless of developer mode, so users can always see the technical information they need to diagnose connectivity issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gtest The Network Chooser screen had three bugs related to RPC password handling: 1. Password input was initialized from Regtest config only, ignoring the current network selection. 2. Password UI was hidden for all networks except Regtest, even though Mainnet/Testnet/Devnet also use RPC mode. 3. Save logic was hardcoded to update Regtest config and triggered a SwitchNetwork(Regtest) action, which disconnected the active network's ZMQ listener unnecessarily. Now the password input shows for any network in RPC mode, reads/writes the correct network config, reinits the RPC client in-place without triggering a network switch, and reloads the stored password when the user switches network tabs. The "Auto Update" (dashmate) button remains Regtest-only since dashmate is only relevant for local networks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n 34 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…er-wallet files Reverts the per-wallet SQLite file approach (63ce0e1). Multiple wallets share the same commitment_tree_* tables in the main database. This is accepted behavior until the SDK adds proper wallet-scoping support (dashpay/grovedb#653). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/ui/wallets/wallets_screen/mod.rs:1
- Filtering by outputs-to-known-addresses can hide legitimate outgoing transactions that spend the entire wallet balance with no change output (i.e., none of the outputs belong to the wallet, but the inputs do). That would make the UI incorrectly show “No transactions found” for real activity. A safer approach is to scope by wallet ownership metadata if you have it (e.g., an
is_oursflag / per-tx wallet association), or include input-side checks (vin/outpoints) where available. Also, doing script→address parsing across all tx outputs every frame can become expensive for large histories; consider cachingrelevant_indiceswhen the transaction set changes instead of recomputing during render.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…te guard SEC-002: Verify shielded balance after anchor retry sync before retrying the operation — prevents doomed retries with zero balance. SEC-003: Handle missing commitment tree tables in clear_commitment_tree_tables (grovedb creates them lazily, so DELETEs fail on fresh installs). Fix clear_network_data to log-and-continue when commitment tree clearing fails — these tables are optional and shouldn't block network data reset. Add SEC-001 INTENTIONAL annotation for panic safety in with_anchor_retry. Document shield_from_asset_lock_task: anchor retry is not applicable since it only reads the payment address and doesn't use the commitment tree. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SEC-006: Store user-friendly message instead of raw RPC error string in network status when chain lock query fails with a non-auth, non-connection error. CODE-002: Remove redundant detail field from CoreRpcConnectionFailed -- format diagnostic info into the url field instead, keeping source for the error chain. Add SEC-005 INTENTIONAL annotation for RPC errors in status tooltip. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SEC-007: Set config file permissions to 0600 on Unix after save (contains RPC credentials). CODE-003: Deduplicate Amount::dash_from_duffs by delegating to dash_from_credits. CODE-006: Remove unused network parameter from AddressKind::detect -- detection is format-based, network validation happens separately. CODE-004/CODE-005: Cache filtered transaction indices per wallet to avoid rebuilding HashSet and filtering on every frame. Invalidated on wallet switch and refresh. CODE-008: Reset AddressInput widgets on network switch so they pick up the new network for validation. Add invalidate_address_input methods to SendScreen, UnshieldCreditsScreen, and WalletsBalancesScreen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- debug! -> trace!: "state transition built, broadcasting" messages in bundle.rs (5 occurrences) — primary-path step-by-step progress - trace! -> debug!: cookie auth fallback in core/mod.rs and context/mod.rs (2 occurrences) — secondary/fallback execution path, not primary flow - info! -> debug!: anchor mismatch retry in context/shielded.rs — error handling branch, not a business event - info! -> debug!: "marked N note(s) spent" in context/shielded.rs — internal bookkeeping, not a user-visible business event - info! -> debug!: post-transfer sync complete in shielded_send_screen.rs — internal plumbing at screen level Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Three prior blocking findings (anchor retry state leak, RPC password session-apply, and commitment tree missing-table crash) are fixed. The global commitment tree clear remains as a known design trade-off. The new wallet UI changes introduce a mine-dialog regression for fresh wallets and a narrow transaction-filter gap. Advanced platform sends lack network validation. No true blockers remain — all findings are suggestions or nitpicks.
Reviewed commit: 810a686
🟡 6 suggestion(s) | 💬 2 nitpick(s)
2 additional findings
🟡 suggestion: Advanced platform sends discard the parsed network without validation
src/ui/wallets/send_screen.rs (lines 2590-2637)
Both send_advanced_utxo_to_platform (line 2590) and send_advanced_platform_to_platform (line 2635) parse platform addresses via PlatformAddress::from_bech32m_string() which returns (PlatformAddress, Network), but both discard the network with .map(|(addr, _)| addr). By contrast, AddressInput.validate_platform() (address_input.rs:526-537) checks the bech32m prefix against the expected network before parsing. The advanced paths bypass AddressInput and read from raw text fields. A user pasting a mainnet dash1... address while on testnet would get a confusing backend error instead of an immediate validation message. No actual cross-network funds risk (the SDK would reject it), but poor UX.
💬 nitpick: Developer 'Resync Notes' button also clears global commitment tree
src/ui/wallets/shielded_tab.rs (line 558)
Line 558 calls clear_commitment_tree_tables() which deletes shared table data for all wallets, same as the safety-net in initialize_shielded_wallet. Developer-mode only, so low user impact, but worth noting as part of the same global-clear pattern.
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/context/shielded.rs`:
- [SUGGESTION] lines 246-268: Safety-net resync clears shared commitment tree for all wallets
When `initialize_shielded_wallet()` detects a wallet with `last_synced_index > 0` but zero persisted notes, it calls `clear_commitment_tree_tables()` (line 254). Verified in `src/database/shielded.rs:170-188`: this function runs `DELETE FROM` on four shared tables (`commitment_tree_shards`, `commitment_tree_cap`, `commitment_tree_checkpoints`, `commitment_tree_checkpoint_marks_removed`) with no per-wallet filtering. If wallet A triggers this recovery, wallet B loses its commitment tree state and must resync. The trigger condition is narrow (only fires when DB state is internally inconsistent), so in practice this mainly affects edge-case recovery scenarios. The worst case is a forced resync (reconstructible from network), not data loss. Acknowledged as a conscious design trade-off after the revert in 712049bc from per-wallet files back to shared DB.
In `src/ui/wallets/wallets_screen/dialogs.rs`:
- [SUGGESTION] lines 1288-1293: Mine dialog offers no addresses for fresh or zero-balance wallets
Verified: `AddressInput::extract_wallet_entries()` (address_input.rs:352) pulls Core addresses exclusively from `wallet.address_balances`, which only contains addresses with current UTXOs. For a fresh wallet, `address_balances` is empty while `known_addresses` contains 48+ bootstrapped BIP44 addresses. Combined with `.with_selection_only(true)` at line 1292, the user cannot select OR type an address. This is a chicken-and-egg problem: the mine dialog exists to fund a wallet, but requires the wallet to already have funded addresses. Regtest-only impact (mining doesn't work on mainnet/testnet), but broken for its primary use case.
In `src/ui/wallets/wallets_screen/mod.rs`:
- [SUGGESTION] lines 1234-1247: Transaction filter drops send-all transactions with no change output
The filter at line 1234 keeps only transactions with at least one output matching `wallet_guard.known_addresses`. A legitimate send-all transaction where total input equals output + fee (no change output back to the wallet) would be filtered out and invisible in the transaction list. This is display-only — no funds risk. The edge case is narrow (requires exact-amount sends or dust-to-fee scenarios), and the comment at line 1231-1233 acknowledges the design: outputs-only checking is intentional as a cross-wallet contamination guard. Worth noting for completeness but low real-world frequency.
In `src/ui/wallets/send_screen.rs`:
- [SUGGESTION] lines 2590-2637: Advanced platform sends discard the parsed network without validation
Both `send_advanced_utxo_to_platform` (line 2590) and `send_advanced_platform_to_platform` (line 2635) parse platform addresses via `PlatformAddress::from_bech32m_string()` which returns `(PlatformAddress, Network)`, but both discard the network with `.map(|(addr, _)| addr)`. By contrast, `AddressInput.validate_platform()` (address_input.rs:526-537) checks the bech32m prefix against the expected network before parsing. The advanced paths bypass AddressInput and read from raw text fields. A user pasting a mainnet `dash1...` address while on testnet would get a confusing backend error instead of an immediate validation message. No actual cross-network funds risk (the SDK would reject it), but poor UX.
In `src/backend_task/core/mod.rs`:
- [SUGGESTION] lines 489-494: chain_lock_rpc_error embeds raw error Display into user-facing url field
Line 492 formats the url as `format!("{}:{} ({})", c.core_host, c.core_rpc_port, e)`, embedding the raw `dashcore_rpc::Error` Display text into the `url` field. Since `CoreRpcConnectionFailed`'s `#[error]` attribute includes `{url}` in the user-facing message, OS-level error text (e.g., "Connection refused (os error 111)") leaks to the user. Additionally, `source: None` is passed because the function borrows `&e` and can't move it, breaking the error chain. The function signature should take `e` by value to preserve the source.
In `src/backend_task/error.rs`:
- [SUGGESTION] lines 820-836: Fee-exceeds-spendable detection relies on fragile English substring parsing
Verified: `parse_fee_exceeds_spendable` extracts numeric fields by searching for hard-coded English substrings ("amount ", "fee ", "exceeds total spendable value ") inside the SDK error text. If the upstream SDK changes wording, the parser silently fails and falls through to the generic `ShieldedTransitionBuildFailed` variant. The graceful degradation means this isn't a crash risk, but the user would see a generic error instead of the specific fee/balance breakdown. Not actionable until the SDK exposes structured error data — noting for awareness.
# Conflicts: # Cargo.lock # src/backend_task/error.rs
Use spawn_blocking + block_on in dispatch_task to avoid Send bound issues with platform SDK types (DataContract/Sdk references across await points). Same pattern already used by AppState::handle_backend_task. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # src/backend_task/error.rs
# Conflicts: # src/backend_task/error.rs # src/backend_task/shielded/bundle.rs # src/backend_task/shielded/sync.rs # src/context/shielded.rs # src/database/mod.rs # src/database/shielded.rs # src/ui/mod.rs # src/ui/network_chooser_screen.rs # src/ui/wallets/send_screen.rs # src/ui/wallets/shield_credits_screen.rs # src/ui/wallets/shield_from_asset_lock_screen.rs # src/ui/wallets/shielded_send_screen.rs # src/ui/wallets/unshield_credits_screen.rs # src/ui/wallets/wallets_screen/mod.rs
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/ui/network_chooser_screen.rs (1)
468-477:⚠️ Potential issue | 🟠 MajorDo not show raw dashmate/config errors in a global banner.
read_dashmate_rpc_password()returns file-system paths and parser details. Passing&estraight intoMessageBanner::set_global()leaks that technical text to end users instead of giving them a short action-oriented message.As per coding guidelines "Never expose raw backend/database errors to users. Use a user-friendly message in the banner and attach technical details via
BannerHandle::with_details()."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/network_chooser_screen.rs` around lines 468 - 477, The global banner currently displays raw errors returned from read_dashmate_rpc_password() (see the Auto Update button branch), leaking filesystem/parser details; change the error handling so MessageBanner::set_global() receives a short, user-friendly message like "Failed to read Dashmate RPC password — check your config" while attaching the original error text as technical details via BannerHandle::with_details() (or similar API) so the detailed e is not shown to end users; update the match arm that logs and sets the banner (and keep tracing::error(e) for logs) to call MessageBanner::set_global() with the friendly message and pass e into BannerHandle::with_details().src/backend_task/shielded/bundle.rs (1)
440-445:⚠️ Potential issue | 🟠 MajorRound the reserved platform fee up before converting to duffs.
This floors
platform_fee_creditsto whole duffs before the 20% cushion. A fee below 1 duff reserves0, and1500credits still reserves only1duff, soasset_lock_duffscan be underfunded despite the safety margin.Suggested fix
- let platform_fee_duffs = (platform_fee_credits / CREDITS_PER_DUFF).saturating_mul(120) / 100; + let platform_fee_credits_with_margin = platform_fee_credits + .saturating_mul(120) + .saturating_add(99) + / 100; + let platform_fee_duffs = platform_fee_credits_with_margin + .saturating_add(CREDITS_PER_DUFF - 1) + / CREDITS_PER_DUFF;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/backend_task/shielded/bundle.rs` around lines 440 - 445, platform_fee_credits is being floored to duffs before applying the 20% cushion, which can underfund asset_lock_duffs; change the calculation of platform_fee_duffs to compute the 20%‑increased credits first and then round up when converting to duffs (i.e. perform ceil((platform_fee_credits * 1.20) / CREDITS_PER_DUFF)), using integer math and saturating multiplications to avoid overflow; keep references: platform_fee_credits, platform_fee_duffs, CREDITS_PER_DUFF, asset_lock_duffs, and amount_duffs when updating the formula.src/ui/wallets/wallets_screen/mod.rs (1)
1198-1221:⚠️ Potential issue | 🟠 MajorDon't block transaction rendering on
Arc::ptr_eq.Wallet selection is keyed by
seed_hash, and registration paths create a newArcfor the same wallet. When that happens, this guard hides the transaction list until the user manually re-selects, even though the canonical wallet is already inapp_context.wallets. Resolve/render from the canonical entry by hash, or swapself.selected_walletover to it, instead of treating pointer mismatch as stale data.💡 Suggested fix
- let Some(wallet_arc) = self.selected_wallet.as_ref() else { + let Some(wallet_arc) = self.selected_wallet.clone() else { ui.label("Select a wallet to view its transaction history."); return; }; - let wallet_guard = wallet_arc.read().unwrap(); - let selected_seed_hash = wallet_guard.seed_hash(); - let arc_matches = self + let selected_seed_hash = wallet_arc.read().unwrap().seed_hash(); + let canonical_wallet = self .app_context .wallets .read() .ok() .and_then(|wallets| wallets.get(&selected_seed_hash).cloned()) - .is_some_and(|canonical| Arc::ptr_eq(wallet_arc, &canonical)); - if !arc_matches { - tracing::warn!( - "selected_wallet Arc does not match app_context.wallets — skipping transaction render" - ); - ui.label("Wallet data is being updated. Please re-select the wallet."); - return; + .unwrap_or_else(|| wallet_arc.clone()); + if !Arc::ptr_eq(&wallet_arc, &canonical_wallet) { + self.selected_wallet = Some(canonical_wallet.clone()); + self.cached_tx_indices = None; } + let wallet_guard = canonical_wallet.read().unwrap();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/wallets_screen/mod.rs` around lines 1198 - 1221, The current guard rejects rendering when Arc::ptr_eq(wallet_arc, &canonical) is false, which wrongly hides transactions for the same wallet registered as a new Arc; instead, look up the canonical Arc by seed_hash from app_context.wallets and use it for rendering (or replace self.selected_wallet with that canonical Arc) so rendering proceeds for the canonical wallet entry; update the logic around selected_wallet / wallet_arc (the variables referenced in this block) to resolve by seed_hash and fall back to the canonical Arc rather than returning on pointer inequality.
♻️ Duplicate comments (3)
src/ui/wallets/wallets_screen/dialogs.rs (1)
1288-1293:⚠️ Potential issue | 🟠 Major
selection_onlycan leave the mine dialog unusable for fresh wallets.This disables typing and delegates all choices to
AddressInput. If that component only offers balance-backed Core entries, a fresh or zero-balance wallet has nothing to select, so the mining dialog cannot bootstrap the wallet it is meant to fund.#!/bin/bash # Verify which wallet addresses AddressInput exposes and how the mine dialog configures it. rg -n -C3 'extract_wallet_entries|address_balances|known_addresses|with_selection_only|with_wallets' src🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/wallets_screen/dialogs.rs` around lines 1288 - 1293, The mine dialog currently calls AddressInput::new(...).with_selection_only(true) which prevents typing and makes the dialog unusable for fresh/zero-balance wallets; change the configuration so typing is allowed when the targeted wallet has no selectable Core addresses — e.g., remove or set with_selection_only(false) for the AddressInput instance used in the mine dialog (the AddressInput::new(...).with_wallets(&[wallet]) call), or conditionally set with_selection_only based on whether the wallet has any Core balance-backed entries (use the same wallet/address inspection helpers such as extract_wallet_entries/address_balances/known_addresses to detect emptiness) so new wallets can type an address to bootstrap funding.src/backend_task/core/mod.rs (1)
489-494:⚠️ Potential issue | 🟠 MajorKeep
urltohost:port; move the raw RPC error into details.
format!("{}:{} ({})", ...)makes the user-facing connection error expose OS/library text instead of just the endpoint, andsource: Nonedrops the diagnostic chain. Keep the endpoint and the low-level error in separate fields.#!/bin/bash # Inspect the current TaskError shape and all constructors/callers. rg -n -C3 'CoreRpcConnectionFailed|chain_lock_rpc_error|rpc_error_with_url' srcAs per coding guidelines "Never put technical details (raw error strings, stack traces, SDK internals, or error codes) in error messages — attach via
BannerHandle::with_details(e)."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/backend_task/core/mod.rs` around lines 489 - 494, The current CoreRpcConnectionFailed builds url with the raw RPC error text and drops the error by using source: None; change the formatted url to only the host:port (use the same config mapping but remove " ({})"), and preserve the low‑level error by populating the source (or attaching details) instead of discarding it — e.g. return TaskError::CoreRpcConnectionFailed { url, source: Some(e.into()) } or ensure the error is passed to BannerHandle::with_details(e) so the diagnostic chain is kept; locate this change around is_rpc_connection_error(...) and the TaskError::CoreRpcConnectionFailed constructor.src/context/shielded.rs (1)
254-258:⚠️ Potential issue | 🟠 MajorAvoid the shared, non-transactional tree wipe in initialization.
clear_commitment_tree_tables()clears the global commitment-tree tables on the shared connection (src/database/shielded.rs:170-188), and it does so table-by-table without a transaction. If one wallet hits this recovery path, other shielded wallets on the same network get forced into resync, and a failure halfway through leaves the tree half-reset. This reset needs a wallet-scoped helper or a single transaction before it is safe to call here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/context/shielded.rs` around lines 254 - 258, The call to self.db.clear_commitment_tree_tables() in shielded.rs uses a shared, non-transactional global wipe and must be replaced with a wallet-scoped, transactional reset to avoid cross-wallet interference and partial wipes; update the code to either (A) call a new DB helper like clear_wallet_commitment_tree(wallet_id: WalletId) that only deletes rows for this wallet, or (B) wrap the existing multi-table deletion in a single database transaction (via your DB connection's transaction API) so all table clears succeed or rollback together, and propagate any error into TaskError::ShieldedTreeUpdateFailed as before; change the call site in the method where self.db.clear_commitment_tree_tables() is invoked to use the new wallet-scoped helper or the transactional operation.
🧹 Nitpick comments (5)
src/database/initialization.rs (4)
1145-1148: Redundant assertion —DEFAULT_DB_VERSIONis already 33.Line 1146 asserts
version == DEFAULT_DB_VERSIONand line 1147 assertsversion == 33. SinceDEFAULT_DB_VERSIONis33, one assertion is sufficient.Proposed fix
assert_eq!(version, DEFAULT_DB_VERSION); - assert_eq!(version, 33);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/database/initialization.rs` around lines 1145 - 1148, The two assertions checking the initialized DB version are redundant; remove one and keep the semantic one using the constant. Replace the pair of asserts on variable `version` with a single assertion `assert_eq!(version, DEFAULT_DB_VERSION);` (remove the literal `assert_eq!(version, 33);`) so the test uses the named constant `DEFAULT_DB_VERSION` instead of a magic number; locate this in the initialization test where `version` is asserted.
928-932: Note: Schema default mismatch between migration and fresh install.The migration adds
statuswithDEFAULT 2(Confirmed), while the freshCREATE TABLEininitialize_wallet_transactions_tablelikely usesDEFAULT 0(Unconfirmed). Per the relevant code snippet fromsrc/database/wallet.rs:435-471, all INSERTs explicitly provide the status value, so this mismatch has no runtime impact. However, it creates a latent schema inconsistency between migrated and fresh databases.This was noted in PR review comments as a non-blocking suggestion. Consider aligning the defaults in a future cleanup if desired.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/database/initialization.rs` around lines 928 - 932, The migration ALTER TABLE statement that adds the status column uses DEFAULT 2 while the fresh CREATE TABLE in initialize_wallet_transactions_table uses DEFAULT 0, creating a schema inconsistency; update the ALTER TABLE call (the conn.execute that runs "ALTER TABLE wallet_transactions ADD COLUMN status INTEGER NOT NULL DEFAULT 2") to use the same default as the fresh table (change DEFAULT 2 to DEFAULT 0) or alternatively align initialize_wallet_transactions_table to DEFAULT 2 so both code paths match—pick one and make the defaults consistent across initialize_wallet_transactions_table and the migration SQL.
925-937: Duplicate comments should be consolidated.The same explanatory comment appears twice: once before
conn.execute(lines 925-927) and once inside it (lines 929-931). Remove one to avoid redundancy.Proposed fix
if !has_status { - // DEFAULT 2 (Confirmed) for migration: existing transactions predate status - // tracking and are assumed confirmed. Fresh installs use DEFAULT 0 (Unconfirmed) - // in the CREATE TABLE (wallet.rs). conn.execute( // DEFAULT 2 (Confirmed) for migration: existing transactions predate status // tracking and are assumed confirmed. Fresh installs use DEFAULT 0 (Unconfirmed) // in the CREATE TABLE (wallet.rs). "ALTER TABLE wallet_transactions ADD COLUMN status INTEGER NOT NULL DEFAULT 2", [], )?; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/database/initialization.rs` around lines 925 - 937, Consolidate the duplicated explanatory comment about default status for the migration by removing one of the two identical comment blocks surrounding the ALTER TABLE call: keep a single comment either immediately above the conn.execute invocation or inside the SQL argument, and remove the other; target the code around conn.execute(...) performing "ALTER TABLE wallet_transactions ADD COLUMN status INTEGER NOT NULL DEFAULT 2" in initialization.rs to make this change.
984-1006: Consider using parameterized query inassert_column_existsfor consistency.
assert_table_existsusesparams![table]for the table name, butassert_column_existsuses string formatting. While this is test-only code with hardcoded values, using consistent parameterization would be cleaner.Proposed fix using pragma_table_xinfo with params
Note: SQLite's
pragma_table_infodoesn't accept the table name as a parameter in the same query. An alternative is to use a subquery pattern or accept this inconsistency given it's test code with controlled inputs. The current approach is acceptable for test helpers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/database/initialization.rs` around lines 984 - 1006, The assert_column_exists helper should avoid interpolating the column name directly; update assert_column_exists to parameterize the column argument (keep the table name formatting if PRAGMA can't be parameterized) by changing the query to format!("SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name=?1") and pass params![column] to query_row, so the function assert_column_exists(conn, table, column) uses a parameter for the column name similar to assert_table_exists while leaving table handling as-is.src/model/address.rs (1)
74-76: Keep the new model type independent ofcrate::ui.
src/model/address.rsnow importscrate::ui::helpers::is_platform_address_string(), whilesrc/ui/components/address_input.rsalready depends on this model module. That flips the dependency direction and makes the address domain types harder to reuse outside the UI. Please move the prefix helper intosrc/model/address.rsor a shared non-UI helper instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/model/address.rs` around lines 74 - 76, The model now depends on crate::ui via is_platform_address_string(trimmed); remove that UI dependency by moving the Bech32m platform-prefix helper into the model (e.g., add a private pub(crate) fn is_platform_address_string or a small helpers module inside src/model/address.rs) and replace the call in the AddressKind detection with that local helper. Also update any callers in src/ui/components/address_input.rs (and other UI code) to import the helper from the model (e.g., model::address::is_platform_address_string or model::address::helpers::is_platform_address_string) so the dependency flows from UI -> model only. Ensure no other references to crate::ui::helpers remain in src/model/address.rs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/backend_task/core/mod.rs`:
- Around line 187-203: The early return when chain_lock_rpc_error detects an
auth/connection failure prevents ConnectionStatus::handle_task_result from
receiving successful CoreItem::ChainLocks payloads; instead of returning
Err(task_err) inside the active_rpc_error check in the match on
self.network/active_result, propagate the RPC failure down the success path by
constructing the rpc_error payload (e.g., set active_rpc_error =
Some(task_err.to_string()) or the same TaskError-wrapping payload expected by
CoreItem::ChainLocks) and continue so the function returns Ok(...) with the
ChainLocks result; update the branch using
Self::chain_lock_rpc_error(active_config, e) to not return but assign/convert
the error into the rpc_error value consumed by
ConnectionStatus::handle_task_result.
In `@src/context/connection_status.rs`:
- Around line 448-450: Replace storing the raw rpc_error string into the UI
state: stop calling self.set_rpc_last_error(rpc_error.clone()) with the raw text
and instead convert rpc_error into a user-facing one-sentence summary or an
error-kind enum (e.g., map transport/auth/timeout cases to concise messages) and
pass that summary to set_rpc_last_error (or introduce set_rpc_last_error_kind).
Keep the original rpc_error only in logs or attach it as details via
BannerHandle::with_details(e) so UI shows a calm sentence while full debug info
is retained for diagnostics.
In `@src/context/shielded.rs`:
- Around line 240-248: The safety-net should force a full resync when the
in-memory notes are empty even if there is historical data in storage, unless
that persisted data contains any unspent/change notes; change the check after
calling self.db.get_all_shielded_notes(&seed_hash, &network_str) so that you
inspect the returned rows (all_notes) and only skip the resync when there exists
at least one unspent/change note (e.g., !all_notes.iter().all(|note|
note.is_spent) or filtering for note.unspent/change), otherwise proceed with the
full resync; adjust the logic around state.last_synced_index,
state.notes.is_empty(), all_notes.is_empty() to use this unspent-note test
instead of treating any persisted row as sufficient.
In `@src/model/address.rs`:
- Around line 69-76: The shielded-address check in the address classification is
case-sensitive and lets uppercase inputs fall through to the platform check;
update the logic in the function that returns AddressKind (the block using the
local variable `trimmed`) so the shielded prefix test is performed
case-insensitively (e.g., compare a lowercase form of `trimmed` or perform a
case-insensitive starts_with) for "dash1z" and "tdash1z" before calling
crate::ui::helpers::is_platform_address_string; keep the shielded check first so
uppercase "DASH1Z..." / "TDASH1Z..." are correctly classified as
AddressKind::Shielded and avoid sending them to validate_platform().
In `@src/ui/components/address_input.rs`:
- Around line 1131-1136: The test function detect_identity_when_enabled is flaky
because Identifier::random() can sometimes produce a string classified as
DetectedType::Core; update the test to reuse the same guard logic used in the
model (i.e., regenerate identifiers until detect_address_type(&id_str, true) is
not DetectedType::Core) before asserting equality to DetectedType::Identity,
referencing the test name detect_identity_when_enabled, the Identifier::random()
call, detect_address_type, and DetectedType::Identity so the test only asserts
on a guaranteed non-Core identity string.
- Around line 467-620: Several validation messages in validate_core,
validate_platform, validate_shielded and the fallback branch (e.g. "This address
belongs to a different network." and "This does not look like a valid address.")
only diagnose the problem; update each returned user-facing string to follow the
required format "What happened + what to do" and include a concrete action. For
example, in validate_core and the network-check branches of
validate_platform/validate_shielded replace the network-only messages with
something like "This address is for a different network. Please use an address
for this network." and for parse failures replace generic "does not look like a
valid address" with "This does not look like a valid address. Please check the
address and try again or paste a different address." Ensure you update all
occurrences returned from validate_core, validate_platform, validate_shielded
and the Unknown/fallback branches so every message contains both a description
and a concrete next step.
- Around line 463-493: Detected identity IDs can be misclassified as Core and
get rejected before identity validation; to fix this, special-case the
identity-only input: if self.enabled_kinds.as_slice() == [AddressKind::Identity]
then bypass the detected-kind enabled check and call
self.validate_identity(trimmed) directly (return its result) instead of relying
on detect_address_type/detected; keep the existing detect_address_type and
normal flow for other enabled_kinds, and ensure references to
detect_address_type, detected, AddressKind::Identity, enabled_kinds, and
validate_identity are used to locate the change.
- Around line 497-505: validate_core currently uses
addr.require_network(self.network) but may keep a Network::Testnet address when
the app network is Devnet/Regtest; after the Ok(checked) from require_network(),
compare checked.network() to self.network and, if they differ due to
Testnet-on-Devnet/Regtest, reconstruct a normalized address using
Address::new(self.network, checked.payload().clone()) before wrapping it in
ValidatedAddress::Core (mirror the logic from check_address_for_network()).
Ensure you replace the original checked with the normalized address when
producing ValidatedAddress::Core.
- Around line 553-586: validate_shielded currently checks prefixes and length
before parsing but doesn't normalize Bech32m case, so uppercase shielded
addresses will be rejected; mirror the normalization in validate_platform by
detecting mixed-case per BIP-350 and converting trimmed to lowercase (use the
same helper/pattern used in validate_platform) before computing expected_prefix,
performing starts_with checks, and calling OrchardAddress::from_bech32m_string;
ensure the returned ValidatedAddress::Shielded stores the canonical (lowercased)
form and update any references to AddressKind::detect guidance so routing and
storage consistently use the normalized form.
In `@src/ui/mod.rs`:
- Around line 818-821: The current invalidate_address_input() helper only drops
the widget wrapper and does not clear the cached validated_destination, so
rebinding app_context in the Screen::WalletSendScreen (and the analogous
Screen::UnshieldCreditsScreen) branches can leave a destination validated
against the old context; update the invalidate_address_input() implementation to
also set validated_destination to None/clear it (the same field used by the send
screen’s full reset) and then call that updated helper from the
Screen::WalletSendScreen and the other screen branch so the cached validation is
fully cleared on context switch.
In `@src/ui/network_chooser_screen.rs`:
- Around line 418-425: When switching networks (compare self.current_network and
prev_network) ensure the password input is cleared if no config entry exists:
after attempting Config::load_from(&self.mainnet_app_context.data_dir) and
config.config_for_network(self.current_network), if either fails then call
self.dashmate_password_input.set_text(String::new()) (or equivalent clear) so
the previous network's password is not retained; otherwise keep the existing
behavior of setting the password from network_config.core_rpc_password.
In `@src/ui/wallets/send_screen.rs`:
- Around line 471-474: invalidate_address_input currently only clears
self.address_input but leaves self.validated_destination populated, risking
stale validated addresses after a network change; update the
invalidate_address_input method to also set self.validated_destination = None so
both the AddressInput and its validated_destination are cleared (mirroring
behavior in reset_form and the source selection handlers) and ensure any
downstream logic that reads validated_destination will see None until
revalidated.
In `@src/ui/wallets/shield_credits_screen.rs`:
- Around line 360-363: The follow-up note sync is only queued from the success
path, missing cases where a batch has some successes (e.g., parallel runs not
calling display_task_result() or sequential runs finishing with an error after
earlier successes); update the batch completion handling so when the batch
transitions to complete you check if batch_succeeded > 0 and then queue a single
SyncNotes refresh (use the same AppAction::BackendTask wrapping) regardless of
the terminal event, instead of only queuing from display_task_result();
reference and adjust the logic around pending_refresh_task,
display_task_result(), and the place where batch completion is detected to set
or enqueue the SyncNotes task.
In `@src/ui/wallets/shield_from_asset_lock_screen.rs`:
- Around line 118-126: The Max amount uses full core_balance_duffs *
CREDITS_PER_DUFF so the Max button can exceed what shield_from_asset_lock() can
actually spend; modify the logic that computes max_credits (used when calling
amount_input.set_max_amount(...)) to subtract the estimated asset-lock fee
reserve (or a constant RESERVE_DUFFS converted to credits) before passing it to
AmountInput::set_max_amount, and ensure the estimate is derived from the same
fee calculation used by shield_from_asset_lock() so the reserve matches runtime
behavior.
- Around line 68-72: The current code takes pending_refresh_task into action and
then overwrites action with AppAction::PopScreen when the user clicks Done,
which drops a queued ShieldedTask::SyncNotes; instead, preserve the
pending_refresh_task by chaining the PopScreen after the BackendTask: when
pending_refresh_task.is_some() create a combined action that runs
AppAction::BackendTask(task) followed by AppAction::PopScreen (e.g.,
create/return a sequence/chained action or push the PopScreen to run after the
BackendTask), and apply the same change for both sites that build action (the
block using pending_refresh_task around
pending_refresh_task.take().map(AppAction::BackendTask) and the other occurrence
at lines 108-113) so the sync task is not lost when Done is clicked.
In `@src/ui/wallets/shielded_send_screen.rs`:
- Around line 236-245: The ShieldedNotesSynced branch currently only logs and
does not update sender-side state; when matching
BackendTaskSuccessResult::ShieldedNotesSynced { seed_hash, new_notes, balance }
if seed_hash == self.seed_hash, update the screen state by setting
self.max_balance = Some(balance) (or otherwise store the new balance), set
self.balance_update_pending = false to clear the pending flag, and optionally
record recipient-specific reminders in a separate field if you still want a
recipient-side note; ensure these updates occur in the same match arm that
currently only calls tracing::debug so the success view reflects the completed
refresh.
In `@src/ui/wallets/unshield_credits_screen.rs`:
- Around line 79-83: The current code always replaces any existing
self.pending_refresh_task-backed AppAction::BackendTask with
AppAction::PopScreen on Done; instead, preserve a queued BackendTask (e.g.,
SyncNotes) if present by only using AppAction::PopScreen when
pending_refresh_task is None. Update the logic around
pending_refresh_task.take().map(AppAction::BackendTask).unwrap_or(AppAction::None)
to return the existing BackendTask when Some and otherwise return PopScreen, and
apply the same fix to the second occurrence around the Done handler (the block
referenced at lines ~121-132) so you don't drop the queued sync.
- Around line 174-183: The current code passes the full shielded balance to
AmountInput via set_max_amount(Some(self.max_balance)) which ignores needed
transition/core fees and can let the user pick a Max that later fails; change it
to compute a fee-reserved maximum (e.g., let max_reserve =
self.max_balance.saturating_sub(estimated_fee)) and pass
set_max_amount(Some(max_reserve)) instead; locate the amount input creation
(AmountInput::new / amount_input variable) and replace use of self.max_balance
with a fee-adjusted value obtained from an existing fee estimator (e.g., a
method like estimate_transition_fee() or a stored field) or a conservative
constant if no estimator exists, ensuring the amount value (self.amount) and UI
validation reflect that reservation.
---
Outside diff comments:
In `@src/backend_task/shielded/bundle.rs`:
- Around line 440-445: platform_fee_credits is being floored to duffs before
applying the 20% cushion, which can underfund asset_lock_duffs; change the
calculation of platform_fee_duffs to compute the 20%‑increased credits first and
then round up when converting to duffs (i.e. perform ceil((platform_fee_credits
* 1.20) / CREDITS_PER_DUFF)), using integer math and saturating multiplications
to avoid overflow; keep references: platform_fee_credits, platform_fee_duffs,
CREDITS_PER_DUFF, asset_lock_duffs, and amount_duffs when updating the formula.
In `@src/ui/network_chooser_screen.rs`:
- Around line 468-477: The global banner currently displays raw errors returned
from read_dashmate_rpc_password() (see the Auto Update button branch), leaking
filesystem/parser details; change the error handling so
MessageBanner::set_global() receives a short, user-friendly message like "Failed
to read Dashmate RPC password — check your config" while attaching the original
error text as technical details via BannerHandle::with_details() (or similar
API) so the detailed e is not shown to end users; update the match arm that logs
and sets the banner (and keep tracing::error(e) for logs) to call
MessageBanner::set_global() with the friendly message and pass e into
BannerHandle::with_details().
In `@src/ui/wallets/wallets_screen/mod.rs`:
- Around line 1198-1221: The current guard rejects rendering when
Arc::ptr_eq(wallet_arc, &canonical) is false, which wrongly hides transactions
for the same wallet registered as a new Arc; instead, look up the canonical Arc
by seed_hash from app_context.wallets and use it for rendering (or replace
self.selected_wallet with that canonical Arc) so rendering proceeds for the
canonical wallet entry; update the logic around selected_wallet / wallet_arc
(the variables referenced in this block) to resolve by seed_hash and fall back
to the canonical Arc rather than returning on pointer inequality.
---
Duplicate comments:
In `@src/backend_task/core/mod.rs`:
- Around line 489-494: The current CoreRpcConnectionFailed builds url with the
raw RPC error text and drops the error by using source: None; change the
formatted url to only the host:port (use the same config mapping but remove "
({})"), and preserve the low‑level error by populating the source (or attaching
details) instead of discarding it — e.g. return
TaskError::CoreRpcConnectionFailed { url, source: Some(e.into()) } or ensure the
error is passed to BannerHandle::with_details(e) so the diagnostic chain is
kept; locate this change around is_rpc_connection_error(...) and the
TaskError::CoreRpcConnectionFailed constructor.
In `@src/context/shielded.rs`:
- Around line 254-258: The call to self.db.clear_commitment_tree_tables() in
shielded.rs uses a shared, non-transactional global wipe and must be replaced
with a wallet-scoped, transactional reset to avoid cross-wallet interference and
partial wipes; update the code to either (A) call a new DB helper like
clear_wallet_commitment_tree(wallet_id: WalletId) that only deletes rows for
this wallet, or (B) wrap the existing multi-table deletion in a single database
transaction (via your DB connection's transaction API) so all table clears
succeed or rollback together, and propagate any error into
TaskError::ShieldedTreeUpdateFailed as before; change the call site in the
method where self.db.clear_commitment_tree_tables() is invoked to use the new
wallet-scoped helper or the transactional operation.
In `@src/ui/wallets/wallets_screen/dialogs.rs`:
- Around line 1288-1293: The mine dialog currently calls
AddressInput::new(...).with_selection_only(true) which prevents typing and makes
the dialog unusable for fresh/zero-balance wallets; change the configuration so
typing is allowed when the targeted wallet has no selectable Core addresses —
e.g., remove or set with_selection_only(false) for the AddressInput instance
used in the mine dialog (the AddressInput::new(...).with_wallets(&[wallet])
call), or conditionally set with_selection_only based on whether the wallet has
any Core balance-backed entries (use the same wallet/address inspection helpers
such as extract_wallet_entries/address_balances/known_addresses to detect
emptiness) so new wallets can type an address to bootstrap funding.
---
Nitpick comments:
In `@src/database/initialization.rs`:
- Around line 1145-1148: The two assertions checking the initialized DB version
are redundant; remove one and keep the semantic one using the constant. Replace
the pair of asserts on variable `version` with a single assertion
`assert_eq!(version, DEFAULT_DB_VERSION);` (remove the literal
`assert_eq!(version, 33);`) so the test uses the named constant
`DEFAULT_DB_VERSION` instead of a magic number; locate this in the
initialization test where `version` is asserted.
- Around line 928-932: The migration ALTER TABLE statement that adds the status
column uses DEFAULT 2 while the fresh CREATE TABLE in
initialize_wallet_transactions_table uses DEFAULT 0, creating a schema
inconsistency; update the ALTER TABLE call (the conn.execute that runs "ALTER
TABLE wallet_transactions ADD COLUMN status INTEGER NOT NULL DEFAULT 2") to use
the same default as the fresh table (change DEFAULT 2 to DEFAULT 0) or
alternatively align initialize_wallet_transactions_table to DEFAULT 2 so both
code paths match—pick one and make the defaults consistent across
initialize_wallet_transactions_table and the migration SQL.
- Around line 925-937: Consolidate the duplicated explanatory comment about
default status for the migration by removing one of the two identical comment
blocks surrounding the ALTER TABLE call: keep a single comment either
immediately above the conn.execute invocation or inside the SQL argument, and
remove the other; target the code around conn.execute(...) performing "ALTER
TABLE wallet_transactions ADD COLUMN status INTEGER NOT NULL DEFAULT 2" in
initialization.rs to make this change.
- Around line 984-1006: The assert_column_exists helper should avoid
interpolating the column name directly; update assert_column_exists to
parameterize the column argument (keep the table name formatting if PRAGMA can't
be parameterized) by changing the query to format!("SELECT COUNT(*) FROM
pragma_table_info('{table}') WHERE name=?1") and pass params![column] to
query_row, so the function assert_column_exists(conn, table, column) uses a
parameter for the column name similar to assert_table_exists while leaving table
handling as-is.
In `@src/model/address.rs`:
- Around line 74-76: The model now depends on crate::ui via
is_platform_address_string(trimmed); remove that UI dependency by moving the
Bech32m platform-prefix helper into the model (e.g., add a private pub(crate) fn
is_platform_address_string or a small helpers module inside
src/model/address.rs) and replace the call in the AddressKind detection with
that local helper. Also update any callers in src/ui/components/address_input.rs
(and other UI code) to import the helper from the model (e.g.,
model::address::is_platform_address_string or
model::address::helpers::is_platform_address_string) so the dependency flows
from UI -> model only. Ensure no other references to crate::ui::helpers remain
in src/model/address.rs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3018d3c6-3e6d-4144-8955-8e7a22015c44
📒 Files selected for processing (35)
CLAUDE.mdsrc/app.rssrc/backend_task/core/mod.rssrc/backend_task/error.rssrc/backend_task/identity/mod.rssrc/backend_task/shielded/bundle.rssrc/backend_task/shielded/sync.rssrc/config.rssrc/context/connection_status.rssrc/context/mod.rssrc/context/shielded.rssrc/database/initialization.rssrc/database/mod.rssrc/database/shielded.rssrc/database/wallet.rssrc/model/address.rssrc/model/amount.rssrc/model/fee_estimation.rssrc/model/mod.rssrc/model/wallet/mod.rssrc/ui/components/README.mdsrc/ui/components/address_input.rssrc/ui/components/mod.rssrc/ui/identities/add_new_identity_screen/mod.rssrc/ui/identities/top_up_identity_screen/mod.rssrc/ui/mod.rssrc/ui/network_chooser_screen.rssrc/ui/wallets/send_screen.rssrc/ui/wallets/shield_credits_screen.rssrc/ui/wallets/shield_from_asset_lock_screen.rssrc/ui/wallets/shielded_send_screen.rssrc/ui/wallets/unshield_credits_screen.rssrc/ui/wallets/wallets_screen/address_table.rssrc/ui/wallets/wallets_screen/dialogs.rssrc/ui/wallets/wallets_screen/mod.rs
|
Review comments handled in #791 |
Summary
Adds a reusable
AddressInputcomponent, improves RPC error handling, and refines shielded pool operations with retry logic and post-operation syncing.New: AddressInput Component
src/ui/components/address_input.rs) with autocomplete, type detection (Core/Platform/Shielded/Identity), DPNS resolution, network validation, and balance displaysrc/model/address.rs) —AddressKindenum andValidatedAddresstype for address classification and validationsrc/ui/components/README.md) — catalog of all reusable componentsWalletSendScreen,UnshieldCreditsScreen, and mine dialog to useAddressInputNew: AmountInput Migration
ShieldCreditsScreen,ShieldFromAssetLockScreen,ShieldedSendScreen, andUnshieldCreditsScreenreplaced hand-rolled amount parsing with theAmountInputcomponentRPC Error Handling
CoreRpcConnectionFailedvariant withis_rpc_connection_error()detector;rpc_error_with_url()enriches connection failures across all RPC call sitesrpc_last_errorfield added toConnectionStatuschmod 0600applied to.envon Unix after save (contains RPC credentials)Shielded Pool Improvements
with_anchor_retry()extracts shared state management (take/run/mark-spent/put-back) from transfer, unshield, and withdrawal tasks, with automatic retry onShieldedAnchorMismatchSyncNotestask after successful operations so balances update without manual refreshError Message Improvements
IdentityInsufficientBalanceshows amounts in DASH instead of raw credit integersAssetLockOutPointInsufficientBalanceshows amounts in DASH with actionable guidanceShieldedFeeExceedsBalanceshows fee in DASH and suggests reducing amountShieldedInsufficientPoolNotesuses plain language ("network needs more activity")ShieldedAnchorMismatchsays "wallet data is slightly outdated"ShieldedAddressInsufficientFundsshows available/required in DASHWalletUtxoReloadFailed,WalletBalanceRecalculationFailedWalletKeyDerivationFailednow preserves error chain via#[source]Other Changes
format_credits_as_dash— usesAmount::dash_from_credits()with trimmed trailing zerosnetworks_address_compatible()— nowpub(crate)inmodel/wallet/mod.rs, used in wallet dialogs so Testnet/Devnet/Regtest addresses are accepted interchangeablyspv_balance_knownflag distinguishes "not yet synced" (None) from "synced with zero balance" (Some(0))shared_connection()narrowed topub(crate);TransactionStatus::from_u8logs unknown valuesTest plan
host:portin error; details visible only in developer modetdash-prefixed address in wallet send dialog — verify it is accepted🤖 Co-authored by Claudius the Magnificent AI Agent
Summary by CodeRabbit
Release Notes
New Features
AddressInputcomponent with address validation, autocomplete, and support for Core, Platform, Shielded, and Identity address types.Bug Fixes
spv_balance_knownflag to prevent premature balance display.Improvements
Documentation