Skip to content

chore(swift-sdk): clean up core transactions, wallet, balance, acccounts, etc in swift sdk#3079

Open
ZocoLini wants to merge 15 commits intov3.1-devfrom
chore/core-swift-sdk-cleanup
Open

chore(swift-sdk): clean up core transactions, wallet, balance, acccounts, etc in swift sdk#3079
ZocoLini wants to merge 15 commits intov3.1-devfrom
chore/core-swift-sdk-cleanup

Conversation

@ZocoLini
Copy link
Collaborator

@ZocoLini ZocoLini commented Feb 11, 2026

Balances where not being shown correctly and the main issue was the enormous amount of logic, the underlying Rust lib contains the logic and the sdk only needs to wrap it, dropped all logic that was unnecessary and added one method to CoreWalletManager wrapper:

    public func getBalance(for wallet: HDWallet, accountIndex: UInt32 = 0) -> Balance {
        let managedAccount = try! sdkWalletManager.getManagedAccount(
            walletId: wallet.walletId,
            accountIndex: accountIndex,
            accountType: .standardBIP44
        )
        
        return try! managedAccount.getBalance()
    }

This follows the same pattern the transactions follow, asking directly the ffi for the balance.

Note that this PR is a cleanup, I drop unused stuff and logic layers that are not useful, the PR fixes the mentioned issue, and a couple more found by cleaning, like wallets being persisted twice, SPVclient reference being hold somewhere preventing it from de-initializing, wallet crashes after delete and maybe more

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

Release Notes

  • Refactor

    • Major architectural redesign: WalletService transitioned from singleton to dependency-injection pattern; SPVClient lifecycle improved with explicit pointer cleanup.
    • Wallet data models simplified: removed transaction, address, and UTXO relationship management from HDWallet; consolidated account/balance retrieval to direct SDK methods.
  • Breaking Changes

    • Removed CoreTransaction, filter matching, and transaction service APIs; wallet creation now requires explicit mnemonic parameter.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

A significant architectural refactoring removes legacy transaction and filter-matching types, restructures WalletService from singleton to dependency-injected initialization, and simplifies HDWallet data model. SPVClient pointers are made optional with nil-cleanup, and public APIs for transaction/address management are consolidated. Multiple UI views are updated or removed to reflect the new service architecture.

Changes

