Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
171 commits
Select commit Hold shift + click to select a range
64fab20
more work
QuantumExplorer Feb 17, 2026
f42b58d
Merge branch 'v1.0-dev' into zk
QuantumExplorer Feb 17, 2026
f8df452
more work
QuantumExplorer Feb 24, 2026
64ca962
Merge branch 'v1.0-dev' into zk
QuantumExplorer Feb 24, 2026
3cbf5ba
Merge remote-tracking branch 'origin/v1.0-dev' into zk
lklimek Feb 24, 2026
0cde65f
fix(spv): zero out stale per-address balances during reconciliation (…
lklimek Feb 24, 2026
0879c45
fix: handle malformed YAML gracefully in load_testnet_nodes_from_yml …
lklimek Feb 24, 2026
4d8b2b2
chore: let Claude write manual test scenarios for PRs (#634)
lklimek Feb 24, 2026
17650e9
build(flatpak): use only-arches for dynamic protoc architecture selec…
lklimek Feb 24, 2026
da00777
Merge branch 'v1.0-dev' into zk-extract/all-merged
lklimek Feb 24, 2026
7668de9
Merge remote-tracking branch 'origin/zk-extract/all-merged' into zk-e…
lklimek Feb 24, 2026
1ec2e7e
fix(ci): remove local path patches, use git deps for platform crates
lklimek Feb 24, 2026
0710295
fix(test): store wallet in DB before registering addresses
lklimek Feb 25, 2026
e078d0b
Merge remote-tracking branch 'origin/v1.0-dev' into zk-extract/all-me…
lklimek Feb 25, 2026
7e95598
Merge remote-tracking branch 'origin/v1.0-dev' into zk-extract/all-me…
lklimek Feb 25, 2026
afd7a4c
fix(build): restore shielded module declaration removed during merge
lklimek Feb 25, 2026
64651c5
fix(test): restore store_wallet calls lost in merge (#663)
Copilot Feb 25, 2026
e2060b2
Merge remote-tracking branch 'origin/v1.0-dev' into zk-extract/all-me…
lklimek Mar 6, 2026
91314ca
Merge remote-tracking branch 'origin/v1.0-dev' into zk-extract/all-me…
lklimek Mar 17, 2026
508817a
chore: update platform dependency to 3.1-dev branch
QuantumExplorer Mar 17, 2026
0cb6d7e
refactor: migrate shielded module from Result<T, String> to typed Tas…
lklimek Mar 17, 2026
a9f6c29
Merge origin/zk and migrate platform dependency to v3.1-dev
lklimek Mar 17, 2026
62961b1
Merge pull request #644 from dashpay/zk-extract/all-merged
QuantumExplorer Mar 22, 2026
d8285fa
Merge remote-tracking branch 'origin/zk' into zk
QuantumExplorer Mar 22, 2026
12b7c53
fix(ui): prevent settings password row from clipping right edge
QuantumExplorer Mar 23, 2026
1d0030d
Merge branch 'zk-fixes' of https://github.com/dashpay/dash-evo-tool i…
lklimek Mar 23, 2026
d7badfe
Merge remote-tracking branch 'origin/v1.0-dev' into zk-fixes
lklimek Mar 23, 2026
08cecbb
fix(rpc): include host:port in connection-refused errors and always s…
lklimek Mar 23, 2026
c4c7b30
Merge remote-tracking branch 'origin/v1.0-dev' into zk-fixes
lklimek Mar 23, 2026
d51745f
fix(ui): save RPC password for active network instead of hardcoded Re…
lklimek Mar 23, 2026
227bbce
fix(ui): ensure funding method dropdown fits all items without scrollbar
lklimek Mar 23, 2026
567e9be
fix(error): show actionable message for insufficient identity balance
lklimek Mar 23, 2026
a38c594
fix(ui): clear stale error banners when saving RPC password
lklimek Mar 23, 2026
e6d72af
fix: use network-compatible comparison for platform address lookups
lklimek Mar 23, 2026
e908b82
fix(error): add actionable messages for shielded fee and pool-size er…
lklimek Mar 23, 2026
c91d53b
fix(error): add actionable message for shielded anchor mismatch
lklimek Mar 23, 2026
1ac5292
fix(shielded): auto-resync notes and retry on anchor mismatch
lklimek Mar 23, 2026
56a1f2e
fix(shielded): ensure shielded tables exist and log DB errors during …
lklimek Mar 23, 2026
2f991f2
fix(db): consolidate migrations v28-v32 into v33
lklimek Mar 23, 2026
f0040f4
fix(db): consolidate migrations v28-v32 into v33
lklimek Mar 23, 2026
b29811c
Merge remote-tracking branch 'origin/v1.0-dev' into fix/consolidate-m…
lklimek Mar 23, 2026
19c4934
chore: pin platform dependency to zk-fixes revision
lklimek Mar 23, 2026
ab8a230
fix(db): address PR review — fresh schema, error propagation, rename …
lklimek Mar 23, 2026
6a36d5f
fix(db): address PR review — fresh schema, error propagation, rename …
lklimek Mar 23, 2026
f4efd64
fix(db): add foreign key constraints to shielded tables
lklimek Mar 23, 2026
1e59763
fix(db): add foreign key constraints to shielded tables
lklimek Mar 23, 2026
9d4d65a
Merge remote-tracking branch 'origin/v1.0-dev' into fix/consolidate-m…
lklimek Mar 23, 2026
60f9031
Merge remote-tracking branch 'origin/fix/consolidate-migrations-v33-z…
lklimek Mar 23, 2026
08495bf
fix(db): remove duplicate shielded methods after v1.0-dev merge
lklimek Mar 23, 2026
c3af91a
fix(test): remove duplicate wallet store in register_test_address
lklimek Mar 23, 2026
77bdd70
fix(shielded): address PR review — error propagation, retry helper, s…
lklimek Mar 23, 2026
67dfab7
fix(error): address PR review — Amount formatting, Display completeness
lklimek Mar 23, 2026
4b1de7b
fix(rpc): address PR review — context fallback, success banner, error…
lklimek Mar 23, 2026
fe69656
chore: simplify shielded helpers comment in initialization.rs
lklimek Mar 23, 2026
4d47ea4
fix(db): address PR #789 review — doc comments, migration default
lklimek Mar 23, 2026
ddea453
fix(db): remove duplicate shielded methods after v1.0-dev merge
lklimek Mar 23, 2026
0d4e359
chore: simplify shielded helpers comment in initialization.rs
lklimek Mar 23, 2026
b0df68b
chore(db): document migration DEFAULT 2 vs fresh DEFAULT 0
lklimek Mar 23, 2026
d563250
fix(wallet): address review — zero-balance detection, status warning
lklimek Mar 23, 2026
3c316a5
fix(error): address review — typed BIP32 source, migration error variant
lklimek Mar 23, 2026
4d6dd98
test(db): add v33 consolidated migration regression tests
lklimek Mar 23, 2026
da4814a
fix(ui): show warning when config save fails instead of success
lklimek Mar 23, 2026
78f25a8
fix(error): consolidate format_credits_as_dash and remove jargon from…
lklimek Mar 23, 2026
89d8a97
fix(test): remove duplicate wallet store in register_test_address
lklimek Mar 23, 2026
aff86d9
fix(core): log chain lock RPC errors that aren't auth/connection fail…
lklimek Mar 24, 2026
1a8c047
feat(ui): surface chain lock RPC errors in Networks tab
lklimek Mar 24, 2026
ddcb742
feat(ui): unified AddressInput component with autocomplete (#787)
lklimek Mar 24, 2026
6e2b2b0
fix(error): handle asset lock and shielded insufficient funds errors …
lklimek Mar 24, 2026
3e744c8
fix(ui): use AmountInput component for all amount inputs in wallet sc…
lklimek Mar 24, 2026
839f1ee
docs: add UI components reference and teach CLAUDE.md to use it
lklimek Mar 24, 2026
3e2fac6
feat(ui): redesign wallet screen information architecture
lklimek Mar 24, 2026
51b16b2
fix(ui): use AddressInput component in Mine dialog for core address s…
lklimek Mar 24, 2026
9bae9f8
fix(ui): trigger shielded balance refresh after all shielding operations
lklimek Mar 24, 2026
ae63652
chore: demote cookie auth fallback log to trace level
lklimek Mar 24, 2026
8dae295
fix(ui): prevent transaction list showing wrong wallet data
lklimek Mar 24, 2026
f1f8ba9
fix(ui): auto-show zero-balance addresses when wallet is empty
lklimek Mar 24, 2026
565cbd2
fix(ui): address PR review — shielded balance, dev tools layout
lklimek Mar 24, 2026
019bce6
Merge remote-tracking branch 'origin/zk-fixes' into feat/wallet-ui-re…
lklimek Mar 24, 2026
ce83455
fix(ui): rename transaction heading, add explorer link, merge zk-fixes
lklimek Mar 24, 2026
9c07f95
fix(ui): trigger shielded sync on wallet refresh and wallet switch
lklimek Mar 24, 2026
df78e49
fix(log): add diagnostic logging for shielded transfer operations
lklimek Mar 24, 2026
7e11a2b
fix(ui): improve Dev Tools dropdown layout and refresh mode cycling
lklimek Mar 24, 2026
e8eccc8
fix(ui): fix shielded balance conversion, add tab balances, reorder s…
lklimek Mar 24, 2026
9790095
feat(ui): collapsible sections, move tx history to Dash Core tab, res…
lklimek Mar 24, 2026
4ad1b1b
Merge remote-tracking branch 'origin/zk-fixes' into feat/wallet-ui-re…
lklimek Mar 24, 2026
ed196d6
fix(ui): improve shielded transfer UX messaging for balance update de…
lklimek Mar 24, 2026
73f2f3b
feat(ui): consolidate dev-mode accounts into System tab, limit balanc…
lklimek Mar 24, 2026
63ce0e1
fix(shielded): scope commitment tree per wallet for multi-wallet corr…
lklimek Mar 24, 2026
f16d982
Merge remote-tracking branch 'origin/zk-fixes' into feat/wallet-ui-re…
lklimek Mar 24, 2026
875ee14
Merge pull request #789 from dashpay/fix/consolidate-migrations-v33-zk
lklimek Mar 24, 2026
0d5a09d
Merge branch 'zk' into zk-fixes
lklimek Mar 24, 2026
6b41301
Merge branch 'zk-fixes' into feat/wallet-ui-redesign
lklimek Mar 24, 2026
e41a72d
fix(shielded): prevent permanent state leak on sync_notes failure in …
lklimek Mar 24, 2026
6e775a4
fix(ui): apply password change for current session even when config s…
lklimek Mar 24, 2026
95782ae
fix(error): use dedicated TaskError variants for non-build wallet errors
lklimek Mar 24, 2026
a12cc6a
fix(error): preserve error chain in CoreRpcConnectionFailed
lklimek Mar 24, 2026
280820f
feat(ui): two-column wallet header layout, rename Tools to Advanced
lklimek Mar 24, 2026
344c229
fix(test): rename test_v33_migration_fresh_install to match DB versio…
lklimek Mar 24, 2026
712049b
revert(shielded): use shared main DB for commitment tree instead of p…
lklimek Mar 24, 2026
ee5b8a9
Merge branch 'zk-fixes' into feat/wallet-ui-redesign
lklimek Mar 24, 2026
daa825b
fix(shielded): fix stale clear_commitment_tree_for_wallet reference a…
lklimek Mar 24, 2026
b561c3e
fix(shielded): harden anchor retry, commitment tree clearing, and sta…
lklimek Mar 24, 2026
f640aaa
fix(core): sanitize RPC errors and simplify CoreRpcConnectionFailed
lklimek Mar 24, 2026
6eed03c
fix(ui): config permissions, address detection, and per-frame caching
lklimek Mar 24, 2026
9ee4e93
Merge remote-tracking branch 'origin/zk-fixes' into feat/wallet-ui-re…
lklimek Mar 24, 2026
810a686
fix(logging): correct log levels per coding best practices
lklimek Mar 24, 2026
801f730
chore: minor ui fixes
lklimek Mar 24, 2026
907cdda
feat(ui): replace Advanced dropdown with inline right-aligned buttons
lklimek Mar 24, 2026
21882ce
fix(ui): use allocate_ui_with_layout to right-align dev buttons
lklimek Mar 24, 2026
b52369e
fix(ui): eagerly initialize shielded balance on wallet screen open
lklimek Mar 24, 2026
bdb48aa
fix: remove stale balance_breakdown_expanded field from cherry-pick
lklimek Mar 24, 2026
d488892
fix(ui): move action buttons to full-width row below header columns
lklimek Mar 24, 2026
e693f15
refactor(ui): change sync status to bullet-point layout
lklimek Mar 24, 2026
54609a9
fix(shielded): prevent double balance from redundant init + sync chain
lklimek Mar 24, 2026
24a3521
refactor(shielded): move initialization entirely to backend, remove U…
lklimek Mar 24, 2026
a5e7930
fix(shielded): deduplicate notes in sync to prevent double balance
lklimek Mar 24, 2026
a4be13b
feat(ui): add shielded diversified address table
lklimek Mar 24, 2026
7581713
fix: address review findings — duplicate controls, perf, tab visibility
lklimek Mar 25, 2026
8e2eb9c
fix(ui): replace mutex unwrap() with graceful error handling in shiel…
lklimek Mar 25, 2026
e706c3f
Merge remote-tracking branch 'origin/zk-fixes' into feat/wallet-ui-re…
lklimek Mar 26, 2026
e140c60
Merge remote-tracking branch 'origin/v1.0-dev' into zk
lklimek Mar 26, 2026
f2e8142
fix(mcp): resolve async lifetime errors for Rust 2024 edition
lklimek Mar 26, 2026
46ea490
chore: fix mcp dispatch
lklimek Mar 26, 2026
cfa1dbb
doc: remove obsolete manual teting docs
lklimek Mar 26, 2026
05a4f20
Merge remote-tracking branch 'origin/zk' into zk-fixes
lklimek Mar 26, 2026
c7a5ca7
Merge remote-tracking branch 'origin/v1.0-dev' into zk-fixes
lklimek Mar 26, 2026
acf4058
fix(ui): include zero-balance addresses in AddressInput wallet entries
lklimek Mar 26, 2026
0e1a958
fix(ui): sort AddressInput wallet entries alphabetically
lklimek Mar 26, 2026
790453c
revert: remove redundant sort in extract_wallet_entries
lklimek Mar 26, 2026
005cadf
fix(ui): clarify shielded network validation logic
lklimek Mar 26, 2026
92f2e70
fix(ui): include send-all transactions in wallet history
lklimek Mar 26, 2026
3cd095e
fix(ui): use is_ours flag for transaction filtering
lklimek Mar 26, 2026
e363c6d
test(e2e): verify is_ours flag for SPV send and receive transactions
lklimek Mar 26, 2026
49d9c5d
Merge remote-tracking branch 'origin/zk-fixes' into feat/wallet-ui-re…
lklimek Mar 26, 2026
24256db
fix(core): clear RPC error state on successful chain lock fetch
lklimek Mar 26, 2026
4187fff
fix(model): move is_platform_address_string from UI helpers to model …
lklimek Mar 26, 2026
bb0c6dc
fix(ui): add actionable guidance to address validation error messages
lklimek Mar 26, 2026
f55b138
fix(ui): invalidate address inputs on all screens during context switch
lklimek Mar 26, 2026
7046358
fix(ui): clear password field on network switch when no config exists
lklimek Mar 26, 2026
1dcf070
fix(ui): clear validated_destination in invalidate_address_input
lklimek Mar 26, 2026
eaccb82
fix(ui): consume ShieldedNotesSynced to update shielded send screen s…
lklimek Mar 26, 2026
6a43e7c
style: apply cargo +nightly fmt formatting
lklimek Mar 26, 2026
5e4df1c
fix(model): disambiguate Core vs Identity address detection by prefix
lklimek Mar 26, 2026
6efba25
Merge remote-tracking branch 'origin/v1.0-dev' into feat/wallet-ui-re…
lklimek Mar 26, 2026
642bd58
fix(core): show actual RPC error on Networks page instead of generic …
lklimek Mar 26, 2026
3c89c3a
fix(db): resolve deadlock in clear_network_data
lklimek Mar 26, 2026
5e934a0
fix(ui): replace mutex unwrap() with graceful .ok() in shielded_sync_…
lklimek Mar 26, 2026
d3faf5e
fix(ui): replace RwLock unwrap() with graceful error handling in shie…
lklimek Mar 26, 2026
a51fb67
fix(spv): add debug log when overriding is_ours for receive transactions
lklimek Mar 26, 2026
e2ba5fc
fix(shielded): log warning on note value divergence during position d…
lklimek Mar 26, 2026
47c8821
perf(model): avoid allocation in is_platform_address_string
lklimek Mar 26, 2026
631aa1a
refactor(ui): remove unused label field and is_key_only from AccountS…
lklimek Mar 26, 2026
b4fe404
refactor(ui): avoid cloning full AccountTab enum in tab content match
lklimek Mar 26, 2026
ceb96af
fix(ui): clear validated_address on network switch in mine dialog
lklimek Mar 26, 2026
d595fb5
refactor(model): move truncate_address to model layer and document AS…
lklimek Mar 26, 2026
d9d6ac9
docs(shielded): document why spawn_blocking trampoline is needed in q…
lklimek Mar 26, 2026
8bab96b
style: apply nightly rustfmt formatting
lklimek Mar 26, 2026
8b73885
fix(wallet): bootstrap platform addresses on wallet creation
lklimek Mar 26, 2026
4d4ee72
fix(ui): show bootstrapped platform addresses in AddressInput
lklimek Mar 26, 2026
1d91082
refactor(ui): pass account filter directly to render_address_table
lklimek Mar 26, 2026
f3ca538
perf(shielded): move wallet initialization to background thread
lklimek Mar 26, 2026
261fe9e
test(spv): add unit tests for is_ours override logic
lklimek Mar 26, 2026
0b323c1
docs(user-stories): add stories for wallet tab redesign
lklimek Mar 26, 2026
11578bd
style: apply nightly rustfmt formatting
lklimek Mar 26, 2026
c096105
feat(ui): distinguish change addresses in AddressInput autocomplete
lklimek Mar 26, 2026
e95a393
fix(ui): exclude change addresses from Send screen destination input
lklimek Mar 26, 2026
b475d1a
feat(ui): add new address generation button to wallet tabs
lklimek Mar 26, 2026
4889de7
fix(ui): exclude system addresses from AddressInput autocomplete
lklimek Mar 26, 2026
f6b7bf7
feat(ui): always show address type label, enable type-based filtering
lklimek Mar 26, 2026
d23388a
fix(error): add user-friendly message for shielded nonce mismatch
lklimek Mar 26, 2026
fabcb73
style: fix stable rustfmt formatting for address_input filter
lklimek Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/user-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,43 @@ As a user, I want to withdraw credits from a Platform address back to a Core add
- Destination Core address input.
- Fee strategy configuration.

### WAL-021: Navigate wallet accounts via tabs [Implemented]
**Persona:** Alex, Priya

As a user, I want to see clear tabs for Dash Core, Platform, and Shielded so that I can switch between account views without searching through a dropdown.

- Tab bar replaces account category dropdown.
- Each tab shows its balance in the label.
- Empty accounts display "(empty)" indicator.
- Switching tabs is instant with no data reload.

### WAL-022: View system accounts in developer mode [Implemented]
**Persona:** Jordan

As a developer, I want a System tab that reveals all internal account categories (Identity Registration, CoinJoin, Provider keys, etc.) so that I can inspect low-level wallet structure without cluttering the default view.

- System tab appears only when developer mode is enabled.
- Each system account category is shown as a collapsible section.
- Section headers display address count and balance.

### WAL-023: Collapsible transaction history [Implemented]
**Persona:** Alex, Priya

As a user, I want the transaction history to be collapsible so that I can focus on addresses or balances without scrolling past a long list of transactions.

- Transaction history section has a collapsible header.
- Collapsed by default to reduce visual clutter.
- Expand/collapse state persists within the session.

### WAL-024: Collapsible balance breakdown [Implemented]
**Persona:** Priya

As a power user, I want the balance breakdown and address table to be collapsible so that I can focus on the information I need at the moment.

- Address table section has a collapsible header.
- Asset locks section has a collapsible header.
- Sections are expanded by default for quick access.
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.

---

## Send and Receive (SND)
Expand Down
9 changes: 6 additions & 3 deletions src/backend_task/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,14 @@ impl AppContext {
if let Some(task_err) = Self::chain_lock_rpc_error(active_config, e) {
return Err(task_err);
}
// Non-auth, non-connection error — log the raw error but show
// a sanitized message in the UI status display.
// Non-auth, non-connection error — show the actual error
// in the Networks page status display for debugging.
tracing::warn!(network = ?self.network, error = %e, "Chain lock query failed on active network");
Some("RPC error — check Dash Core status".to_string())
Some(format!("RPC error: {e}"))
} else {
// Successful chain lock fetch — clear any lingering RPC error
// so the connection status recovers after a transient outage.
self.connection_status.set_rpc_last_error(None);
Comment thread
lklimek marked this conversation as resolved.
None
};

Expand Down
17 changes: 17 additions & 0 deletions src/backend_task/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,16 @@ pub enum TaskError {
source: Box<dash_sdk::Error>,
},

/// The nonce used for a shielded transaction was stale. The wallet's cached
/// nonce was behind Platform's expected nonce. Retrying will use the correct nonce.
#[error(
"The transaction used an outdated sequence number. Please retry — the wallet will use the correct number automatically."
)]
ShieldedNonceMismatch {
#[source]
source_error: Box<dash_sdk::Error>,
},
Comment thread
lklimek marked this conversation as resolved.

/// The address used for a shielded transaction does not have enough locked funds.
#[error(
"Not enough funds locked for this shielded transaction. \
Expand Down Expand Up @@ -1006,6 +1016,13 @@ pub fn shielded_broadcast_error(e: SdkError) -> TaskError {
source_error: Box::new(e),
};
}
if let Some(ConsensusError::StateError(StateError::AddressInvalidNonceError(_))) =
consensus_error
{
return TaskError::ShieldedNonceMismatch {
source_error: Box::new(e),
};
}
TaskError::ShieldedBroadcastFailed {
source: Box::new(e),
}
Expand Down
20 changes: 20 additions & 0 deletions src/backend_task/shielded/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,31 @@ pub async fn sync_notes(
}

