Skip to content

Commit 9f99ffb

Browse files
committed
feat: support restore from RN app
1 parent 3a9b188 commit 9f99ffb

File tree

9 files changed

+989
-61
lines changed

9 files changed

+989
-61
lines changed

Bitkit.xcodeproj/project.pbxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
Services/GeoService.swift,
9393
Services/LightningService.swift,
9494
Services/MigrationsService.swift,
95+
Services/RNBackupClient.swift,
9596
Services/ServiceQueue.swift,
9697
Services/VssStoreIdProvider.swift,
9798
Utilities/Crypto.swift,

Bitkit/AppScene.swift

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,18 @@ struct AppScene: View {
248248

249249
if wallet.isRestoringWallet {
250250
Task {
251-
await BackupService.shared.performFullRestoreFromLatestBackup()
251+
await restoreFromMostRecentBackup()
252252

253253
await MainActor.run {
254254
widgets.loadSavedWidgets()
255255
widgets.objectWillChange.send()
256256
}
257+
258+
await startWallet()
257259
}
260+
} else {
261+
Task { await startWallet() }
258262
}
259-
260-
Task { await startWallet() }
261263
}
262264

263265
private func startWallet() async {
@@ -332,6 +334,44 @@ struct AppScene: View {
332334
}
333335
}
334336

337+
private func restoreFromMostRecentBackup() async {
338+
guard let mnemonicData = try? Keychain.load(key: .bip39Mnemonic(index: 0)),
339+
let mnemonic = String(data: mnemonicData, encoding: .utf8)
340+
else { return }
341+
342+
let passphrase: String? = {
343+
guard let data = try? Keychain.load(key: .bip39Passphrase(index: 0)) else { return nil }
344+
return String(data: data, encoding: .utf8)
345+
}()
346+
347+
// Check for RN backup and get its timestamp
348+
let hasRNBackup = await MigrationsService.shared.hasRNRemoteBackup(mnemonic: mnemonic, passphrase: passphrase)
349+
let rnTimestamp: UInt64? = await hasRNBackup ? (try? RNBackupClient.shared.getLatestBackupTimestamp()) : nil
350+
351+
// Get VSS backup timestamp
352+
let vssTimestamp = BackupService.shared.getLatestBackupTime()
353+
354+
// Determine which backup is more recent
355+
let shouldRestoreRN: Bool = {
356+
guard hasRNBackup else { return false }
357+
guard let vss = vssTimestamp, vss > 0 else { return true } // No VSS, use RN
358+
guard let rn = rnTimestamp else { return false } // No RN timestamp, use VSS
359+
return rn >= vss // RN is same or newer
360+
}()
361+
362+
if shouldRestoreRN {
363+
do {
364+
try await MigrationsService.shared.restoreFromRNRemoteBackup(mnemonic: mnemonic, passphrase: passphrase)
365+
} catch {
366+
Logger.error("RN remote backup restore failed: \(error)", context: "AppScene")
367+
// Fall back to VSS
368+
await BackupService.shared.performFullRestoreFromLatestBackup()
369+
}
370+
} else {
371+
await BackupService.shared.performFullRestoreFromLatestBackup()
372+
}
373+
}
374+
335375
private func handleNodeLifecycleChange(_ state: NodeLifecycleState) {
336376
if state == .initializing {
337377
walletIsInitializing = true

Bitkit/Constants/Env.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,20 @@ enum Env {
204204
}
205205
}
206206

207+
static var rnBackupServerHost: String {
208+
switch network {
209+
case .bitcoin: "https://blocktank.synonym.to/backups-ldk"
210+
default: "https://bitkit.stag0.blocktank.to/backups-ldk"
211+
}
212+
}
213+
214+
static var rnBackupServerPubKey: String {
215+
switch network {
216+
case .bitcoin: "0236efd76e37f96cf2dced9d52ff84c97e5b3d4a75e7d494807291971783f38377"
217+
default: "02c03b8b8c1b5500b622646867d99bf91676fac0f38e2182c91a9ff0d053a21d6d"
218+
}
219+
}
220+
207221
static var blockExplorerUrl: String {
208222
switch network {
209223
case .bitcoin: "https://mempool.space"

Bitkit/Services/CoreService.swift

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -352,15 +352,16 @@ class ActivityService {
352352
let value = payment.amountSats ?? 0
353353

354354
// Determine confirmation status from payment's txStatus
355-
// Ensure confirmTimestamp is at least equal to paymentTimestamp when confirmed
356-
// This handles cases where payment.latestUpdateTimestamp is more recent than blockTimestamp
357-
let (isConfirmed, confirmedTimestamp): (Bool, UInt64?) =
358-
if case let .onchain(_, txStatus) = payment.kind,
359-
case let .confirmed(_, _, blockTimestamp) = txStatus {
360-
(true, max(blockTimestamp, paymentTimestamp))
361-
} else {
362-
(false, nil)
363-
}
355+
var blockTimestamp: UInt64?
356+
let isConfirmed: Bool
357+
if case let .onchain(_, txStatus) = payment.kind,
358+
case let .confirmed(_, _, bts) = txStatus
359+
{
360+
isConfirmed = true
361+
blockTimestamp = bts
362+
} else {
363+
isConfirmed = false
364+
}
364365

365366
// Extract existing activity data
366367
let existingOnchain: OnchainActivity? = {
@@ -412,6 +413,12 @@ class ActivityService {
412413
// Build and save the activity
413414
let finalDoesExist = isConfirmed ? true : doesExist
414415

416+
let activityTimestamp: UInt64 = if existingActivity == nil, let bts = blockTimestamp, bts < paymentTimestamp {
417+
bts
418+
} else {
419+
existingOnchain?.timestamp ?? paymentTimestamp
420+
}
421+
415422
let onchain = OnchainActivity(
416423
id: payment.id,
417424
txType: payment.direction == .outbound ? .sent : .received,
@@ -421,12 +428,12 @@ class ActivityService {
421428
feeRate: feeRate,
422429
address: address,
423430
confirmed: isConfirmed,
424-
timestamp: paymentTimestamp,
431+
timestamp: activityTimestamp,
425432
isBoosted: isBoosted,
426433
boostTxIds: boostTxIds,
427434
isTransfer: isTransfer,
428435
doesExist: finalDoesExist,
429-
confirmTimestamp: confirmedTimestamp,
436+
confirmTimestamp: blockTimestamp,
430437
channelId: channelId,
431438
transferTxId: transferTxId,
432439
createdAt: UInt64(payment.creationTime.timeIntervalSince1970),

0 commit comments

Comments
 (0)