refactor(swift-sdk,platform-wallet): rebuild DashPay/DPNS persistence + identity sync, drop TokenWallet - #3564
Merged
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 singlestore(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@Querysubscriptions).What was done?
SwiftData schema (DashSchemaV1, dev stores rebuild)
PersistentDPNSName,PersistentDashpayProfile,PersistentDashpayContactRequest. Non-optionalidentity/ownerrelationships;#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#Uniqueon(wallet, accountType, accountIndex, standardTag, registrationIndex, keyClass, userIdentityId, friendIdentityId).accountExtendedPubKeyBytesflipped toData?with@Attribute(.unique).walletmade non-optional.isWatchOnlyremoved.PersistentWallet:isWatchOnlyremoved.PersistentCoreAddress.txoscascade-deletes (was nullify); upserts by Base58Check string so pool refreshes are non-destructive.PersistentTxo/PersistentTransaction.txidHexreverses bytes for canonical block-explorer display.Persistence trait unification
PlatformWalletPersistencecollapses to a singlestore(changeset)write path. Legacystore_account/store_wallet_metadata/store_account_addressesremoved; their data rides on three new fields onPlatformWalletChangeSet. FFI exposes correspondingon_persist_wallet_metadata/on_persist_account_registrations/on_persist_account_address_poolscallbacks dispatched from insideFFIPersister::store.TokenWallet drop, IdentitySyncManager consolidation
TokenWallet. Watch list / balance cache moved toIdentitySyncManager.state.IdentitySyncManager(inmanager/identity_sync.rs) mirrorsPlatformAddressSyncManager: registry + periodic loop, batches up to 100 token ids perIdentityTokenBalancesQuery, sequential per identity. Generic overP: PlatformWalletPersistencefor static dispatch. Decoupled fromPlatformWallet/WalletManager— caller drives the registry viaregister_identity/update_watched_tokens/unregister_identity.PlatformWalletInfo.token_balances/token_watchedandwatched/unwatchedchannels onTokenBalanceChangeSet; per-balance writes still flow throughbalances/removed_balances.Manager layout
identity_sync.rsandplatform_address_sync.rsmoved intosrc/manager/.spawn_wallet_event_adapteris generic overP: PlatformWalletPersistence + 'static(wasArc<dyn ...>); caller passes the manager's ownArc<P>for static dispatch.DashPay profile / contact requests
IdentityEntryFFIextended with profile fields (display_name/bio/public_message/avatar_url/avatar_hash/avatar_fingerprint).on_persist_contacts_fncallback +ContactRequestFFI/ContactRequestRemovalFFIprojectingContactChangeSet.sent/incoming/established/removed_*.establishedprojects as two rows per entry (one outgoing, one incoming) so the per-direction Swift unique key upserts cleanly.Account-row dedup
AccountChangeSetFFInow carries the full typedAccountTypetags (type_tag,standard_tag,registration_index,key_class,user_identity_id,friend_identity_id). Swift persister keys upsert on those fields instead of the legacyDebug-formattedaccount_type_namestring. Eliminates duplicateStandard { … }rows that appeared next to cleanBIP44 Account #0rows when the load path and sync changeset both emitted the same account with different name strings.TXO graph
markUtxoSpentpopulatesPersistentTxo.spendingTransaction.AccountChangeSetFFI.utxos_spentshape changed fromOutPointFFItoSpentOutPointFFI { 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.coreAddressis backfilled insidepersistAccountAddresses: when an address row is upserted, any TXO at that Base58Check with a nilcoreAddresslink gets attached. Closes the race where SPV emits a UTXO before the address-pool row lands.Wallet recovery + identity discovery
register_walletcallsidentity().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
NavigationLinkre-runs the destination'sinit(and its@Queryregistration) on every parent body invocation. Switched both pushes in the wallets stack to value-basedNavigationLink(value:)+.navigationDestination(for:)onWalletsContentView. The per-wallet TX query now traverses relationships in SQLite (no Swift dedupe).Storage Explorer
PersistentPlatformAddressesSyncStateStorageDetailViewwalletIdscope key fix.coreAddressis linked; redundant "Address Row" removed..searchableover Base58Check / derivation path / address index.Core balance display (derive from TXOs)
BalanceCardViewandAccountListViewread from a single shared@Query<PersistentTxo>inWalletDetailViewfiltered 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.rsdoc comment fixed (was referencing a non-existentWalletEventAdapter).RegisterNameViewpasses the user-typed display label towallet.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 buildfor the platform-wallet + FFI crates.packages/swift-sdk/build_ios.sh.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:77is broken; out of scope for this branch.Breaking Changes
Public Rust API surface inside
platform-wallet/rs-platform-wallet-ffishifts (PlatformWalletPersistencetrait collapses,TokenWalletremoved, FFI changeset / IdentityEntry shapes extended). Swift SDKPersistent*schema is incompatible with the previous on-disk store; dev databases must be reset. No consensus-affecting changes.Checklist:
For repository code-owners and collaborators only
Summary by CodeRabbit
New Features
Improvements