Skip to content

feat(swift-sdk): surface payload-only special-tx involvement in account transaction lists#4108

Merged
QuantumExplorer merged 3 commits into
v4.1-devfrom
claude/jovial-swanson-861a07
Jul 13, 2026
Merged

feat(swift-sdk): surface payload-only special-tx involvement in account transaction lists#4108
QuantumExplorer merged 3 commits into
v4.1-devfrom
claude/jovial-swanson-861a07

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jul 13, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A special transaction can involve an account purely through its payload — e.g. a ProRegTx whose owner key hash matches a Provider Owner Keys pool address — creating no TXO in that account. Per-account transaction membership in the Swift SDK was recovered exclusively through the TXO graph (coreAddresses.flatMap(\.txos) → creating/spending tx), so payload-only transactions never appeared in the account's Transactions count, the wallet-level count, or the wallet transaction list. This is the remaining UI gap (G3) of the mainnet owner-key "Absent" investigation; #4107 fixed the Rust-side persistence seam (G1/G2) this builds on.

Stacked on #4107 — the first commit here is #4107's fix(platform-wallet) commit cherry-picked; review only the second commit. Will rebase once #4107 merges.

What was done?

  • PersistentTransaction.involvedAccountsPersistentAccount.involvedTransactions: new explicit many-to-many recording every account whose WalletChangeSetFFI bucket carried the tx record — a superset of TXO-derived membership that includes payload-only involvement. .nullify delete rule on both ends (participation, not value ownership; wallet-wipe order unaffected).
  • PlatformWalletPersistenceHandler.upsertTransaction(account:tx:) appends the matched account idempotently (compared by persistentModelID). It is already invoked once per matched account — the Rust changeset buckets cs.records by record.account_type, including payload-only matches — so no FFI change is needed.
  • AccountDetailView: the pool-summary Transactions count now unions the TXO-derived set with involvedTransactions, and a new Transactions card lists the account's distinct txs (unconfirmed first, then firstSeen desc), each opening TransactionDetailView. Rendered for both address-pool and provider key-material accounts; skipped for PlatformPayment.
  • TransactionListView / WalletDetailView: union the same join so payload-only txs appear consistently in the wallet-level list and count.

How Has This Been Tested?

  • ./build_ios.sh --target sim + SwiftExampleApp simulator build, both clean.
  • End-to-end on a mainnet wallet in the simulator: armed a compact-filter rescan from height 1,030,600 (rescan button from feat(swift-sdk): compact-filter rescan button in Core Sync Status #4103) and let SPV re-process. ProRegTx 5076f9ed731bcb7b5ddf47e21bf1262d22d99665ca89bb6329e6e706d605da85 (height 1,030,673, zero TXOs in the account) now shows in the Provider Owner Keys account detail: Transactions 21 / TXOs 0, listed in the new card, and its detail sheet opens with the right txid/height. SwiftData ground truth confirmed via sqlite: join rows written for (ProRegTx ↔ Provider Owner Keys), externalHighestUsed advanced from −1, owner/voting pool addresses flipped isUsed=1 (fix(platform-wallet): persist address usage discovered during SPV block processing #4107's seam verified end-to-end as well).

Breaking Changes

None.

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

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for tracking addresses marked as used and highest-used address indexes.
    • Added persistence and display of payload-only transactions linked to accounts without transaction outputs.
    • Account and wallet transaction lists now include these transactions, with duplicate entries removed.
    • Added transaction detail access from account transaction lists.
  • Bug Fixes

    • Prevented referenced payload-only transaction records from being deleted during wallet cleanup.
    • Ensured address usage updates are retained and accurately reflected.

@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@QuantumExplorer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8d918ef-0f3e-46e8-9597-da18f86f5b69

📥 Commits

Reviewing files that changed from the base of the PR and between bd08764 and e1e3dcf.

📒 Files selected for processing (7)
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/TransactionListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletDeletionTests.swift
📝 Walkthrough

Walkthrough

Rust wallet changesets now carry marked-used addresses and highest-used watermarks through event processing and FFI persistence. Swift persistence models track payload-only transaction involvement, include it in transaction views and counts, and preserve referenced transactions during wallet deletion.

Changes

Rust usage delta persistence

Layer / File(s) Summary
Changeset usage delta contract
packages/rs-platform-wallet/src/changeset/changeset.rs, packages/rs-platform-wallet/src/changeset/mod.rs
CoreChangeSet stores marked-used addresses and per-account highest-used indexes, merges them with deduplication and monotonic maxima, includes them in emptiness checks, and retains the same re-exports.
Event usage delta derivation
packages/rs-platform-wallet/src/changeset/core_bridge.rs
TransactionDetected and BlockProcessed derive marked-used addresses and account watermarks through collect_usage_deltas.
FFI watermark and marked-used persistence
packages/rs-platform-wallet-ffi/src/core_wallet_types.rs, packages/rs-platform-wallet-ffi/src/persistence.rs
FFI conversion emits highest-used values and flags, while persistence groups marked-used addresses by account and pool before invoking the address-pool callback; helper tests cover grouping and used values.

Swift payload-only transaction involvement

Layer / File(s) Summary
Payload-only relationship model
packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentAccount.swift, packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swift
Persistent account and transaction models add inverse many-to-many relationships for transactions associated through payloads without TXOs.
Involvement upsert and deletion lifecycle
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletDeletionTests.swift
Transaction upserts add involved accounts idempotently, deletion checks the involvement relationship, and regression coverage preserves sibling payload-only transactions.
Transaction list and detail integration
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/TransactionListView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
Views union TXO-derived and involved transactions, deduplicate by txid, update counts, show transaction cards, and present transaction details.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WalletEvent
  participant build_core_changeset
  participant collect_usage_deltas
  participant CoreChangeSet
  participant FFIPersister
  WalletEvent->>build_core_changeset: process transaction or block event
  build_core_changeset->>collect_usage_deltas: derive usage deltas
  collect_usage_deltas->>CoreChangeSet: store marked-used addresses and watermarks
  CoreChangeSet->>FFIPersister: persist usage deltas
  FFIPersister->>FFIPersister: group marked-used addresses by pool
Loading
sequenceDiagram
  participant PlatformWalletPersistenceHandler
  participant PersistentTransaction
  participant PersistentAccount
  participant AccountDetailView
  PlatformWalletPersistenceHandler->>PersistentTransaction: upsert transaction
  PlatformWalletPersistenceHandler->>PersistentAccount: record involved account
  PersistentAccount->>PersistentTransaction: expose involvedTransactions
  AccountDetailView->>PersistentAccount: read account involvement
  AccountDetailView->>PersistentTransaction: display unified transaction list
Loading

Possibly related PRs

Suggested reviewers: shumkov, llbartekll, zocolini, lklimek

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main Swift SDK change: surfacing payload-only special-transaction involvement in account transaction lists.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/jovial-swanson-861a07

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

❤️ Share

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

@thepastaclaw

thepastaclaw commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit 339431a)

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

