Skip to content

fix(swift-sdk): fixed wallet balance calculation#3082

Merged
ZocoLini merged 1 commit intov3.1-devfrom
chore/swift-sdk-improvements-fixes
Mar 17, 2026
Merged

fix(swift-sdk): fixed wallet balance calculation#3082
ZocoLini merged 1 commit intov3.1-devfrom
chore/swift-sdk-improvements-fixes

Conversation

@ZocoLini
Copy link
Copy Markdown
Collaborator

@ZocoLini ZocoLini commented Feb 12, 2026

Wallet balance was implemented by retrieving Main account balance, this PR fixes that and replaces that logic with the sum of all account balances inside a wallet

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

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • Bug Fixes

    • Removed transient loading indicators and alternate error paths in account list/detail views for more immediate UI feedback.
    • Consolidated balance reporting to a single Balance representation so displayed balances are consistent.
  • Chores

    • Removed the next-receive-address from account displays and related UI controls.
    • Switched account and balance loading to synchronous, non-throwing flows for more predictable view updates.
  • Refactor

    • Streamlined per-network account counting to avoid repeated fetches.

@ZocoLini ZocoLini marked this pull request as draft February 12, 2026 19:34
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Account balance moved from a tuple to a concrete Balance type; AccountInfo.nextReceiveAddress removed. Several wallet/account APIs changed from async/throwing to synchronous/non-throwing returns. Example app views updated to call the synchronous APIs and removed loading/error intermediate UI.

Changes

Cohort / File(s) Summary
Model Restructuring
packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
AccountInfo.balance changed from (confirmed: UInt64, unconfirmed: UInt64) to Balance; removed nextReceiveAddress; initializer signature and assignments updated.
Core Wallet & KeyWallet APIs
packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift, packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift, packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
Converted async/throwing APIs to synchronous/non-throwing variants: getAccounts, getBalance, getAccountDetails signatures changed; getManagedAccountCollection returns optional; error paths replaced by nil-guards or preconditionFailure; balances now concrete Balance values.
UI View Adjustments
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
Replaced async/await with synchronous calls to wallet manager; removed loading/error state UI and nextReceiveAddress UI/copy controls; account counts aggregated from synchronous getAccounts results.

Sequence Diagram(s)

sequenceDiagram
    participant UI as "SwiftUI View\n(AccountList/Detail)"
    participant Core as "CoreWalletManager"
    participant WM as "WalletManager"
    participant MA as "ManagedAccount"

    rect rgba(200,230,250,0.5)
    UI->>Core: getAccounts(for: wallet) (synchronous)
    Core->>WM: getManagedAccountCollection(walletId) (optional)
    alt collection present
        WM->>MA: getBalance() (synchronous)
        MA-->>WM: Balance
        WM-->>Core: [AccountInfo with Balance]
        Core-->>UI: [AccountInfo...] (synchronous)
    else collection missing
        WM-->>Core: nil
        Core-->>UI: [] (synchronous)
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through async, left awaits behind,
Packed confirmed and unconfirmed into one kind.
No next-receive address to stash or trace,
Sync calls now march at a steadier pace.
A tiny rabbit cheers: "Refactor—grace!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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 main change: fixing wallet balance calculation by aggregating all account balances instead of retrieving only the Main account balance.

✏️ 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 chore/swift-sdk-improvements-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@ZocoLini ZocoLini changed the base branch from v3.1-dev to feat/iOSSupport February 12, 2026 19:34
Base automatically changed from feat/iOSSupport to v3.1-dev March 4, 2026 02:23
@github-actions github-actions Bot added this to the v3.1.0 milestone Mar 4, 2026
@ZocoLini ZocoLini changed the title fix(swift-skd): fixed wallet balance calculation fix(swift-sdk): fixed wallet balance calculation Mar 16, 2026
@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch 3 times, most recently from f267961 to 251de42 Compare March 16, 2026 18:56
@ZocoLini ZocoLini marked this pull request as ready for review March 16, 2026 23:00
@ZocoLini ZocoLini requested a review from shumkov as a code owner March 16, 2026 23:00
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

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/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift (1)

480-486: ⚠️ Potential issue | 🔴 Critical

Add wallet ID length validation before FFI call.

The method passes walletId directly to managed_wallet_get_account_collection without validating the 32-byte requirement that all other wallet ID-based FFI methods in this class enforce. This creates an unsafe FFI boundary.