Cohort / File(s) Summary
Core Model Type Removals
Core/Models/CoreTransaction.swift, Core/Models/CoreTypes.swift, Core/Models/FilterMatch.swift
Removed CoreTransaction structs (CoreTransaction, CoreTransactionInput, CoreTransactionOutput, CoreTransactionBuilder), CoreTypes enums (SyncState, WatchStatus, InstantLock, AssetLockError), and FilterMatch types (CompactFilter, CompactFilters, FilterMatchEntry, FilterMatches, FilterMatchError). Eliminated 404 lines of type definitions and formatting logic.
Core Service Restructuring
Core/Services/WalletService.swift, Core/Services/FilterMatchService.swift, Core/SPV/SPVClient.swift, Core/Utils/ModelContainerHelper.swift
Converted WalletService from singleton to dependency-injected initialization with constructor init(modelContainer:, network:). Made SPVClient client/config pointers optional with nil-cleanup on destroy. Removed FilterMatchService entirely (273 lines). Removed HD model registrations from schema (HDAddress, HDTransaction, HDUTXO, HDWatchedAddress).
Wallet/Transaction Model Changes
Core/Wallet/HDWallet.swift, Core/Wallet/HDTransaction.swift, Core/Wallet/TransactionService.swift, Core/Wallet/TransactionErrors.swift, KeyWallet/Wallet.swift
Removed HDTransaction class and related types. Simplified HDWallet by removing encryptedSeed, accounts, currentAccountIndex properties; changed network from String to AppNetwork; made walletId/serializedWalletBytes required unique attributes. Updated constructor to require walletId/serializedWalletBytes. Removed balance methods from Wallet class. Deleted TransactionService and TransactionErrors entirely.
Wallet Manager Core Logic
Core/Wallet/CoreWalletManager.swift
Refactored initialization from SDK WalletManager to SPVClient-based construction. Updated createWallet to require non-optional mnemonic. Added public deleteWallet, getBalance, getReceiveAddress methods. Removed currentWallet/isLoading properties. Restructured wallet restoration via SPV import workflow instead of legacy restoration logic. Adjusted derivation path strategy using wallet ID and network context.
Shared State Management
Shared/UnifiedStateManager.swift, SwiftExampleApp/UnifiedAppState.swift
Removed coreTransactions property from UnifiedStateManager. Updated UnifiedAppState to initialize WalletService with explicit dependencies (modelContainer, network) instead of singleton pattern.
UI View Removals
SwiftExampleApp/Core/Views/AddressManagementView.swift, SwiftExampleApp/Core/Views/FilterMatchesView.swift
Deleted AddressManagementView (161 lines) with address selector, generation, and copy-to-clipboard functionality. Deleted FilterMatchesView (412 lines) with filter display, mode switching, and jump-to interaction.
UI View Updates - Wallet/Balance Display
SwiftExampleApp/Core/Views/WalletDetailView.swift, SwiftExampleApp/Core/Views/CoreContentView.swift, SwiftExampleApp/Core/Views/ReceiveAddressView.swift
Updated balance retrieval to use walletService.walletManager.getBalance() instead of direct wallet properties. Changed network references from dashNetwork to network (AppNetwork). Removed wallet loading via .task. Simplified wallet deletion flow. Added walletService EnvironmentObject dependencies.
UI View Updates - Transaction/Data Retrieval
SwiftExampleApp/Core/Views/TransactionListView.swift, SwiftExampleApp/Core/Views/AccountListView.swift, SwiftExampleApp/Core/Views/AccountDetailView.swift
Replaced throwing async transaction/account fetching with direct walletService.walletManager calls. Removed optional chaining guards and nil-coalescing fallbacks, making walletManager non-optional access. Simplified error handling by removing try/catch in some contexts.
UI View Updates - Send/Receive
SwiftExampleApp/Core/Views/SendTransactionView.swift, SwiftExampleApp/Core/Views/CreateWalletView.swift
Hardcoded max amount to 99999 instead of wallet.confirmedBalance. Replaced sendTransaction logic with TODO placeholder. Updated createWallet to call walletManager instead of walletService and removed post-creation persistence. Made mnemonic non-optional parameter.
Test Updates
SwiftExampleAppTests/WalletTests/TransactionTests.swift
Removed AddressType property from MockAddress and from AddressProtocol requirement. Removed conformance extensions for HDUTXO: UTXOProtocol and HDAddress: AddressProtocol.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops through the code with glee so bright,
Old types are gone, the paths now light!
SPV blooms where filters danced before,
Wallets spring forth from every door!
A simpler shape, the SDK grows,
Off we hop where the SPV flows! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.75% 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 describes a cleanup of core transactions, wallet, balance, and accounts in the Swift SDK, which accurately reflects the primary changes across multiple deleted files and refactored APIs.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/core-swift-sdk-cleanup

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 marked this pull request as draft February 11, 2026 01:12
@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 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: "27b315aadd0b2282dc995986a2b21c483f346b3634a4c5936cdf9cccee040ce4"
)

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 changed the title chore(swift-sdk): core transactions, wallet, balance, acccounts, etc in swift sdk cleanup chore(swift-sdk): clean up core transactions, wallet, balance, acccounts, etc in swift sdk Feb 11, 2026
@ZocoLini ZocoLini marked this pull request as ready for review February 11, 2026 17:19
@ZocoLini ZocoLini force-pushed the chore/core-swift-sdk-cleanup branch from 2aaca9d to d37de8f Compare February 11, 2026 18:50
Base automatically changed from feat/iOSSupport to v3.1-dev March 4, 2026 02:22
@QuantumExplorer QuantumExplorer requested a review from shumkov as a code owner March 4, 2026 02:22
@github-actions github-actions bot added this to the v3.1.0 milestone Mar 4, 2026
Copy link
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: 10

Caution

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

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

21-26: ⚠️ Potential issue | 🟠 Major

Hardcoded 99,999 duffs breaks amount validation and shows a fake balance.

This caps sends at 0.00099999 DASH regardless of actual wallet funds, and the “Insufficient balance” state is no longer tied to real account balance.

Also applies to: 51-59

