fix(ui): Send Dash screen improvements + unified shield screen merge - #802
Conversation
…selection Merge ShieldCreditsScreen and ShieldFromAssetLockScreen into a single ShieldScreen that uses AddressInput to let users pick the source. The screen adapts its behavior based on the selected address type: platform addresses use ShieldCredits, core addresses use ShieldFromAssetLock. One button in the shielded tab replaces two. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When OverallConnectionState transitions to Synced, dispatch a CurrentEpochInfo backend task to fetch the protocol version and fee multiplier. This ensures supports_shielded() returns the correct value immediately after connection, without requiring the user to visit the Platform Info screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cached transaction indices could become out of bounds after a wallet refresh reduced the transaction count, causing an index-out-of-bounds panic during sort. Now validates cached indices against current transaction count and invalidates the cache when stale. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
select_notes_for_amount now takes a fee_headroom parameter and selects notes covering amount + fee. This prevents "fee exceeds spendable" errors when sending the full shielded balance. All three callers (shielded_transfer, unshield_credits, shielded_withdrawal) use a 500M credit headroom constant — generous enough for the most expensive transition type (withdrawal at 400M). Any excess remains as change in the shielded pool. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bootstrap_wallet_addresses only ran when known_addresses was empty, but new_from_seed already derives one Core address. Platform payment addresses were never bootstrapped for new wallets. Now checks for PlatformPayment addresses in watched_addresses and runs bootstrap if missing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The "Failed to authenticate using .cookie file" message fires on every RPC client creation when no cookie file exists (normal for user/pass auth). Demoted from debug to trace to stop log spam. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Identities (with alias/DPNS name) now appear in the Send screen's destination autocomplete. Typing an identity alias like "i1" or "identity" will match. Pre-loads identities and shielded info before the AddressInput closure to avoid double-borrow of self. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Callers must separately call with_identities() and with_shielded_balance() for those address types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When selecting Identity as send source, default to the identity with the most credits instead of the first one in the list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ions When sending from an Identity source, the same identity is excluded from the destination autocomplete dropdown. This prevents the user from accidentally sending credits to themselves. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add split transaction input on the Send screen when sending from Platform to Shielded. The total amount is divided into N randomized sub-amounts (±30% jitter, min 0.1 DASH each) that sum exactly to the requested total. Transactions are dispatched sequentially. The split_amount_randomized() helper is in model/amount.rs for reuse by the shield screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This reverts commit 6213ddb.
When the requested shield amount exceeds a single platform address balance, allocate across multiple addresses (highest balance first). Each address gets its own ShieldCredits task, dispatched sequentially. Matches the "Source breakdown" display which already shows the multi-address allocation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 15 minutes and 45 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughLowered some cookie-auth log levels, broadened wallet bootstrap conditions, implemented multi-source Platform→Shielded allocation with fee headroom and sequential tasks, converted many panic-on-poison lock usages to fallible handling, added UI wallet caching/refresh hooks, and improved Core address parsing/validation. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as "UI (Send/Shield screen)"
participant App as "AppContext"
participant Wallets as "Wallets store"
participant Backend as "BackendTask Dispatcher"
participant Shielded as "ShieldedState / CommitmentTree"
UI->>App: request shield_action(amount, optional source filter)
App->>Wallets: read platform addresses & balances
Wallets-->>App: list of addresses with balances
App->>App: sort by balance, compute fee headroom, greedy allocate across addresses
App->>Backend: create 1..N ShieldCredits tasks (per allocated address)
Backend->>Shielded: request anchor & witnesses (extract_spends_and_anchor)
Shielded-->>Backend: anchor + spends
Backend->>Shielded: submit shield operations (sequential if >1)
Shielded-->>Backend: task results (wait_for_response StateTransitionProofResult)
Backend-->>UI: final status/result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
- Format 3 long lines in bundle.rs (select_notes_for_amount calls) - Collapse nested if-let+if into if-let&&condition in wallets_screen/mod.rs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use |= for pending_next_task dispatch to avoid overwriting top-panel actions - Add invalidate_address_input() to ShieldScreen and call it on context change - Replace .expect() on core_client lock with TaskError::LockPoisoned propagation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…reen - Move identity/shielded loading inside the address_input initialization guard so DB queries and mutex locks only run when building a new AddressInput, not every frame. - Remove unnecessary addresses.clone() in multi-address shielding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The autocomplete filter prevents selecting the source identity in the dropdown, but users can still manually type their own identity ID. Now validates at send time: if source and destination identity IDs match, returns a clear error instead of dispatching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`send_core_to_shielded` used an if-let with an empty success branch and a return-error in the else, which is confusing and inconsistent with `send_platform_to_shielded` in the same file. Replace with the `if !matches!(...)` form used throughout the codebase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix(shielded): correct SHIELDED_FEE_HEADROOM to 0.1 DASH (10_000_000_000 credits); previous value of 10_000_000 was 0.0001 DASH — 100x too small, insufficient to cover actual builder fees (~180M credits observed) (Copilot comment #3000464620) - fix(shield-screen): pass selected Core address to ShieldFromAssetLock task so asset lock UTXO selection is restricted to the user-chosen address; add source_address field to ShieldFromAssetLock enum variant, thread it through context/shielded dispatch and bundle.rs using a temporary UTXO swap that is restored before Step 4 removal (thepastaclaw BLOCKING #2997616620) - fix(shield-screen): read_core_balance_duffs now returns the per-address balance when a specific Core address is selected, keeping the Max button consistent with the UTXO selection constraint - fix(send-screen): prevent identity self-send in send_identity_to_identity; return an error early if to_identity_id equals the source identity id (coderabbit #3000255245) - fix(wallets-screen): fix cache invalidation for transaction list growth; add cached_tx_source_len field and clear cache when tx count changes, not only when indices go out-of-bounds (coderabbit #2999428009) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The `select_notes_for_amount` function had two consecutive rustdoc summary lines. Merge into a single, complete sentence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r function Pull the inline `restrict_utxos` closure from `shield_from_asset_lock` into a private standalone function with an explicit signature and doc comment. Both callsites are updated; behaviour is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use `compute_minimum_shielded_fee` from `dpp` instead of a hardcoded 0.1 DASH constant. The new estimate uses the Orchard minimum of 2 actions with a 2× safety multiplier, reducing headroom from ~0.1 DASH to ~0.0025 DASH while automatically adapting to protocol version changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add `source_address: Option<&Address>` to the UTXO selection chain (`select_unspent_utxos_for` -> `select_utxos_with_fee_retry` -> `asset_lock_transaction_from_private_key` -> `generic_asset_lock_transaction`) instead of temporarily swapping the wallet's UTXO map with `std::mem::replace`. This eliminates the `restrict_utxos` hack in `shield_from_asset_lock`, which mutated shared wallet state to work around the missing parameter. The save/restore/re-filter dance is replaced with a clean parameter pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…te selection Instead of using a fixed 2x multiplier on the minimum fee as headroom, iteratively select notes and compute the exact fee based on the actual note count. The loop converges in 2-3 iterations and always produces the correct fee, even for edge cases with many small notes. Pass the pre-computed exact fee to the DPP builders via Some(exact_fee) instead of None, and correctly subtract the fee from change calculation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, fee-aware max, and banners - Replace .lock().unwrap() in render_batch_progress with .lock().ok() fallback to prevent UI thread panics on poisoned mutex - Replace all hardcoded Color32::from_rgb values with DashColors semantic constants (ERROR, SUCCESS, GRAY, INFO, WARNING, BUTTON_DISABLED) - Deduct estimated platform fee and L1 tx fee from Core max amount, and shielded fee headroom from Platform max amount, so "Max" reflects the actual shieldable balance - Migrate error_message and success_message fields to MessageBanner, the project's standard centralized banner system Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace static DashColors constants (ERROR, SUCCESS, WARNING, INFO, GRAY) with their theme-aware counterparts (error_color, success_color, etc.) that adapt to light/dark mode via the `dark_mode` boolean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…imation.rs Extract the duplicated platform fee + L1 tx fee calculation from `shield_from_asset_lock` (bundle.rs) and the shield screen UI into a shared `estimate_shield_from_core_fees_duffs` function. Both callsites now use the centralized function. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Never parse error strings — always use typed error chains. Define new error variants if needed rather than relying on fragile string matching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert progress bar fills to static DashColors constants (SUCCESS, ERROR, WARNING, INFO, GRAY). The theme-aware text-color functions (error_color, success_color, etc.) are too dark/muted for bar fills in light mode — they're designed for text on backgrounds, not fill areas. Keep theme-aware colors for text labels only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…atch On AddressInvalidNonceError, fail only the current item and continue to the next — regardless of whether our nonce is ahead or behind Platform's expected nonce. The next item may succeed if Platform catches up. Only cascade-fail on non-nonce errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…stError The AddressInvalidNonceError arrives as `Error::Protocol( ProtocolError::ConsensusError(StateError::AddressInvalidNonceError))` — not through `StateTransitionBroadcastError`. Handle both paths in `extract_expected_nonce` so nonce mismatches are properly detected and the batch continues instead of cascade-failing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Uncomment and fix Core->Platform max amount fee deduction - Use PlatformFeeEstimator::estimate_shield_from_core_fees_duffs for Core->Shielded max amount (same as shield screen) - Deduct L1 tx fee estimate for Core->Core max amount - Replace static DashColors::SUCCESS with theme-aware success_color for balance text labels - Replace hardcoded Color32 values with DashColors theme functions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dcasts Address nonces are strictly sequential on Platform (no gap tolerance). Replace the fire-and-forget wait_for_response with mandatory confirmation (3 attempts, 5s between) before proceeding to the next broadcast. If confirmation fails, cascade-fail remaining items — the nonce chain is broken without confirmed state inclusion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… fix/send-dash-screen-improvements # Conflicts: # src/ui/wallets/send_screen.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…een-improvements # Conflicts: # src/ui/wallets/send_screen.rs
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/ui/wallets/send_screen.rs`:
- Around line 1552-1582: The current allocation loop builds multiple
ShieldedTask::ShieldCredits tasks from sorted_addrs and sends them via
mark_sending into BackendTasks with Sequential mode, but
run_backend_tasks_sequential will continue on errors causing irrecoverable
partial shielding; before creating tasks in the loop (or before returning
BackendTasks) pre-validate each platform address balance against amount to spend
plus estimated fee per ShieldCredits call (use the same fee estimation logic as
the shield flow) and reject/adjust the overall send if any address cannot cover
its share, or collapse to a single task when only one address can cover the full
amount; alternatively, if partial sends are permitted, switch to an execution
flow that records expected per-task effects and update display_task_result to
properly handle BackendTaskSuccessResult::Multiple with mixed success/failure
(and ensure the UI clearly reports which addresses succeeded and which failed)
so users are not surprised by irreversible partial shielding.
- Around line 1555-1572: The loop that builds ShieldedTask::ShieldCredits tasks
allocates spend = remaining.min(*balance) without reserving per-task fees, so
tasks can be created that lack funds for fees; update this allocation to use the
centralized fee estimator (PlatformFeeEstimator from model/fee_estimation.rs)
the same way allocate_platform_addresses/send_platform_to_platform do: estimate
per-shield-task fee and subtract it from available balance when computing spend,
skip an address if balance <= fee, and ensure remaining is reduced by spend (not
including reserved fees) while each created ShieldedTask includes amounts that
leave room for its fee.
🪄 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: 529a8d83-6fd5-473b-bf00-5d4c4bf390c3
📒 Files selected for processing (5)
src/backend_task/core/mod.rssrc/context/mod.rssrc/context/wallet_lifecycle.rssrc/ui/components/address_input.rssrc/ui/wallets/send_screen.rs
- Document [0u8; 36] as empty memo parameter (not randomness) at all 6 call sites - Replace lock .unwrap() with ? in Result-returning functions (bundle.rs, shielded.rs) - Replace lock .unwrap() with .unwrap_or_else(|e| e.into_inner()) in non-Result functions - Extract extract_spends_and_anchor() helper to eliminate 3x duplicated witness blocks - Fix assume_checked() -> require_network() validation in MCP shielded/identity tools - Add source_address param to MCP ShieldFromCore tool with network validation - Add INTENTIONAL(CODE-006) comment for bootstrap address check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix multi-address shield allocation to deduct per-operation fees - Replace lock .unwrap() with .ok()? in shield_screen.rs helpers - Prevent zero-amount shield confirmation - Use context fee_multiplier_permille for shield fee headroom - Cache balance/nonce in ShieldScreen to avoid per-frame lock reads - Implement refresh_on_arrival() for shielded screens Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review GateCommit:
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/ui/wallets/shield_screen.rs (1)
329-344:⚠️ Potential issue | 🟠 MajorKeep batch failure text non-technical.
ShieldStage::label()renderserrorverbatim, and these paths now fill it with rawto_string()output plus strings like “nonce mismatch” and “Skipped: earlier nonce failed”. That leaks SDK/internal details into the progress UI. Please keeperroras a short action-oriented sentence and move diagnostics to tracing andst_json, which you already expose separately.As per coding guidelines: "All user-facing strings (labels, messages, tooltips, errors) must be simple, complete sentences." and "Never include technical details in user-facing error messages — no raw error strings, stack traces, SDK internals, or error codes. Attach via
BannerHandle::with_details(e)instead."Also applies to: 414-429, 449-473, 485-487
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/shield_screen.rs` around lines 329 - 344, The code currently injects raw error strings into ShieldStage::Failed (e.g., error: e.clone(), st_json: None) which leaks technical details to the UI; instead, replace those verbatim error assignments in the places that set ShieldStage::Failed (the match arms that lock stage around ShieldStage::Failed) with a short, user-facing sentence (e.g., "Failed to submit batch; please retry.") assigned to the error field, serialize the original error into st_json (e.g., st_json: Some(e.to_string()) or a structured diagnostic) and emit the full error to logs/tracing and/or attach it to any BannerHandle via BannerHandle::with_details(e) so diagnostics remain available but not shown verbatim in ShieldStage::label(). Ensure changes are applied to all similar blocks referenced (the shown block and the other occurrences noted).
♻️ Duplicate comments (2)
src/ui/wallets/send_screen.rs (2)
1545-1580:⚠️ Potential issue | 🔴 CriticalReject partial Platform→Shielded allocations before returning tasks.
The upfront check uses raw balances, but the loop subtracts a shield fee from every address. If that fee-adjusted capacity is smaller than
amount_credits,remainingstays positive and we still enqueue a partial set ofShieldCreditstasks. That can irreversibly shield less than the user requested. Fail when allocation ends with nonzeroremaining, and move the fee-headroom calculation into a shared helper so it stays consistent with the configured multiplier.🛠 Suggested guard
for (platform_addr, _, balance) in &sorted_addrs { if remaining == 0 { break; } let available = balance.saturating_sub(per_op_fee); if available == 0 { continue; } let spend = remaining.min(available); tasks.push(BackendTask::ShieldedTask( crate::backend_task::shielded::ShieldedTask::ShieldCredits { seed_hash, amount: spend, from_address: *platform_addr, nonce_override: None, }, )); remaining -= spend; } + + if remaining > 0 { + let max_sendable = amount_credits.saturating_sub(remaining); + return Err(format!( + "Insufficient platform balance. Need {} but only {} is available after estimated shield fees.", + format_credits_as_dash(amount_credits), + format_credits_as_dash(max_sendable), + )); + }As per coding guidelines: "All fee estimation logic must be centralized in
model/fee_estimation.rs— never inline fee math in UI or backend task code."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/send_screen.rs` around lines 1545 - 1580, The allocation currently uses raw balances (sorted_addrs) but subtracts per-op fee inline (shielded_fee_for_actions) when building tasks, which can leave remaining > 0 and produce a partial set of ShieldCredits tasks; change this by moving the fee headroom calculation into the centralized helper in model/fee_estimation.rs (add a function to compute per-address available balance given the configured multiplier), use that helper when checking total capacity and when iterating (use the same per_op_fee logic consistently instead of calling shielded_fee_for_actions inline), and if after the loop remaining != 0 return an Err indicating insufficient fee-adjusted capacity rather than returning partial BackendTask::ShieldedTask::ShieldCredits results.
1583-1589:⚠️ Potential issue | 🟠 MajorSequence multiple shields through a batch-aware result path.
BackendTasks(...Sequential)turns one send into multiple independentShieldCreditsoperations, but the screen still treats eachShieldedCreditsShieldedresult as a complete send. A two-address shield will finish with the last fragment’s amount in the success screen, and a later failure can hide earlier successful fragments entirely. This needs an aggregate batch result or explicit accumulation indisplay_task_result()/display_message().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/wallets/send_screen.rs` around lines 1583 - 1589, The current branch returns multiple ShieldCredits as separate BackendTasks which leads display_task_result()/display_message() to treat each ShieldedCreditsShielded as a complete send; change the flow to produce and handle a batch-aware result: when tasks.len() > 1 return a batch result (e.g., BackendTasksBatch or include an aggregate enum variant) instead of BackendTasks(...Sequential), and update display_task_result() and display_message() to recognize that batch variant and accumulate per-fragment outcomes (sum amounts for successful fragments, collect per-recipient success/failure and errors) so the success screen shows the aggregated total and partial failures rather than only the last fragment; refer to BackendTasks, BackendTask, ShieldCredits, display_task_result(), and display_message() when locating and implementing these changes.
🧹 Nitpick comments (1)
src/mcp/tools/shielded.rs (1)
32-33: Use a full sentence for thesource_addressschema description.This doc comment is exposed in the generated tool schema, so the parenthetical fragment is harder to localize cleanly.
✏️ Suggested wording
- /// Optional Core address to fund from (restricts UTXO selection to this address) + /// Optional Core address to fund the transaction from. When provided, UTXO selection is restricted to this address.As per coding guidelines, "All user-facing strings (labels, messages, tooltips, errors) must be simple, complete sentences."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/mcp/tools/shielded.rs` around lines 32 - 33, The doc comment for the struct field `source_address` is a fragment; change it to a full sentence so the generated tool schema shows a complete user-facing description. Update the comment above `pub source_address: Option<String>,` in src/mcp/tools/shielded.rs to something like: "Optional Core address to fund from. This restricts UTXO selection to the specified address." or an equivalent single-sentence description that preserves the parenthetical meaning.
🤖 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/context/shielded.rs`:
- Around line 110-117: The mutex poison handlers in bump_platform_address_nonce
and set_platform_address_nonce must stop silently recovering (the
unwrap_or_else(|e| e.into_inner()) calls around self.wallets.read() and
wallet_arc.write()) and instead propagate lock-poison via
TaskError::LockPoisoned; change each helper to return Result<..., TaskError> (or
Result<(), TaskError>) and replace the unwrap_or_else calls with ?-style
propagation (e.g., self.wallets.read().map_err(|_| TaskError::LockPoisoned)? and
wallet_arc.write().map_err(|_| TaskError::LockPoisoned)?). If the API cannot
change to return a Result, log a clear warning before any recovery (mirroring
context_provider.rs) and avoid persisting recovered state without explicit
caller handling; apply the same changes to the other helper that uses the same
pattern.
In `@src/mcp/tools/identity.rs`:
- Around line 468-474: The current map_err closures on the Core address parsing
and require_network calls leak parser/SDK error details by interpolating {e};
change both to return generic McpToolError::InvalidParam messages (e.g.,
"Invalid Core address" and "Core address does not match active network") without
including the error variable, updating the map_err closures that produce
McpToolError::InvalidParam for the parser path and the require_network() call so
they no longer interpolate or propagate the original error string.
In `@src/mcp/tools/shielded.rs`:
- Around line 94-100: Replace user-facing error strings that interpolate the
underlying parser error (e.g., map_err closures producing
McpToolError::InvalidParam with format!("... {e}")) with generic, stable
messages that do not include the SDK/parser error text; specifically update the
map_err after the Core address parse and the map_err after require_network to
return fixed messages like "Invalid source Core address" and "Source address
does not match active network" (and make the same change for the similar
occurrences around lines 522–527), keeping McpToolError::InvalidParam but
removing any `{e}` or underlying error details.
---
Outside diff comments:
In `@src/ui/wallets/shield_screen.rs`:
- Around line 329-344: The code currently injects raw error strings into
ShieldStage::Failed (e.g., error: e.clone(), st_json: None) which leaks
technical details to the UI; instead, replace those verbatim error assignments
in the places that set ShieldStage::Failed (the match arms that lock stage
around ShieldStage::Failed) with a short, user-facing sentence (e.g., "Failed to
submit batch; please retry.") assigned to the error field, serialize the
original error into st_json (e.g., st_json: Some(e.to_string()) or a structured
diagnostic) and emit the full error to logs/tracing and/or attach it to any
BannerHandle via BannerHandle::with_details(e) so diagnostics remain available
but not shown verbatim in ShieldStage::label(). Ensure changes are applied to
all similar blocks referenced (the shown block and the other occurrences noted).
---
Duplicate comments:
In `@src/ui/wallets/send_screen.rs`:
- Around line 1545-1580: The allocation currently uses raw balances
(sorted_addrs) but subtracts per-op fee inline (shielded_fee_for_actions) when
building tasks, which can leave remaining > 0 and produce a partial set of
ShieldCredits tasks; change this by moving the fee headroom calculation into the
centralized helper in model/fee_estimation.rs (add a function to compute
per-address available balance given the configured multiplier), use that helper
when checking total capacity and when iterating (use the same per_op_fee logic
consistently instead of calling shielded_fee_for_actions inline), and if after
the loop remaining != 0 return an Err indicating insufficient fee-adjusted
capacity rather than returning partial BackendTask::ShieldedTask::ShieldCredits
results.
- Around line 1583-1589: The current branch returns multiple ShieldCredits as
separate BackendTasks which leads display_task_result()/display_message() to
treat each ShieldedCreditsShielded as a complete send; change the flow to
produce and handle a batch-aware result: when tasks.len() > 1 return a batch
result (e.g., BackendTasksBatch or include an aggregate enum variant) instead of
BackendTasks(...Sequential), and update display_task_result() and
display_message() to recognize that batch variant and accumulate per-fragment
outcomes (sum amounts for successful fragments, collect per-recipient
success/failure and errors) so the success screen shows the aggregated total and
partial failures rather than only the last fragment; refer to BackendTasks,
BackendTask, ShieldCredits, display_task_result(), and display_message() when
locating and implementing these changes.
---
Nitpick comments:
In `@src/mcp/tools/shielded.rs`:
- Around line 32-33: The doc comment for the struct field `source_address` is a
fragment; change it to a full sentence so the generated tool schema shows a
complete user-facing description. Update the comment above `pub source_address:
Option<String>,` in src/mcp/tools/shielded.rs to something like: "Optional Core
address to fund from. This restricts UTXO selection to the specified address."
or an equivalent single-sentence description that preserves the parenthetical
meaning.
🪄 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: ec525b8c-9a49-4e52-87f9-490902c610a8
📒 Files selected for processing (10)
src/backend_task/shielded/bundle.rssrc/context/shielded.rssrc/context/wallet_lifecycle.rssrc/mcp/tools/identity.rssrc/mcp/tools/shielded.rssrc/ui/mod.rssrc/ui/wallets/send_screen.rssrc/ui/wallets/shield_screen.rssrc/ui/wallets/shielded_send_screen.rssrc/ui/wallets/unshield_credits_screen.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/context/wallet_lifecycle.rs
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Verified 3 findings (1 blocking, 2 suggestions). The convergent multi-address allocation underfunding bug is confirmed — Platform→Shielded allocation accepts amounts that exceed post-fee spendable balance and can produce empty or under-funded task lists.
Reviewed commit: 2c9fe49
🔴 1 blocking | 🟡 2 suggestion(s)
1 additional finding
🟡 suggestion: Unified send requires a shielded destination for Core/Platform->Shielded even though the destination is not used
src/ui/wallets/send_screen.rs (lines 1419-1427)
Verified. Both shielding handlers require validated_destination to be a shielded address (1419-1427 and 1525-1530), but the comments state shielding deposits into the wallet's own shielded pool, and neither backend task includes the destination address. ShieldFromAssetLock is created only with { seed_hash, amount_duffs, source_address: None } (1448-1453), and ShieldCredits is created only with { seed_hash, amount, from_address, nonce_override } (1571-1577). The UI therefore asks for a shielded destination that has no effect on execution.
🤖 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/ui/wallets/send_screen.rs`:
- [BLOCKING] lines 1520-1590: Platform->Shielded allocation can silently underfund the requested amount or dispatch an empty task list
Verified. `send_platform_to_shielded()` first accepts any request where `amount_credits <= total_available` using the raw sum of balances (`1545-1552`), but then allocates each address with `balance.saturating_sub(per_op_fee)` (`1566`). Unlike the platform->platform and platform->core paths, it never checks whether `remaining` is still nonzero after allocation and never rejects `tasks.is_empty()`. As a result, a request can be accepted even when the post-fee spendable total is insufficient, producing either a partial sequence of `ShieldCredits` tasks for less than the requested amount or `AppAction::BackendTasks([])` if every address balance is at or below the reserved fee.
- [SUGGESTION] lines 1554-1559: Platform->Shielded per-address fee reservation ignores the current fee multiplier
Verified. The per-operation headroom is computed as `shielded_fee_for_actions(2, PlatformVersion::latest())` with no multiplier adjustment. In contrast, the shield screen reserves `shielded_fee_for_actions(2, PlatformVersion::latest())` and then applies `self.app_context.fee_multiplier_permille().max(1000)` before subtracting it from the available balance (`src/ui/wallets/shield_screen.rs:777-786`). This means unified Platform->Shielded can overestimate spendable balance whenever the network fee multiplier is above 1x.
- [SUGGESTION] lines 1419-1427: Unified send requires a shielded destination for Core/Platform->Shielded even though the destination is not used
Verified. Both shielding handlers require `validated_destination` to be a shielded address (`1419-1427` and `1525-1530`), but the comments state shielding deposits into the wallet's own shielded pool, and neither backend task includes the destination address. `ShieldFromAssetLock` is created only with `{ seed_hash, amount_duffs, source_address: None }` (`1448-1453`), and `ShieldCredits` is created only with `{ seed_hash, amount, from_address, nonce_override }` (`1571-1577`). The UI therefore asks for a shielded destination that has no effect on execution.
- Reject partial Platform→Shielded allocation when remaining > 0 after
fee-adjusted loop; reject empty task list when all balances below fee
- Apply fee_multiplier_permille to per-operation fee in send_platform_to_shielded
for consistency with ShieldScreen
- Remove parser detail leakage ({e}) from MCP error messages in identity.rs
and shielded.rs — use generic user-facing messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…m/dashpay/dash-evo-tool into fix/send-dash-screen-improvements
…urning Shielded operations (transfer, unshield, shield, withdrawal) previously returned immediately after broadcast without waiting for block confirmation. This caused the subsequent SyncNotes (triggered by the Send screen) to find no updates because the state transition wasn't yet in a block. Now each operation calls wait_for_response() after broadcast to ensure the state transition is confirmed before the UI triggers a note resync. The wait is best-effort — broadcast success is still the primary success indicator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/backend_task/shielded/bundle.rs (1)
490-503:⚠️ Potential issue | 🟠 MajorRemove the pending-finality entry on every Step 3 failure.
After
tx_idis inserted intotransactions_waiting_for_finality, any failure in Step 3 (core_client.read()orsend_raw_transaction()) exits through?and leaves a stale pending entry behind. That makes the finality tracker inconsistent with a failed asset-lock submission, unlike the cleanup pattern already used insrc/backend_task/core/create_asset_lock.rs.Suggested fix
- app_context - .core_client - .read() - .map_err(|_| TaskError::LockPoisoned { - resource: "core_client", - })? - .send_raw_transaction(&asset_lock_transaction)?; + let broadcast_result = (|| -> Result<(), TaskError> { + app_context + .core_client + .read() + .map_err(|_| TaskError::LockPoisoned { + resource: "core_client", + })? + .send_raw_transaction(&asset_lock_transaction)?; + Ok(()) + })(); + + if let Err(e) = broadcast_result { + if let Ok(mut proofs) = app_context.transactions_waiting_for_finality.lock() { + proofs.remove(&tx_id); + } else { + tracing::warn!( + "Failed to clean up finality tracking for asset lock transaction {tx_id}" + ); + } + return Err(e); + }🤖 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 490 - 503, After inserting tx_id into transactions_waiting_for_finality, any early return from app_context.core_client.read() or .send_raw_transaction(&asset_lock_transaction) can leave a stale entry; modify the Step 3 call sites so that on any error you first remove the pending entry (lock app_context.transactions_waiting_for_finality and remove tx_id) before returning the TaskError. Concretely, replace the chained map_err/? usage around app_context.core_client.read() and the subsequent send_raw_transaction call with explicit error handling: attempt core_client.read(), on Err remove tx_id then return TaskError::LockPoisoned; then call send_raw_transaction, and on Err remove tx_id then propagate the send error. Use the existing symbols transactions_waiting_for_finality, tx_id, app_context.core_client.read(), send_raw_transaction, and asset_lock_transaction to locate and update the code paths.
🤖 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/shielded/bundle.rs`:
- Around line 828-846: Replace the current error messages that pipe
commitment-tree internals (from tree.witness() and tree.anchor()) into
TaskError::ShieldedMerkleWitnessUnavailable by returning a single non-technical,
user-facing message (e.g. "Unable to verify shielded note; please retry or
contact support") and move the original low-level error (the `e` value) into
diagnostic/details metadata via BannerHandle::with_details(e) or equivalent;
update both the map_err branches used around merkle_path (the tree.witness(...)
closure) and the anchor = tree.anchor() error mapping so they produce the fixed
everyday-user message while attaching the original error as details.
---
Outside diff comments:
In `@src/backend_task/shielded/bundle.rs`:
- Around line 490-503: After inserting tx_id into
transactions_waiting_for_finality, any early return from
app_context.core_client.read() or .send_raw_transaction(&asset_lock_transaction)
can leave a stale entry; modify the Step 3 call sites so that on any error you
first remove the pending entry (lock
app_context.transactions_waiting_for_finality and remove tx_id) before returning
the TaskError. Concretely, replace the chained map_err/? usage around
app_context.core_client.read() and the subsequent send_raw_transaction call with
explicit error handling: attempt core_client.read(), on Err remove tx_id then
return TaskError::LockPoisoned; then call send_raw_transaction, and on Err
remove tx_id then propagate the send error. Use the existing symbols
transactions_waiting_for_finality, tx_id, app_context.core_client.read(),
send_raw_transaction, and asset_lock_transaction to locate and update the code
paths.
🪄 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: 70e37ff0-1aba-4c5e-ae95-f00964de3ff8
📒 Files selected for processing (1)
src/backend_task/shielded/bundle.rs
There was a problem hiding this comment.
Pull request overview
This PR improves wallet send + shielded UX/performance and merges the unified shield screen work by hardening lock handling, centralizing fee estimation usage in UI flows, and enhancing platform→shielded sends (multi-address allocation + fee headroom). It also extends MCP shield tools with an optional Core source address and makes wallet address bootstrapping more robust.
Changes:
- Add refresh-on-arrival behavior for shielded screens and cache wallet balances/nonces to avoid per-frame lock reads.
- Implement multi-address greedy allocation for Platform→Shielded sends with per-operation fee headroom and improved self-send prevention.
- Harden backend/UI locking and shield transition flow (lock poisoning handling, witness extraction helper, optional confirmation wait, reduced cookie-auth log verbosity).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ui/wallets/unshield_credits_screen.rs | Avoid panicking on shielded state lock + refresh max balance when arriving. |
| src/ui/wallets/shielded_send_screen.rs | Avoid panicking on shielded state lock + refresh max balance when arriving. |
| src/ui/wallets/shield_screen.rs | Cache nonce/balances and refresh them on arrival/source change/task completion; fee headroom uses context multiplier; block zero-amount. |
| src/ui/wallets/send_screen.rs | Multi-address Platform→Shielded allocation with per-op fee reservation; self-send prevention; destination input preloading for fewer per-frame queries. |
| src/ui/mod.rs | Wire shielded screens into refresh() / refresh_on_arrival() dispatch. |
| src/ui/components/address_input.rs | Clarify with_wallets() only provides Core/Platform autocomplete and how to add identities/shielded entries. |
| src/mcp/tools/shielded.rs | Add optional source_address for Core→Shielded tool; tighten Core address parsing/network validation. |
| src/mcp/tools/identity.rs | Tighten Core address parsing/network validation for identity withdrawal tool. |
| src/context/wallet_lifecycle.rs | Broaden wallet address bootstrapping to cover wallets missing PlatformPayment derivations. |
| src/context/shielded.rs | Replace lock panics with poisoned-lock recovery or ? propagation in shielded context operations. |
| src/context/mod.rs | Reduce cookie-auth failure log from debug → trace. |
| src/backend_task/shielded/bundle.rs | Reduce panics on locks, dedupe witness extraction, add best-effort confirmation wait after broadcasts. |
| src/backend_task/core/mod.rs | Reduce cookie-auth failure log from debug → trace. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…_screen - Fix potential deadlock in refresh_cached_balances: clone wallet Arc and drop wallets map lock before acquiring per-wallet read lock - Replace hardcoded Color32::DARK_GREEN and Color32::from_rgb(255,100,100) with DashColors::success_color/error_color in unshield_credits_screen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
feat/unified-shield-screen: Centralized fee estimation (fee_estimation.rs), theme-aware colors replacing hardcodedDashColors::SUCCESS, parallel batch nonce handling, iterative note selection, shield screen hardening with banners and fee-aware max,source_addressfield onShieldFromAssetLockv1.0-dev: Picks up PR feat(ui): unified shield screen with address selection #801 (unified shield screen with address selection)Key changes
model/fee_estimation.rs— both platform state transition fees and shielded fee calculationssuccess_color(dark_mode)for dark mode supportReview fixes applied
After code review triage, the following fixes were applied:
Backend (
ae7b3471)[0u8; 36]memo parameter at all 6 ZK proof builder call sites (confirmed as structured memo, not entropy)assume_checked()withrequire_network()for Core address validation in MCP withdrawal tools.unwrap()calls with?or.unwrap_or_else()in bundle.rs + shielded.rsextract_spends_and_anchor()helper, eliminating 3x witness extraction duplicationsource_addressparameter to MCPShieldedShieldFromCoretoolINTENTIONAL(CODE-006)comment for bootstrap address type checkUI (
f5edcd31).unwrap()with.ok()?in shield/send/shielded screens (5 files).is_some_and(|v| v > 0)fee_multiplier_permillefrom context instead of hardcoded 2xrefresh_on_arrival()implemented for all 3 shielded screensTest plan
cargo clippy --all-features --all-targets -- -D warningspasses ✅cargo +nightly fmt --allpasses ✅🤖 Co-authored by Claudius the Magnificent AI Agent
Summary by CodeRabbit
New Features
Improvements
Documentation