// Persist and record decrypted notes that are new (position >= already_have).
// Also skip notes already in memory (loaded from DB during init) to prevent
// double-counting when the commitment tree resets but persisted notes remain.
// Build a HashMap of position->value for O(1) lookups and divergence detection.
let existing_notes: std::collections::HashMap<u64, u64> = shielded_state
.notes
.iter()
.map(|n| (u64::from(n.position), n.note.value().inner()))
.collect();
let mut new_note_count = 0u32;
for dn in result.decrypted_notes {
if dn.position < already_have {
continue; // already stored in a previous sync
}
if let Some(&existing_value) = existing_notes.get(&dn.position) {
let new_value = dn.note.value().inner();
if new_value != existing_value {
tracing::warn!(
position = dn.position,
existing_value,
new_value,
"Shielded note dedup: value divergence at existing position"
);
}
continue; // already loaded from DB during init
Comment thread
lklimek marked this conversation as resolved.
}
Comment thread
lklimek marked this conversation as resolved.

// Compute the spending nullifier from our FVK (dn.nullifier is the rho/nf
// field from the compact action, not the spending nullifier).
Expand Down
6 changes: 3 additions & 3 deletions src/context/shielded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl AppContext {
}

/// Initialize shielded wallet state by deriving ZIP32 keys from the wallet seed.
fn initialize_shielded_wallet(
pub(crate) fn initialize_shielded_wallet(
self: &Arc<Self>,
seed_hash: WalletSeedHash,
) -> Result<BackendTaskSuccessResult, TaskError> {
Expand Down Expand Up @@ -277,7 +277,7 @@ impl AppContext {
}

/// Sync shielded notes from platform.
async fn sync_shielded_notes(
pub(crate) async fn sync_shielded_notes(
self: &Arc<Self>,
seed_hash: WalletSeedHash,
) -> Result<BackendTaskSuccessResult, TaskError> {
Expand Down Expand Up @@ -539,7 +539,7 @@ impl AppContext {
}

/// Check nullifiers to detect spent notes.
async fn check_nullifiers_task(
pub(crate) async fn check_nullifiers_task(
self: &Arc<Self>,
seed_hash: WalletSeedHash,
) -> Result<BackendTaskSuccessResult, TaskError> {
Expand Down
140 changes: 134 additions & 6 deletions src/context/wallet_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,18 @@ impl AppContext {
}

pub fn bootstrap_wallet_addresses(&self, wallet: &Arc<RwLock<Wallet>>) {
if let Ok(mut guard) = wallet.write()
&& guard.known_addresses.is_empty()
{
tracing::info!(wallet = %hex::encode(guard.seed_hash()), "Bootstrapping wallet addresses");
guard.bootstrap_known_addresses(self);
if let Ok(mut guard) = wallet.write() {
// Bootstrap when no addresses exist (fresh wallet) or when
// platform payment addresses haven't been derived yet (wallet
// created with only a Core address via new_from_seed).
let has_platform_addresses = guard.watched_addresses.values().any(|info| {
info.path_reference
== crate::model::wallet::DerivationPathReference::PlatformPayment
});
if guard.known_addresses.is_empty() || !has_platform_addresses {
tracing::info!(wallet = %hex::encode(guard.seed_hash()), "Bootstrapping wallet addresses");
guard.bootstrap_known_addresses(self);
}
}
}

Expand All @@ -150,6 +157,13 @@ impl AppContext {
self.queue_spv_wallet_load(seed_hash, seed_bytes);
// Note: Platform address sync is not done here.
// Core UTXO refresh is handled at startup in bootstrap_loaded_wallets.

// Initialize shielded wallet on a background thread to avoid
// blocking the UI — ZIP32 key derivation and DB reads can stall.
// After init completes, queue async SyncNotes -> CheckNullifiers.
// This is the single init path — the UI never dispatches
// InitializeShieldedWallet.
self.queue_shielded_init_and_sync(seed_hash);
Comment thread
lklimek marked this conversation as resolved.
}
}

Expand All @@ -164,6 +178,72 @@ impl AppContext {
self.queue_spv_wallet_unload(seed_hash);
}

/// Queue shielded wallet initialization on a blocking thread, then
/// follow up with note sync + nullifier check. Tracked via `subtasks`
/// so it participates in graceful shutdown and cancellation.
fn queue_shielded_init_and_sync(self: &Arc<Self>, seed_hash: WalletSeedHash) {
let ctx = Arc::clone(self);
self.subtasks.spawn_sync("shielded_init", async move {
let ctx2 = Arc::clone(&ctx);
let init_result =
tokio::task::spawn_blocking(move || ctx2.initialize_shielded_wallet(seed_hash))
.await;
match init_result {
Ok(Ok(_)) => {
tracing::trace!(
seed = %hex::encode(seed_hash),
"Shielded wallet state initialized on unlock"
);
ctx.run_shielded_sync(seed_hash).await;
}
Ok(Err(e)) => tracing::debug!(
seed = %hex::encode(seed_hash),
error = %e,
"Shielded wallet init skipped on unlock"
),
Err(e) => tracing::debug!(
seed = %hex::encode(seed_hash),
error = %e,
"Shielded init task panicked"
),
}
});
}

/// Run SyncNotes -> CheckNullifiers sequence on a blocking thread.
async fn run_shielded_sync(self: &Arc<Self>, seed_hash: WalletSeedHash) {
let ctx = Arc::clone(self);
let handle = tokio::runtime::Handle::current();
let result = tokio::task::spawn_blocking(move || {
handle.block_on(async {
match ctx.sync_shielded_notes(seed_hash).await {
Ok(_) => {
if let Err(e) = ctx.check_nullifiers_task(seed_hash).await {
tracing::debug!(
seed = %hex::encode(seed_hash),
error = %e,
"Shielded nullifier check after init failed"
);
}
}
Err(e) => tracing::debug!(
seed = %hex::encode(seed_hash),
error = %e,
"Shielded note sync after init failed"
),
}
})
})
.await;
if let Err(e) = result {
tracing::debug!(
seed = %hex::encode(seed_hash),
error = %e,
"Shielded sync task panicked"
);
}
}
Comment thread
lklimek marked this conversation as resolved.

fn wallet_seed_snapshot(wallet: &Arc<RwLock<Wallet>>) -> Option<(WalletSeedHash, [u8; 64])> {
let guard = wallet.read().ok()?;
if !guard.is_open() {
Expand Down Expand Up @@ -841,7 +921,7 @@ impl AppContext {
net_amount: record.net_amount,
fee: record.fee,
label: record.label.clone(),
is_ours: record.is_ours,
is_ours: spv_is_ours_override(record.is_ours, record.net_amount),
status,
}
})
Expand Down Expand Up @@ -889,3 +969,51 @@ impl AppContext {
self.connection_status.reset_timer();
}
}

/// SPV transaction history is per-wallet — all entries involve our addresses
/// (they passed bloom filter + `check_transaction()` address matching).
/// Upstream sets `is_ours` only for sends (`net_amount < 0`); we override
/// to `true` for all matched transactions since address ownership was
/// already verified by the SPV layer.
///
/// Bloom filter false positives are filtered by `check_transaction()` before
/// records reach this point, so the override is safe. Testing actual bloom
/// filter FP behavior would require mocking the SPV layer's bloom filter,
/// which is out of scope for unit tests.
fn spv_is_ours_override(upstream_is_ours: bool, net_amount: i64) -> bool {
if !upstream_is_ours && net_amount >= 0 {
tracing::debug!(
net_amount,
"SPV: overriding is_ours to true for receive transaction"
);
}
true
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn is_ours_override_true_for_outgoing_already_ours() {
assert!(spv_is_ours_override(true, -50_000));
}

#[test]
fn is_ours_override_true_for_incoming_not_ours() {
// Upstream marks receive transactions as !is_ours — we override.
assert!(spv_is_ours_override(false, 100_000));
}

#[test]
fn is_ours_override_true_for_zero_amount_not_ours() {
// Edge case: net_amount == 0 (e.g. self-transfer minus fee)
assert!(spv_is_ours_override(false, 0));
}

#[test]
fn is_ours_override_true_for_outgoing_not_ours() {
// Even if upstream says !is_ours for a send, we override.
assert!(spv_is_ours_override(false, -10_000));
}
}
Loading
Loading