Skip to content

refactor(swift-sdk,platform-wallet): rebuild DashPay/DPNS persistence + identity sync, drop TokenWallet - #3564

Merged
QuantumExplorer merged 2 commits into
v3.1-devfrom
april29changes
Apr 29, 2026
Merged

refactor(swift-sdk,platform-wallet): rebuild DashPay/DPNS persistence + identity sync, drop TokenWallet#3564
QuantumExplorer merged 2 commits into
v3.1-devfrom
april29changes

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Apr 29, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Multi-package refactor that reshapes the iOS SDK's persistence layer for DashPay/DPNS, consolidates token + identity sync behind a single manager, drops the now-redundant TokenWallet, and unifies the persistence trait around a single store(changeset) write path. Also fixes several UX/perf issues observed against the previous schema (per-wallet push stalls, double account rows, missing "Spent By" / "Address" links, sync slowdown from duplicated @Query subscriptions).

What was done?

SwiftData schema (DashSchemaV1, dev stores rebuild)

  • New persistent models: PersistentDPNSName, PersistentDashpayProfile, PersistentDashpayContactRequest. Non-optional identity / owner relationships; #Unique<...> composite keys mirror the corresponding contract uniqueness rules (DPNS: (networkRaw, normalizedParentDomainName, normalizedLabel); DashPay profile: (networkRaw, identity); contact request: (networkRaw, ownerIdentityId, contactIdentityId, isOutgoing)).
  • PersistentAccount: compound #Unique on (wallet, accountType, accountIndex, standardTag, registrationIndex, keyClass, userIdentityId, friendIdentityId). accountExtendedPubKeyBytes flipped to Data? with @Attribute(.unique). wallet made non-optional. isWatchOnly removed.
  • PersistentWallet: isWatchOnly removed.
  • PersistentCoreAddress.txos cascade-deletes (was nullify); upserts by Base58Check string so pool refreshes are non-destructive.
  • PersistentTxo / PersistentTransaction.txidHex reverses bytes for canonical block-explorer display.

Persistence trait unification

PlatformWalletPersistence collapses to a single store(changeset) write path. Legacy store_account / store_wallet_metadata / store_account_addresses removed; their data rides on three new fields on PlatformWalletChangeSet. FFI exposes corresponding on_persist_wallet_metadata / on_persist_account_registrations / on_persist_account_address_pools callbacks dispatched from inside FFIPersister::store.

TokenWallet drop, IdentitySyncManager consolidation

  • Deleted TokenWallet. Watch list / balance cache moved to IdentitySyncManager.state.
  • New IdentitySyncManager (in manager/identity_sync.rs) mirrors PlatformAddressSyncManager: registry + periodic loop, batches up to 100 token ids per IdentityTokenBalancesQuery, sequential per identity. Generic over P: PlatformWalletPersistence for static dispatch. Decoupled from PlatformWallet / WalletManager — caller drives the registry via register_identity / update_watched_tokens / unregister_identity.
  • Removed PlatformWalletInfo.token_balances / token_watched and watched / unwatched channels on TokenBalanceChangeSet; per-balance writes still flow through balances / removed_balances.

Manager layout

  • identity_sync.rs and platform_address_sync.rs moved into src/manager/.
  • spawn_wallet_event_adapter is generic over P: PlatformWalletPersistence + 'static (was Arc<dyn ...>); caller passes the manager's own Arc<P> for static dispatch.

DashPay profile / contact requests

  • IdentityEntryFFI extended with profile fields (display_name / bio / public_message / avatar_url / avatar_hash / avatar_fingerprint).
  • New on_persist_contacts_fn callback + ContactRequestFFI / ContactRequestRemovalFFI projecting ContactChangeSet.sent / incoming / established / removed_*. established projects as two rows per entry (one outgoing, one incoming) so the per-direction Swift unique key upserts cleanly.

Account-row dedup

AccountChangeSetFFI now carries the full typed AccountType tags (type_tag, standard_tag, registration_index, key_class, user_identity_id, friend_identity_id). Swift persister keys upsert on those fields instead of the legacy Debug-formatted account_type_name string. Eliminates duplicate Standard { … } rows that appeared next to clean BIP44 Account #0 rows when the load path and sync changeset both emitted the same account with different name strings.

TXO graph

  • markUtxoSpent populates PersistentTxo.spendingTransaction. AccountChangeSetFFI.utxos_spent shape changed from OutPointFFI to SpentOutPointFFI { outpoint, spending_txid } — the spending tx's txid rides through so Swift can resolve and link the parent transaction, populating "Spent By" in the storage explorer.
  • PersistentTxo.coreAddress is backfilled inside persistAccountAddresses: when an address row is upserted, any TXO at that Base58Check with a nil coreAddress link gets attached. Closes the race where SPV emits a UTXO before the address-pool row lands.

Wallet recovery + identity discovery

register_wallet calls identity().sync() after platform-address init. For a recovery flow this hydrates every identity the wallet had on Platform without an explicit "Re-scan" step. Failures are logged but never block wallet registration. UI string renamed: "Search Wallets for Identities" → "Re-scan for Identities".

Per-wallet TransactionListView push

Was stalling on iOS 26 even on empty wallets because closure-based NavigationLink re-runs the destination's init (and its @Query registration) on every parent body invocation. Switched both pushes in the wallets stack to value-based NavigationLink(value:) + .navigationDestination(for:) on WalletsContentView. The per-wallet TX query now traverses relationships in SQLite (no Swift dedupe).

Storage Explorer

  • List + detail views for the three new persistent models.
  • Comprehensive audit + fill across every other detail view (PersistentToken control rules / distributions / localizations, PersistentDocument block heights / payload sizes, PersistentDataContract keywords / blob sizes, etc.).
  • PersistentPlatformAddressesSyncStateStorageDetailView walletId scope key fix.
  • TXO detail: Address moved from Core to Relationships, NavigationLink when coreAddress is linked; redundant "Address Row" removed.
  • Core Addresses list gets .searchable over Base58Check / derivation path / address index.

Core balance display (derive from TXOs)

BalanceCardView and AccountListView read from a single shared @Query<PersistentTxo> in WalletDetailView filtered by walletId. Wallet-level balance partitions the result; per-account balance further partitions by address-membership in the account's pool. One subscription instead of three across the view tree.

Misc

  • core_bridge.rs doc comment fixed (was referencing a non-existent WalletEventAdapter).
  • DPNS registration fix: RegisterNameView passes the user-typed display label to wallet.registerDpnsName, not the homograph-normalized form. Names registered before this fix have the normalized form locked in on Platform.

How Has This Been Tested?

  • cargo check / cargo build for the platform-wallet + FFI crates.
  • Built the iOS xcframework via packages/swift-sdk/build_ios.sh.
  • Built and ran SwiftExampleApp in iPhone 16 simulator across mainnet + testnet wallets:
    • Wallet creation + recovery (auto-discovery of identities verified).
    • SPV sync to tip; verified wallet-level balance matches block-explorer.
    • Storage Explorer audit: each new persistent model lists + opens detail correctly; "Spent By" populates after spend; "Address" link reachable from TXO detail.
    • DPNS registration with raw display label survives the round trip.
    • Per-wallet TransactionListView push: no stall on empty wallets, no stall during sync.
    • Token balance sync via IdentitySyncManager — verified balance row writes / removals over a re-scan.

Known issue (deferred): per-account row balances can read 0 in some states even when the wallet-level total is correct. Filed as follow-up; not blocking this PR.

Pre-existing: tests/spv_sync.rs:77 is broken; out of scope for this branch.

Breaking Changes

Public Rust API surface inside platform-wallet / rs-platform-wallet-ffi shifts (PlatformWalletPersistence trait collapses, TokenWallet removed, FFI changeset / IdentityEntry shapes extended). Swift SDK Persistent* schema is incompatible with the previous on-disk store; dev databases must be reset. No consensus-affecting changes.

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

  • New Features

    • Added identity token synchronization system for managing and syncing token balances per identity.
    • Added DashPay contact request management with persistence for outgoing and incoming requests.
    • Added DPNS name and DashPay profile caching for improved identity data management.
  • Improvements

    • Enhanced wallet registration and account address pool persistence.
    • Consolidated token balance management through centralized identity sync manager.

Loading
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