From 6b19dfacbd9973a30875ae088f2e3e0dcc8dfa9f Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 18 Feb 2026 00:28:16 +0700 Subject: [PATCH 1/3] Replace node restart with dynamic address type APIs --- Bitkit.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Localization/cs.lproj/Localizable.strings | 2 - .../Localization/de.lproj/Localizable.strings | 2 - .../Localization/en.lproj/Localizable.strings | 9 -- .../es-419.lproj/Localizable.strings | 2 - .../Localization/fr.lproj/Localizable.strings | 2 - .../Localization/it.lproj/Localizable.strings | 2 - .../Localization/nl.lproj/Localizable.strings | 2 - .../Localization/pl.lproj/Localizable.strings | 2 - .../pt-BR.lproj/Localizable.strings | 2 - .../Localization/ru.lproj/Localizable.strings | 2 - Bitkit/Services/LightningService.swift | 40 +++++++++ Bitkit/Utilities/Errors.swift | 12 +++ Bitkit/ViewModels/SettingsViewModel.swift | 59 ++++++------- .../Advanced/AddressTypeLoadingView.swift | 77 ---------------- .../Advanced/AddressTypePreferenceView.swift | 88 ++----------------- BitkitTests/AddressTypeIntegrationTests.swift | 4 +- 18 files changed, 92 insertions(+), 219 deletions(-) delete mode 100644 Bitkit/Views/Settings/Advanced/AddressTypeLoadingView.swift diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 8ec7a9246..8f3f67bd4 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -926,7 +926,7 @@ repositoryURL = "https://github.com/synonymdev/ldk-node"; requirement = { kind = revision; - revision = 4ef1a66b5390a09ab49dcb2b1e0d32d2ca5e890f; + revision = 857056a27def6ee281d0a82d1799637b04244c3a; }; }; 96DEA0382DE8BBA1009932BF /* XCRemoteSwiftPackageReference "bitkit-core" */ = { diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 044af8109..f66c122e8 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,7 +24,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/synonymdev/ldk-node", "state" : { - "revision" : "4ef1a66b5390a09ab49dcb2b1e0d32d2ca5e890f" + "revision" : "857056a27def6ee281d0a82d1799637b04244c3a" } }, { diff --git a/Bitkit/Resources/Localization/cs.lproj/Localizable.strings b/Bitkit/Resources/Localization/cs.lproj/Localizable.strings index 1844973aa..3786aefa7 100644 --- a/Bitkit/Resources/Localization/cs.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/cs.lproj/Localizable.strings @@ -710,8 +710,6 @@ "settings__adv__section_other" = "Další"; "settings__adv__address_type" = "Typ bitcoinové adresy"; "settings__adv__monitored_address_types" = "Sledované typy adres"; -"settings__adv__monitored_address_types_update_title" = "Aktualizace sledovaných typů adres"; -"settings__adv__monitored_address_types_update_description" = "Změny se plně projeví po restartu aplikace."; "settings__adv__gap_limit" = "Limit mezery v adrese"; "settings__adv__coin_selection" = "Výběr mince"; "settings__adv__cs_method" = "Metoda výběru mince"; diff --git a/Bitkit/Resources/Localization/de.lproj/Localizable.strings b/Bitkit/Resources/Localization/de.lproj/Localizable.strings index 89415cb11..c21b68021 100644 --- a/Bitkit/Resources/Localization/de.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/de.lproj/Localizable.strings @@ -708,8 +708,6 @@ "settings__adv__section_other" = "Andere"; "settings__adv__address_type" = "Bitcoin Adressen-Typ"; "settings__adv__monitored_address_types" = "Überwachte Adress-Typen"; -"settings__adv__monitored_address_types_update_title" = "Überwachte Adress-Typen aktualisiert"; -"settings__adv__monitored_address_types_update_description" = "Änderungen werden nach dem Neustart der App vollständig wirksam."; "settings__adv__gap_limit" = "Address Gap Limit"; "settings__adv__coin_selection" = "Coin-Auswahl"; "settings__adv__cs_method" = "Coin-Auswahlmethode"; diff --git a/Bitkit/Resources/Localization/en.lproj/Localizable.strings b/Bitkit/Resources/Localization/en.lproj/Localizable.strings index 0bd8efcf1..72caccfbb 100644 --- a/Bitkit/Resources/Localization/en.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/en.lproj/Localizable.strings @@ -738,10 +738,6 @@ "settings__adv__section_other" = "Other"; "settings__adv__address_type" = "Bitcoin Address Type"; "settings__adv__monitored_address_types" = "Monitored Address Types"; -"settings__adv__monitored_address_types_update_title" = "Monitored Address Types Updated"; -"settings__adv__monitored_address_types_update_description" = "Changes will take full effect after app restarts."; -"settings__adv__addr_type_timeout_title" = "Timeout"; -"settings__adv__addr_type_timeout_desc" = "The operation took too long. Please try again."; "settings__adv__addr_type_failed_title" = "Failed"; "settings__adv__addr_type_change_failed_desc" = "Could not change address type. Please try again."; "settings__adv__addr_type_applying" = "Applying changes…"; @@ -755,11 +751,6 @@ "settings__adv__addr_type_monitored_failed_desc" = "Could not update monitoring settings. Please try again."; "settings__adv__addr_type_currently_selected" = "Currently selected"; "settings__adv__addr_type_monitored_note" = "Enable monitoring to track funds received at different address types. The app will watch these addresses for incoming transactions. Disabling monitoring for a type with balance may hide your funds."; -"settings__adv__addr_type_loading_nav_address" = "Address Type"; -"settings__adv__addr_type_loading_nav_monitoring" = "Address Monitoring"; -"settings__adv__addr_type_loading_headline" = "Switching to {type}"; -"settings__adv__addr_type_loading_updating" = "Updating Wallet"; -"settings__adv__addr_type_loading_desc" = "Please wait while the wallet restarts..."; "settings__adv__gap_limit" = "Address Gap Limit"; "settings__adv__coin_selection" = "Coin Selection"; "settings__adv__cs_method" = "Coin Selection Method"; diff --git a/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings b/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings index e3decc0ec..4bd44c092 100644 --- a/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/es-419.lproj/Localizable.strings @@ -717,8 +717,6 @@ "settings__adv__section_other" = "Otros"; "settings__adv__address_type" = "Tipo de dirección Bitcoin"; "settings__adv__monitored_address_types" = "Tipos de Direcciones en monitoreo"; -"settings__adv__monitored_address_types_update_title" = "Tipos de dirección monitoreados actualizados"; -"settings__adv__monitored_address_types_update_description" = "Los cambios surtirán pleno efecto tras reiniciar la aplicación."; "settings__adv__gap_limit" = "Límite de la brecha de direcciones"; "settings__adv__coin_selection" = "Selección de monedas"; "settings__adv__cs_method" = "Método de selección de monedas"; diff --git a/Bitkit/Resources/Localization/fr.lproj/Localizable.strings b/Bitkit/Resources/Localization/fr.lproj/Localizable.strings index 4f65eb31b..efb6a5578 100644 --- a/Bitkit/Resources/Localization/fr.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/fr.lproj/Localizable.strings @@ -721,8 +721,6 @@ "settings__adv__section_other" = "Autre"; "settings__adv__address_type" = "Type d\'adresse Bitcoin"; "settings__adv__monitored_address_types" = "Types d\'adresses suivies"; -"settings__adv__monitored_address_types_update_title" = "Mise à jour des types d\'adresses suivies"; -"settings__adv__monitored_address_types_update_description" = "Les modifications prendront effet après le redémarrage de l\'application."; "settings__adv__gap_limit" = "Limite de l\'écart d\'adresse"; "settings__adv__coin_selection" = "Sélection des UTXOs"; "settings__adv__cs_method" = "Méthode de sélection des UTXOs"; diff --git a/Bitkit/Resources/Localization/it.lproj/Localizable.strings b/Bitkit/Resources/Localization/it.lproj/Localizable.strings index 9832602b8..56fa30ce5 100644 --- a/Bitkit/Resources/Localization/it.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/it.lproj/Localizable.strings @@ -699,8 +699,6 @@ "settings__adv__section_other" = "Altro"; "settings__adv__address_type" = "Tipologia Indirizzo Bitcoin"; "settings__adv__monitored_address_types" = "Tipi di indirizzi monitorati"; -"settings__adv__monitored_address_types_update_title" = "Tipi di indirizzi monitorati aggiornati"; -"settings__adv__monitored_address_types_update_description" = "Le modifiche avranno pieno effetto dopo il riavvio dell\'app."; "settings__adv__gap_limit" = "Limite del gap di indirizzi"; "settings__adv__coin_selection" = "Coin Selection"; "settings__adv__cs_method" = "Metodo di Coin Selection"; diff --git a/Bitkit/Resources/Localization/nl.lproj/Localizable.strings b/Bitkit/Resources/Localization/nl.lproj/Localizable.strings index 890b6bcf2..ccce1096c 100644 --- a/Bitkit/Resources/Localization/nl.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/nl.lproj/Localizable.strings @@ -718,8 +718,6 @@ "settings__adv__section_other" = "Overige"; "settings__adv__address_type" = "Bitcoin adres type"; "settings__adv__monitored_address_types" = "Bewaakte adrestypes"; -"settings__adv__monitored_address_types_update_title" = "Bewaakte adrestypes bijgewerkt"; -"settings__adv__monitored_address_types_update_description" = "Wijzigingen worden volledig van kracht nadat de app opnieuw is opgestart."; "settings__adv__gap_limit" = "Adres Gap Limit"; "settings__adv__coin_selection" = "Coin selectie"; "settings__adv__cs_method" = "Coin Selectie Methode"; diff --git a/Bitkit/Resources/Localization/pl.lproj/Localizable.strings b/Bitkit/Resources/Localization/pl.lproj/Localizable.strings index 809c1c9b4..8a6182bfd 100644 --- a/Bitkit/Resources/Localization/pl.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pl.lproj/Localizable.strings @@ -722,8 +722,6 @@ "settings__adv__section_other" = "Inne"; "settings__adv__address_type" = "Typ adresu Bitcoin"; "settings__adv__monitored_address_types" = "Monitorowane typy adresów"; -"settings__adv__monitored_address_types_update_title" = "Zaktualizowano monitorowane typy adresów"; -"settings__adv__monitored_address_types_update_description" = "Zmiany zostaną zastosowane po ponownym uruchomieniu aplikacji."; "settings__adv__gap_limit" = "Limit odstępu adresów"; "settings__adv__coin_selection" = "Wybór monet"; "settings__adv__cs_method" = "Metoda wyboru monet"; diff --git a/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings b/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings index 4d0911b8f..2abbeca01 100644 --- a/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/pt-BR.lproj/Localizable.strings @@ -722,8 +722,6 @@ "settings__adv__section_other" = "Outros"; "settings__adv__address_type" = "Tipo de endereço Bitcoin"; "settings__adv__monitored_address_types" = "Tipos de Endereços Monitorados"; -"settings__adv__monitored_address_types_update_title" = "Tipos de Endereços Monitorados Atualizados"; -"settings__adv__monitored_address_types_update_description" = "As alterações terão após a reinicialização do aplicativo."; "settings__adv__gap_limit" = "Limite de Endereços"; "settings__adv__coin_selection" = "Controle de Moedas"; "settings__adv__cs_method" = "Método de Controle de Moedas"; diff --git a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings index 517fc0f82..8376885f1 100644 --- a/Bitkit/Resources/Localization/ru.lproj/Localizable.strings +++ b/Bitkit/Resources/Localization/ru.lproj/Localizable.strings @@ -722,8 +722,6 @@ "settings__adv__section_other" = "Другое"; "settings__adv__address_type" = "Тип Биткойн-Адреса"; "settings__adv__monitored_address_types" = "Отслеживаемые Типы Адресов"; -"settings__adv__monitored_address_types_update_title" = "Отслеживаемые Типы Адресов Обновлены"; -"settings__adv__monitored_address_types_update_description" = "Изменения вступят в силу после перезапуска приложения."; "settings__adv__gap_limit" = "Gap Limit Адресов"; "settings__adv__coin_selection" = "Выбор Монет"; "settings__adv__cs_method" = "Метод Выбора Монет"; diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 99743dda0..64a2a9244 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -884,6 +884,46 @@ extension LightningService { } } + func addAddressTypeToMonitor(_ addressType: LDKNode.AddressType) async throws { + guard let node else { + throw AppError(serviceError: .nodeNotSetup) + } + guard let mnemonic = try Keychain.loadString(key: .bip39Mnemonic(index: currentWalletIndex)) else { + throw CustomServiceError.mnemonicNotFound + } + let passphraseRaw = try? Keychain.loadString(key: .bip39Passphrase(index: currentWalletIndex)) + let passphrase = passphraseRaw?.isEmpty == true ? nil : passphraseRaw + + try await ServiceQueue.background(.ldk) { + try node.addAddressTypeToMonitorWithMnemonic(addressType: addressType, mnemonic: mnemonic, passphrase: passphrase) + } + } + + func removeAddressTypeFromMonitor(_ addressType: LDKNode.AddressType) async throws { + guard let node else { + throw AppError(serviceError: .nodeNotSetup) + } + + try await ServiceQueue.background(.ldk) { + try node.removeAddressTypeFromMonitor(addressType: addressType) + } + } + + func setPrimaryAddressType(_ addressType: LDKNode.AddressType) async throws { + guard let node else { + throw AppError(serviceError: .nodeNotSetup) + } + guard let mnemonic = try Keychain.loadString(key: .bip39Mnemonic(index: currentWalletIndex)) else { + throw CustomServiceError.mnemonicNotFound + } + let passphraseRaw = try? Keychain.loadString(key: .bip39Passphrase(index: currentWalletIndex)) + let passphrase = passphraseRaw?.isEmpty == true ? nil : passphraseRaw + + try await ServiceQueue.background(.ldk) { + try node.setPrimaryAddressTypeWithMnemonic(addressType: addressType, mnemonic: mnemonic, passphrase: passphrase) + } + } + func getChannelFundableBalance() async throws -> UInt64 { guard let node else { throw AppError(serviceError: .nodeNotSetup) diff --git a/Bitkit/Utilities/Errors.swift b/Bitkit/Utilities/Errors.swift index b1301a7b4..3b9494c01 100644 --- a/Bitkit/Utilities/Errors.swift +++ b/Bitkit/Utilities/Errors.swift @@ -400,6 +400,18 @@ struct AppError: LocalizedError { case let .BackgroundSyncNotEnabled(ldkMessage): message = "Background sync not enabled" debugMessage = ldkMessage + case let .AddressTypeAlreadyMonitored(message: ldkMessage): + message = "Address type already monitored" + debugMessage = ldkMessage + case let .AddressTypeIsPrimary(message: ldkMessage): + message = "Address type is primary" + debugMessage = ldkMessage + case let .AddressTypeNotMonitored(message: ldkMessage): + message = "Address type not monitored" + debugMessage = ldkMessage + case let .InvalidSeedBytes(message: ldkMessage): + message = "Invalid seed bytes" + debugMessage = ldkMessage } Logger.error("\(message) [\(debugMessage ?? "")]", context: "ldk-node error") } diff --git a/Bitkit/ViewModels/SettingsViewModel.swift b/Bitkit/ViewModels/SettingsViewModel.swift index f8d1ede86..ffde1d045 100644 --- a/Bitkit/ViewModels/SettingsViewModel.swift +++ b/Bitkit/ViewModels/SettingsViewModel.swift @@ -338,6 +338,15 @@ class SettingsViewModel: NSObject, ObservableObject { if !current.contains(addressType) { current.append(addressType) addressTypesToMonitor = current + + do { + try await lightningService.addAddressTypeToMonitor(addressType) + try await lightningService.sync() + } catch { + Logger.error("Failed to add address type to monitor: \(error)") + addressTypesToMonitor = previousAddressTypesToMonitor + return false + } } } else { if addressType == selectedAddressType { return false } @@ -346,12 +355,10 @@ class SettingsViewModel: NSObject, ObservableObject { let balance = try await getBalanceForAddressType(addressType) if balance > 0 { return false } } catch { - // Fail safely: block disable if balance check fails Logger.error("Failed to check balance for \(addressType), preventing disable: \(error)") return false } - // At least one native witness type required for Lightning let nativeWitnessTypes: [AddressScriptType] = [.nativeSegwit, .taproot] let remainingNativeWitness = current.filter { $0 != addressType && nativeWitnessTypes.contains($0) } if remainingNativeWitness.isEmpty { @@ -360,18 +367,15 @@ class SettingsViewModel: NSObject, ObservableObject { current.removeAll { $0 == addressType } addressTypesToMonitor = current - } - UserDefaults.standard.synchronize() - - do { - try await lightningService.restart() - try await lightningService.sync() - } catch { - Logger.error("Failed to restart node after monitored types change: \(error)") - addressTypesToMonitor = previousAddressTypesToMonitor - UserDefaults.standard.synchronize() - return false + do { + try await lightningService.removeAddressTypeFromMonitor(addressType) + try await lightningService.sync() + } catch { + Logger.error("Failed to remove address type from monitor: \(error)") + addressTypesToMonitor = previousAddressTypesToMonitor + return false + } } wallet?.syncState() @@ -434,18 +438,23 @@ class SettingsViewModel: NSObject, ObservableObject { guard changed else { return } + let toRemove = addressTypesToMonitor.filter { !newMonitored.contains($0) } addressTypesToMonitor = newMonitored - UserDefaults.standard.synchronize() - + for type in toRemove { + do { + try await lightningService.removeAddressTypeFromMonitor(type) + } catch { + Logger.error("Failed to remove address type \(type) from monitor: \(error)") + } + } do { - try await lightningService.restart() try await lightningService.sync() Logger.info( "Pruned empty address types after restore: \(newMonitored.map { Self.addressTypeToString($0) }.joined(separator: ","))", context: "SettingsViewModel" ) } catch { - Logger.error("Failed to restart after prune: \(error)") + Logger.error("Failed to sync after prune: \(error)") } } @@ -502,26 +511,16 @@ class SettingsViewModel: NSObject, ObservableObject { selectedAddressType = addressType ensureMonitoring(addressType) - UserDefaults.standard.set("", forKey: "onchainAddress") - UserDefaults.standard.set("", forKey: "bip21") - UserDefaults.standard.synchronize() - - if let wallet { - wallet.onchainAddress = "" - wallet.bip21 = "" - } - do { - try await lightningService.restart() + try await lightningService.setPrimaryAddressType(addressType) try await lightningService.sync() await generateAndUpdateAddress(addressType: addressType, wallet: wallet) } catch { - Logger.error("Failed to restart node after address type change: \(error)") + Logger.error("Failed to set primary address type: \(error)") selectedAddressType = previousSelectedAddressType addressTypesToMonitor = previousAddressTypesToMonitor UserDefaults.standard.set(previousOnchainAddress, forKey: "onchainAddress") UserDefaults.standard.set(previousBip21, forKey: "bip21") - UserDefaults.standard.synchronize() if let wallet { wallet.onchainAddress = previousOnchainAddress wallet.bip21 = previousBip21 @@ -539,7 +538,6 @@ class SettingsViewModel: NSObject, ObservableObject { let newAddress = try await lightningService.newAddressForType(addressType) UserDefaults.standard.set(newAddress, forKey: "onchainAddress") - UserDefaults.standard.synchronize() if let wallet { wallet.onchainAddress = newAddress @@ -548,7 +546,6 @@ class SettingsViewModel: NSObject, ObservableObject { } catch { Logger.error("Failed to generate new address: \(error)") UserDefaults.standard.set("", forKey: "onchainAddress") - UserDefaults.standard.synchronize() if let wallet { wallet.onchainAddress = "" } diff --git a/Bitkit/Views/Settings/Advanced/AddressTypeLoadingView.swift b/Bitkit/Views/Settings/Advanced/AddressTypeLoadingView.swift deleted file mode 100644 index 8b694d287..000000000 --- a/Bitkit/Views/Settings/Advanced/AddressTypeLoadingView.swift +++ /dev/null @@ -1,77 +0,0 @@ -import SwiftUI - -struct AddressTypeLoadingView: View { - let targetAddressType: AddressScriptType? - let isMonitoringChange: Bool - - private var navTitle: String { - isMonitoringChange ? t("settings__adv__addr_type_loading_nav_monitoring") : t("settings__adv__addr_type_loading_nav_address") - } - - private var headline: String { - if let addressType = targetAddressType, !isMonitoringChange { - return t("settings__adv__addr_type_loading_headline", variables: ["type": addressType.localizedTitle]) - } - return t("settings__adv__addr_type_loading_updating") - } - - private var description: String { - t("settings__adv__addr_type_loading_desc") - } - - var body: some View { - VStack(spacing: 0) { - NavigationBar(title: navTitle, showBackButton: false, showMenuButton: false) - - VStack(spacing: 0) { - VStack { - Spacer() - - Image("wallet") - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity) - .aspectRatio(1, contentMode: .fit) - - Spacer() - } - .frame(maxWidth: .infinity) - .frame(maxHeight: .infinity) - .layoutPriority(1) - - VStack(alignment: .leading, spacing: 14) { - DisplayText(headline) - .frame(maxWidth: .infinity, alignment: .leading) - .fixedSize(horizontal: false, vertical: true) - BodyMText(description) - .frame(maxWidth: .infinity, alignment: .leading) - .fixedSize(horizontal: false, vertical: true) - } - - ActivityIndicator(size: 32) - .padding(.top, 32) - } - .padding(.horizontal, 16) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 16) - .bottomSafeAreaPadding() - .background(Color.customBlack) - .navigationBarHidden(true) - .accessibilityIdentifier("AddressTypeLoadingView") - .onAppear { - UIApplication.shared.isIdleTimerDisabled = true - } - .onDisappear { - UIApplication.shared.isIdleTimerDisabled = false - } - } -} - -#Preview { - AddressTypeLoadingView( - targetAddressType: .taproot, - isMonitoringChange: false - ) - .preferredColorScheme(.dark) -} diff --git a/Bitkit/Views/Settings/Advanced/AddressTypePreferenceView.swift b/Bitkit/Views/Settings/Advanced/AddressTypePreferenceView.swift index 5349747b1..98f0f04db 100644 --- a/Bitkit/Views/Settings/Advanced/AddressTypePreferenceView.swift +++ b/Bitkit/Views/Settings/Advanced/AddressTypePreferenceView.swift @@ -152,17 +152,10 @@ struct AddressTypePreferenceView: View { @EnvironmentObject private var settingsViewModel: SettingsViewModel @EnvironmentObject private var wallet: WalletViewModel @EnvironmentObject private var app: AppViewModel - @EnvironmentObject private var navigation: NavigationViewModel @AppStorage("showDevSettings") private var showDevSettings = Env.isDebug @State private var showMonitoredTypesNote = false - @State private var showLoadingView = false - @State private var loadingAddressType: AddressScriptType? - @State private var isMonitoringChange = false - @State private var loadingTask: Task? - - private let timeoutSeconds: UInt64 = 60 var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -183,27 +176,12 @@ struct AddressTypePreferenceView: View { ) { guard settingsViewModel.selectedAddressType != addressType else { return } - loadingAddressType = addressType - isMonitoringChange = false - showLoadingView = true - - loadingTask = Task { - var success = false - let didTimeout = await withTimeout(seconds: timeoutSeconds) { - success = await settingsViewModel.updateAddressType(addressType, wallet: wallet) - } - - showLoadingView = false + app.toast(type: .info, title: t("settings__adv__addr_type_applying"), autoHide: false) - if didTimeout { - app.toast( - type: .error, - title: t("settings__adv__addr_type_timeout_title"), - description: t("settings__adv__addr_type_timeout_desc") - ) - } else if success { + Task { + let success = await settingsViewModel.updateAddressType(addressType, wallet: wallet) + if success { Haptics.notify(.success) - navigation.reset() app.toast( type: .success, title: t("settings__adv__addr_type_changed_title"), @@ -253,25 +231,11 @@ struct AddressTypePreferenceView: View { isMonitored: settingsViewModel.isMonitoring(addressType), isSelectedType: settingsViewModel.selectedAddressType == addressType ) { enabled in - loadingAddressType = addressType - isMonitoringChange = true - showLoadingView = true - - loadingTask = Task { - var success = false - let didTimeout = await withTimeout(seconds: timeoutSeconds) { - success = await settingsViewModel.setMonitoring(addressType, enabled: enabled, wallet: wallet) - } + app.toast(type: .info, title: t("settings__adv__addr_type_applying"), autoHide: false) - showLoadingView = false - - if didTimeout { - app.toast( - type: .error, - title: t("settings__adv__addr_type_timeout_title"), - description: t("settings__adv__addr_type_timeout_desc") - ) - } else if success { + Task { + let success = await settingsViewModel.setMonitoring(addressType, enabled: enabled, wallet: wallet) + if success { Haptics.notify(.success) app.toast( type: .success, @@ -318,41 +282,6 @@ struct AddressTypePreferenceView: View { .navigationBarHidden(true) .padding(.horizontal, 16) .bottomSafeAreaPadding() - .fullScreenCover(isPresented: $showLoadingView) { - AddressTypeLoadingView( - targetAddressType: loadingAddressType, - isMonitoringChange: isMonitoringChange - ) - } - .onDisappear { - loadingTask?.cancel() - } - } -} - -private struct TimeoutError: Error {} - -/// Returns true if operation timed out. -private func withTimeout(seconds: UInt64, operation: @escaping () async -> some Any) async -> Bool { - do { - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - _ = await operation() - } - - group.addTask { - try await Task.sleep(nanoseconds: seconds * 1_000_000_000) - throw TimeoutError() - } - - try await group.next() - group.cancelAll() - } - return false - } catch is TimeoutError { - return true - } catch { - return false } } @@ -363,7 +292,6 @@ private func withTimeout(seconds: UInt64, operation: @escaping () async -> some .environmentObject(SettingsViewModel.shared) .environmentObject(app) .environmentObject(WalletViewModel()) - .environmentObject(NavigationViewModel()) } .preferredColorScheme(.dark) } diff --git a/BitkitTests/AddressTypeIntegrationTests.swift b/BitkitTests/AddressTypeIntegrationTests.swift index cab531740..bbef5ce7e 100644 --- a/BitkitTests/AddressTypeIntegrationTests.swift +++ b/BitkitTests/AddressTypeIntegrationTests.swift @@ -106,7 +106,7 @@ final class AddressTypeIntegrationTests: XCTestCase { func testSetMonitoringDisableForEmptyTypeSucceeds() async throws { try await setupWalletAndNode() - // Add taproot via setMonitoring (handles restart internally so LDK creates taproot wallet) + // Add taproot via setMonitoring (uses addAddressTypeToMonitor runtime API) settings.addressTypesToMonitor = [.nativeSegwit] UserDefaults.standard.synchronize() let addSuccess = await settings.setMonitoring(.taproot, enabled: true, wallet: nil) @@ -156,7 +156,7 @@ final class AddressTypeIntegrationTests: XCTestCase { settings.addressTypesToMonitor = [.nativeSegwit, .taproot] UserDefaults.standard.synchronize() - try await lightning.restart() + try await lightning.addAddressTypeToMonitor(.taproot) try await lightning.sync() Logger.test("Pruning empty address types after restore", context: "AddressTypeIntegrationTests") From b9c9dcf1b185845f845b96b1cc732b26a93bbbde Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 18 Feb 2026 13:38:48 +0700 Subject: [PATCH 2/3] fix tests --- Bitkit.xcodeproj/project.pbxproj | 2 +- BitkitTests/AddressTypeIntegrationTests.swift | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 39f000bf5..7c2413b34 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -895,7 +895,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/synonymdev/vss-rust-client-ffi"; requirement = { - branch = "master"; + branch = master; kind = branch; }; }; diff --git a/BitkitTests/AddressTypeIntegrationTests.swift b/BitkitTests/AddressTypeIntegrationTests.swift index 5d56e0692..02328bb32 100644 --- a/BitkitTests/AddressTypeIntegrationTests.swift +++ b/BitkitTests/AddressTypeIntegrationTests.swift @@ -6,7 +6,6 @@ import XCTest final class AddressTypeIntegrationTests: XCTestCase { let walletIndex = 0 - let lightning = LightningService.shared let settings = SettingsViewModel.shared override func setUp() async throws { @@ -16,6 +15,7 @@ final class AddressTypeIntegrationTests: XCTestCase { } override func tearDown() async throws { + let lightning = await MainActor.run { settings.lightningService } lightning.dumpLdkLogs() try Keychain.wipeEntireKeychain() let isRunning = await MainActor.run { lightning.status?.isRunning == true } @@ -39,6 +39,7 @@ final class AddressTypeIntegrationTests: XCTestCase { try skipIfNotRegtest() let mnemonic = try StartupHandler.createNewWallet(bip39Passphrase: nil, walletIndex: walletIndex) XCTAssertFalse(mnemonic.isEmpty) + let lightning = await MainActor.run { settings.lightningService } try await lightning.setup(walletIndex: walletIndex) try await lightning.start() try await lightning.sync() @@ -49,17 +50,18 @@ final class AddressTypeIntegrationTests: XCTestCase { try await setupWalletAndNode() Logger.test("Getting balance for nativeSegwit", context: "AddressTypeIntegrationTests") - let balance = try await lightning.getBalanceForAddressType(.nativeSegwit) + let balance = try await settings.lightningService.getBalanceForAddressType(.nativeSegwit) XCTAssertGreaterThanOrEqual(balance.totalSats, 0) Logger.test("Balance: \(balance.totalSats) sats", context: "AddressTypeIntegrationTests") } + @MainActor func testGetChannelFundableBalance() async throws { try await setupWalletAndNode() Logger.test("Getting channel fundable balance", context: "AddressTypeIntegrationTests") - let (selectedType, monitoredTypes) = LightningService.addressTypeStateFromUserDefaults() - let fundable = try await lightning.getChannelFundableBalance(selectedType: selectedType, monitoredTypes: monitoredTypes) + let (selectedType, monitoredTypes) = Bitkit.LightningService.addressTypeStateFromUserDefaults() + let fundable = try await settings.lightningService.getChannelFundableBalance(selectedType: selectedType, monitoredTypes: monitoredTypes) XCTAssertGreaterThanOrEqual(fundable, 0) Logger.test("Channel fundable: \(fundable) sats", context: "AddressTypeIntegrationTests") } @@ -107,7 +109,6 @@ final class AddressTypeIntegrationTests: XCTestCase { func testSetMonitoringDisableForEmptyTypeSucceeds() async throws { try await setupWalletAndNode() - // Add taproot via setMonitoring (uses addAddressTypeToMonitor runtime API) settings.addressTypesToMonitor = [.nativeSegwit] UserDefaults.standard.synchronize() let addSuccess = await settings.setMonitoring(.taproot, enabled: true, wallet: nil) @@ -137,7 +138,6 @@ final class AddressTypeIntegrationTests: XCTestCase { func testSetMonitoringDisableSelectedTypeFails() async throws { try await setupWalletAndNode() - // Add taproot, then set taproot as selected; cannot disable selected type settings.addressTypesToMonitor = [.nativeSegwit] UserDefaults.standard.synchronize() let addSuccess = await settings.setMonitoring(.taproot, enabled: true, wallet: nil) @@ -157,8 +157,8 @@ final class AddressTypeIntegrationTests: XCTestCase { settings.addressTypesToMonitor = [.nativeSegwit, .taproot] UserDefaults.standard.synchronize() - try await lightning.addAddressTypeToMonitor(.taproot) - try await lightning.sync() + try await settings.lightningService.addAddressTypeToMonitor(.taproot) + try await settings.lightningService.sync() Logger.test("Pruning empty address types after restore", context: "AddressTypeIntegrationTests") await settings.pruneEmptyAddressTypesAfterRestore() From 5b7b42c82d2d824f9db81aed5e31398162bd14a2 Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 18 Feb 2026 14:33:57 +0700 Subject: [PATCH 3/3] update ldk-node version --- Bitkit.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 7c2413b34..d24478101 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -928,7 +928,7 @@ repositoryURL = "https://github.com/synonymdev/ldk-node"; requirement = { kind = revision; - revision = 857056a27def6ee281d0a82d1799637b04244c3a; + revision = 51528ebda1925f3d2d882166170f7fcdaf33b784; }; }; 96DEA0382DE8BBA1009932BF /* XCRemoteSwiftPackageReference "bitkit-core" */ = { diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 82c019a42..672d8e331 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,7 +24,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/synonymdev/ldk-node", "state" : { - "revision" : "857056a27def6ee281d0a82d1799637b04244c3a" + "revision" : "51528ebda1925f3d2d882166170f7fcdaf33b784" } }, {