This PR adds an explicit PersistentTransaction.involvedAccounts / PersistentAccount.involvedTransactions join to represent payload-only special-transaction involvement, and correctly unions it into the TXO-derived transaction sets across the account and wallet views. The join and its population are sound, but the existing global orphan sweep in deleteWalletData is not updated for the new invariant: deleting any wallet can silently erase payload-only transactions belonging to another live wallet.

Source: Sol reviewer gpt-5.6-sol; Sonnet reviewer claude-sonnet-5; verifier claude-sonnet-5.

🔴 1 blocking

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/PlatformWallet/PlatformWalletPersistenceHandler.swift`:
- [BLOCKING] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift:3759-3764: Wallet deletion's global orphan sweep deletes other wallets' payload-only transactions
  deleteWalletData ends with an unscoped FetchDescriptor<PersistentTransaction>() and deletes every row whose outputs, inputs, and pendingInputs are empty. This PR changes the invariant behind that predicate: payload-only special transactions can now legitimately belong to a live account solely through involvedAccounts while having no TXO rows. Deleting an unrelated wallet therefore sweeps the other wallet's payload-only transaction, SwiftData nullifies its involvedTransactions relationship, and the transaction disappears from every list this PR adds. Include involvedAccounts.isEmpty in the orphan predicate and cover the unrelated-wallet deletion path with a regression test.

@QuantumExplorer

Copy link
Copy Markdown
Member Author

Addressed the blocking finding in bd08764: the orphan sweep in deleteWalletData now also requires involvedAccounts.isEmpty, so payload-only special txs owned by a live sibling wallet survive an unrelated wallet's deletion. The deleted wallet's own payload-only rows are still collected — its account deletion nullifies their join links before the sweep runs. Added testDeleteWalletDataPreservesSiblingPayloadOnlyTransactions covering the unrelated-wallet path; all 5 WalletDeletionTests pass on the iOS simulator.

🤖 Addressed by Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 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/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift (1)

57-67: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Exclude sibling-network accounts from the transaction count.

The same walletId may exist on multiple networks. Include wallet.networkRaw in this query or the badge can count another network’s payload-only transactions.

Proposed change
 let walletId = wallet.walletId
+let networkRaw = wallet.networkRaw
 ...
 _walletAccounts = Query(
-    filter: `#Predicate`<PersistentAccount> { $0.wallet.walletId == walletId }
+    filter: `#Predicate`<PersistentAccount> {
+        $0.wallet.walletId == walletId
+            && $0.wallet.networkRaw == networkRaw
+    }
 )

