Problem
Android and iOS backup restore currently decode app-owned backup JSON directly into generated Core model types before calling Core.
That is brittle for wallet-scoped activity changes. If a backup was written before walletId existed, the app can fail while decoding Activity, ActivityTags, or PreActivityMetadata, before Core gets a chance to apply defaults or migrate the data.
Current app restore paths:
- Android
ActivityBackupV1 contains activities: List<Activity>, activityTags: List<ActivityTags>, and closedChannels: List<ClosedChannelDetails>. BackupRepo decodes that payload, then ActivityRepo.restoreFromBackup() calls Core upsertActivities, upsertTags, and upsertClosedChannels.
- Android
MetadataBackupV1 contains tagMetadata: List<PreActivityMetadata>. BackupRepo decodes that payload, then upserts the metadata through Core.
- iOS has the same backup shapes in
ActivityBackupV1 and MetadataBackupV1. BackupService decodes them with JSONDecoder, then calls Core upsertList, upsertTags, upsertClosedChannelList, and upsertPreActivityMetadata.
- Android's normal wallet id is
bitkit via ActivityWalletType.BITKIT.id; Trezor wallet ids are trezor:{sha256(sorted xpubs joined by \n)}.
- iOS uses
WalletScope.default = getDefaultWalletId() for the normal wallet; Trezor wallet ids are derived in HwWalletId with the same trezor:{sha256(sorted xpubs joined by \n)} shape when xpubs are available.
- Both apps already pass wallet ids to Core for hardware-wallet activity reads/writes/deletes. The backup problem is the typed decode boundary, not a need for platform-specific JSON patching.
TransactionDetails is not in the current Android/iOS activity backup envelopes. It still needs DB migration coverage because Core persists it and scopes it by (walletId, txId).
Requested change
Add Core-owned migration support at both Core-owned boundaries:
- migrate the activity DB on init, because Core owns the persisted activity layer.
- migrate Core-owned JSON slices embedded in Android/iOS VSS backup envelopes, so the apps can rewrite those backups with current entries.
The apps should not hand-edit Core model JSON or decode legacy Core models before migration.
Required helpers:
- migrate activity arrays from backup JSON into current
Activity entries.
- migrate activity tag arrays into current
ActivityTags entries.
- migrate pre-activity metadata arrays into current
PreActivityMetadata entries.
- migrate transaction details JSON if/when it is included in backups.
- provide a way for apps to get current/migrated backup JSON for the Core-owned fields so VSS can be updated after migration.
Migration rules:
- Missing
walletId means DEFAULT_WALLET_ID / bitkit.
- Empty
walletId should normalize to DEFAULT_WALLET_ID only for legacy migration input. Normal current writes should still reject empty wallet ids.
- Preserve non-default wallet ids such as
trezor:{hash}.
- Preserve activity ids, txids, tags, closed channel data, timestamps, contact fields, seen state, and pre-activity metadata fields.
- Do not require Android or iOS to hand-edit Core model JSON.
DB migration
Core should own activity DB migration.
Required DB behavior:
- On init, tables without
wallet_id migrate to wallet-scoped primary keys.
- Existing activity rows, tags, pre-activity metadata, and transaction details without a wallet scope migrate to
DEFAULT_WALLET_ID.
activity_tags, pre_activity_metadata, and transaction_details use wallet-scoped keys after migration.
- Migration preserves user metadata where possible: tags, contacts, seen state, and pre-activity metadata.
- There is no separate cleanup/wipe requirement for dev-only pre-release DB states in this issue.
App/VSS responsibility
Android and iOS own the VSS backup envelopes. Core owns the activity data inside those envelopes.
Expected app-side flow:
- Read the app-owned VSS backup envelope as raw JSON.
- Keep app-owned fields, such as cache/settings/pubky session data, in the app layer.
- Pass the raw Core-owned activity/metadata JSON to Core migration/import APIs.
- Let Core decode, migrate, and persist Core-owned activity data into the activity DB.
- Ask Core for the migrated/current Core-owned backup entries.
- Write the updated app-owned VSS backup envelope with those migrated entries.
The app responsibility is to refresh VSS with v2/current Core entries after migration. The app should not own the Core model migration itself.
Search and scoping checks
After DB migration or backup migration:
walletId = DEFAULT_WALLET_ID returns only the normal Bitkit wallet rows.
walletId = trezor:{hash} returns only that hardware wallet's rows.
- global list/search calls that intentionally pass no wallet id can merge wallets, but must not duplicate a logical row.
- activity id and txid collisions across wallet ids stay scoped.
- tags and transaction details are looked up with the same wallet scope as the activity.
Acceptance criteria
- Android-shaped backup JSON with
activities, activityTags, and tagMetadata missing walletId migrates without app-side JSON patching.
- iOS-shaped backup JSON with the same Core-owned fields missing
walletId migrates without app-side JSON patching.
- Migrated legacy backup rows use
DEFAULT_WALLET_ID.
- Existing non-default wallet ids survive backup migration unchanged.
- Core DB migration covers
activities, onchain_activity, lightning_activity, activity_tags, pre_activity_metadata, and transaction_details.
- Core exposes generated Android and iOS binding APIs for migrating/importing Core-owned backup JSON and exporting migrated/current backup entries.
- Tests cover legacy backup JSON, current wallet-scoped backup JSON, DB migration from tables without
wallet_id, VSS backup rewrite data, and scoped lookup/search/tag/detail behavior after migration.
Problem
Android and iOS backup restore currently decode app-owned backup JSON directly into generated Core model types before calling Core.
That is brittle for wallet-scoped activity changes. If a backup was written before
walletIdexisted, the app can fail while decodingActivity,ActivityTags, orPreActivityMetadata, before Core gets a chance to apply defaults or migrate the data.Current app restore paths:
ActivityBackupV1containsactivities: List<Activity>,activityTags: List<ActivityTags>, andclosedChannels: List<ClosedChannelDetails>.BackupRepodecodes that payload, thenActivityRepo.restoreFromBackup()calls CoreupsertActivities,upsertTags, andupsertClosedChannels.MetadataBackupV1containstagMetadata: List<PreActivityMetadata>.BackupRepodecodes that payload, then upserts the metadata through Core.ActivityBackupV1andMetadataBackupV1.BackupServicedecodes them withJSONDecoder, then calls CoreupsertList,upsertTags,upsertClosedChannelList, andupsertPreActivityMetadata.bitkitviaActivityWalletType.BITKIT.id; Trezor wallet ids aretrezor:{sha256(sorted xpubs joined by \n)}.WalletScope.default = getDefaultWalletId()for the normal wallet; Trezor wallet ids are derived inHwWalletIdwith the sametrezor:{sha256(sorted xpubs joined by \n)}shape when xpubs are available.TransactionDetailsis not in the current Android/iOS activity backup envelopes. It still needs DB migration coverage because Core persists it and scopes it by(walletId, txId).Requested change
Add Core-owned migration support at both Core-owned boundaries:
The apps should not hand-edit Core model JSON or decode legacy Core models before migration.
Required helpers:
Activityentries.ActivityTagsentries.PreActivityMetadataentries.Migration rules:
walletIdmeansDEFAULT_WALLET_ID/bitkit.walletIdshould normalize toDEFAULT_WALLET_IDonly for legacy migration input. Normal current writes should still reject empty wallet ids.trezor:{hash}.DB migration
Core should own activity DB migration.
Required DB behavior:
wallet_idmigrate to wallet-scoped primary keys.DEFAULT_WALLET_ID.activity_tags,pre_activity_metadata, andtransaction_detailsuse wallet-scoped keys after migration.App/VSS responsibility
Android and iOS own the VSS backup envelopes. Core owns the activity data inside those envelopes.
Expected app-side flow:
The app responsibility is to refresh VSS with v2/current Core entries after migration. The app should not own the Core model migration itself.
Search and scoping checks
After DB migration or backup migration:
walletId = DEFAULT_WALLET_IDreturns only the normal Bitkit wallet rows.walletId = trezor:{hash}returns only that hardware wallet's rows.Acceptance criteria
activities,activityTags, andtagMetadatamissingwalletIdmigrates without app-side JSON patching.walletIdmigrates without app-side JSON patching.DEFAULT_WALLET_ID.activities,onchain_activity,lightning_activity,activity_tags,pre_activity_metadata, andtransaction_details.wallet_id, VSS backup rewrite data, and scoped lookup/search/tag/detail behavior after migration.