refactor(swift-sdk): redesign Persistent* tx schema and fix per-wallet tx push stall#3561
Conversation
…View Restrict the @query to the columns TransactionRowView actually reads and key List rows on PersistentIdentifier instead of txid. Prevents the navigation push from stalling on wallets with ~1.5k transactions while SwiftData faulted in transactionData blobs for every match on the main thread. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t tx push stall
Restructures the Core SwiftData persistence around a single shared
transaction record (multi-account, multi-wallet) and replaces the
broken per-wallet TransactionListView push.
Schema changes (all under DashSchemaV1, dev stores get rebuilt):
- PersistentTransaction.txid: String hex -> raw 32-byte Data;
drops walletId and account fields (a tx can land in several
accounts/wallets); renames `utxos` -> `outputs`; adds inputs
relationship paired with PersistentTxo.spendingTransaction.
- PersistentUtxo renamed to PersistentTxo (it represents any output,
spent or unspent). outpoint switched to raw 36-byte Data, gains
walletId denorm, coreAddress link, and spendingTransaction back-
reference.
- PersistentCoreAddress gains txos: [PersistentTxo] reverse
relationship (.nullify) so detail views can navigate to the
outputs at an address; persistence handler now links the TXO to
its address row on upsert.
- PersistentAccount drops the cascade-owned outputs collection
(per-account TXOs derive through coreAddresses.flatMap(\.txos));
wallet tightened from optional to non-optional; isWatchOnly
removed (FFI-side state, never persisted).
- PersistentWallet drops isWatchOnly for the same reason.
- PlatformWalletPersistenceHandler updated for every site touched
by the above.
Storage Explorer updates to match the new graph: TXO detail shows
the canonical coreAddress?.account path with the one-way account
field as fallback, plus Created By / Spent By rows. Core address
detail surfaces txos.count. Account detail derives distinct-tx
count via coreAddresses.flatMap(\.txos) (transactions are no longer
account-scoped, so the count must be computed in Swift).
Per-wallet "View All Transactions" push was stalling on iOS 26
even on an empty wallet. Root cause was closure-based
NavigationLink { Destination } re-running the destination's init
(and its @query registration) on every parent body invocation,
which fired hundreds of times during sync. Switched both pushes in
the wallets stack to value-based navigation: NavigationLink(value:)
plus two .navigationDestination(for:) modifiers on
WalletsContentView (the stack root). Mixing paradigms (closure
outer + value inner) was producing animate-then-pop, so both
layers go value-based now.
TransactionListView now queries PersistentTransaction directly
with a relationship-traversal predicate (tx.outputs.contains
{ \$0.walletId == walletId } || tx.inputs.contains { ... }),
sorted in SQLite, no Swift dedupe and no fault chain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
🕓 Ready for review — 6 ahead in queue (commit 1f71165) |
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR replaces the UTXO persistence model with a new TXO model, converts txid storage from hex String to raw Data, makes account.wallet non-optional, removes persisted watch-only flags, and reworks relationships so transaction/TXO membership is derived from address/TXO graphs. Changes
Sequence Diagram(s)sequenceDiagram
participant Handler as PlatformWalletPersistenceHandler
participant TX as PersistentTransaction
participant TXO as PersistentTxo
participant Addr as PersistentCoreAddress
participant DB as SwiftData/SQLite
Handler->>TX: ensure/create transaction (txid: Data)
Handler->>TXO: makeOutpoint(txid,vout) → create/update TXO row
TXO->>TX: set creatingTransaction / spendingTransaction references
Handler->>Addr: lookup coreAddress by address string
Addr-->>TXO: attach inverse relationship (txos)
TXO->>DB: persist TXO (walletId denormalized, timestamps, flags)
TX->>DB: persist Transaction (txid Data, outputs/inputs relations)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
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/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift (1)
53-60:⚠️ Potential issue | 🟠 MajorHandle incompatible schema changes gracefully instead of relying on empty migration plan.
The
DashSchemaV1changes include destructive modifications (String→Data retype fortxidandoutpoint) that SwiftData's lightweight migration cannot handle. While the comment suggests pre-refactor dev databases will be "rebuilt," the code only passes an emptyDashMigrationPlantoModelContainerwith no error recovery. If container creation fails due to schema incompatibility,SwiftExampleAppAppcallsfatalError()instead of deleting the old store and retrying.Either add migration stages to handle the destructive changes, or catch the incompatibility error, delete the backing store files, and retry container creation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift` around lines 53 - 60, DashSchemaV1 introduces destructive type changes that SwiftData cannot lightweight-migrate, so update the ModelContainer creation in DashModelContainer (where ModelContainer(for:schema:migrationPlan:configurations:)) to either (A) add explicit stages to DashMigrationPlan.stages that transform txid/outpoint types to Data, or (B) wrap the ModelContainer init in a do/catch that on migration-incompatibility errors deletes the existing persistent store files (resolve the store URL(s) used by the container via the same logic that constructs modelConfiguration) and retries ModelContainer creation instead of propagating/fatalError; ensure the catch targets SwiftData migration/schema incompatibility errors, performs safe file removal with FileManager, and retries once before surfacing an error to SwiftExampleAppApp.packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift (1)
347-395:⚠️ Potential issue | 🟠 MajorKeep wallet-relative fields off the shared transaction row.
After keying
PersistentTransactiononly bytxid, this method still overwritescontext,direction,netAmount, andlabelon that shared row. If the same tx is emitted for multiple accounts or wallets, the last account processed decides what every wallet sees. These values need a wallet-scoped home, or they need to be derived from wallet-scoped TXOs at read time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift` around lines 347 - 395, The upsertTransaction(account: TransactionRecordFFI) function is writing wallet-specific values into the shared PersistentTransaction row (fields like context, direction, netAmount, label, fee), which causes cross-wallet pollution; stop mutating those wallet-relative fields on PersistentTransaction and instead persist them to a wallet-scoped entity (e.g., per-account membership or TXO record) or derive them from TXOs at read-time. Specifically, in upsertTransaction replace assignments to PersistentTransaction.context, .direction, .netAmount, .label, and .fee (and any other wallet-specific fields) with logic that upserts/updates a wallet-scoped record (use the consumed account parameter and your existing per-account/TXO models) or remove those writes so PersistentTransaction remains txid-global; leave only truly global fields (txid, blockHeight, blockHash, blockTimestamp, transactionType, transactionData, lastUpdated) on PersistentTransaction.
🤖 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/Persistence/Models/PersistentAccount.swift`:
- Around line 65-68: PersistentAccount.wallet was made non-optional but callers
still perform nil checks (e.g., allAccounts.filter { $0.wallet == nil } and
rendering an “Unlinked” section in AccountStorageListView); update the call
sites to stop checking for nil (remove or rewrite allAccounts.filter { $0.wallet
== nil } usages) and adjust AccountStorageListView to no longer render an
“Unlinked” section or to compute its content from a different criterion, or
alternatively revert wallet to Optional if the semantic requirement remains;
locate references to PersistentAccount.wallet and the AccountStorageListView /
allAccounts.filter usages and make the corresponding cleanup so the codebase
compiles without nil comparisons.
In
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swift`:
- Around line 26-31: PersistentTransaction stores txid as little-endian raw Data
but the txidHex accessor currently hex-encodes txid as-is (so UI shows the
reversed/canonical ID incorrectly); update the txidHex logic in the
PersistentTransaction type to reverse the bytes of txid before converting to a
hex string (and similarly fix any other hex-accessors/formatters that reference
txid, e.g., the other txidHex implementation around the 103-107 region) so the
exposed hex is the big-endian/canonical transaction id; ensure you only reverse
for presentation (keep stored txid unchanged).
In
`@packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift`:
- Around line 482-496: markUtxoSpent currently only sets txo.isSpent = true,
leaving PersistentTxo.spendingTransaction nil which breaks the inputs/outputs
graph for spend-only transactions; update markUtxoSpent(_ op: OutPointFFI) to
look up or create the spending PersistentTransaction (using the outpoint/op info
or a referenced spending txid if available from FFI), set
txo.spendingTransaction to that PersistentTransaction, and update
txo.lastUpdated before saving to backgroundContext so spend-only transactions
are included in per-wallet queries; reference markUtxoSpent,
PersistentTxo.spendingTransaction, OutPointFFI and the backgroundContext.fetch
descriptor when making the change.
---
Outside diff comments:
In
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift`:
- Around line 53-60: DashSchemaV1 introduces destructive type changes that
SwiftData cannot lightweight-migrate, so update the ModelContainer creation in
DashModelContainer (where
ModelContainer(for:schema:migrationPlan:configurations:)) to either (A) add
explicit stages to DashMigrationPlan.stages that transform txid/outpoint types
to Data, or (B) wrap the ModelContainer init in a do/catch that on
migration-incompatibility errors deletes the existing persistent store files
(resolve the store URL(s) used by the container via the same logic that
constructs modelConfiguration) and retries ModelContainer creation instead of
propagating/fatalError; ensure the catch targets SwiftData migration/schema
incompatibility errors, performs safe file removal with FileManager, and retries
once before surfacing an error to SwiftExampleAppApp.
In
`@packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift`:
- Around line 347-395: The upsertTransaction(account: TransactionRecordFFI)
function is writing wallet-specific values into the shared PersistentTransaction
row (fields like context, direction, netAmount, label, fee), which causes
cross-wallet pollution; stop mutating those wallet-relative fields on
PersistentTransaction and instead persist them to a wallet-scoped entity (e.g.,
per-account membership or TXO record) or derive them from TXOs at read-time.
Specifically, in upsertTransaction replace assignments to
PersistentTransaction.context, .direction, .netAmount, .label, and .fee (and any
other wallet-specific fields) with logic that upserts/updates a wallet-scoped
record (use the consumed account parameter and your existing per-account/TXO
models) or remove those writes so PersistentTransaction remains txid-global;
leave only truly global fields (txid, blockHeight, blockHash, blockTimestamp,
transactionType, transactionData, lastUpdated) on PersistentTransaction.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 993e7042-3fca-4a71-9f18-66d9fa3f234b
📒 Files selected for processing (21)
packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentAccount.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentCoreAddress.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTxo.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentUtxo.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentWallet.swiftpackages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountListView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/ReceiveAddressView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/TransactionDetailView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/TransactionListView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletsContentView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageExplorerView.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageModelListViews.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/TopUpIdentityView.swift
💤 Files with no reviewable changes (3)
- packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
- packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentUtxo.swift
- packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentWallet.swift
| /// Parent wallet. Every account currently belongs to a wallet. If | ||
| /// standalone non-wallet accounts are introduced later, this | ||
| /// becomes optional again. | ||
| public var wallet: PersistentWallet |
There was a problem hiding this comment.
This non-optional relationship still has nil-based callers.
After this change, PersistentAccount.wallet can no longer be compared against nil, but the provided example app code still does allAccounts.filter { $0.wallet == nil } and renders an “Unlinked” section in AccountStorageListView. That leaves this PR in a compile-broken state unless those callers are removed or rewritten.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentAccount.swift`
around lines 65 - 68, PersistentAccount.wallet was made non-optional but callers
still perform nil checks (e.g., allAccounts.filter { $0.wallet == nil } and
rendering an “Unlinked” section in AccountStorageListView); update the call
sites to stop checking for nil (remove or rewrite allAccounts.filter { $0.wallet
== nil } usages) and adjust AccountStorageListView to no longer render an
“Unlinked” section or to compute its content from a different criterion, or
alternatively revert wallet to Optional if the semantic requirement remains;
locate references to PersistentAccount.wallet and the AccountStorageListView /
allAccounts.filter usages and make the corresponding cleanup so the codebase
compiles without nil comparisons.
| private func markUtxoSpent(_ op: OutPointFFI) { | ||
| let outpoint = "\(hashHex(op.txid)):\(op.vout)" | ||
| let descriptor = FetchDescriptor<PersistentUtxo>( | ||
| let outpoint = PersistentTxo.makeOutpoint(txid: hashData(op.txid), vout: op.vout) | ||
| let descriptor = FetchDescriptor<PersistentTxo>( | ||
| predicate: #Predicate { $0.outpoint == outpoint } | ||
| ) | ||
| if let utxo = try? backgroundContext.fetch(descriptor).first { | ||
| utxo.isSpent = true | ||
| utxo.lastUpdated = Date() | ||
| if let txo = try? backgroundContext.fetch(descriptor).first { | ||
| txo.isSpent = true | ||
| // The FFI's spent-utxo notification only carries the | ||
| // outpoint, not the spending tx — so we cannot populate | ||
| // `txo.spendingTransaction` here. `isSpent = true` with | ||
| // `spendingTransaction == nil` is the steady-state we | ||
| // reach for now; future work: have the FFI emit the | ||
| // spending txid alongside each spent outpoint and link | ||
| // them up here. | ||
| txo.lastUpdated = Date() |
There was a problem hiding this comment.
This leaves the new inputs graph unusable for spend-only transactions.
The per-wallet transaction query now depends on outputs/inputs, but spent TXOs are only marked with isSpent = true here. Without setting txo.spendingTransaction, any transaction that only spends this wallet's outputs and creates no wallet-owned outputs will never match the wallet query, so it drops out of "View All Transactions".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift`
around lines 482 - 496, markUtxoSpent currently only sets txo.isSpent = true,
leaving PersistentTxo.spendingTransaction nil which breaks the inputs/outputs
graph for spend-only transactions; update markUtxoSpent(_ op: OutPointFFI) to
look up or create the spending PersistentTransaction (using the outpoint/op info
or a referenced spending txid if available from FFI), set
txo.spendingTransaction to that PersistentTransaction, and update
txo.lastUpdated before saving to backgroundContext so spend-only transactions
are included in per-wallet queries; reference markUtxoSpent,
PersistentTxo.spendingTransaction, OutPointFFI and the backgroundContext.fetch
descriptor when making the change.
…ytes for canonical display
Two follow-ups from the schema redesign:
1. StorageModelListViews.swift had a leftover orphanAccounts
filter (allAccounts.filter { \$0.wallet == nil }) and an
"Unlinked" section, both predicated on PersistentAccount.wallet
being optional. With wallet now non-optional the filter is dead
code and the warnings-as-errors CI build was rejecting the
nil comparison.
2. PersistentTransaction.txidHex (and PersistentTxo.txidHex
which now delegates to it) was hex-encoding the raw 32 bytes
in storage/wire order. Bitcoin/Dash convention reverses those
bytes for display — dashcore::Txid: Display does the same
flip — so the previous output was the reverse of what users
see in block explorers. Fix reverses bytes only on the read
side; storage stays unflipped so existing predicate fetches
keep working without re-encoding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Fixes pushed in 72afd0e, plus a note on the third comment: 1. 2. 3. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/Persistence/Models/PersistentTxo.swift`:
- Around line 136-159: The txid, txidHex, and outpointHex accessors currently
return empty values when transaction is nil even though the 36-byte outpoint
contains the TXID; update those accessors (txid, txidHex, outpointHex) to derive
values from the existing outpoint when transaction == nil by parsing the 36-byte
outpoint: extract the first 32 bytes as the raw txid Data (and the trailing
bytes for vout if needed), use the same byte-order/hex formatting used by
PersistentTransaction.txidHex to produce txidHex, and build outpointHex from
that derived txidHex and vout; keep falling back to transaction when present
(i.e., transaction?.txid / transaction?.txidHex) and only parse outpoint when
transaction is nil.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0c610d25-0a24-4eeb-a364-24dbed9bdfbe
📒 Files selected for processing (3)
packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swiftpackages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTxo.swiftpackages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageModelListViews.swift
…transaction is unattached The 36-byte outpoint already carries the txid in its first 32 bytes, so falling back to that when the `transaction` inverse is briefly nil during insert keeps storage-explorer rows from collapsing to empty `:vout` strings. `txidHex` now mirrors `PersistentTransaction.txidHex`'s byte-reversal directly (rather than forwarding) so the canonical block-explorer flip is applied in both the attached and unattached cases. `outpointHex` flows through `txidHex`, picking up the same fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Fixed in 1f71165. Good catch — |
|
✅ 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: "ecf8009bf0cf6f558d2a2bd8ed31f19663bb964a39243ebbe30ede878fd717aa"
)Xcode manual integration:
|
|
Self Reviewed |
Issue being fixed or feature implemented
Two intertwined problems in the Core/SwiftData side of the iOS SDK:
PersistentTransactioncarriedwalletIdandaccount, andPersistentAccountcascadedoutputs: [PersistentUtxo]. A single on-chain tx can produce outputs into multiple accounts (or even multiple wallets) — the schema couldn't represent that without ambiguity, and several display paths showed wrong totals.NavigationLink { Destination }re-running the destination'sinit(and its@Queryregistration) on every parent body invocation — sync triggers fire that constantly, so the destination's setup work was paid hundreds of times before the user could tap.What was done?
Schema redesign (all under
DashSchemaV1; dev stores get rebuilt)PersistentTransaction.txid: hexString→ raw 32-byteData. DropswalletIdandaccount(a tx can span several accounts/wallets). Renamesutxos→outputs; addsinputs: [PersistentTxo]paired withPersistentTxo.spendingTransaction(.nullify, since inputs reference TXOs owned by other transactions).PersistentUtxorenamed toPersistentTxo— it represents any output, spent or unspent.outpointswitched to raw 36-byteData. AddswalletIddenorm (so per-wallet predicates avoid the optional-relationship-chain crash),coreAddress: PersistentCoreAddress?(canonical address linkage), andspendingTransaction: PersistentTransaction?.PersistentCoreAddressgainstxos: [PersistentTxo]reverse relationship (.nullify) so detail views can navigate to the outputs at an address. The persistence handler links the TXO to its address row on upsert.PersistentAccountdrops the cascade-ownedoutputscollection — per-account TXOs derive throughcoreAddresses.flatMap(\.txos).wallettightened from optional to non-optional.isWatchOnlyremoved (FFI-side state, never persisted).PersistentWalletdropsisWatchOnlyfor the same reason.PlatformWalletPersistenceHandlerupdated for every site touched by the above (the stub-tx path no longer stampsaccount, the TXO upsert now stampswalletId+coreAddress).Storage Explorer
Updated to match the new graph:
coreAddress?.accountpath with the one-wayaccountfield as fallback, plus Created By / Spent By rows.txos.countand the parent account.coreAddresses.flatMap(\.txos)— transactions are no longer account-scoped, so the count must be computed in Swift. Same shift inTransactionListViewandWalletDetailView.Per-wallet transaction push fix
TransactionListViewnow queriesPersistentTransactiondirectly with a relationship-traversal predicate (tx.outputs.contains { $0.walletId == walletId } || tx.inputs.contains { ... }), sorted in SQLite, no Swift dedupe and no fault chain. TakeswalletId: Datarather thanPersistentWallet.NavigationLink(value:)+ two.navigationDestination(for:)modifiers onWalletsContentView(the stack root). Mixing paradigms (closure outer + value inner) produced animate-then-pop, so both layers go value-based. Now the destination is built only on actual push.How Has This Been Tested?
xcodebuild -project SwiftExampleApp/SwiftExampleApp.xcodeproj -scheme SwiftExampleApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16,arch=arm64,OS=18.6' clean build→** BUILD SUCCEEDED **.Breaking Changes
None at the consensus / protocol level. The Swift SDK's persistence schema changes are destructive within
DashSchemaV1(existing dev stores fail to open and get rebuilt on next sync). The migration policy comment inDashModelContainer.swiftenumerates each delta. Public Swift types renamed:PersistentUtxo→PersistentTxo. Per project policy, SDK/FFI public-API churn doesn't qualify forfeat!/fix!.Checklist:
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI Changes
Breaking Changes
Behavioral