🤖 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/SendTransactionView.swift`
around lines 21 - 26, The canSend computed property (and the related validation
at lines 51-59) uses a hardcoded 99999 duffs cap which prevents sending based on
the real wallet balance; replace that hardcoded limit with a check against the
actual account/wallet balance (e.g., compare amount to a Wallet.balance or
Account.availableBalance value), ensure amount is safely unwrapped (use optional
binding) and greater than zero, and update the “Insufficient balance” logic to
derive from the same real balance variable so the UI reflects true funds rather
than the fixed 99,999 duffs ceiling.
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CreateWalletView.swift (1)

256-265: ⚠️ Potential issue | 🟠 Major

Validate mnemonic content before creation call.

At Line 257-265, validation does not check mnemonic content (empty/whitespace/word count). Add explicit mnemonic validation before entering the async create path.

Suggested fix
     private func createWallet(using mnemonic: String) {
+        let normalizedMnemonic = mnemonic
+            .trimmingCharacters(in: .whitespacesAndNewlines)
+            .lowercased()
+        let wordCount = normalizedMnemonic.split(whereSeparator: \.isWhitespace).count
+
         guard !walletLabel.isEmpty,
+              !normalizedMnemonic.isEmpty,
+              [12, 15, 18, 21, 24].contains(wordCount),
               walletPin == confirmPin,
               walletPin.count >= 4 && walletPin.count <= 6 else {
             print("=== WALLET CREATION VALIDATION FAILED ===")
             print("Label empty: \(walletLabel.isEmpty)")
             print("PINs match: \(walletPin == confirmPin)")
             print("PIN length valid: \(walletPin.count >= 4 && walletPin.count <= 6)")
             return
         }
🤖 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/CreateWalletView.swift`
around lines 256 - 265, The createWallet(using:) method currently only validates
walletLabel, walletPin and confirmPin but never checks the mnemonic; add
explicit validation in createWallet(using:) that the mnemonic string is
non-empty, not just whitespace, and meets expected word count (e.g., 12 or 24
words) before proceeding to the async creation path—use the existing parameters
walletLabel, walletPin, confirmPin and the mnemonic argument to perform these
checks and return early with the same failure logging if the mnemonic is
invalid.
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift (2)

607-615: ⚠️ Potential issue | 🟠 Major

Validate derived private key against the address public key before display.

At Line 612–615, the derived private key is accepted and rendered without a private/public key consistency check. Add KeyValidation.validatePrivateKeyForPublicKey (when detail.publicKey is present) before setting privateKeyToShow.

As per coding guidelines, "Always validate private keys match their public keys using KeyValidation.validatePrivateKeyForPublicKey before cryptographic operations".

🤖 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 607 - 615, The derived WIF private key must be validated against
the address public key before being shown: after calling
walletService.walletManager.derivePrivateKeyAsWIF(for:accountInfo:addressIndex:)
and before setting showingPrivateKey/privateKeyToShow, if detail.publicKey is
non-nil convert the derived WIF to the raw private key form expected by
KeyValidation.validatePrivateKeyForPublicKey (or derive hex via the wallet
manager if available) and call
KeyValidation.validatePrivateKeyForPublicKey(privateKey: , publicKey:
detail.publicKey); only set self.privateKeyToShow and self.showingPrivateKey
when validation succeeds, and otherwise handle failure (clear vars and surface
an error/alert or log) so invalid private keys are never displayed.

604-611: ⚠️ Potential issue | 🔴 Critical

Enforce PIN before private-key derivation.

At Line 607, pin is not used at all, so the PIN prompt currently does not protect key export. Add explicit PIN verification (or pass the PIN into the FFI call if that is the intended contract) before deriving the WIF key.

🤖 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 604 - 611, The derivePrivateKeyWithPIN function currently ignores
the supplied pin and directly calls
walletService.walletManager.derivePrivateKeyAsWIF, so the PIN prompt does not
protect key export; fix by verifying the PIN before derivation (e.g., call the
existing PIN-check API such as walletService.verifyPIN(pin) or
walletService.walletManager.verifyPIN(pin) if available) or, if the FFI requires
it, call a derivePrivateKeyAsWIF overload that accepts the pin (e.g.,
derivePrivateKeyAsWIF(for: wallet, accountInfo: account, addressIndex:
detail.index, pin: pin)); ensure you use the AddressDetail (detail), wallet, and
account identifiers already present and only proceed to call
derivePrivateKeyAsWIF after successful PIN verification.
🧹 Nitpick comments (1)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift (1)

389-402: Consolidate duplicated getAccounts(for:) calls.

Line 389, Line 395, and Line 401 call the same async method in separate branches. Fetch once, then assign to the enabled network slot(s). This reduces repeated FFI calls and simplifies maintenance.

🤖 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/WalletDetailView.swift`
around lines 389 - 402, The code currently calls
walletService.walletManager.getAccounts(for:) three times; instead, call it once
(await try? walletService.walletManager.getAccounts(for: wallet)) store the
result in a local variable (e.g., accountsList) and then set
mainnetAccountCount/testnetAccountCount/devnetAccountCount from
accountsList.count only when the corresponding flags (testnetEnabled,
devnetEnabled, and mainnetEnabled if present) are true, otherwise set them to
nil; preserve the same error-tolerant behavior (use try? semantics) and ensure
you only perform the single async call in the scope where those counts are
assigned.
🤖 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/Services/WalletService.swift`:
- Around line 118-129: The use of try! when constructing SPVClient and
CoreWalletManager in WalletService.init (creating spvClient and walletManager)
can crash the host app; update WalletService.init to either be a throwing
initializer or catch these errors and store them in a recoverable property
(e.g., lastSyncError) instead of force-unwrapping. Locate the SPVClient(...) and
CoreWalletManager(spvClient:..., modelContainer:...) calls and replace try! with
proper do/catch handling that assigns the caught Error to lastSyncError (and
leaves walletManager/spvClient nil) or change the init signature to throws and
propagate the errors upward, applying the same pattern for the similar block at
the other occurrence (around lines 153-171).
- Around line 237-247: switchNetwork currently logs the wrong "from" network and
recreates the SPV client twice; move the print of "Switching from ..." to before
assigning self.network (so it reports the prior network), and remove the
redundant initializeNewSPVClient() call because stopSync() already recreates the
client; update the switchNetwork function to log first, then set self.network,
call stopSync(), and do not call initializeNewSPVClient() again (references:
switchNetwork, stopSync, initializeNewSPVClient).

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift`:
- Around line 266-271: The destroy() method currently calls
dash_spv_ffi_client_destroy(client) and dash_spv_ffi_config_destroy(config)
unconditionally, which can double-free when destroy() is invoked multiple times
(explicitly or from deinit); make destroy() idempotent by guarding the optional
handles: check if client and config are non-nil before calling
dash_spv_ffi_client_destroy and dash_spv_ffi_config_destroy respectively, call
each FFI destructor only once, then set client = nil and config = nil; ensure
the same guard protects any callers from WalletService or deinit
double-invocation.

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift`:
- Around line 175-177: The getReceiveAddress(for:accountIndex:) implementation
incorrectly hardcodes accountIndex to 0; update the call to
sdkWalletManager.getReceiveAddress(walletId:account.walletId, accountIndex:
accountIndex) so it passes the provided accountIndex parameter (reference:
function getReceiveAddress(for: HDWallet, accountIndex: UInt32) and
sdkWalletManager.getReceiveAddress(walletId:accountIndex:) and use
wallet.walletId). Ensure the passed accountIndex variable (not literal 0) is
used so non-default accounts return the correct address.
- Around line 151-173: The public methods getTransactions(for:accountIndex:) and
getBalance(for:accountIndex:) currently use force-try (try!) and can crash;
change their signatures to throw and propagate errors (or translate to a
WalletError) instead of force-unwrapping. Replace try!
sdkWalletManager.getManagedAccount(...) and try!
managedAccount.getTransactions(...) / try! managedAccount.getBalance() with
do/catch or direct throws so failures from sdkWalletManager.getManagedAccount
and ManagedAccount.getTransactions/getBalance are returned to callers (or mapped
to a WalletError enum) rather than terminating the app.
- Around line 441-443: The warning print uses a nil-coalescing operator on a
non-optional property causing a compile error; in CoreWalletManager (the
restoration block where wallet.walletId is compared to restoredWalletId) remove
the invalid "?? \"nil\"" from wallet.walletId.hexString and just use
wallet.walletId.hexString (and similarly ensure restoredWalletId.hexString is
used directly) so the print call becomes a plain interpolation of the two
hexString values.

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/HDWallet.swift`:
- Line 7: The Example app breaks because removed types HDAddress and HDUTXO are
still referenced; either reintroduce them as compatibility typealiases/structs
in the SDK (e.g., add public typealias HDAddress = NewAddressType and public
typealias HDUTXO = NewUTXOType or lightweight wrapper structs) within
HDWallet.swift or update the Example and test code to use the new canonical
types and shapes used by HDWallet (replace HDAddress/HDUTXO usages in
WalletDetailView and TransactionTests with the new types and adjust any
property/initializer names accordingly), and remove or update the comment in
TransactionTests that refers to HDUTXO. Ensure the chosen fix preserves API
visibility (public) so the Example compiles.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CreateWalletView.swift`:
- Around line 273-275: Remove the sensitive logging: delete the print that
outputs the recovery phrase (the local variable mnemonic computed from
showImportOption/importMnemonic/mnemonic) and also avoid logging wallet PIN
details (walletPin or its length) in CreateWalletView; if you need diagnostic
info keep only non-sensitive flags (e.g., "hasMnemonic: true/false") or use
sanitized booleans, and ensure any debugging messages referencing mnemonic or
walletPin are removed or replaced with safe indicators.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/SendTransactionView.swift`:
- Around line 125-127: The sendTransaction() method is a TODO and currently a
no-op; wire it to the existing wallet service to perform a send and show
user-facing errors: call walletService.walletManager.sendTransaction(...) (or
the appropriate send method on walletManager) to submit the transaction, handle
the async result with success and failure branches, update UI state on success,
and on failure present an Alert or error label with the
NSError.localizedDescription; also add a temporary fallback path that validates
required fields (amount, recipient) and returns a clear error if validation
fails before calling walletManager so taps always produce feedback.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift`:
- Line 459: Replace the force-try call to
walletService.walletManager.deleteWallet(wallet) with an explicit do-catch
around the await so failures don't crash; call try await
walletService.walletManager.deleteWallet(wallet) inside a do block and in catch
assign the caught Error to the view's existing error UI state (for example by
setting the viewModel/errorMessage state or calling the view's presentError(_:)
helper) so the error is surfaced to the UI instead of terminating the app.

---

Outside diff comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Around line 607-615: The derived WIF private key must be validated against the
address public key before being shown: after calling
walletService.walletManager.derivePrivateKeyAsWIF(for:accountInfo:addressIndex:)
and before setting showingPrivateKey/privateKeyToShow, if detail.publicKey is
non-nil convert the derived WIF to the raw private key form expected by
KeyValidation.validatePrivateKeyForPublicKey (or derive hex via the wallet
manager if available) and call
KeyValidation.validatePrivateKeyForPublicKey(privateKey: , publicKey:
detail.publicKey); only set self.privateKeyToShow and self.showingPrivateKey
when validation succeeds, and otherwise handle failure (clear vars and surface
an error/alert or log) so invalid private keys are never displayed.
- Around line 604-611: The derivePrivateKeyWithPIN function currently ignores
the supplied pin and directly calls
walletService.walletManager.derivePrivateKeyAsWIF, so the PIN prompt does not
protect key export; fix by verifying the PIN before derivation (e.g., call the
existing PIN-check API such as walletService.verifyPIN(pin) or
walletService.walletManager.verifyPIN(pin) if available) or, if the FFI requires
it, call a derivePrivateKeyAsWIF overload that accepts the pin (e.g.,
derivePrivateKeyAsWIF(for: wallet, accountInfo: account, addressIndex:
detail.index, pin: pin)); ensure you use the AddressDetail (detail), wallet, and
account identifiers already present and only proceed to call
derivePrivateKeyAsWIF after successful PIN verification.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CreateWalletView.swift`:
- Around line 256-265: The createWallet(using:) method currently only validates
walletLabel, walletPin and confirmPin but never checks the mnemonic; add
explicit validation in createWallet(using:) that the mnemonic string is
non-empty, not just whitespace, and meets expected word count (e.g., 12 or 24
words) before proceeding to the async creation path—use the existing parameters
walletLabel, walletPin, confirmPin and the mnemonic argument to perform these
checks and return early with the same failure logging if the mnemonic is
invalid.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/SendTransactionView.swift`:
- Around line 21-26: The canSend computed property (and the related validation
at lines 51-59) uses a hardcoded 99999 duffs cap which prevents sending based on
the real wallet balance; replace that hardcoded limit with a check against the
actual account/wallet balance (e.g., compare amount to a Wallet.balance or
Account.availableBalance value), ensure amount is safely unwrapped (use optional
binding) and greater than zero, and update the “Insufficient balance” logic to
derive from the same real balance variable so the UI reflects true funds rather
than the fixed 99,999 duffs ceiling.

---

Nitpick comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift`:
- Around line 389-402: The code currently calls
walletService.walletManager.getAccounts(for:) three times; instead, call it once
(await try? walletService.walletManager.getAccounts(for: wallet)) store the
result in a local variable (e.g., accountsList) and then set
mainnetAccountCount/testnetAccountCount/devnetAccountCount from
accountsList.count only when the corresponding flags (testnetEnabled,
devnetEnabled, and mainnetEnabled if present) are true, otherwise set them to
nil; preserve the same error-tolerant behavior (use try? semantics) and ensure
you only perform the single async call in the scope where those counts are
assigned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36421e20-01fb-4bc3-83dc-4859f8a500dd

📥 Commits

Reviewing files that changed from the base of the PR and between 44dbf4d and f23769a.

📒 Files selected for processing (26)
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/CoreTransaction.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/CoreTypes.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/FilterMatch.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/SPV/SPVClient.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Services/FilterMatchService.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Services/WalletService.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/ModelContainerHelper.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/CoreWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/HDTransaction.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/HDWallet.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/TransactionErrors.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/TransactionService.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Wallet.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Shared/UnifiedStateManager.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/AddressManagementView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CreateWalletView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/FilterMatchesView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/ReceiveAddressView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/SendTransactionView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/TransactionListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/UnifiedAppState.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift
💤 Files with no reviewable changes (13)
  • packages/swift-sdk/Sources/SwiftDashSDK/Shared/UnifiedStateManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/KeyWallet/Wallet.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/HDTransaction.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/CoreTypes.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/TransactionService.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/FilterMatch.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Models/CoreTransaction.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/ModelContainerHelper.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AddressManagementView.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Services/FilterMatchService.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/FilterMatchesView.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Wallet/TransactionErrors.swift

Comment on lines +118 to +129
let spvClient = try! SPVClient(
network: network.sdkNetwork,
dataDir: dataDir,
startHeight: 0,
)

// Just sync the current wallet from WalletManager
if let walletManager = self.walletManager {
Task {
// WalletManager's loadWallets() is called in its init
// We just need to sync the current wallet
if let wallet = walletManager.currentWallet {
self.currentWallet = wallet
await loadWallet(wallet)
} else if let firstWallet = walletManager.wallets.first {
self.currentWallet = firstWallet
await loadWallet(firstWallet)
}
}
}
self.spvClient = spvClient

SDKLogger.log("=== WalletService.configure END ===", minimumLevel: .medium)
// Create the SDK wallet manager by reusing the SPV client's shared manager
// TODO: Investigate this error
self.walletManager = try! CoreWalletManager(spvClient: spvClient, modelContainer: modelContainer)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid try! in SDK service initialization paths.

A recoverable startup error currently crashes the host app. This should either throw from WalletService.init or be converted to lastSyncError.

Proposed fix direction
-public init(modelContainer: ModelContainer, network: AppNetwork) {
+public init(modelContainer: ModelContainer, network: AppNetwork) throws {
 ...
-    let spvClient = try! SPVClient(
+    let spvClient = try SPVClient(
         network: network.sdkNetwork,
         dataDir: dataDir,
         startHeight: 0,
     )
 ...
-    self.walletManager = try! CoreWalletManager(spvClient: spvClient, modelContainer: modelContainer)
+    self.walletManager = try CoreWalletManager(spvClient: spvClient, modelContainer: modelContainer)
 }

Also applies to: 153-171

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

In `@packages/swift-sdk/Sources/SwiftDashSDK/Core/Services/WalletService.swift`
around lines 118 - 129, The use of try! when constructing SPVClient and
CoreWalletManager in WalletService.init (creating spvClient and walletManager)
can crash the host app; update WalletService.init to either be a throwing
initializer or catch these errors and store them in a recoverable property
(e.g., lastSyncError) instead of force-unwrapping. Locate the SPVClient(...) and
CoreWalletManager(spvClient:..., modelContainer:...) calls and replace try! with
proper do/catch handling that assigns the caught Error to lastSyncError (and
leaves walletManager/spvClient nil) or change the init signature to throws and
propagate the errors upward, applying the same pattern for the similar block at
the other occurrence (around lines 153-171).

Comment on lines +151 to +173
public func getTransactions(for wallet: HDWallet, accountIndex: UInt32 = 0) -> [WalletTransaction] {
// Get managed account
let managedAccount = try sdkWalletManager.getManagedAccount(
walletId: walletId,
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)
return try! managedAccount.getTransactions(currentHeight: currentHeight)
}

// MARK: - Account Management

public func getBalance(for wallet: HDWallet, accountIndex: UInt32 = 0) -> Balance {
let managedAccount = try! sdkWalletManager.getManagedAccount(
walletId: wallet.walletId,
accountIndex: accountIndex,
accountType: .standardBIP44
)

return try! managedAccount.getBalance()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid try! in public wallet query APIs.

These methods can terminate the app on normal failure paths (missing account, FFI read error). Convert to throws (or map to WalletError) instead of force-try.

Proposed fix direction
-public func getTransactions(for wallet: HDWallet, accountIndex: UInt32 = 0) -> [WalletTransaction] {
-    let managedAccount = try! sdkWalletManager.getManagedAccount(
+public func getTransactions(for wallet: HDWallet, accountIndex: UInt32 = 0) throws -> [WalletTransaction] {
+    let managedAccount = try sdkWalletManager.getManagedAccount(
         walletId: wallet.walletId,
         accountIndex: accountIndex,
         accountType: .standardBIP44
     )
     let currentHeight: UInt32 = 0
-    return try! managedAccount.getTransactions(currentHeight: currentHeight)
+    return try managedAccount.getTransactions(currentHeight: currentHeight)
 }
 
-public func getBalance(for wallet: HDWallet, accountIndex: UInt32 = 0) -> Balance {
-    let managedAccount = try! sdkWalletManager.getManagedAccount(
+public func getBalance(for wallet: HDWallet, accountIndex: UInt32 = 0) throws -> Balance {
+    let managedAccount = try sdkWalletManager.getManagedAccount(
         walletId: wallet.walletId,
         accountIndex: accountIndex,
         accountType: .standardBIP44
     )
-    return try! managedAccount.getBalance()
+    return try managedAccount.getBalance()
 }
📝 Committable suggestion

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

Suggested change
public func getTransactions(for wallet: HDWallet, accountIndex: UInt32 = 0) -> [WalletTransaction] {
// Get managed account
let managedAccount = try sdkWalletManager.getManagedAccount(
walletId: walletId,
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)
return try! managedAccount.getTransactions(currentHeight: currentHeight)
}
// MARK: - Account Management
public func getBalance(for wallet: HDWallet, accountIndex: UInt32 = 0) -> Balance {
let managedAccount = try! sdkWalletManager.getManagedAccount(
walletId: wallet.walletId,
accountIndex: accountIndex,
accountType: .standardBIP44
)
return try! managedAccount.getBalance()
}
public func getTransactions(for wallet: HDWallet, accountIndex: UInt32 = 0) throws -> [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)
}
public func getBalance(for wallet: HDWallet, accountIndex: UInt32 = 0) throws -> Balance {
let managedAccount = try sdkWalletManager.getManagedAccount(
walletId: wallet.walletId,
accountIndex: accountIndex,
accountType: .standardBIP44
)
return try managedAccount.getBalance()
}
🤖 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 151 - 173, The public methods getTransactions(for:accountIndex:)
and getBalance(for:accountIndex:) currently use force-try (try!) and can crash;
change their signatures to throw and propagate errors (or translate to a
WalletError) instead of force-unwrapping. Replace try!
sdkWalletManager.getManagedAccount(...) and try!
managedAccount.getTransactions(...) / try! managedAccount.getBalance() with
do/catch or direct throws so failures from sdkWalletManager.getManagedAccount
and ManagedAccount.getTransactions/getBalance are returned to callers (or mapped
to a WalletError enum) rather than terminating the app.


@Model
public final class HDWallet: HDWalletModels {
public final class HDWallet {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find remaining usages in Example app.
rg -nP '\b(HDAddress|HDUTXO|HDAccount|HDWatchedAddress)\b' packages/swift-sdk/SwiftExampleApp --type swift

# Confirm type definitions are no longer present in SDK sources.
rg -nP '\b(class|struct)\s+(HDAddress|HDUTXO|HDAccount|HDWatchedAddress)\b' packages/swift-sdk/Sources/SwiftDashSDK --type swift

Repository: dashpay/platform

Length of output: 709


Breaking type removals are still referenced by the Example app.

HDAddress and HDUTXO types are removed from SDK sources but remain in use within packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift (lines 637, 656, 685, 704). This creates a compile blocker. Additionally, HDUTXO is referenced in a comment at packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletTests/TransactionTests.swift:21.

🤖 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/HDWallet.swift` at line
7, The Example app breaks because removed types HDAddress and HDUTXO are still
referenced; either reintroduce them as compatibility typealiases/structs in the
SDK (e.g., add public typealias HDAddress = NewAddressType and public typealias
HDUTXO = NewUTXOType or lightweight wrapper structs) within HDWallet.swift or
update the Example and test code to use the new canonical types and shapes used
by HDWallet (replace HDAddress/HDUTXO usages in WalletDetailView and
TransactionTests with the new types and adjust any property/initializer names
accordingly), and remove or update the comment in TransactionTests that refers
to HDUTXO. Ensure the chosen fix preserves API visibility (public) so the
Example compiles.