Proposed fix
 public func getManagedAccountCollection(walletId: Data) -> ManagedAccountCollection? {
+    guard walletId.count == 32 else {
+        return nil
+    }
     var error = FFIError()

     let collectionHandle = walletId.withUnsafeBytes { idBytes in
         let idPtr = idBytes.bindMemory(to: UInt8.self).baseAddress
         return managed_wallet_get_account_collection(handle, idPtr, &error)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift` around
lines 480 - 486, The getManagedAccountCollection method lacks a 32-byte wallet
ID length check before calling the FFI function
managed_wallet_get_account_collection; add a guard that validates walletId.count
== 32 (or returns nil/sets FFIError appropriately) at the start of
getManagedAccountCollection in WalletManager to avoid passing improperly sized
buffers into the FFI boundary, and only proceed to the withUnsafeBytes call when
the length is correct.
🧹 Nitpick comments (2)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift (1)

15-20: Reintroduce explicit loading/error states in this view.

This path now jumps directly to empty/list rendering, so transient fetch failures and first-load progress are not represented.

As per coding guidelines, packages/swift-sdk/SwiftExampleApp/**/*View.swift: Implement proper loading and error states in SwiftUI views.

Also applies to: 32-39

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift`
around lines 15 - 20, AccountListView currently jumps straight to empty/list
rendering based only on accounts.isEmpty; add explicit loading and error UI
states by introducing a loading flag and an optional error state (e.g., `@State`
var isLoading: Bool and `@State` var loadError: Error?) and use them to drive the
view: show a ProgressView while isLoading, show a ContentUnavailableView with
the error message and a Retry button when loadError != nil (calling the existing
fetchAccounts() or the view's data-loading method), and only then fall back to
the accounts.isEmpty / list rendering; apply the same pattern for the other
block referenced (lines ~32-39) to ensure first-load progress and transient
failures are represented.
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift (1)

28-35: Keep a loading state for account-detail fetch.

The current flow renders error/content only; there is no explicit loading phase during detail retrieval.

As per coding guidelines, packages/swift-sdk/SwiftExampleApp/**/*View.swift: Implement proper loading and error states in SwiftUI views.

Also applies to: 60-62, 621-625

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`
around lines 28 - 35, The view currently only shows error or content based on
errorMessage and detailInfo; add an explicit loading state in AccountDetailView
by introducing a Bool (e.g., isLoading) or a Result/enum state and render a
ProgressView (or a dedicated LoadingView) when loading is true or when both
errorMessage and detailInfo are nil and a fetch is in progress; update the
fetch/account-detail logic (where detailInfo is populated) to set isLoading =
true before the network call and set it false on success or failure so the UI
transitions through Loading -> Content or Loading -> Error consistently.
🤖 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/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 196-201: Replace the unsafe guard-assert pattern in
getAccountDetails(for:accountInfo:) and getAccounts by deterministically
handling a nil collection from
sdkWalletManager.getManagedAccountCollection(walletId:), i.e., if the collection
is nil return a sensible default instead of asserting: in getAccountDetails
return a default AccountDetailInfo value (matching the struct's zero/empty
state) and in getAccounts return an empty [AccountDetailInfo] array; update both
functions to early-return these defaults when collection is nil so subsequent
calls like collection.getBIP44Account() and collection.getBIP44Indices() never
run on nil.

In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift`:
- Around line 138-143: The getBalance() function uses assert(success, ...) after
calling managed_core_account_get_balance(handle, &ffiBalance), which is disabled
in Release builds; replace this with a runtime check that fails in production
(e.g., guard success else { preconditionFailure("...") }) so a missing/invalid
FFI result cannot silently proceed; update the check in getBalance() to mirror
the robust validation style used in getTransactions() but using
preconditionFailure for mandatory FFI validation and include a clear message
referencing managed_core_account_get_balance and the FFIBalance/Balance
conversion.

---

Outside diff comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift`:
- Around line 480-486: The getManagedAccountCollection method lacks a 32-byte
wallet ID length check before calling the FFI function
managed_wallet_get_account_collection; add a guard that validates walletId.count
== 32 (or returns nil/sets FFIError appropriately) at the start of
getManagedAccountCollection in WalletManager to avoid passing improperly sized
buffers into the FFI boundary, and only proceed to the withUnsafeBytes call when
the length is correct.

---

Nitpick comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Around line 28-35: The view currently only shows error or content based on
errorMessage and detailInfo; add an explicit loading state in AccountDetailView
by introducing a Bool (e.g., isLoading) or a Result/enum state and render a
ProgressView (or a dedicated LoadingView) when loading is true or when both
errorMessage and detailInfo are nil and a fetch is in progress; update the
fetch/account-detail logic (where detailInfo is populated) to set isLoading =
true before the network call and set it false on success or failure so the UI
transitions through Loading -> Content or Loading -> Error consistently.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift`:
- Around line 15-20: AccountListView currently jumps straight to empty/list
rendering based only on accounts.isEmpty; add explicit loading and error UI
states by introducing a loading flag and an optional error state (e.g., `@State`
var isLoading: Bool and `@State` var loadError: Error?) and use them to drive the
view: show a ProgressView while isLoading, show a ContentUnavailableView with
the error message and a Retry button when loadError != nil (calling the existing
fetchAccounts() or the view's data-loading method), and only then fall back to
the accounts.isEmpty / list rendering; apply the same pattern for the other
block referenced (lines ~32-39) to ensure first-load progress and transient
failures are represented.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a90865fd-5260-403c-8ca9-09b83a469ee6

📥 Commits

Reviewing files that changed from the base of the PR and between 9ebc7e4 and 251de42.

📒 Files selected for processing (8)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/HDWallet.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift

Comment thread packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift Outdated
Comment thread packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift Outdated
@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from 251de42 to 0449ad4 Compare March 17, 2026 02:50
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 17, 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: "5a6e810b82fa0eba8ee37cbd983fea676d625ad695667595c074ea6f3da0d958"
)

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.

@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from 0449ad4 to e234771 Compare March 17, 2026 02:57
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.

♻️ Duplicate comments (2)
packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift (1)

138-145: ⚠️ Potential issue | 🔴 Critical

Use preconditionFailure or guard instead of assert for FFI validation.

This was flagged in a previous review: assert() is disabled in Release builds (-O optimization), so if managed_core_account_get_balance fails in production, the function silently continues with a potentially invalid ffiBalance.

Compare with getTransactions() (lines 62-66) which correctly uses guard success else { throw ... }.

Proposed fix
 public func getBalance() -> Balance {
     var ffiBalance = FFIBalance()
     let success = managed_core_account_get_balance(handle, &ffiBalance)
-    
-    assert(success, "This can only fail if handle or ffiBalance are null")
-    
+    guard success else {
+        preconditionFailure("managed_core_account_get_balance failed: handle or ffiBalance is null")
+    }
     return Balance(ffiBalance: ffiBalance)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift`
around lines 138 - 145, The assert in ManagedAccount.getBalance should be
replaced with a runtime-safe check so failures in Release builds don’t proceed
with an invalid FFIBalance; update the call to managed_core_account_get_balance
to use either a guard that throws a descriptive error (matching the pattern used
in getTransactions) or use preconditionFailure with a clear message on
failure—e.g., check the Bool success from managed_core_account_get_balance and
on false either throw a managed-core failure error or call
preconditionFailure("managed_core_account_get_balance failed: handle or
ffiBalance is null") before returning Balance(ffiBalance: ffiBalance).
packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift (1)

196-201: ⚠️ Potential issue | 🔴 Critical

Replace guard-assert pattern with deterministic failure handling.

This was flagged in a previous review: assert(false, ...) is disabled in Release builds. If getManagedAccountCollection returns nil, the guard block does not terminate execution, and subsequent code accesses collection (e.g., collection.getBIP44Account()), causing undefined behavior or crashes.

Both occurrences at lines 199-201 and 357 need deterministic exits.

Proposed fix for both locations
 public func getAccountDetails(for wallet: HDWallet, accountInfo: AccountInfo) -> AccountDetailInfo {
     let collection = sdkWalletManager.getManagedAccountCollection(walletId: wallet.walletId)
     
-    guard let collection else {
-        assert(false, "The walletId is always valid")
-    }
+    guard let collection else {
+        // Return a minimal AccountDetailInfo when collection unavailable
+        return AccountDetailInfo(
+            account: accountInfo,
+            accountType: FFIAccountType(rawValue: 0),
+            xpub: nil,
+            derivationPath: "",
+            gapLimit: 20,
+            usedAddresses: 0,
+            unusedAddresses: 0,
+            externalAddresses: [],
+            internalAddresses: []
+        )
+    }
 public func getAccounts(for wallet: HDWallet) -> [AccountInfo] {
     let collection = sdkWalletManager.getManagedAccountCollection(walletId: wallet.walletId)
     
-    guard let collection else { assert(false, "The walletId is always valid") }
+    guard let collection else {
+        return []  // Return empty list when collection unavailable
+    }
     
     var list: [AccountInfo] = []

Also applies to: 354-358

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

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`
around lines 196 - 201, The guard-assert pattern in
getAccountDetails(for:accountInfo:) (and the other occurrence around
collection.getBIP44Account()) must be replaced with deterministic failure
handling because assert is disabled in Release; change the guard to either throw
a meaningful error or call fatalError with a clear message when
sdkWalletManager.getManagedAccountCollection(walletId:) returns nil so execution
cannot continue with a nil collection, and apply the same change at the other
occurrence that accesses collection.getBIP44Account(); ensure the replacement
provides a descriptive message referencing the walletId/context so failures are
diagnosable.
🧹 Nitpick comments (2)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift (1)

16-16: Remove unused isLoading state variable.

The isLoading state variable at line 16 is no longer used since loadAccountDetails() is now synchronous and the loading UI was removed. This is dead code.

Remove unused state
 `@State` private var detailInfo: AccountDetailInfo?
-@State private var isLoading = true
 `@State` private var errorMessage: String?

Also applies to: 620-626

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`
at line 16, Remove the dead state variable by deleting the `@State` private var
isLoading declaration and any related unused references to isLoading in
AccountDetailView (it was left over after loadAccountDetails() became
synchronous and loading UI was removed); search for and remove any other unused
isLoading occurrences in the same file (e.g., the other instance referenced in
the range around lines 620-626) so the SwiftUI view no longer contains an unused
state property.
packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift (1)

480-498: Removed walletId validation creates inconsistency with other methods.

Other methods in this class (e.g., getWallet, getReceiveAddress, getChangeAddress, getManagedAccount) validate that walletId.count == 32 before calling FFI. This method now skips that validation, allowing invalid wallet IDs to reach the FFI layer.

Additionally, the FFI error information is discarded when returning nil — callers cannot distinguish between "invalid walletId" and "collection unavailable for valid wallet."

Consider either:

  1. Restoring the 32-byte validation for consistency, or
  2. Documenting that callers must validate walletId before calling this method.
Option 1: Restore validation for consistency
 public func getManagedAccountCollection(walletId: Data) -> ManagedAccountCollection? {
+    guard walletId.count == 32 else {
+        return nil
+    }
     var error = FFIError()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift` around
lines 480 - 498, The method getManagedAccountCollection removed the 32-byte
walletId validation and can pass invalid IDs into
managed_wallet_get_account_collection, causing inconsistent behavior and masking
whether nil is due to invalid input; restore the same validation used by
getWallet/getReceiveAddress/getChangeAddress/getManagedAccount by adding a guard
walletId.count == 32 at the start of getManagedAccountCollection and return nil
immediately when the length is wrong so the FFI is not called (leave the
existing FFI error handling/defer as-is for the valid-path case).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 196-201: The guard-assert pattern in
getAccountDetails(for:accountInfo:) (and the other occurrence around
collection.getBIP44Account()) must be replaced with deterministic failure
handling because assert is disabled in Release; change the guard to either throw
a meaningful error or call fatalError with a clear message when
sdkWalletManager.getManagedAccountCollection(walletId:) returns nil so execution
cannot continue with a nil collection, and apply the same change at the other
occurrence that accesses collection.getBIP44Account(); ensure the replacement
provides a descriptive message referencing the walletId/context so failures are
diagnosable.

In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift`:
- Around line 138-145: The assert in ManagedAccount.getBalance should be
replaced with a runtime-safe check so failures in Release builds don’t proceed
with an invalid FFIBalance; update the call to managed_core_account_get_balance
to use either a guard that throws a descriptive error (matching the pattern used
in getTransactions) or use preconditionFailure with a clear message on
failure—e.g., check the Bool success from managed_core_account_get_balance and
on false either throw a managed-core failure error or call
preconditionFailure("managed_core_account_get_balance failed: handle or
ffiBalance is null") before returning Balance(ffiBalance: ffiBalance).

---

Nitpick comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift`:
- Around line 480-498: The method getManagedAccountCollection removed the
32-byte walletId validation and can pass invalid IDs into
managed_wallet_get_account_collection, causing inconsistent behavior and masking
whether nil is due to invalid input; restore the same validation used by
getWallet/getReceiveAddress/getChangeAddress/getManagedAccount by adding a guard
walletId.count == 32 at the start of getManagedAccountCollection and return nil
immediately when the length is wrong so the FFI is not called (leave the
existing FFI error handling/defer as-is for the valid-path case).

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Line 16: Remove the dead state variable by deleting the `@State` private var
isLoading declaration and any related unused references to isLoading in
AccountDetailView (it was left over after loadAccountDetails() became
synchronous and loading UI was removed); search for and remove any other unused
isLoading occurrences in the same file (e.g., the other instance referenced in
the range around lines 620-626) so the SwiftUI view no longer contains an unused
state property.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 832c430e-3c39-4504-b5d8-0e1f4f802ede

📥 Commits

Reviewing files that changed from the base of the PR and between 251de42 and 0449ad4.

📒 Files selected for processing (7)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

This PR correctly changes wallet balance to sum all accounts instead of using only the main account. However, it introduces two critical bugs: guard let x else { assert(false, ...) } is invalid Swift in release builds (guard-else requires a Never-returning exit), and assert(success) in ManagedAccount.getBalance() is stripped in release builds, silently returning zeroed balances on FFI failure. Both patterns replace previously correct error handling with constructs that fail under -O optimization.

Reviewed commit: e234771

🔴 2 blocking | 🟡 3 suggestion(s) | 💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- [BLOCKING] lines 196-201: `guard let ... else { assert(false) }` is invalid in release builds
  Swift `guard` requires the else-clause to unconditionally exit scope (return, throw, break, continue, or a `Never`-returning call). `assert(false, ...)` returns `Void`, not `Never`. Under `-Onone` (debug), the runtime trap may mask this, but under `-O` (release) `assert` is stripped entirely, leaving a guard-else body that falls through — a compile error. This pattern appears at two call sites: `getAccountDetails` (line 199) and `getAccounts` (line 357).
- [SUGGESTION] lines 164-186: Wallet-level `Balance.total` formula differs from FFI per-account total
  The wallet-level balance is constructed via `Balance(confirmed:unconfirmed:immature:locked:)` which computes `total = confirmed + unconfirmed + immature` (excludes locked). Per-account balances come from `Balance(ffiBalance:)` which sets `total = ffiBalance.total` (FFI-defined formula). If the FFI includes locked in its total, or uses a different formula, the wallet-level total will be inconsistent with the sum of per-account totals. Verify the FFI total formula matches, or use the same computation path.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift`:
- [BLOCKING] lines 138-144: `assert(success)` stripped in release builds — silent zero-balance on FFI failure
  The previous code used `guard success else { throw KeyWalletError.invalidState(...) }` which properly surfaced FFI failures. The replacement `assert(success, ...)` is a no-op under `-O`. If the FFI call fails in a release build, `ffiBalance` stays default-initialized (all zeros) and `Balance(ffiBalance: ffiBalance)` returns a zero balance that flows into the wallet-level aggregation — silent data corruption. Use `precondition` (not stripped in release) or restore the throwing guard.

In `packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift`:
- [SUGGESTION] lines 480-498: Removed walletId length validation and FFI error propagation
  The previous implementation validated `walletId.count == 32` before passing the raw pointer to the Rust FFI, and threw `KeyWalletError(ffiError: error)` on failure. Both are removed: the length check is gone (shorter Data passed via `withUnsafeBytes` could cause a buffer over-read on the Rust side if it assumes 32 bytes), and the FFI error message is freed in `defer` without being read or logged. Consider restoring the length precondition and at minimum logging the error before freeing it.

In `packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift`:
- [SUGGESTION] lines 386-401: loadAccountCounts uses single count for all network slots
  The method calls `getAccounts(for: wallet).count` once and assigns the same value to whichever network is enabled. Since only one network is active at a time, this is functionally correct (the others get nil), but the code structure with three separate if-branches applying the same count is misleading. A simpler approach would set the count for the active network directly.

Comment thread packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift Outdated
Comment thread packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift Outdated
@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from e234771 to 0e617f8 Compare March 17, 2026 13:20
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.

Caution

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

⚠️ Outside diff range comments (2)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift (1)

28-35: ⚠️ Potential issue | 🟠 Major

Handle nil detail responses explicitly to avoid blank screen.

When getAccountDetails(...) returns nil, neither errorMessage nor detailInfo is set, so this view renders no content instead of a loading/failure state.

Suggested fix
 ScrollView {
-    if let error = errorMessage {
+    if isLoading {
+        ProgressView("Loading account details...")
+            .frame(maxWidth: .infinity, minHeight: 240)
+    } else if let error = errorMessage {
         ContentUnavailableView(
             "Failed to Load Details",
             systemImage: "exclamationmark.triangle",
             description: Text(error)
         )
@@
 private func loadAccountDetails() {
-    self.detailInfo = walletService.walletManager.getAccountDetails(
+    isLoading = true
+    let details = walletService.walletManager.getAccountDetails(
         for: wallet,
         accountInfo: account
     )
+    self.detailInfo = details
+    self.errorMessage = (details == nil) ? "Account details are unavailable." : nil
+    isLoading = false
 }

As per coding guidelines, "Implement proper loading and error states in SwiftUI views".

Also applies to: 621-625

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`
around lines 28 - 35, AccountDetailView currently only shows
ContentUnavailableView when errorMessage is set or detailInfo when present, so a
nil response from getAccountDetails leaves the view blank; update the view body
to add a final else branch (after the existing if let error = errorMessage /
else if let info = detailInfo) that explicitly renders a loading or fallback
state (e.g., ProgressView or a ContentUnavailableView with "No details
available" and a retry hint) and ensure the retry action calls getAccountDetails
or triggers the same loader logic used elsewhere; reference AccountDetailView,
getAccountDetails(...), errorMessage and detailInfo when making this change.
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift (1)

15-39: ⚠️ Potential issue | 🟠 Major

Reintroduce explicit loading/error states; empty-state-only flow masks failures.

This branch now maps both “still loading” and “account fetch failed” to "No Accounts", so users can’t distinguish an actual empty wallet from a retrieval problem.

Suggested fix
 struct AccountListView: View {
@@
     `@State` private var accounts: [AccountInfo] = []
+    `@State` private var isLoading = true
+    `@State` private var errorMessage: String?
@@
-            if accounts.isEmpty {
+            if isLoading {
+                ProgressView("Loading accounts...")
+            } else if let errorMessage {
+                ContentUnavailableView(
+                    "Failed to Load Accounts",
+                    systemImage: "exclamationmark.triangle",
+                    description: Text(errorMessage)
+                )
+            } else if accounts.isEmpty {
                 ContentUnavailableView(
                     "No Accounts",
                     systemImage: "folder",
@@
     private func loadAccounts() {
-        self.accounts = walletService.walletManager.getAccounts(for: wallet)
+        isLoading = true
+        let loaded = walletService.walletManager.getAccounts(for: wallet)
+        self.accounts = loaded
+        // If manager cannot surface errors yet, at least keep a separate non-loading state.
+        self.errorMessage = nil
+        isLoading = false
     }
 }

As per coding guidelines, "Implement proper loading and error states in SwiftUI views".

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift`
around lines 15 - 39, The view currently treats loading and fetch errors the
same as an empty account list; add explicit loading and error state handling in
AccountListView by introducing `@State` vars (e.g., isLoading: Bool and loadError:
Error?) alongside accounts, update loadAccounts to set isLoading = true, clear
loadError, perform the fetch via walletService.walletManager.getAccounts(for:),
catching and assigning any error to loadError and finally setting isLoading =
false, and update the body to show a ProgressView when isLoading, an error view
(or ContentUnavailableView with the error message and retry action) when
loadError != nil, and only show the "No Accounts" ContentUnavailableView when
accounts.isEmpty and loadError == nil and !isLoading; ensure NavigationLink/List
remain unchanged for the success path.
♻️ Duplicate comments (1)
packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift (1)

196-233: ⚠️ Potential issue | 🔴 Critical

Fix invalid optional chaining and nil-path fallback in account details lookup.

Line 197 uses invalid Swift syntax (...)?), and the function currently falls through to a synthetic AccountDetailInfo when collection/account lookup fails instead of returning nil.

#!/bin/bash
# Verify the invalid optional-chaining token and inspect the surrounding control flow.
rg -n 'getManagedAccountCollection\(walletId:.*\)\?' packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
sed -n '190,260p' packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
Suggested fix
 public func getAccountDetails(for wallet: HDWallet, accountInfo: AccountInfo) -> AccountDetailInfo? {
-    let collection = sdkWalletManager.getManagedAccountCollection(walletId: wallet.walletId)?
+    guard let collection = sdkWalletManager.getManagedAccountCollection(walletId: wallet.walletId) else {
+        return nil
+    }

     // Resolve managed account from category and optional index
     var managed: ManagedAccount?
@@
-    var ffiType = FFIAccountType(rawValue: 0)
-    if let m = managed {
-        ffiType = FFIAccountType(rawValue: m.accountType?.rawValue ?? 0)
+    guard let m = managed else {
+        return nil
+    }
+
+    var ffiType = FFIAccountType(rawValue: m.accountType?.rawValue ?? 0)
+    do {
         // Query all generated addresses (0 to 0 means "all addresses" in FFI)
-        if let pool = m.getExternalAddressPool(), let infos = try? pool.getAddresses(from: 0, to: 0) {
+        if let pool = m.getExternalAddressPool(), let infos = try? pool.getAddresses(from: 0, to: 0) {
             externalDetails = infos.map { info in
                 AddressDetail(address: info.address, index: info.index, path: info.path, isUsed: info.used, publicKey: info.publicKey?.map { String(format: "%02x", $0) }.joined() ?? "")
             }
         }
@@
-    }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`
around lines 196 - 233, The getAccountDetails function uses invalid
optional-chaining syntax on sdkWalletManager.getManagedAccountCollection(...)
and currently falls through to build a synthetic AccountDetailInfo when the
collection or managed account is missing; change the line to safely unwrap the
collection with optional binding (e.g., guard let collection =
sdkWalletManager.getManagedAccountCollection(walletId: wallet.walletId) else {
return nil }) and similarly guard or return nil if the resolved ManagedAccount
(from getBIP44Account/getBIP32Account/getIdentityRegistrationAccount/etc.) is
nil before continuing; ensure ffiType, externalDetails/internalDetails
population and derivationPath logic only run when managed is present so the
function returns nil for missing collection/account instead of fabricating an
AccountDetailInfo.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Around line 28-35: AccountDetailView currently only shows
ContentUnavailableView when errorMessage is set or detailInfo when present, so a
nil response from getAccountDetails leaves the view blank; update the view body
to add a final else branch (after the existing if let error = errorMessage /
else if let info = detailInfo) that explicitly renders a loading or fallback
state (e.g., ProgressView or a ContentUnavailableView with "No details
available" and a retry hint) and ensure the retry action calls getAccountDetails
or triggers the same loader logic used elsewhere; reference AccountDetailView,
getAccountDetails(...), errorMessage and detailInfo when making this change.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift`:
- Around line 15-39: The view currently treats loading and fetch errors the same
as an empty account list; add explicit loading and error state handling in
AccountListView by introducing `@State` vars (e.g., isLoading: Bool and loadError:
Error?) alongside accounts, update loadAccounts to set isLoading = true, clear
loadError, perform the fetch via walletService.walletManager.getAccounts(for:),
catching and assigning any error to loadError and finally setting isLoading =
false, and update the body to show a ProgressView when isLoading, an error view
(or ContentUnavailableView with the error message and retry action) when
loadError != nil, and only show the "No Accounts" ContentUnavailableView when
accounts.isEmpty and loadError == nil and !isLoading; ensure NavigationLink/List
remain unchanged for the success path.

---

Duplicate comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 196-233: The getAccountDetails function uses invalid
optional-chaining syntax on sdkWalletManager.getManagedAccountCollection(...)
and currently falls through to build a synthetic AccountDetailInfo when the
collection or managed account is missing; change the line to safely unwrap the
collection with optional binding (e.g., guard let collection =
sdkWalletManager.getManagedAccountCollection(walletId: wallet.walletId) else {
return nil }) and similarly guard or return nil if the resolved ManagedAccount
(from getBIP44Account/getBIP32Account/getIdentityRegistrationAccount/etc.) is
nil before continuing; ensure ffiType, externalDetails/internalDetails
population and derivationPath logic only run when managed is present so the
function returns nil for missing collection/account instead of fabricating an
AccountDetailInfo.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3243c84-57e6-46e5-b65a-27a437080cf7

📥 Commits

Reviewing files that changed from the base of the PR and between 0449ad4 and 0e617f8.

📒 Files selected for processing (7)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift

@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from 0e617f8 to 17c9932 Compare March 17, 2026 14:27
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/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift (1)

150-162: ⚠️ Potential issue | 🟠 Major

Force-unwrap (try!) can crash the app on FFI failure.

Lines 152 and 161 use try! which will crash the application if the FFI calls fail. This contradicts the PR's pattern of converting to non-throwing, graceful-failure APIs elsewhere.

Consider returning an empty array on failure to maintain consistency:

Proposed fix
     public func getTransactions(for wallet: HDWallet, accountIndex: UInt32 = 0) -> [WalletTransaction] {
-        // Get managed account
-        let managedAccount = try! sdkWalletManager.getManagedAccount(
-            walletId: wallet.walletId,
-            accountIndex: accountIndex,
-            accountType: .standardBIP44
-        )
-
-        // Get current height (TODO: get from SPV client when available)
-        let currentHeight: UInt32 = 0
-
-        return try! managedAccount.getTransactions(currentHeight: currentHeight)
+        do {
+            let managedAccount = try sdkWalletManager.getManagedAccount(
+                walletId: wallet.walletId,
+                accountIndex: accountIndex,
+                accountType: .standardBIP44
+            )
+            let currentHeight: UInt32 = 0
+            return try managedAccount.getTransactions(currentHeight: currentHeight)
+        } catch {
+            print("Failed to get transactions: \(error)")
+            return []
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`
around lines 150 - 162, getTransactions currently uses force-throws (try!) on
sdkWalletManager.getManagedAccount and managedAccount.getTransactions which can
crash on FFI failure; change getTransactions to catch errors instead: perform
the getManagedAccount and getTransactions calls inside a do-catch (or use
optional try with guard/if let) and on any error return an empty
[WalletTransaction] to match the non-throwing API pattern, ensuring you
reference getTransactions(for:accountIndex:),
sdkWalletManager.getManagedAccount(...), and
managedAccount.getTransactions(currentHeight:) when editing.
🧹 Nitpick comments (1)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift (1)

16-16: Dead code: isLoading state is declared but never used.

The isLoading state variable is no longer read or modified after the refactor to synchronous loading. Remove it to clean up the code.

Proposed fix
     `@State` private var detailInfo: AccountDetailInfo?
-    `@State` private var isLoading = true
     `@State` private var errorMessage: String?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`
at line 16, Remove the unused `@State` variable isLoading from AccountDetailView:
locate the declaration "@State private var isLoading = true" in the
AccountDetailView struct and delete it, and then run a quick compile to ensure
no references remain; if any residual checks or bindings reference isLoading
(e.g., in view modifiers or conditional logic), remove or replace those
references so the view uses the current synchronous loading flow.
🤖 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/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 187-189: The getReceiveAddress method currently uses try! on
sdkWalletManager.getReceiveAddress which can crash; replace the force-try with
proper error handling by either (a) changing the signature of
getReceiveAddress(for:accountIndex:) to throw and rethrow the underlying error
from sdkWalletManager.getReceiveAddress, or (b) change the return type to
String? (or return an empty string) and use do/catch to call
sdkWalletManager.getReceiveAddress(walletId:accountIndex:) and return the
successful value or nil/"" on failure; update any callers accordingly (refer to
getReceiveAddress and sdkWalletManager.getReceiveAddress).

---

Outside diff comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 150-162: getTransactions currently uses force-throws (try!) on
sdkWalletManager.getManagedAccount and managedAccount.getTransactions which can
crash on FFI failure; change getTransactions to catch errors instead: perform
the getManagedAccount and getTransactions calls inside a do-catch (or use
optional try with guard/if let) and on any error return an empty
[WalletTransaction] to match the non-throwing API pattern, ensuring you
reference getTransactions(for:accountIndex:),
sdkWalletManager.getManagedAccount(...), and
managedAccount.getTransactions(currentHeight:) when editing.

---

Nitpick comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Line 16: Remove the unused `@State` variable isLoading from AccountDetailView:
locate the declaration "@State private var isLoading = true" in the
AccountDetailView struct and delete it, and then run a quick compile to ensure
no references remain; if any residual checks or bindings reference isLoading
(e.g., in view modifiers or conditional logic), remove or replace those
references so the view uses the current synchronous loading flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6668d9a-8f7d-45cd-bb4d-98cc51249ae3

📥 Commits

Reviewing files that changed from the base of the PR and between 0e617f8 and 17c9932.

📒 Files selected for processing (7)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift

@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from 17c9932 to 9b8da2c Compare March 17, 2026 14:59
Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

This push fully resolves both blocking findings from the prior review. The assert(false) in guard-else bodies (which is invalid Swift in release builds) has been replaced with proper returns (return nil / return []). The assert(success) in ManagedAccount.getBalance() (which was stripped under -O, silently returning zero balances) has been replaced with preconditionFailure which correctly traps in all build modes. The walletId.count == 32 length check in getManagedAccountCollection has also been restored. The wallet-level Balance.total formula inconsistency (prior suggestion) remains unaddressed but is low-risk.

Reviewed commit: 17c9932

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/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift (1)

150-162: ⚠️ Potential issue | 🟠 Major

Same force-unwrap pattern in getTransactions.

Both getManagedAccount and getTransactions use try! which will crash on FFI failures. Consider using try? with appropriate fallbacks.

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

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`
around lines 150 - 162, The getTransactions function currently uses force-try
(try!) on sdkWalletManager.getManagedAccount and managedAccount.getTransactions
which will crash on FFI failures; change these to safe error handling (either
use try? with a sensible fallback such as returning an empty [WalletTransaction]
when obtaining managedAccount or fetching transactions fails, or wrap both calls
in a do-catch and propagate or log the error instead), replacing the forced
unwraps around getManagedAccount and managedAccount.getTransactions and ensuring
currentHeight handling remains the same.
🧹 Nitpick comments (2)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift (1)

16-16: Dead state variable: isLoading is never updated.

The isLoading state is initialized to true but never set to false after loadAccountDetails() completes. With the switch to synchronous loading, this variable is now unused. Consider removing it to avoid confusion.

-    `@State` private var isLoading = true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`
at line 16, The isLoading `@State` variable in AccountDetailView is dead
(initialized true but never updated after loadAccountDetails()), so remove the
unused property to avoid confusion: delete the declaration "@State private var
isLoading = true" and any references to isLoading; verify loadAccountDetails()
and the view's synchronous loading logic remain correct without relying on
isLoading.
packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift (1)

480-503: FFI error information silently discarded.

The error from managed_wallet_get_account_collection is freed without being logged or propagated. This makes debugging failures difficult. Consider logging the error before returning nil.

Suggested improvement
         guard let collection = collectionHandle else {
+            if let msg = error.message {
+                print("getManagedAccountCollection failed: \(String(cString: msg))")
+            }
             return nil
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift` around
lines 480 - 503, getManagedAccountCollection currently discards FFIError from
managed_wallet_get_account_collection by freeing error.message without logging
or propagating it; capture the error details (error.message and/or error.code)
after the FFI call and before calling error_message_free, and emit them via the
module's logging facility (or a debug print if no logger exists) so failures are
visible, then free the message and return nil as before; reference symbols:
getManagedAccountCollection, managed_wallet_get_account_collection, FFIError,
and error_message_free.
🤖 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/Core/Views/AccountDetailView.swift`:
- Around line 621-626: The loadAccountDetails function currently assigns
detailInfo from walletService.walletManager.getAccountDetails without handling a
nil result; update loadAccountDetails to check the returned value from
getAccountDetails(for:account:) and if it’s nil set an appropriate errorMessage
(and clear or set detailInfo to nil) so the SwiftUI view can show an error
state; reference the loadAccountDetails method,
walletService.walletManager.getAccountDetails, the detailInfo property, and the
errorMessage property when implementing this conditional assignment and UI state
update.

---

Outside diff comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 150-162: The getTransactions function currently uses force-try
(try!) on sdkWalletManager.getManagedAccount and managedAccount.getTransactions
which will crash on FFI failures; change these to safe error handling (either
use try? with a sensible fallback such as returning an empty [WalletTransaction]
when obtaining managedAccount or fetching transactions fails, or wrap both calls
in a do-catch and propagate or log the error instead), replacing the forced
unwraps around getManagedAccount and managedAccount.getTransactions and ensuring
currentHeight handling remains the same.

---

Nitpick comments:
In `@packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift`:
- Around line 480-503: getManagedAccountCollection currently discards FFIError
from managed_wallet_get_account_collection by freeing error.message without
logging or propagating it; capture the error details (error.message and/or
error.code) after the FFI call and before calling error_message_free, and emit
them via the module's logging facility (or a debug print if no logger exists) so
failures are visible, then free the message and return nil as before; reference
symbols: getManagedAccountCollection, managed_wallet_get_account_collection,
FFIError, and error_message_free.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Line 16: The isLoading `@State` variable in AccountDetailView is dead
(initialized true but never updated after loadAccountDetails()), so remove the
unused property to avoid confusion: delete the declaration "@State private var
isLoading = true" and any references to isLoading; verify loadAccountDetails()
and the view's synchronous loading logic remain correct without relying on
isLoading.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9af4700-17c2-41cf-93e3-dad70e9dae93

📥 Commits

Reviewing files that changed from the base of the PR and between 17c9932 and 9b8da2c.

📒 Files selected for processing (7)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/HDWalletModels.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/ManagedAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/WalletManager.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift

Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

This push is a rebase onto the updated base branch (v3.1-dev) with no changes to the PR's own code. The prior review's approval stands — all previously identified blocking issues (assert(false) in guard-else, assert(success) stripped in release builds) remain resolved.

Reviewed commit: 9b8da2c

@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from 9b8da2c to ac1541c Compare March 17, 2026 17:05
@ZocoLini ZocoLini force-pushed the chore/swift-sdk-improvements-fixes branch from ac1541c to 897b021 Compare March 17, 2026 17:15
@ZocoLini ZocoLini merged commit b119c59 into v3.1-dev Mar 17, 2026
34 checks passed
@ZocoLini ZocoLini deleted the chore/swift-sdk-improvements-fixes branch March 17, 2026 19:23
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.

3 participants