Also applies to: 80-84

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

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift`
around lines 57 - 67, Update WalletDetailView.init(wallet:) so both the
PersistentTxo and PersistentAccount queries match the wallet’s networkRaw in
addition to walletId. Use the current wallet.networkRaw value in each predicate,
ensuring transaction counts and account data exclude records from sibling
networks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/rs-platform-wallet-ffi/src/core_wallet_types.rs`:
- Around line 341-350: Update the FFI account bucketing logic that builds
`ffi_accounts`/`by_account` from `cs.records` to also include every key in
`cs.account_highest_used`, creating an empty record bucket when an account has
no records. Preserve existing record grouping and ensure watermark-only accounts
are emitted.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift`:
- Around line 41-43: Replace the selectedTransaction state and transaction sheet
presentation in AccountDetailView with a NavigationLink-driven drill-down to
TransactionDetailView. Update the transaction row/button handling and the
corresponding presentation block to pass the selected PersistentTransaction
through the navigation link, removing the sheet-based flow while preserving the
existing transaction selection behavior.

---

Outside diff comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift`:
- Around line 57-67: Update WalletDetailView.init(wallet:) so both the
PersistentTxo and PersistentAccount queries match the wallet’s networkRaw in
addition to walletId. Use the current wallet.networkRaw value in each predicate,
ensuring transaction counts and account data exclude records from sibling
networks.
🪄 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: ff9ae0c8-85b6-434b-98dd-6e651ce94b1d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b10ad8 and bd08764.

📒 Files selected for processing (12)
  • packages/rs-platform-wallet-ffi/src/core_wallet_types.rs
  • packages/rs-platform-wallet-ffi/src/persistence.rs
  • packages/rs-platform-wallet/src/changeset/changeset.rs
  • packages/rs-platform-wallet/src/changeset/core_bridge.rs
  • packages/rs-platform-wallet/src/changeset/mod.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentAccount.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentTransaction.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/AccountDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/TransactionListView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/WalletDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/WalletDeletionTests.swift

Comment thread packages/rs-platform-wallet-ffi/src/core_wallet_types.rs
QuantumExplorer and others added 3 commits July 13, 2026 15:46
… txs

A special transaction can involve an account purely through its
payload — a ProRegTx whose owner / voting key hash matches a Provider
Owner / Voting Keys pool address — creating no TXO in that account.
Per-account transaction membership was recovered exclusively through
the TXO graph, so those txs never appeared in the account's
transaction count (AccountDetailView), the wallet-level count
(WalletDetailView), or the wallet transaction list
(TransactionListView).

- PersistentTransaction.involvedAccounts <-> PersistentAccount
  .involvedTransactions: explicit many-to-many recording every
  account whose changeset bucket carried the tx record (a superset of
  the TXO-derived membership; .nullify on both ends — participation,
  not value ownership).
- PlatformWalletPersistenceHandler.upsertTransaction appends the
  matched account idempotently (by persistentModelID) — it is already
  invoked once per matched account since the Rust changeset buckets
  records by account_type, including payload-only matches.
- AccountDetailView unions involvedTransactions with the TXO-derived
  set for the pool-summary count and a new Transactions card that
  lists the account's txs (opens TransactionDetailView).
- TransactionListView / WalletDetailView union the same join so
  payload-only txs show consistently at wallet level.

Verified on a mainnet wallet: after a compact-filter rescan from
height 1030600, ProRegTx 5076f9ed…d605da85 (height 1030673, zero
TXOs in the account) is counted and listed by the Provider Owner
Keys account detail view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deleteWalletData's post-delete orphan sweep matched any
PersistentTransaction with empty outputs / inputs / pendingInputs.
Payload-only special txs have no TXOs anywhere yet belong to a live
account through involvedAccounts, so deleting an unrelated wallet
swept the other wallet's payload-only history. Include
involvedAccounts.isEmpty in the sweep predicate; the deleted wallet's
own payload-only rows are still collected because its account
deletion nullifies their join links before the sweep runs.

Adds testDeleteWalletDataPreservesSiblingPayloadOnlyTransactions
covering the unrelated-wallet deletion path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…work

The same 32-byte walletId legitimately exists once per network (same
mnemonic imported on mainnet and testnet), so matching the payload-
only involvement query on walletId alone folded the sibling network's
accounts — and their payload-only txs — into this wallet row's
transaction badge. Add wallet.networkRaw to the predicate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer force-pushed the claude/jovial-swanson-861a07 branch from 339431a to e1e3dcf Compare July 13, 2026 08:48
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Rebased onto v4.1-dev now that #4107 is merged — the cherry-picked commit and the watermark-only bucket fix both dropped out (the merged #4107 already carries that fix plus an equivalent regression test), leaving only this PR's own commits.

Also addressed the outside-diff finding in e1e3dcf: WalletDetailView's payload-only account query is now scoped to wallet.networkRaw in addition to walletId, so a same-mnemonic sibling-network wallet can't leak its payload-only txs into the badge. The walletTxos side of the count is unchanged: PersistentTxo carries no network column and its walletId join predates this PR.

🤖 Addressed by Claude Code

@QuantumExplorer QuantumExplorer merged commit 6fb175b into v4.1-dev Jul 13, 2026
29 of 30 checks passed
@QuantumExplorer QuantumExplorer deleted the claude/jovial-swanson-861a07 branch July 13, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants