Skip to content

feat(swift-sdk): use SPV-synced quorums for Platform proof verification#3417

Open
QuantumExplorer wants to merge 7 commits intov3.1-devfrom
feat/ios-spv-quorums
Open

feat(swift-sdk): use SPV-synced quorums for Platform proof verification#3417
QuantumExplorer wants to merge 7 commits intov3.1-devfrom
feat/ios-spv-quorums

Conversation

@QuantumExplorer
Copy link
Copy Markdown
Member

@QuantumExplorer QuantumExplorer commented Mar 31, 2026

Issue being fixed or feature implemented

The iOS app currently relies on a TrustedHttpContextProvider that fetches quorum public keys from a centralized HTTP endpoint (quorums.testnet.networks.dash.org). This means the app trusts a centralized server, doesn't work offline, and can't work for regtest/local environments where no endpoint exists.

This PR bridges the SPV client's locally synced masternode list data to the Platform SDK's context provider, enabling trustless proof verification using quorum data already synced by the Core SPV client.

What was done?

Pure Rust: platform-wallet + rs-sdk-ffi

  • platform-wallet/spv_context_provider.rs (new)SpvContextProvider holding Arc<RwLock<MasternodeListEngine>> + Network, implementing ContextProvider by reading quorum data directly from the in-memory masternode list engine. Zero FFI. Gated behind spv-context feature flag.
  • rs-sdk-ffi/sdk.rsdash_sdk_create_with_spv_context(config, spv_client) casts the FFIDashSpvClient*, extracts the MasternodeListEngine via the new public accessor (rust-dashcore PR fix(dashmate): invalid testnet TenderDash genesis #608), and creates the pure-Rust provider. No FFI bridge module needed.
  • Bumped rust-dashcore to 3f65002 which includes FFIDashSpvClient::masternode_list_engine().

Swift SDK

  • SPVClient.swift — Added unsafeFFIClientPointer to expose the FFIDashSpvClient*.
  • SDK.swift — Added init(network:spvClientHandle:) calling dash_sdk_create_with_spv_context.
  • WalletService.swift — Added spvClientHandle computed property.

SwiftExampleApp

  • AppState.swift — Added useTrustedQuorumFallback setting (default ON), rewrote initializeSDK / switchNetwork to try SPV quorums first with trusted fallback. Toggle triggers SDK reconnect immediately.
  • UnifiedAppState.swift — Passes walletService.spvClientHandle to SDK init; nils out old SDK before SPV client teardown on network switch to prevent use-after-free.
  • OptionsView.swift — Added "Fallback to Trusted Quorums" toggle.

Architecture

Platform SDK proof verification
  → platform_wallet::SpvContextProvider (pure Rust)
    → Arc<RwLock<MasternodeListEngine>> (shared with SPV client)
      → direct in-memory quorum data reads, zero FFI

Swift only passes the raw FFIDashSpvClient* pointer once at SDK creation. All quorum lookups stay entirely in Rust, reading directly from the same in-memory masternode list engine that the SPV client populates during sync.

How Has This Been Tested?

  • cargo check -p rs-sdk-ffi passes
  • cargo check -p platform-wallet --features spv-context passes
  • Code review of FFI type compatibility and memory safety

Breaking Changes

None. The existing SDK.init(network:) trusted initializer remains unchanged. The new SPV path is additive and the trusted fallback defaults to ON.

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

Bridge the SPV client's locally synced masternode list data to the
Platform SDK's context provider, replacing the dependency on a trusted
HTTP quorum endpoint. When an SPV client handle is available, the SDK
is created with callback-based quorum lookups that call through to
ffi_dash_spv_get_quorum_public_key and
ffi_dash_spv_get_platform_activation_height.

A "Fallback to Trusted Quorums" toggle (default: ON) in Settings lets
users fall back to the HTTP provider when SPV data is not yet synced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added this to the v3.1.0 milestone Mar 31, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

Warning

Rate limit exceeded

@QuantumExplorer has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 42 seconds before requesting another review.

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 7 minutes and 42 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61a19e43-2874-4450-9b28-2d59721c5278

📥 Commits

Reviewing files that changed from the base of the PR and between 8d2bb27 and cfde224.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • packages/rs-platform-wallet/Cargo.toml
  • packages/rs-platform-wallet/src/spv_context_provider.rs
  • packages/rs-sdk-ffi/Cargo.toml
  • packages/rs-sdk-ffi/src/sdk.rs
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift
📝 Walkthrough

Walkthrough

Adds SPV-backed quorum support across Rust FFI, platform wallet, Swift SDK, and example app: new SPV context providers and FFI entrypoint, Swift exposures of raw SPV client handles, an SDK initializer accepting that handle, UI toggle and app-state fallback logic, and Cargo feature/dependency updates.

Changes

Cohort / File(s) Summary
FFI: SPV context & SDK entrypoint
packages/rs-sdk-ffi/src/spv_context_provider.rs, packages/rs-sdk-ffi/src/sdk.rs, packages/rs-sdk-ffi/src/lib.rs, packages/rs-sdk-ffi/Cargo.toml
Added SpvContextProvider (FFI wrapper over a raw SPV client pointer), exported dash_sdk_create_with_spv_context with pointer validation, temporary wrapper lifecycle handling, and added dash-spv-ffi dependency/module export.
Platform wallet: SPV provider feature
packages/rs-platform-wallet/Cargo.toml, packages/rs-platform-wallet/src/lib.rs, packages/rs-platform-wallet/src/spv_context_provider.rs
Added spv-context Cargo feature and optional deps (dash-spv, dash-context-provider, tokio); implemented MasternodeList-based SpvContextProvider (ContextProvider) for quorum key lookup and activation heights; feature-gated module export.
Swift SDK: expose SPV handle
packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift, packages/swift-sdk/Sources/SwiftDashSDK/Core/Services/WalletService.swift
Exposed internal SPV client pointer via SPVClient.unsafeFFIClientPointer and WalletService.spvClientHandle to surface a raw UnsafeMutableRawPointer FFI handle.
Swift SDK: SDK initializer
packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift
Added public init(network: Network, spvClientHandle: UnsafeMutableRawPointer) throws which calls dash_sdk_create_with_spv_context, handles FFI errors (frees error C struct and throws), validates result, and assigns the SDK handle and network.
Swift app state & flow
packages/swift-sdk/SwiftExampleApp/.../AppState.swift, packages/swift-sdk/SwiftExampleApp/.../UnifiedAppState.swift
Added persisted @Published var useTrustedQuorumFallback, stored spvClientHandle with updateSPVClientHandle(_:), initializeSDK now accepts optional spvClientHandle and prefers SPV init with optional trusted fallback; UnifiedAppState propagates/updates SPV handle during network switches.
Swift UI
packages/swift-sdk/SwiftExampleApp/.../Views/OptionsView.swift
Added a Toggle “Fallback to Trusted Quorums” bound to useTrustedQuorumFallback with explanatory help text.

Sequence Diagrams

sequenceDiagram
    participant UI as App (Swift UI)
    participant AppState as AppState
    participant Wallet as WalletService
    participant SDK as Swift SDK
    participant FFI as Rust FFI
    participant Spv as SPV Provider

    UI->>AppState: initializeSDK(spvClientHandle?)
    AppState->>Wallet: request spvClientHandle
    Wallet-->>AppState: spvClientHandle
    alt spvClientHandle present
        AppState->>SDK: init(network, spvClientHandle)
        SDK->>FFI: dash_sdk_create_with_spv_context(config, handle)
        FFI->>Spv: create SpvContextProvider(handle)
        Spv-->>FFI: provider constructed
        FFI->>FFI: dash_sdk_create_extended(config with provider)
        FFI-->>SDK: result (handle or error)
        SDK-->>AppState: initialized / throws
    else fallback allowed
        AppState->>SDK: init(network) (trusted)
        SDK->>FFI: dash_sdk_create_trusted(config)
        FFI-->>SDK: result
        SDK-->>AppState: initialized
    else no fallback
        AppState-->>UI: error / throw SDKError.invalidState
    end
    AppState-->>UI: SDK ready or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I tunneled through code with nimble paws,
I carried raw handles across language laws.
Quorums whisper proofs, FFI bridges hum,
A rabbit's hop made SPV and SDK come.
Thump — fallback toggles keep the dance from glum.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective of the PR: enabling Platform SDK to use SPV-synced quorums for proof verification instead of HTTP endpoints.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ios-spv-quorums

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 and usage tips.

@thepastaclaw
Copy link
Copy Markdown
Collaborator

thepastaclaw commented Mar 31, 2026

Review Gate

Commit: cfde2243

  • Debounce: 3013m ago (need 30m)

  • CI checks: build failure: Swift SDK build / Swift SDK and Example build (warnings as errors), codecov/patch

  • CodeRabbit review: comment found

  • Off-peak hours: off-peak (09:58 AM PT Saturday)

  • Run review now (check to override)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "93f085d7f665b623b4cc170feb6c8fc04da6702b601f887c52f8e5181e471994"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift`:
- Around line 45-49: The published property useTrustedQuorumFallback currently
only persists the flag; update its didSet to immediately reapply the SDK
proof-verification mode so the running app reflects the change. After
UserDefaults.standard.set(...), invoke the function that configures the SDK
trust mode (for example the AppState method that initializes or configures the
SDK client—if you have a method like applySDKMode(), reconfigureClient(), or
setTrustedQuorumFallback(on:)), passing the new useTrustedQuorumFallback value;
if no such function exists, call the code path that reconnects or reinitializes
the SDK client so proof verification is updated immediately. Ensure you
reference AppState and useTrustedQuorumFallback when wiring this change.

In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift`:
- Around line 126-133: The current network-switch flow lets
walletService.switchNetwork(to:) destroy the SPV client while platformState.sdk
still holds callbacks to the old core handle; update
UnifiedAppState.handleNetworkSwitch to explicitly detach and destroy the old SDK
before tearing down the SPV client: capture the existing platformState.sdk (or
call a teardown method like
platformState.sdk?.destroy()/platformState.clearSDK()), set platformState.sdk =
nil (or call the SDK's close/unbind) on the MainActor to remove callbacks, then
call walletService.switchNetwork(to:) and only after obtaining
walletService.spvClientHandle recreate and assign the new SDK via
platformState.updateSPVClientHandle/new SDK creation — ensuring the old SDK is
fully unbound before SPV teardown.
🪄 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: ed48fb63-d5a9-41ff-b965-9df30eae9aab

📥 Commits

Reviewing files that changed from the base of the PR and between 739754b and f4703d6.

📒 Files selected for processing (7)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVContextProvider.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Services/WalletService.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/OptionsView.swift

The quorum/activation-height bridging no longer round-trips through
Swift callbacks. Instead, rs-sdk-ffi now depends on dash-spv-ffi
directly and implements SpvContextProvider in Rust, calling
ffi_dash_spv_get_quorum_public_key and
ffi_dash_spv_get_platform_activation_height without leaving Rust.

A new FFI function dash_sdk_create_with_spv_context(config, spv_client)
replaces the Swift-side ContextProviderCallbacks setup. Swift just
passes the raw FFIDashSpvClient pointer.

- Add dash-spv-ffi dependency to rs-sdk-ffi
- Add spv_context_provider.rs implementing ContextProvider trait
- Add dash_sdk_create_with_spv_context FFI function
- Remove SPVContextProvider.swift (no longer needed)
- Update SDK.swift to call dash_sdk_create_with_spv_context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 0% with 76 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.90%. Comparing base (41d57d6) to head (cfde224).
⚠️ Report is 3 commits behind head on v3.1-dev.

Files with missing lines Patch % Lines
...ges/rs-platform-wallet/src/spv_context_provider.rs 0.00% 76 Missing ⚠️

❌ Your patch check has failed because the patch coverage (0.00%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##           v3.1-dev    #3417      +/-   ##
============================================
- Coverage     79.93%   79.90%   -0.03%     
============================================
  Files          2861     2862       +1     
  Lines        280236   280312      +76     
============================================
- Hits         223997   223993       -4     
- Misses        56239    56319      +80     
Components Coverage Δ
dpp 71.82% <ø> (ø)
drive 82.30% <ø> (ø)
drive-abci 86.67% <ø> (ø)
sdk 36.55% <ø> (ø)
dapi-client 79.06% <ø> (ø)
platform-version ∅ <ø> (∅)
platform-value 83.36% <ø> (ø)
platform-wallet 70.74% <0.00%> (-5.36%) ⬇️
drive-proof-verifier 55.26% <ø> (ø)
🚀 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.

Add SpvContextProvider to platform-wallet behind the `spv-context`
feature flag. It holds Arc<RwLock<MasternodeListEngine>> + Network
and implements the ContextProvider trait by reading quorum data
directly from the in-memory masternode list — zero FFI involvement.

The rs-sdk-ffi bridge (which calls dash-spv-ffi functions as Rust
calls in the same binary) remains as the pragmatic adapter until
dash-spv-ffi exposes a public accessor for FFIDashSpvClient's
MasternodeListEngine (currently pub(crate)).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/rs-platform-wallet/Cargo.toml`:
- Line 25: The tokio dependency must enable its sync feature for the spv-context
feature and the feature list must use the modern dep: syntax; update Cargo.toml
so the tokio dependency becomes optional with features = ["sync"] (e.g. tokio =
{ version = "1.41", optional = true, features = ["sync"] }) and update the
spv-context feature entry to include "dep:tokio/sync" (instead of the old name)
so spv_context_provider.rs can safely use tokio::sync::RwLock.

In `@packages/rs-platform-wallet/src/spv_context_provider.rs`:
- Around line 115-120: The match that sets `height` currently falls through to
`_ => 0`, silently mapping unsupported `self.network` to 0; replace that
fallback with an explicit error return (e.g., return Err(...) or propagate a
suitable error) so callers are notified of an unsupported Network variant.
Modify the block that computes `height` (the match on `self.network` referencing
`Network::Mainnet`, `Network::Testnet`, `Network::Devnet`) to return a
Result<u64, E> (or use the crate's existing error type) and produce a clear
error for the unsupported branch instead of using 0. Ensure callers of the
enclosing function are updated to handle the Result if necessary.
- Around line 79-80: The call to self.masternode_engine.blocking_read() inside
get_quorum_public_key (which is invoked from async
parse_proof_with_metadata_and_proof) can panic; replace blocking_read() with
try_read() on the Tokio RwLock (self.masternode_engine.try_read()) and handle
the None case gracefully by returning an appropriate error or early failure from
get_quorum_public_key (or falling back to a safe default) instead of unwrapping.
Ensure you reference and use the masternode_lists_around_height result from the
acquired guard when present, and propagate a clear error back to the async proof
verification path so it doesn’t panic.
- Around line 76-77: Validate and reject out-of-range quorum_type before
converting to LLMQType: replace the lossy cast "(quorum_type as u8).into()" with
a fallible conversion using TryInto (e.g., let llmq_type: LLMQType =
(quorum_type.try_into()?) or map the TryInto error to a meaningful error) so
values >255 are handled instead of wrapping; keep
QuorumHash::from_byte_array(quorum_hash) as-is and ensure any error returned
propagates to the caller before using ml.quorums.get(&llmq_type).
🪄 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: 0b462740-73cb-43f3-9a1b-7653a611fe5e

📥 Commits

Reviewing files that changed from the base of the PR and between ad6904a and d9e478e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • packages/rs-platform-wallet/Cargo.toml
  • packages/rs-platform-wallet/src/lib.rs
  • packages/rs-platform-wallet/src/spv_context_provider.rs
  • packages/rs-sdk-ffi/src/spv_context_provider.rs
✅ Files skipped from review due to trivial changes (1)
  • packages/rs-platform-wallet/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-sdk-ffi/src/spv_context_provider.rs

QuantumExplorer and others added 2 commits April 2, 2026 17:24
- AppState: trigger SDK reconnect when useTrustedQuorumFallback
  changes so the running app reflects the new setting immediately
- UnifiedAppState: nil out old SDK before destroying SPV client
  during network switch to prevent use-after-free on the context
  provider's core_handle pointer
- Cargo.toml: explicitly enable tokio "sync" feature for
  blocking_read() support
- spv_context_provider.rs: replace lossy `as u8` cast with
  try_from + error, add comment explaining why blocking_read is
  safe, return error for unsupported networks instead of silent 0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump rust-dashcore to 3f65002 which includes the
FFIDashSpvClient::masternode_list_engine() accessor (PR #608).

dash_sdk_create_with_spv_context now:
1. Casts the void* to FFIDashSpvClient
2. Extracts Arc<RwLock<MasternodeListEngine>> directly
3. Creates platform_wallet::SpvContextProvider (pure Rust)

The FFI bridge module (rs-sdk-ffi/src/spv_context_provider.rs) is
deleted — quorum lookups go directly from the Platform SDK through
the pure-Rust provider to the in-memory masternode list, with zero
FFI involvement in the data path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@QuantumExplorer
Copy link
Copy Markdown
Member Author

All review feedback addressed. Additionally, bumped rust-dashcore to 3f65002 which includes the FFIDashSpvClient::masternode_list_engine() accessor (rust-dashcore PR #608). The FFI bridge module (rs-sdk-ffi/src/spv_context_provider.rs) is now deleted — dash_sdk_create_with_spv_context extracts the engine directly and uses the pure-Rust platform_wallet::SpvContextProvider. Zero FFI in the quorum lookup path.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift (1)

215-267: ⚠️ Potential issue | 🟠 Major

Make the network switch commit atomically.

The new SPV-first failure paths can throw after the collections are cleared and dataManager.currentNetwork is advanced, but before sdk is replaced. On that path the UI says “new network” while platform calls still go through the old SDK instance. If currentNetwork should also be authoritative, it needs the same success-only commit behavior.

♻️ Possible fix
 func switchNetwork(to network: AppNetwork) async {
     guard let modelContext = modelContext else { return }
-
-    // Clear current data
-    identities.removeAll()
-    contracts.removeAll()
-    documents.removeAll()
-    tokens.removeAll()
-
-    // Update DataManager's current network
-    dataManager?.currentNetwork = network

     // Re-initialize SDK with new network
     do {
         isLoading = true
@@
-        sdk = newSDK
+        identities.removeAll()
+        contracts.removeAll()
+        documents.removeAll()
+        tokens.removeAll()
+        dataManager?.currentNetwork = network
+        sdk = newSDK
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift` around
lines 215 - 267, The switchNetwork method mutates state (clearing
identities/contracts/documents/tokens and advancing dataManager?.currentNetwork)
before potentially failing during SDK creation and load; make the switch atomic
by first creating and initializing the new SDK and loading contracts/data (use
local temporaries like let newSDK and temp collections), and only after
loadKnownContractsIntoSDK and loadPersistedData succeed assign sdk = newSDK, set
dataManager?.currentNetwork = network, and replace
identities/contracts/documents/tokens with the newly loaded data; keep all
error-throwing work (SDK(network:), loadKnownContractsIntoSDK,
loadPersistedData) before mutating shared state so failures leave the existing
sdk and currentNetwork unchanged.
🧹 Nitpick comments (1)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift (1)

104-124: Extract the SPV/trusted SDK creation path into one helper.

The same fallback tree now lives in both initializeSDK and switchNetwork. The next change to error handling or provider selection is easy to land in one path and miss in the other.

Also applies to: 234-253

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift` around
lines 104 - 124, Extract the SPV/trusted SDK creation logic used in
initializeSDK and switchNetwork into a single helper function (e.g.,
makeSDKForNetwork(network:sdkNetwork, spvHandle:spvClientHandle?,
useFallback:useTrustedQuorumFallback) -> SDK) that encapsulates the fallback
tree: try constructing SDK(network: , spvClientHandle:) if spvHandle != nil, on
failure fall back to SDK(network:) only if useFallback is true, otherwise
rethrow; if spvHandle is nil create SDK(network:) when useFallback is true or
throw SDKError.invalidState when false. Replace the duplicated blocks in
initializeSDK and switchNetwork to call this new helper and preserve the same
NSLog messages and error propagation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift`:
- Around line 143-146: The race occurs because updateSPVClientHandle(_:) runs in
handleNetworkSwitch() while switchNetwork(...) can be started asynchronously
from the currentNetwork didSet in OptionsView; modify handleNetworkSwitch() so
that after calling updateSPVClientHandle(_:) it immediately triggers/awaits the
SDK rebuild (call switchNetwork(...) directly or await the spawned Task) to
ensure the SDK is rebuilt with the new spvClientHandle before returning;
reference updateSPVClientHandle(_:), handleNetworkSwitch(), switchNetwork(...),
currentNetwork didSet/OptionsView to locate and change the code paths so the
handle update is always followed synchronously by the SDK rebuild.

---

Outside diff comments:
In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift`:
- Around line 215-267: The switchNetwork method mutates state (clearing
identities/contracts/documents/tokens and advancing dataManager?.currentNetwork)
before potentially failing during SDK creation and load; make the switch atomic
by first creating and initializing the new SDK and loading contracts/data (use
local temporaries like let newSDK and temp collections), and only after
loadKnownContractsIntoSDK and loadPersistedData succeed assign sdk = newSDK, set
dataManager?.currentNetwork = network, and replace
identities/contracts/documents/tokens with the newly loaded data; keep all
error-throwing work (SDK(network:), loadKnownContractsIntoSDK,
loadPersistedData) before mutating shared state so failures leave the existing
sdk and currentNetwork unchanged.

---

Nitpick comments:
In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift`:
- Around line 104-124: Extract the SPV/trusted SDK creation logic used in
initializeSDK and switchNetwork into a single helper function (e.g.,
makeSDKForNetwork(network:sdkNetwork, spvHandle:spvClientHandle?,
useFallback:useTrustedQuorumFallback) -> SDK) that encapsulates the fallback
tree: try constructing SDK(network: , spvClientHandle:) if spvHandle != nil, on
failure fall back to SDK(network:) only if useFallback is true, otherwise
rethrow; if spvHandle is nil create SDK(network:) when useFallback is true or
throw SDKError.invalidState when false. Replace the duplicated blocks in
initializeSDK and switchNetwork to call this new helper and preserve the same
NSLog messages and error propagation behavior.
🪄 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: 1d056420-6f71-49b1-ad49-3434618ac7b3

📥 Commits

Reviewing files that changed from the base of the PR and between d9e478e and 8d2bb27.

📒 Files selected for processing (4)
  • packages/rs-platform-wallet/Cargo.toml
  • packages/rs-platform-wallet/src/spv_context_provider.rs
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/AppState.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/rs-platform-wallet/Cargo.toml
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift
  • packages/rs-platform-wallet/src/spv_context_provider.rs

QuantumExplorer and others added 2 commits April 2, 2026 17:41
Remove the switchNetwork() call from currentNetwork.didSet to prevent
it racing with UnifiedAppState.handleNetworkSwitch(). The didSet now
only persists the preference. handleNetworkSwitch() is the single
coordinator: it nils the old SDK, switches the wallet, updates the
SPV handle, then calls switchNetwork() to rebuild the SDK.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace blocking_read() with try_read() in SpvContextProvider to
  avoid panicking when called from within a Tokio async context
  (proof verification runs inside async tasks)
- Use dep: prefix syntax in spv-context feature to suppress implicit
  feature creation for optional dependencies (Cargo 2021 edition)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants