Skip to content

chore: sync v4.1-dev with v4.0-dev#3990

Open
QuantumExplorer wants to merge 8 commits into
v4.1-devfrom
chore/sync-v4.1-dev-with-v4.0-dev
Open

chore: sync v4.1-dev with v4.0-dev#3990
QuantumExplorer wants to merge 8 commits into
v4.1-devfrom
chore/sync-v4.1-dev-with-v4.0-dev

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jul 3, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Routine sync merge (same pattern as #3978): brings the 4.0.x release line's fixes up to the v4.1 development branch.

What was done?

Merged v4.0-dev into v4.1-dev — a clean merge, no conflicts. Carries:

How Has This Been Tested?

cargo test -p platform-wallet --lib --features shielded → 326 passed on the merged tree; CI runs the full suite.

Breaking Changes

None (merge only).

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new local reset action for platform address sync, clearing cached state and forcing a full rescan on next sync.
    • Improved wallet flows so identity top-ups, registrations, transfers, withdrawals, and shielded sends now reconcile balances more consistently after proofs.
  • Bug Fixes

    • Added a clearer retryable error when a shielded spend lacks a recorded anchor.
    • Improved shielded spend handling so unconfirmed or pruned cases are retried or released more reliably.
    • Fixed stale sync completions so cleared or stopped sync states are no longer repainted in the UI.

llbartekll and others added 8 commits July 2, 2026 15:00
…p-from-addresses (#3969)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Quantum Explorer <quantum@dash.org>
…ed anchor (#3977)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Quantum Explorer <quantum@dash.org>
… into one guarded seam (#3987)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ion on sync (#3982)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Quantum Explorer <quantum@dash.org>
…3988)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a ShieldedNoRecordedAnchor error propagated through Rust, FFI, and Swift layers with retry guidance, reworks shielded spend anchor selection and redrive/stranded-spend release logic, introduces an address-funded identity reconciliation seam so composite wallet operations sync platform-address balances from proofs, adds a platform-address sync reset API with Swift generation guards and a SwiftExampleApp clear flow, and reshapes wasm-sdk's protocol version upgrade state.

Changes

Shielded Anchor Validation and Redrive

Layer / File(s) Summary
ShieldedNoRecordedAnchor error contract
packages/rs-platform-wallet/src/error.rs, packages/rs-platform-wallet-ffi/src/error.rs, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
Adds the new error variant, FFI code 19, and matching Swift enum case/mapping with retry documentation.
Store schema for pending/redrive state
packages/rs-platform-wallet/src/wallet/shielded/store.rs
Extends ShieldedStore with StalePendingSpend/PendingRedrive, pending-anchor attach/release and redrive management methods, witness_at_depth, and per-nullifier map bookkeeping, with tests.
SQLite persistence for pending/redrive
packages/rs-platform-wallet/src/wallet/shielded/file_store.rs
Adds a dedicated pending_conn, shielded_pending_spends table, rehydration on open, durable redrive CRUD, non-aborting mark_spent/clear_pending, and depth-based witness lookups, with regression tests.
Anchor probing and redrive-armed spend flows
packages/rs-platform-wallet/src/wallet/shielded/operations.rs
Reworks extract_spends_and_anchor to probe recorded anchors by checkpoint depth; arms pending release and persisted redrive on unshield/transfer/withdraw/identity-create flows; adds redrive_pending_spends and classification with tests.
Coordinator stranded-spend release
packages/rs-platform-wallet/src/wallet/shielded/coordinator.rs
Prefetches stale reservations/recorded anchors, redrives armed spends, and releases reservations whose anchors are pruned, flipping activity to Failed.
FFI retry mapping and sync-poke
packages/rs-platform-wallet-ffi/src/shielded_send.rs
Adds poke_sync_on_unconfirmed to force sync on ambiguous outcomes and extends error mapping with retryable ShieldedNoRecordedAnchor handling.
Drive-ABCI anchor regression tests
packages/rs-drive-abci/.../shielded_withdrawal/tests.rs
Adds tests asserting InvalidAnchorError for unrecorded anchors and SuccessfulExecution for older recorded-anchor proofs.

Address-Funded Identity Reconciliation

Layer / File(s) Summary
Identity sub-wallet returns AddressInfos
packages/rs-platform-wallet/src/wallet/identity/network/*.rs
register_from_addresses, top_up_from_addresses, and transfer_credits_to_addresses_with_external_signer now return proof-attested AddressInfos alongside their results.
PlatformWallet composite orchestration
packages/rs-platform-wallet/src/wallet/platform_wallet.rs
Adds composite top_up_from_addresses, register_from_addresses, and transfer_credits_to_addresses_with_external_signer methods that reconcile returned AddressInfos.
Provider reconciliation seam
packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs
Adds build_address_balance_entries, ReconciliationOutcome, commit_reconciliation, per_wallet_state, and reset_sync_state with extensive tests.
Wallet reconcile wiring & lock ordering
packages/rs-platform-wallet/src/wallet/platform_addresses/wallet.rs, sync.rs
Reworks reconcile_address_infos to use commit_reconciliation with live-pool fallback, applies/persists under provider lock, and reorders persist timing in sync_balances.
Delegated reconciliation in transfer/withdrawal/fund flows
packages/rs-platform-wallet/src/wallet/platform_addresses/{transfer,withdrawal,fund_from_asset_lock}.rs
Replaces manual balance-update/persist logic with calls into reconcile_address_infos, removing now-unused helpers.
FFI composite wallet wiring
packages/rs-platform-wallet-ffi/src/identity_{registration_with_signer,top_up,transfer}.rs
Registration, top-up, and transfer FFI entry points now clone/call the composite PlatformWallet instead of the identity sub-wallet.

Platform Address Sync Reset

Layer / File(s) Summary
Manager/FFI reset API
packages/rs-platform-wallet/src/manager/mod.rs, packages/rs-platform-wallet-ffi/src/platform_address_sync.rs
Adds reset_platform_address_sync_state and matching FFI entry point that quiesce sync and reset watermarks/cached balances.
Swift generation guard
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager*.swift
Adds platformAddressSyncGeneration bumped on stop/reset, generation-checked completion handling, and resetPlatformAddressPublishedMirror, with dedicated tests.
SwiftExampleApp fail-closed clear
packages/swift-sdk/SwiftExampleApp/.../PlatformBalanceSyncService.swift, CoreContentView.swift, tests
Adds clearLocalState that resets Rust state then zeroes/removes SwiftData rows before updating the display mirror, wired to the Clear button, with tests.

WASM Protocol Version Upgrade State

Layer / File(s) Summary
next_version_upgrade helper and state reshape
packages/wasm-sdk/src/queries/protocol.rs, packages/wasm-sdk/tests/unit/conversion-simple-types.spec.ts
Removes activationHeight/isThresholdReached, changes voteCount to Option<u64>, adds vote-based next-version selection, and updates JS fixtures/assertions.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operations
  participant ShieldedStore
  participant Platform
  participant Coordinator

  Operations->>Platform: fetch recorded anchor set
  Operations->>ShieldedStore: witness_at_depth per checkpoint
  Operations->>Operations: select shallowest recorded anchor
  Operations->>ShieldedStore: arm_pending_release(anchor, activity_id)
  Operations->>Platform: broadcast_shielded_spend_with_redrive
  Platform-->>Operations: ShieldedSpendUnconfirmed (ambiguous)
  Operations->>ShieldedStore: arm_redrive_record
  Coordinator->>ShieldedStore: prefetch_stranded_release
  Coordinator->>Platform: fetch recorded anchors
  Coordinator->>ShieldedStore: redrive_pending_spends
  Coordinator->>ShieldedStore: release_stranded_spends (pruned anchors)
Loading
sequenceDiagram
  participant FFI
  participant PlatformWallet
  participant IdentityWallet
  participant PlatformAddressWallet
  participant Provider

  FFI->>PlatformWallet: top_up_from_addresses/register/transfer
  PlatformWallet->>IdentityWallet: perform identity operation
  IdentityWallet-->>PlatformWallet: (AddressInfos, result)
  PlatformWallet->>PlatformAddressWallet: reconcile_address_infos(AddressInfos)
  PlatformAddressWallet->>Provider: commit_reconciliation
  Provider-->>PlatformAddressWallet: ReconciliationOutcome
  PlatformAddressWallet->>PlatformAddressWallet: persist PlatformAddressChangeSet
  PlatformWallet-->>FFI: identity result
Loading

Possibly related PRs

  • dashpay/platform#3838: Both touch the shielded identity-create-from-pool flow in shielded_send.rs.
  • dashpay/platform#3959: Both implement the platform-address sync reset/clear flow across Rust, FFI, and SwiftExampleApp.
  • dashpay/platform#3969: Both implement reconciling spent platform-address balances after top_up_from_addresses.

Suggested labels: ready for final review

Suggested reviewers: lklimek, shumkov, llbartekll, ZocoLini, thepastaclaw

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the routine sync merge from v4.0-dev into v4.1-dev.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/sync-v4.1-dev-with-v4.0-dev

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

❤️ Share

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

@thepastaclaw

thepastaclaw commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit 42019c5)

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.19%. Comparing base (9f9092c) to head (42019c5).
⚠️ Report is 1 commits behind head on v4.1-dev.

Additional details and impacted files
@@            Coverage Diff             @@
##           v4.1-dev    #3990    +/-   ##
==========================================
  Coverage     87.18%   87.19%            
==========================================
  Files          2632     2634     +2     
  Lines        327563   327718   +155     
==========================================
+ Hits         285592   285748   +156     
+ Misses        41971    41970     -1     
Components Coverage Δ
dpp 87.70% <ø> (ø)
drive 86.14% <ø> (ø)
drive-abci 89.45% <ø> (+<0.01%) ⬆️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.20% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 49.55% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/rs-platform-wallet/src/wallet/identity/network/register_from_addresses.rs (1)

137-156: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don’t let local identity registration failure suppress address reconciliation.

After the SDK call succeeds, Platform has accepted the registration and returned address_infos; any local add_identity/manager error here prevents the composite wallet from reconciling spent funding addresses. Treat the local registration step as best-effort after acceptance, then still return (identity, address_infos).

Suggested shape
-        {
-            let mut wm = self.wallet_manager.write().await;
-            let info = wm.get_wallet_info_mut(&self.wallet_id).ok_or_else(|| {
-                crate::error::PlatformWalletError::WalletNotFound(
-                    "Wallet info not found in wallet manager".to_string(),
-                )
-            })?;
-            info.identity_manager.add_identity(
-                identity.clone(),
-                identity_index,
-                self.wallet_id,
-                &self.persister,
-            )?;
-        }
+        {
+            let mut wm = self.wallet_manager.write().await;
+            match wm.get_wallet_info_mut(&self.wallet_id) {
+                Some(info) => {
+                    if let Err(e) = info.identity_manager.add_identity(
+                        identity.clone(),
+                        identity_index,
+                        self.wallet_id,
+                        &self.persister,
+                    ) {
+                        tracing::warn!(
+                            identity_index,
+                            error = %e,
+                            "Identity registration succeeded on Platform but local manager \
+                             registration failed; continuing so address balances can reconcile"
+                        );
+                    }
+                }
+                None => {
+                    tracing::warn!(
+                        identity_index,
+                        "Identity registration succeeded on Platform but wallet info was missing; \
+                         continuing so address balances can reconcile"
+                    );
+                }
+            }
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rs-platform-wallet/src/wallet/identity/network/register_from_addresses.rs`
around lines 137 - 156, The local identity persistence step in
register_from_addresses is blocking reconciliation after the SDK has already
accepted the registration and returned address_infos. Update the
identity_manager.add_identity path in register_from_addresses to be best-effort
only: capture or log any add_identity/manager error, but do not return early
from the accepted registration flow, and still return (identity, address_infos)
so PlatformWallet::register_from_addresses can reconcile spent addresses.
packages/rs-platform-wallet/src/wallet/shielded/store.rs (1)

537-549: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Resolve pending/redrive state even for already-spent notes.

Line 539 skips cleanup when the note is already marked spent. A rehydrated redrive row plus a restored spent note can therefore leave stale reservations/redrive metadata alive and eligible for later redrive/release handling. Make mark_spent idempotently clear pending/redrives whenever the nullifier is known, while preserving the existing return value for “newly marked spent”.

Suggested direction
     pub(super) fn mark_spent(&mut self, nullifier: &[u8; 32]) -> bool {
         if let Some(&idx) = self.nullifier_index.get(nullifier) {
-            if !self.notes[idx].is_spent {
-                self.notes[idx].is_spent = true;
-                // Promotion implies the spend confirmed; drop any
-                // matching pending reservation. Idempotent — the
-                // common path already cleared pending in the
-                // spend-flow finalizer.
-                self.pending_nullifiers.remove(nullifier);
-                // The spend landed — its redrive record (if armed) is
-                // resolved for every nullifier it carries.
-                self.drop_redrives_containing(nullifier);
-                return true;
-            }
+            let newly_spent = !self.notes[idx].is_spent;
+            self.notes[idx].is_spent = true;
+            self.pending_nullifiers.remove(nullifier);
+            self.drop_redrives_containing(nullifier);
+            return newly_spent;
         }
         false
     }

Also mirror durable redrive-row deletion in the file-backed store for this idempotent path, not only when the boolean return is true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/shielded/store.rs` around lines 537 -
549, The mark_spent path in shielded store cleanup is not idempotent: when a
known nullifier is already marked spent, it currently skips clearing pending
reservations and redrive metadata. Update mark_spent to always remove the
nullifier from pending_nullifiers and drop_redrives_containing once
nullifier_index contains it, while keeping the return value true only for the
first transition in note state. Also mirror this behavior in the file-backed
store’s durable redrive-row deletion logic so the already-spent path clears
persisted redrive rows too, not just the newly-spent case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/rs-platform-wallet/src/wallet/platform_addresses/fund_from_asset_lock.rs`:
- Around line 286-296: The fund-from-asset-lock flow in `fund_from_asset_lock`
can still call `consume_asset_lock` even when `reconcile_address_infos` hit a
persistence failure and only logged it. Update this path to distinguish
successful durable balance persistence from a logged/hidden failure, either by
having `reconcile_address_infos` return persistence status or by introducing a
strict reconciliation variant for asset-lock funding. Then guard the
`consume_asset_lock` call so the lock is only marked `Consumed` when the balance
changeset was actually stored.

In `@packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs`:
- Around line 1002-1026: The conflict handling in commit_reconciliation is
incomplete: when the live-pool address_index already exists in state.addresses,
the code only logs and still pushes the entry after mutating state.found. Update
the commit path in provider::commit_reconciliation so conflicting entries are
skipped or removed before outcome.entries.push(entry), and ensure the
found/bijection updates only happen for non-conflicting entries. Use the
existing state.addresses.contains_left, state.addresses.get_by_right, and
outcome.entries flow to locate and gate the conflicting case.

In `@packages/rs-platform-wallet/src/wallet/platform_addresses/wallet.rs`:
- Around line 440-452: The reset flow in reset_sync_state currently updates
wallet_manager and then later acquires the provider lock, leaving a window where
reconciliation/sync can repopulate managed balances. Move the wallet_info
balance clearing and provider.reset_sync_state() into the same provider.write()
critical section, or otherwise serialize both operations under the provider lock
in reset_sync_state so the reset is atomic and cannot race with
sync/reconciliation.

In `@packages/rs-platform-wallet/src/wallet/shielded/file_store.rs`:
- Around line 108-117: The durable shielded_pending_spends table is not being
cleared during wallet teardown, so stale redrive state can survive a Clear or
unregister and come back on reopen. Update the purge path in purge_wallet and
purge_all_subwallets in file_store.rs to delete rows from
shielded_pending_spends alongside the existing in-memory subwallet and
commitment tree cleanup, using the same wallet/account scoping already used by
the wallet purge logic.

In `@packages/rs-platform-wallet/src/wallet/shielded/operations.rs`:
- Around line 1736-1743: The anchor probe in shielded/operations.rs is
conflating a depth-specific witness failure with a real “note not present”
result, causing later checkpoint probing to stop early. Update the flow around
the anchor probe logic in the relevant witness lookup and the checkpoint loop so
a depth>0 store/witness error returns a distinct “skip this depth” outcome
instead of Ok(None), and make the caller only break on the true
post-dates/not-present condition. Use the existing witness_at_depth and
checkpoint selection logic to distinguish transient probe failures from actual
absence.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/PlatformBalanceSyncService.swift`:
- Around line 225-291: The clear flow in
PlatformBalanceSyncService.clearLocalState can be overwritten by an already
in-flight refreshBalanceSnapshot() that completes after the wipe. Add a
service-local generation/token that is bumped when clearLocalState starts or
succeeds, then have refreshBalanceSnapshot() and its snapshot-apply path verify
the current token before publishing results so stale balance data is ignored.
Make the check part of the same PlatformBalanceSyncService state used by
clearDisplay()/persisted snapshot updates.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift`:
- Around line 374-381: The Clear action can overlap with Sync Now or another
Clear because it is fire-and-forget, so add an `isClearing` state to
`PlatformBalanceSyncService` and use it to gate both actions. Update the
`clearLocalState` flow to set `isClearing` true for the full Rust reset plus
SwiftData cleanup, then reset it in a finally path, and wire `CoreContentView`
button states to disable both Clear and Sync Now while `isClearing` is true.

---

Outside diff comments:
In
`@packages/rs-platform-wallet/src/wallet/identity/network/register_from_addresses.rs`:
- Around line 137-156: The local identity persistence step in
register_from_addresses is blocking reconciliation after the SDK has already
accepted the registration and returned address_infos. Update the
identity_manager.add_identity path in register_from_addresses to be best-effort
only: capture or log any add_identity/manager error, but do not return early
from the accepted registration flow, and still return (identity, address_infos)
so PlatformWallet::register_from_addresses can reconcile spent addresses.

In `@packages/rs-platform-wallet/src/wallet/shielded/store.rs`:
- Around line 537-549: The mark_spent path in shielded store cleanup is not
idempotent: when a known nullifier is already marked spent, it currently skips
clearing pending reservations and redrive metadata. Update mark_spent to always
remove the nullifier from pending_nullifiers and drop_redrives_containing once
nullifier_index contains it, while keeping the return value true only for the
first transition in note state. Also mirror this behavior in the file-backed
store’s durable redrive-row deletion logic so the already-spent path clears
persisted redrive rows too, not just the newly-spent case.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66867827-aefd-4996-82c9-2ba90bfb28c7

📥 Commits

Reviewing files that changed from the base of the PR and between 4341d07 and 42019c5.

📒 Files selected for processing (32)
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs
  • packages/rs-platform-wallet-ffi/src/identity_top_up.rs
  • packages/rs-platform-wallet-ffi/src/identity_transfer.rs
  • packages/rs-platform-wallet-ffi/src/platform_address_sync.rs
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet/src/error.rs
  • packages/rs-platform-wallet/src/manager/mod.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/register_from_addresses.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/top_up_from_addresses.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/transfer_to_addresses.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/fund_from_asset_lock.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/sync.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/transfer.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/wallet.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/withdrawal.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/rs-platform-wallet/src/wallet/shielded/coordinator.rs
  • packages/rs-platform-wallet/src/wallet/shielded/file_store.rs
  • packages/rs-platform-wallet/src/wallet/shielded/operations.rs
  • packages/rs-platform-wallet/src/wallet/shielded/store.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerAddressSync.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/PlatformBalanceSyncService.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/PlatformAddressSyncGenerationTests.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/PlatformBalanceSyncServiceClearTests.swift
  • packages/wasm-sdk/src/queries/protocol.rs
  • packages/wasm-sdk/tests/unit/conversion-simple-types.spec.ts

Comment on lines +286 to 296
// The seam persists before returning, and the persist MUST
// happen before `consume_asset_lock` so we never have a
// Consumed lock paired with a stale balance row on disk.
// Persistence errors are logged inside the seam rather than
// propagated: Platform already accepted the transition, and a
// persistence hiccup shouldn't mask that.
let cs = self
.reconcile_address_infos(&address_infos, "fund from asset lock")
.await;

if let Some(out_point) = tracked_out_point {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don’t consume the asset lock after an unobservable balance-persist failure.

Line 286 says persistence must happen before consume_asset_lock, but reconcile_address_infos logs store errors and still returns a changeset. This path can mark the lock Consumed while durable balance rows remain stale.

Consider exposing persistence status from the reconciliation seam or adding a strict variant for asset-lock funding that skips consume_asset_lock when the balance changeset could not be stored.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rs-platform-wallet/src/wallet/platform_addresses/fund_from_asset_lock.rs`
around lines 286 - 296, The fund-from-asset-lock flow in `fund_from_asset_lock`
can still call `consume_asset_lock` even when `reconcile_address_infos` hit a
persistence failure and only logged it. Update this path to distinguish
successful durable balance persistence from a logged/hidden failure, either by
having `reconcile_address_infos` return persistence status or by introducing a
strict reconciliation variant for asset-lock funding. Then guard the
`consume_asset_lock` call so the lock is only marked `Consumed` when the balance
changeset was actually stored.

Comment on lines +1002 to +1026
if is_removal {
state.found.remove(&entry.address);
} else {
state.found.insert(entry.address, entry.funds);
}
// Merge pool-resolved addresses into the bijection so
// `current_balances` can pair the fresh funds with a
// derivation index. Never overwrite an existing pairing —
// `BiBTreeMap::insert` evicts conflicting pairs, which
// would orphan another address's `found` entry.
if state.addresses.get_by_right(&entry.address).is_none() {
if state.addresses.contains_left(&entry.address_index) {
tracing::error!(
account_index = entry.account_index,
address_index = entry.address_index,
address = %entry.address,
"commit_reconciliation: derivation index already \
maps to a different address — state drift; \
leaving the bijection untouched"
);
} else {
state.addresses.insert(entry.address_index, entry.address);
}
}
outcome.entries.push(entry);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Drop entries when the live-pool index conflicts with the persisted bijection.

Line 1013 logs the conflict but Line 1026 still emits the entry after found was already mutated. That can persist an address under an index already owned by another address and make current_balances unable to round-trip the committed seed.

Proposed fix
+            if state.addresses.get_by_right(&entry.address).is_none()
+                && state.addresses.contains_left(&entry.address_index)
+            {
+                tracing::error!(
+                    account_index = entry.account_index,
+                    address_index = entry.address_index,
+                    address = %entry.address,
+                    "commit_reconciliation: derivation index already maps to a different address; dropping reconciliation entry"
+                );
+                continue;
+            }
+
             if is_removal {
                 state.found.remove(&entry.address);
             } else {
                 state.found.insert(entry.address, entry.funds);
             }
@@
             if state.addresses.get_by_right(&entry.address).is_none() {
-                if state.addresses.contains_left(&entry.address_index) {
-                    tracing::error!(
-                        account_index = entry.account_index,
-                        address_index = entry.address_index,
-                        address = %entry.address,
-                        "commit_reconciliation: derivation index already \
-                         maps to a different address — state drift; \
-                         leaving the bijection untouched"
-                    );
-                } else {
-                    state.addresses.insert(entry.address_index, entry.address);
-                }
+                state.addresses.insert(entry.address_index, entry.address);
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if is_removal {
state.found.remove(&entry.address);
} else {
state.found.insert(entry.address, entry.funds);
}
// Merge pool-resolved addresses into the bijection so
// `current_balances` can pair the fresh funds with a
// derivation index. Never overwrite an existing pairing —
// `BiBTreeMap::insert` evicts conflicting pairs, which
// would orphan another address's `found` entry.
if state.addresses.get_by_right(&entry.address).is_none() {
if state.addresses.contains_left(&entry.address_index) {
tracing::error!(
account_index = entry.account_index,
address_index = entry.address_index,
address = %entry.address,
"commit_reconciliation: derivation index already \
maps to a different address — state drift; \
leaving the bijection untouched"
);
} else {
state.addresses.insert(entry.address_index, entry.address);
}
}
outcome.entries.push(entry);
if state.addresses.get_by_right(&entry.address).is_none()
&& state.addresses.contains_left(&entry.address_index)
{
tracing::error!(
account_index = entry.account_index,
address_index = entry.address_index,
address = %entry.address,
"commit_reconciliation: derivation index already maps to a different address; dropping reconciliation entry"
);
continue;
}
if is_removal {
state.found.remove(&entry.address);
} else {
state.found.insert(entry.address, entry.funds);
}
// Merge pool-resolved addresses into the bijection so
// `current_balances` can pair the fresh funds with a
// derivation index. Never overwrite an existing pairing —
// `BiBTreeMap::insert` evicts conflicting pairs, which
// would orphan another address's `found` entry.
if state.addresses.get_by_right(&entry.address).is_none() {
state.addresses.insert(entry.address_index, entry.address);
}
outcome.entries.push(entry);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs` around
lines 1002 - 1026, The conflict handling in commit_reconciliation is incomplete:
when the live-pool address_index already exists in state.addresses, the code
only logs and still pushes the entry after mutating state.found. Update the
commit path in provider::commit_reconciliation so conflicting entries are
skipped or removed before outcome.entries.push(entry), and ensure the
found/bijection updates only happen for non-conflicting entries. Use the
existing state.addresses.contains_left, state.addresses.get_by_right, and
outcome.entries flow to locate and gate the conflicting case.

Comment on lines +440 to +452
pub async fn reset_sync_state(&self) {
{
let mut wm = self.wallet_manager.write().await;
if let Some(info) = wm.get_wallet_info_mut(&self.wallet_id) {
for account in info.core_wallet.all_platform_payment_managed_accounts_mut() {
account.clear_balances();
}
}
}
let mut guard = self.provider.write().await;
if let Some(provider) = guard.as_mut() {
provider.reset_sync_state();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize reset with sync/reconciliation under the provider lock.

Line 448 releases wallet_manager before Line 449 acquires the provider lock, so a reconciliation/sync can repopulate managed balances and persist rows between those steps; reset then clears only the provider seed.

Proposed fix
     pub async fn reset_sync_state(&self) {
-        {
-            let mut wm = self.wallet_manager.write().await;
-            if let Some(info) = wm.get_wallet_info_mut(&self.wallet_id) {
-                for account in info.core_wallet.all_platform_payment_managed_accounts_mut() {
-                    account.clear_balances();
-                }
-            }
-        }
         let mut guard = self.provider.write().await;
         if let Some(provider) = guard.as_mut() {
             provider.reset_sync_state();
         }
+        let mut wm = self.wallet_manager.write().await;
+        if let Some(info) = wm.get_wallet_info_mut(&self.wallet_id) {
+            for account in info.core_wallet.all_platform_payment_managed_accounts_mut() {
+                account.clear_balances();
+            }
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub async fn reset_sync_state(&self) {
{
let mut wm = self.wallet_manager.write().await;
if let Some(info) = wm.get_wallet_info_mut(&self.wallet_id) {
for account in info.core_wallet.all_platform_payment_managed_accounts_mut() {
account.clear_balances();
}
}
}
let mut guard = self.provider.write().await;
if let Some(provider) = guard.as_mut() {
provider.reset_sync_state();
}
pub async fn reset_sync_state(&self) {
let mut guard = self.provider.write().await;
if let Some(provider) = guard.as_mut() {
provider.reset_sync_state();
}
let mut wm = self.wallet_manager.write().await;
if let Some(info) = wm.get_wallet_info_mut(&self.wallet_id) {
for account in info.core_wallet.all_platform_payment_managed_accounts_mut() {
account.clear_balances();
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/platform_addresses/wallet.rs` around
lines 440 - 452, The reset flow in reset_sync_state currently updates
wallet_manager and then later acquires the provider lock, leaving a window where
reconciliation/sync can repopulate managed balances. Move the wallet_info
balance clearing and provider.reset_sync_state() into the same provider.write()
critical section, or otherwise serialize both operations under the provider lock
in reset_sync_state so the reset is atomic and cannot race with
sync/reconciliation.

Comment on lines +108 to +117
"CREATE TABLE IF NOT EXISTS shielded_pending_spends (
wallet_id BLOB NOT NULL,
account_index INTEGER NOT NULL,
activity_id BLOB NOT NULL,
anchor BLOB NOT NULL,
nullifiers BLOB NOT NULL,
st_bytes BLOB NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (wallet_id, account_index, activity_id)
)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear the new pending-spend table during wallet purge/clear.

The new shielded_pending_spends table is durable, but the existing purge/clear lifecycle only clears in-memory subwallet state and the commitment tree. After a host Clear or wallet unregister, stale redrive rows can rehydrate on a later open and resurrect old reservations/rebroadcast state. Add deletion of this table in purge_wallet and purge_all_subwallets.

Suggested direction
 fn purge_wallet(&mut self, wallet_id: WalletId) -> Result<(), Self::Error> {
     self.subwallets.retain(|id, _| id.wallet_id != wallet_id);
+    let conn = self.pending_conn.lock().expect("pending_conn mutex");
+    conn.execute(
+        "DELETE FROM shielded_pending_spends WHERE wallet_id = ?1",
+        rusqlite::params![wallet_id.as_slice()],
+    )
+    .map_err(|e| FileShieldedStoreError(format!("purge pending spends for wallet: {e}")))?;
     Ok(())
 }

 fn purge_all_subwallets(&mut self) -> Result<(), Self::Error> {
     self.subwallets.clear();
+    let conn = self.pending_conn.lock().expect("pending_conn mutex");
+    conn.execute("DELETE FROM shielded_pending_spends", [])
+        .map_err(|e| FileShieldedStoreError(format!("purge all pending spends: {e}")))?;
     Ok(())
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/shielded/file_store.rs` around lines
108 - 117, The durable shielded_pending_spends table is not being cleared during
wallet teardown, so stale redrive state can survive a Clear or unregister and
come back on reopen. Update the purge path in purge_wallet and
purge_all_subwallets in file_store.rs to delete rows from
shielded_pending_spends alongside the existing in-memory subwallet and
commitment tree cleanup, using the same wallet/account scoping already used by
the wallet purge logic.

Comment on lines +1736 to +1743
Err(e) => {
tracing::warn!(
position = *position,
depth,
error = %e,
"shielded anchor probe: witness_at_depth failed at depth > 0; skipping depth"
);
return Ok(None);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t let a skipped depth terminate the anchor probe.

Line 1743 maps depth>0 store errors to Ok(None), but Line 1804 treats None as “the selected note post-dates this checkpoint; deeper checkpoints cannot cover it” and breaks. That means a transient/depth-specific witness error can prevent probing older recorded checkpoints. Return a distinct outcome for “skip this depth” and only break on a real “note not present at this checkpoint”.

Suggested direction
+    enum DepthBuild {
+        Built(Vec<SpendableNote>, Anchor),
+        NoteNotCovered,
+        SkipDepth,
+    }
+
     let build_at_depth = |depth: usize,
                           strict: bool|
-     -> Result<Option<(Vec<SpendableNote>, Anchor)>, PlatformWalletError> {
+     -> Result<DepthBuild, PlatformWalletError> {
...
-                Ok(None) => return Ok(None),
+                Ok(None) => return Ok(DepthBuild::NoteNotCovered),
...
-                    return Ok(None);
+                    return Ok(DepthBuild::SkipDepth);
...
-        Ok(Some((spends, anchor)))
+        Ok(DepthBuild::Built(spends, anchor))
     };
...
-            Some((spends, anchor)) if recorded.contains(&anchor.to_bytes()) => {
+            DepthBuild::Built(spends, anchor) if recorded.contains(&anchor.to_bytes()) => {
                 return Ok((spends, anchor));
             }
-            Some(_) => continue,
-            None => break,
+            DepthBuild::Built(_, _) | DepthBuild::SkipDepth => continue,
+            DepthBuild::NoteNotCovered => break,

Also applies to: 1802-1804

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/shielded/operations.rs` around lines
1736 - 1743, The anchor probe in shielded/operations.rs is conflating a
depth-specific witness failure with a real “note not present” result, causing
later checkpoint probing to stop early. Update the flow around the anchor probe
logic in the relevant witness lookup and the checkpoint loop so a depth>0
store/witness error returns a distinct “skip this depth” outcome instead of
Ok(None), and make the caller only break on the true post-dates/not-present
condition. Use the existing witness_at_depth and checkpoint selection logic to
distinguish transient probe failures from actual absence.

Comment on lines +225 to +291
func clearLocalState(
modelContext: ModelContext,
network: Network,
walletIdsOnNetwork: Set<Data>
) async {
// 1) Reset the Rust-owned state BEFORE touching disk. Without
// this the in-memory watermark survives and the next "Sync
// Now" resumes incrementally (fast) instead of doing a full
// rescan; a still-registered background pass could also
// re-persist the rows we're about to delete. Fail closed —
// the reset is load-bearing for the wipe, so abort (surfacing
// the error) rather than leave a half-cleared state.
if let walletManager {
do {
try await walletManager.resetPlatformAddressSyncState()
} catch {
lastError = "Failed to reset platform-address sync state: \(error.localizedDescription)"
SDKLogger.error(lastError ?? "")
return
}
}

// 2) Zero the volatile balance/sync fields of this network's
// address rows IN PLACE — do NOT delete them. A
// `PersistentPlatformAddress` row is durable derivation state
// (bech32m address, 20-byte hash, 33-byte public key,
// account/address index, derivation path), not just a balance
// cache. The BLAST balance callback `persistAddressBalances`
// only *updates* existing rows (it skips by `addressHash` when
// the row is missing), and no sync re-emits the rows in-session:
// Rust's `reset_sync_state` preserves the address bijection, so
// there is no pool extension to re-fire the address-emit path.
// Deleting would therefore leave the next "Sync Now" with
// nothing to upsert against — balances (and the spend/signing
// derivation lookups) would stay gone until an app restart
// re-seeds the rows. Zeroing clears what the user sees while
// keeping the metadata the rescan needs.
do {
let addresses = try modelContext.fetch(FetchDescriptor<PersistentPlatformAddress>())
for row in addresses where walletIdsOnNetwork.contains(row.walletId) {
row.balance = 0
row.nonce = 0
row.isUsed = false
row.firstSeenHeight = 0
row.lastSeenHeight = 0
row.lastUpdated = Date()
}

// The sync-state watermark is pure volatile sync state (no
// durable metadata) and is what actually forces a full rescan,
// so delete it outright — the next sync re-creates it.
let networkRaw = network.rawValue
let syncStates = try modelContext.fetch(FetchDescriptor<PersistentPlatformAddressesSyncState>())
for row in syncStates where row.networkRaw == networkRaw {
modelContext.delete(row)
}

try modelContext.save()
} catch {
lastError = "Failed to clear persisted platform-address state: \(error.localizedDescription)"
SDKLogger.error(lastError ?? "")
return
}

// 3) Zero the published display mirror — only on full success.
clearDisplay()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invalidate in-flight balance refreshes before clearing.

A sync completion can already have queued refreshBalanceSnapshot() before Clear runs; if that async snapshot returns after Line 290, it can republish pre-clear balances over the cleared UI. Add a service-local generation/token and check it before applying snapshot results.

🐛 Proposed direction
 class PlatformBalanceSyncService: ObservableObject {
+    private var balanceSnapshotGeneration: UInt64 = 0
 func clearLocalState(
     modelContext: ModelContext,
     network: Network,
     walletIdsOnNetwork: Set<Data>
 ) async {
+    balanceSnapshotGeneration &+= 1
     // 1) Reset the Rust-owned state BEFORE touching disk.
-            Task {
-                await refreshBalanceSnapshot()
+            let generation = balanceSnapshotGeneration
+            Task {
+                await refreshBalanceSnapshot(generation: generation)
             }
-    private func refreshBalanceSnapshot() async {
+    private func refreshBalanceSnapshot(generation: UInt64) async {
         guard let wallet = platformAddressWallet else { return }
         ...
+        guard generation == balanceSnapshotGeneration else { return }
         addressBalances = newBalances
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/PlatformBalanceSyncService.swift`
around lines 225 - 291, The clear flow in
PlatformBalanceSyncService.clearLocalState can be overwritten by an already
in-flight refreshBalanceSnapshot() that completes after the wipe. Add a
service-local generation/token that is bumped when clearLocalState starts or
succeeds, then have refreshBalanceSnapshot() and its snapshot-apply path verify
the current token before publishing results so stale balance data is ignored.
Make the check part of the same PlatformBalanceSyncService state used by
clearDisplay()/persisted snapshot updates.

Comment on lines 374 to +381
Button {
platformBalanceSyncService.clearDisplay()
Task {
await platformBalanceSyncService.clearLocalState(
modelContext: modelContext,
network: platformState.currentNetwork,
walletIdsOnNetwork: walletIdsOnNetwork
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Gate sync actions while Clear is in progress.

The Clear task is fire-and-forget, so Sync Now or a second Clear can interleave with the Rust reset and SwiftData zero/delete sequence. Track isClearing in PlatformBalanceSyncService and disable both actions while it is true.

🐛 Proposed direction
-                        .disabled(platformBalanceSyncService.isSyncing)
+                        .disabled(platformBalanceSyncService.isSyncing || platformBalanceSyncService.isClearing)
                         Button {
                             Task {
                                 await platformBalanceSyncService.clearLocalState(
                                     modelContext: modelContext,
                                     network: platformState.currentNetwork,
                                     walletIdsOnNetwork: walletIdsOnNetwork
                                 )
                             }
                         .buttonStyle(.borderedProminent)
                         .tint(.red)
                         .controlSize(.mini)
+                        .disabled(platformBalanceSyncService.isSyncing || platformBalanceSyncService.isClearing)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift`
around lines 374 - 381, The Clear action can overlap with Sync Now or another
Clear because it is fire-and-forget, so add an `isClearing` state to
`PlatformBalanceSyncService` and use it to gate both actions. Update the
`clearLocalState` flow to set `isClearing` true for the full Rust reset plus
SwiftData cleanup, then reset it in a finally path, and wire `CoreContentView`
button states to disable both Clear and Sync Now while `isClearing` is true.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean, conflict-free sync merge of v4.0-dev into v4.1-dev (git diff --cc is empty). All seven forwarded commits were individually reviewed at their origin PRs. Every finding surfaced by the sub-reviewers traces back to those already-reviewed underlying PRs — no merge-time defects, no v4.1-specific logic clobbered, no scope-in issues.

Source: reviewers: opus (general), claude-sonnet-5 (general), gpt-5.5[high] (general, failed_or_unparseable), opus (security-auditor), claude-sonnet-5 (security-auditor), gpt-5.5[high] (security-auditor, failed_or_unparseable), opus (rust-quality), claude-sonnet-5 (rust-quality), gpt-5.5[high] (rust-quality, failed_or_unparseable), opus (ffi-engineer), claude-sonnet-5 (ffi-engineer), gpt-5.5[high] (ffi-engineer, failed_or_unparseable); verifier: opus; specialists: security-auditor, rust-quality, ffi-engineer

@thepastaclaw

Copy link
Copy Markdown
Collaborator

Review-bot triage for the CodeRabbit batch: these findings are against the v4.0-dev commits being forwarded into v4.1-dev, not against a conflict/resolution introduced by this sync merge itself.

The current automated review at 42019c54 treated the PR as a clean merge of already-reviewed upstream branch content and found no merge-time/v4.1-specific defect. I am not opening an automated fix PR against this sync branch from CodeRabbit suggestions; if maintainers want any of those data-integrity follow-ups, they should be handled as normal follow-up work against the relevant development branch/source PR rather than folded into this merge-sync PR.

@thepastaclaw

thepastaclaw commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

I prepared fixes for the CodeRabbit review feedback in #3991, targeting this PR head branch.

I could not push directly to dashpay/platform:chore/sync-v4.1-dev-with-v4.0-dev from this account (403/permission denied), so #3991 contains the follow-up commit for maintainers to merge/cherry-pick.

Validation run on the follow-up branch:

  • cargo fmt --manifest-path packages/rs-platform-wallet/Cargo.toml --check
  • cargo check -p platform-wallet --features shielded

Additional note: xcodebuild -list -project packages/swift-sdk/SwiftExampleApp/SwiftExampleApp.xcodeproj could not resolve the local DashSDKFFI.xcframework binary artifact in this worktree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants