-
Notifications
You must be signed in to change notification settings - Fork 1
Support restore from RN app #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/rn-migration
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for restoring iOS app state from backups created by the React Native version of the app. The restore process intelligently selects the most recent backup between RN remote backups and existing VSS backups.
Key changes:
- Implements RN remote backup client with authentication and decryption
- Adds restore logic that compares RN and VSS backup timestamps to select the most recent
- Migrates RN backup data including settings, widgets, activities, metadata, wallet state, and LDK channel data
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Bitkit/Services/RNBackupClient.swift | New client for authenticating with and retrieving encrypted backups from RN backup server |
| Bitkit/Services/MigrationsService.swift | Adds RN remote backup restore functionality and metadata reapplication logic |
| Bitkit/Utilities/Crypto.swift | Implements Lightning Network message signing and public key derivation for backup authentication |
| Bitkit/AppScene.swift | Implements backup timestamp comparison and restore orchestration during wallet restoration |
| Bitkit/ViewModels/AppViewModel.swift | Adds listener for RN remote backup restoration completion |
| Bitkit/Services/CoreService.swift | Fixes activity timestamp logic to use block timestamp when available |
| Bitkit/Constants/Env.swift | Adds RN backup server endpoints and public keys |
| Bitkit/Utilities/AppReset.swift | Prevents RN migration from triggering after app wipe |
| Bitkit.xcodeproj/project.pbxproj | Adds RNBackupClient.swift to project |
Bitkit/Services/RNBackupClient.swift
Outdated
| else { continue } | ||
|
|
||
| let ts = UInt64(timestamp / 1000) // Convert ms to seconds | ||
| if latestTimestamp == nil || ts > latestTimestamp! { |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid force unwrapping with !. Use optional binding or the nil-coalescing operator instead. For example: if let latest = latestTimestamp, ts <= latest { continue } or latestTimestamp = max(latestTimestamp ?? 0, ts).
| if latestTimestamp == nil || ts > latestTimestamp! { | |
| if let latest = latestTimestamp { | |
| if ts > latest { | |
| latestTimestamp = ts | |
| } | |
| } else { |
| extension MigrationsService { | ||
| func hasRNRemoteBackup(mnemonic: String, passphrase: String?) async -> Bool { | ||
| do { | ||
| let effectivePassphrase = passphrase?.isEmpty == true ? nil : passphrase |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This passphrase normalization logic is duplicated in lines 1204 and 1215. Consider extracting it into a helper method to avoid repetition.
| let activityTimestamp: UInt64 = if existingActivity == nil, let bts = blockTimestamp, bts < paymentTimestamp { | ||
| bts | ||
| } else { | ||
| existingOnchain?.timestamp ?? paymentTimestamp | ||
| } |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This timestamp selection logic is complex and would benefit from a comment explaining when block timestamp vs payment timestamp is used and why the condition checks for existingActivity == nil.
|
|
||
| // Determine which backup is more recent | ||
| let shouldRestoreRN: Bool = { | ||
| guard hasRNBackup else { return false } | ||
| guard let vss = vssTimestamp, vss > 0 else { return true } // No VSS, use RN |
Copilot
AI
Dec 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The vss > 0 check appears to be a magic number. Consider using a named constant or adding a comment explaining what a timestamp of 0 or less represents.
| // Determine which backup is more recent | |
| let shouldRestoreRN: Bool = { | |
| guard hasRNBackup else { return false } | |
| guard let vss = vssTimestamp, vss > 0 else { return true } // No VSS, use RN | |
| // A VSS timestamp of 0 indicates that no VSS backup exists. | |
| let vssNoBackupTimestamp: UInt64 = 0 | |
| // Determine which backup is more recent | |
| let shouldRestoreRN: Bool = { | |
| guard hasRNBackup else { return false } | |
| guard let vss = vssTimestamp, vss > vssNoBackupTimestamp else { return true } // No VSS, use RN |
This PR is based on the RN migration branch, and adds support for restoring an app which was backed up by the RN app.
Testing: Run the RN app, make test actions like txs, tags, widgets and settings updates etc. Then save the backup mnemonic and delete the app. Then install this branch of the iOS app (make sure to run it with the same network of the RN app used). and restore wallet using the mnemonic. All old data should show up.