@@ -27,6 +27,7 @@ class TransferViewModel: ObservableObject {
2727 private let lightningService : LightningService
2828 private let currencyService : CurrencyService
2929 private let transferService : TransferService
30+ private let sheetViewModel : SheetViewModel
3031
3132 private var refreshTimer : Timer ?
3233 private var refreshTask : Task < Void , Never > ?
@@ -39,19 +40,22 @@ class TransferViewModel: ObservableObject {
3940 coreService: CoreService = . shared,
4041 lightningService: LightningService = . shared,
4142 currencyService: CurrencyService = . shared,
42- transferService: TransferService
43+ transferService: TransferService ,
44+ sheetViewModel: SheetViewModel
4345 ) {
4446 self . coreService = coreService
4547 self . lightningService = lightningService
4648 self . currencyService = currencyService
4749 self . transferService = transferService
50+ self . sheetViewModel = sheetViewModel
4851 }
4952
5053 /// Convenience initializer for testing and previews
5154 convenience init (
5255 coreService: CoreService = . shared,
5356 lightningService: LightningService = . shared,
54- currencyService: CurrencyService = . shared
57+ currencyService: CurrencyService = . shared,
58+ sheetViewModel: SheetViewModel = SheetViewModel ( )
5559 ) {
5660 let transferService = TransferService (
5761 lightningService: lightningService,
@@ -61,7 +65,8 @@ class TransferViewModel: ObservableObject {
6165 coreService: coreService,
6266 lightningService: lightningService,
6367 currencyService: currencyService,
64- transferService: transferService
68+ transferService: transferService,
69+ sheetViewModel: sheetViewModel
6570 )
6671 }
6772
@@ -529,22 +534,9 @@ class TransferViewModel: ObservableObject {
529534
530535 func closeChannels( channels: [ ChannelDetails ] ) async throws -> [ ChannelDetails ] {
531536 var failedChannels : [ ChannelDetails ] = [ ]
537+ var successfulChannels : [ ChannelDetails ] = [ ]
532538
533- // Create transfer tracking records for each channel being closed
534- for channel in channels {
535- do {
536- let transferId = try await transferService. createTransfer (
537- type: . toSavings,
538- amountSats: channel. amountOnClose,
539- channelId: channel. channelId. description
540- )
541- Logger . info ( " Created transfer tracking record for channel closure: \( transferId) " , context: " TransferViewModel " )
542- } catch {
543- Logger . error ( " Failed to create transfer tracking record for channel: \( channel. channelId) " , context: error. localizedDescription)
544- // Continue with closure even if tracking fails
545- }
546- }
547-
539+ // Close channels in parallel and track which ones succeeded
548540 try await withThrowingTaskGroup ( of: ChannelDetails ? . self) { group in
549541 for channel in channels {
550542 group. addTask {
@@ -566,6 +558,26 @@ class TransferViewModel: ObservableObject {
566558 }
567559 }
568560
561+ // Determine which channels closed successfully
562+ successfulChannels = channels. filter { channel in
563+ !failedChannels. contains { $0. channelId == channel. channelId }
564+ }
565+
566+ // Create transfer tracking records only for successfully closed channels
567+ for channel in successfulChannels {
568+ do {
569+ let transferId = try await transferService. createTransfer (
570+ type: . toSavings,
571+ amountSats: channel. amountOnClose,
572+ channelId: channel. channelId. description
573+ )
574+ Logger . info ( " Created transfer tracking record for channel closure: \( transferId) " , context: " TransferViewModel " )
575+ } catch {
576+ Logger . error ( " Failed to create transfer tracking record for channel: \( channel. channelId) " , context: error. localizedDescription)
577+ // Don't fail the entire operation - the channel is already closed
578+ }
579+ }
580+
569581 // Sync transfer states after attempting closures
570582 try ? await transferService. syncTransferStates ( )
571583
@@ -605,8 +617,71 @@ class TransferViewModel: ObservableObject {
605617 try ? await Task . sleep ( nanoseconds: UInt64 ( retryInterval * 1_000_000_000 ) )
606618 }
607619
608- Logger . info ( " Giving up on coop close. " )
609- // TODO: Show force transfer UI
620+ Logger . info ( " Giving up on coop close. Showing force transfer UI. " )
621+
622+ // Show force transfer sheet
623+ sheetViewModel. showSheet ( . forceTransfer)
624+ }
625+ }
626+
627+ /// Force close all channels that failed to cooperatively close
628+ func forceCloseChannel( ) async throws {
629+ guard !channelsToClose. isEmpty else {
630+ Logger . warn ( " No channels to force close " )
631+ return
632+ }
633+
634+ Logger . info ( " Force closing \( channelsToClose. count) channel(s) " )
635+
636+ var errors : [ ( channelId: String , error: Error ) ] = [ ]
637+ var successfulChannels : [ ChannelDetails ] = [ ]
638+
639+ for channel in channelsToClose {
640+ do {
641+ // Force close the channel first
642+ try await lightningService. closeChannel (
643+ channel,
644+ force: true ,
645+ forceCloseReason: " User requested force close after cooperative close failed "
646+ )
647+ Logger . info ( " Successfully initiated force close for channel: \( channel. channelId) " )
648+ successfulChannels. append ( channel)
649+
650+ // Only create transfer tracking record if force close succeeded
651+ do {
652+ let transferId = try await transferService. createTransfer (
653+ type: . toSavings,
654+ amountSats: channel. amountOnClose,
655+ channelId: channel. channelId. description
656+ )
657+ Logger . info ( " Created transfer tracking record for force channel closure: \( transferId) " , context: " TransferViewModel " )
658+ } catch {
659+ Logger . error (
660+ " Failed to create transfer tracking record for force-closed channel: \( channel. channelId) " ,
661+ context: error. localizedDescription
662+ )
663+ // Don't fail the entire operation - the channel is already force-closed
664+ }
665+ } catch {
666+ Logger . error ( " Failed to force close channel: \( channel. channelId) " , context: error. localizedDescription)
667+ errors. append ( ( channelId: channel. channelId, error: error) )
668+ }
669+ }
670+
671+ // Remove successfully closed channels from the list
672+ channelsToClose. removeAll { channel in
673+ successfulChannels. contains { $0. channelId == channel. channelId }
674+ }
675+
676+ try ? await transferService. syncTransferStates ( )
677+
678+ // If any errors occurred, throw an aggregated error
679+ if !errors. isEmpty {
680+ let errorMessages = errors. map { " \( $0. channelId) : \( $0. error. localizedDescription) " } . joined ( separator: " , " )
681+ throw AppError (
682+ message: " Failed to force close \( errors. count) of \( errors. count + successfulChannels. count) channel(s) " ,
683+ debugMessage: errorMessages
684+ )
610685 }
611686 }
612687}
0 commit comments