showError = true
}
}
try! await walletService.walletManager.deleteWallet(wallet)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Replace try! in wallet deletion path (crash risk).

At Line 459, deletion failure will hard-crash the app. Handle the error explicitly and route it through existing error UI state.

Suggested fix
-        try! await walletService.walletManager.deleteWallet(wallet)
+        do {
+            try await walletService.walletManager.deleteWallet(wallet)
+        } catch {
+            await MainActor.run {
+                errorMessage = "Failed to delete wallet: \(error.localizedDescription)"
+                showError = true
+            }
+        }
📝 Committable suggestion

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

Suggested change
try! await walletService.walletManager.deleteWallet(wallet)
do {
try await walletService.walletManager.deleteWallet(wallet)
} catch {
await MainActor.run {
errorMessage = "Failed to delete wallet: \(error.localizedDescription)"
showError = 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/WalletDetailView.swift`
at line 459, Replace the force-try call to
walletService.walletManager.deleteWallet(wallet) with an explicit do-catch
around the await so failures don't crash; call try await
walletService.walletManager.deleteWallet(wallet) inside a do block and in catch
assign the caught Error to the view's existing error UI state (for example by
setting the viewModel/errorMessage state or calling the view's presentError(_:)
helper) so the error is surfaced to the UI instead of terminating the app.

@ZocoLini ZocoLini force-pushed the chore/core-swift-sdk-cleanup branch from f23769a to acd0c33 Compare March 9, 2026 21:14
@ZocoLini ZocoLini had a problem deploying to test-suite-approval March 9, 2026 21:16 — with GitHub Actions Error
@ZocoLini ZocoLini had a problem deploying to test-suite-approval March 9, 2026 21:16 — with GitHub Actions Error
@ZocoLini ZocoLini had a problem deploying to test-suite-approval March 9, 2026 21:39 — with GitHub Actions Error
@ZocoLini ZocoLini had a problem deploying to test-suite-approval March 9, 2026 21:39 — with GitHub Actions Error
@ZocoLini ZocoLini force-pushed the chore/core-swift-sdk-cleanup branch from fdddf5d to ccee368 Compare March 9, 2026 21:44
@ZocoLini ZocoLini had a problem deploying to test-suite-approval March 9, 2026 21:45 — with GitHub Actions Error
@ZocoLini ZocoLini had a problem deploying to test-suite-approval March 9, 2026 21:45 — with GitHub Actions Error
@ZocoLini ZocoLini force-pushed the chore/core-swift-sdk-cleanup branch from ccee368 to 5b55768 Compare March 9, 2026 21:52
@ZocoLini ZocoLini requested a deployment to test-suite-approval March 9, 2026 21:53 — with GitHub Actions Waiting
@ZocoLini ZocoLini requested a deployment to test-suite-approval March 9, 2026 21:53 — with GitHub Actions Waiting
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.

1 participant