@@ -59,8 +59,6 @@ class WalletViewModel: ObservableObject {
5959
6060 static var peerSimulation : BlocktankPeerSimulation = . none
6161
62- private static let channelRecoveryRestartDelayMs : UInt64 = 500
63-
6462 @Published var isRestoringWallet = false
6563 @Published var balanceInTransferToSavings : Int = 0
6664 @Published var balanceInTransferToSpending : Int = 0
@@ -138,6 +136,21 @@ class WalletViewModel: ObservableObject {
138136 MigrationsService . shared. pendingChannelMigration = nil
139137 }
140138
139+ // If no local migration data, try fetching from RN remote backup (one-time)
140+ if channelMigration == nil {
141+ let ( remoteMigration, allRetrieved) = await fetchOrphanedChannelMonitorsIfNeeded ( walletIndex: walletIndex)
142+ if let remoteMigration {
143+ channelMigration = ChannelDataMigration (
144+ channelManager: [ UInt8] ( remoteMigration. channelManager) ,
145+ channelMonitors: remoteMigration. channelMonitors. map { [ UInt8] ( $0) }
146+ )
147+ MigrationsService . shared. pendingChannelMigration = nil
148+ }
149+ if allRetrieved {
150+ MigrationsService . shared. isChannelRecoveryChecked = true
151+ }
152+ }
153+
141154 await runLegacyNetworkGraphCleanupIfNeeded ( )
142155
143156 try await lightningService. setup (
@@ -236,77 +249,42 @@ class WalletViewModel: ObservableObject {
236249 Task { @MainActor in
237250 try await sync ( )
238251 }
239-
240- // One-time check for orphaned channel monitors from RN migration
241- Task {
242- await checkForOrphanedChannelMonitorRecovery ( )
243- }
244252 }
245253
246- private func checkForOrphanedChannelMonitorRecovery( ) async {
254+ /// Fetches orphaned channel monitors from RN remote backup before LDK setup.
255+ /// Returns (migration data if found, whether all monitors were successfully retrieved).
256+ private func fetchOrphanedChannelMonitorsIfNeeded( walletIndex: Int ) async -> ( PendingChannelMigration ? , Bool ) {
247257 let migrations = MigrationsService . shared
248- guard !migrations. isChannelRecoveryChecked else { return }
258+ guard !migrations. isChannelRecoveryChecked else { return ( nil , true ) }
249259
250- Logger . info ( " Running one-time channel monitor recovery check " , context: " WalletViewModel " )
260+ Logger . info ( " Running pre-startup channel monitor recovery check " , context: " WalletViewModel " )
251261
252- var allMonitorsRetrieved = false
253262 do {
254- guard let mnemonic = try Keychain . loadString ( key: . bip39Mnemonic( index: 0 ) ) else {
263+ guard let mnemonic = try Keychain . loadString ( key: . bip39Mnemonic( index: walletIndex ) ) else {
255264 Logger . debug ( " Channel recovery: no mnemonic, skipping " , context: " WalletViewModel " )
256265 migrations. isChannelRecoveryChecked = true
257- return
266+ return ( nil , true )
258267 }
259- let passphrase = try ? Keychain . loadString ( key: . bip39Passphrase( index: 0 ) )
268+ let passphrase = try ? Keychain . loadString ( key: . bip39Passphrase( index: walletIndex ) )
260269
261270 RNBackupClient . shared. reset ( )
262271 try await RNBackupClient . shared. setup ( mnemonic: mnemonic, passphrase: passphrase)
263272
264- let retrieved = await migrations. fetchRNRemoteLdkData ( )
273+ let allRetrieved = await migrations. fetchRNRemoteLdkData ( )
265274
266275 if let migration = migrations. pendingChannelMigration {
267- let monitorCount = migration. channelMonitors. count
268276 Logger . info (
269- " Found \( monitorCount ) monitors on RN backup, attempting recovery " ,
277+ " Found \( migration . channelMonitors . count ) monitors on RN backup for pre-startup recovery " ,
270278 context: " WalletViewModel "
271279 )
272-
273- let channelMigration = ChannelDataMigration (
274- channelManager: [ UInt8] ( migration. channelManager) ,
275- channelMonitors: migration. channelMonitors. map { [ UInt8] ( $0) }
276- )
277- migrations. pendingChannelMigration = nil
278-
279- // Stop and restart the lightning service directly (not via self.start())
280- // to avoid the nodeLifecycleState guard racing with concurrent sync Tasks
281- try await lightningService. stop ( )
282- try await Task . sleep ( nanoseconds: Self . channelRecoveryRestartDelayMs * 1_000_000 )
283-
284- let electrumServerUrl = electrumConfigService. getCurrentServer ( ) . fullUrl
285- let rgsServerUrl = rgsConfigService. getCurrentServerUrl ( )
286- try await lightningService. setup (
287- walletIndex: 0 ,
288- electrumServerUrl: electrumServerUrl,
289- rgsServerUrl: rgsServerUrl. isEmpty ? nil : rgsServerUrl,
290- channelMigration: channelMigration
291- )
292- try await lightningService. start ( )
293-
294- nodeLifecycleState = . running
295- syncState ( )
296- Logger . info ( " Channel monitor recovery complete " , context: " WalletViewModel " )
280+ return ( migration, allRetrieved)
297281 } else {
298282 Logger . info ( " No channel monitors found on RN backup " , context: " WalletViewModel " )
283+ return ( nil , allRetrieved)
299284 }
300-
301- allMonitorsRetrieved = retrieved
302285 } catch {
303- Logger . error ( " Channel monitor recovery check failed: \( error) " , context: " WalletViewModel " )
304- }
305-
306- if allMonitorsRetrieved {
307- migrations. isChannelRecoveryChecked = true
308- } else {
309- Logger . warn ( " Some monitors failed to download, will retry on next startup " , context: " WalletViewModel " )
286+ Logger . error ( " Pre-startup channel monitor fetch failed: \( error) " , context: " WalletViewModel " )
287+ return ( nil , false )
310288 }
311289 }
312290
0 commit comments