From b489af0564eb76fa69ff228f600dfca4ed3d058a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 1 Jan 2026 09:29:43 -0300 Subject: [PATCH 1/4] fix: don't prevent the user from using purchased channels when geoblocked --- Bitkit/ViewModels/WalletViewModel.swift | 8 +------- Bitkit/Views/Wallets/Receive/ReceiveQr.swift | 15 +++------------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/Bitkit/ViewModels/WalletViewModel.swift b/Bitkit/ViewModels/WalletViewModel.swift index 83b620dc..3848d356 100644 --- a/Bitkit/ViewModels/WalletViewModel.swift +++ b/Bitkit/ViewModels/WalletViewModel.swift @@ -591,13 +591,7 @@ class WalletViewModel: ObservableObject { let amountSats = invoiceAmountSats > 0 ? invoiceAmountSats : nil - // When geoblocked, only create Lightning invoice if we have non-LSP channels - let isGeoblocked = GeoService.shared.isGeoBlocked - let hasUsableChannels: Bool = if isGeoblocked { - hasNonLspChannels() - } else { - channels?.count ?? 0 > 0 - } + let hasUsableChannels = channels?.contains(where: \.isChannelReady) ?? false if hasUsableChannels { if forceRefreshBolt11 || bolt11.isEmpty { diff --git a/Bitkit/Views/Wallets/Receive/ReceiveQr.swift b/Bitkit/Views/Wallets/Receive/ReceiveQr.swift index c1e415e2..5e79a064 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveQr.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveQr.swift @@ -43,11 +43,7 @@ struct ReceiveQr: View { } private var hasUsableChannels: Bool { - if GeoService.shared.isGeoBlocked { - return wallet.hasNonLspChannels() - } else { - return wallet.channelCount != 0 - } + return wallet.channels?.contains(where: \.isChannelReady) ?? false } private var availableTabItems: [TabItem] { @@ -67,12 +63,7 @@ struct ReceiveQr: View { } var showingCjitOnboarding: Bool { - // Show CJIT onboarding when: - // 1. No channels at all, OR - // 2. Geoblocked with only Blocktank channels (treat as no usable channels) - let hasNoUsableChannels = (wallet.channelCount == 0) || - (GeoService.shared.isGeoBlocked && !wallet.hasNonLspChannels()) - return hasNoUsableChannels && cjitInvoice == nil && selectedTab == .spending + return !hasUsableChannels && cjitInvoice == nil && selectedTab == .spending } var body: some View { @@ -109,7 +100,7 @@ struct ReceiveQr: View { .foregroundColor(.purpleAccent), isDisabled: wallet.nodeLifecycleState != .running ) { - if GeoService.shared.isGeoBlocked && !wallet.hasNonLspChannels() { + if GeoService.shared.isGeoBlocked && !hasUsableChannels { navigationPath.append(.cjitGeoBlocked) } else { navigationPath.append(.cjitAmount) From 191db3945389998115a9b238362b869cd2359965 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 1 Jan 2026 09:46:39 -0300 Subject: [PATCH 2/4] refactor: extract hasUsableChannels to WalletViewModel --- Bitkit/ViewModels/WalletViewModel.swift | 6 ++++-- Bitkit/Views/Wallets/Receive/ReceiveQr.swift | 12 ++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Bitkit/ViewModels/WalletViewModel.swift b/Bitkit/ViewModels/WalletViewModel.swift index 3848d356..c709eb11 100644 --- a/Bitkit/ViewModels/WalletViewModel.swift +++ b/Bitkit/ViewModels/WalletViewModel.swift @@ -559,6 +559,10 @@ class WalletViewModel: ObservableObject { return capacity } + var hasUsableChannels: Bool { + return channels?.contains(where: \.isChannelReady) ?? false + } + /// Check if there are non-LSP (non-Blocktank) channels available /// Used for geoblocking to determine if Lightning operations can proceed func hasNonLspChannels() -> Bool { @@ -591,8 +595,6 @@ class WalletViewModel: ObservableObject { let amountSats = invoiceAmountSats > 0 ? invoiceAmountSats : nil - let hasUsableChannels = channels?.contains(where: \.isChannelReady) ?? false - if hasUsableChannels { if forceRefreshBolt11 || bolt11.isEmpty { bolt11 = try await createInvoice(amountSats: amountSats, note: invoiceNote) diff --git a/Bitkit/Views/Wallets/Receive/ReceiveQr.swift b/Bitkit/Views/Wallets/Receive/ReceiveQr.swift index 5e79a064..88eb3467 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveQr.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveQr.swift @@ -42,13 +42,9 @@ struct ReceiveQr: View { } } - private var hasUsableChannels: Bool { - return wallet.channels?.contains(where: \.isChannelReady) ?? false - } - private var availableTabItems: [TabItem] { // Only show unified tab if there are usable channels - if hasUsableChannels { + if wallet.hasUsableChannels { return [ TabItem(.savings), TabItem(.unified, activeColor: .white), @@ -63,7 +59,7 @@ struct ReceiveQr: View { } var showingCjitOnboarding: Bool { - return !hasUsableChannels && cjitInvoice == nil && selectedTab == .spending + return !wallet.hasUsableChannels && cjitInvoice == nil && selectedTab == .spending } var body: some View { @@ -79,7 +75,7 @@ struct ReceiveQr: View { TabView(selection: $selectedTab) { tabContent(for: .savings) - if hasUsableChannels { + if wallet.hasUsableChannels { tabContent(for: .unified) } @@ -100,7 +96,7 @@ struct ReceiveQr: View { .foregroundColor(.purpleAccent), isDisabled: wallet.nodeLifecycleState != .running ) { - if GeoService.shared.isGeoBlocked && !hasUsableChannels { + if GeoService.shared.isGeoBlocked && !wallet.hasUsableChannels { navigationPath.append(.cjitGeoBlocked) } else { navigationPath.append(.cjitAmount) From 771737a7ad0bcbf12fcda372fb788362229f1394 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 1 Jan 2026 09:57:35 -0300 Subject: [PATCH 3/4] fix: remove geoblock restrictions from inbound and outbound calculation --- Bitkit/Services/LightningService.swift | 41 +------------------ Bitkit/ViewModels/WalletViewModel.swift | 21 ---------- .../Views/Wallets/Receive/ReceiveEdit.swift | 5 +-- 3 files changed, 2 insertions(+), 65 deletions(-) diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index bf9fdd93..4798268d 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -368,12 +368,8 @@ class LightningService { return false } - // When geoblocked, only count non-LSP channels - let isGeoblocked = GeoService.shared.isGeoBlocked - let channelsToUse = isGeoblocked ? getNonLspChannels() : channels - let totalNextOutboundHtlcLimitSats = - channelsToUse + channels .filter(\.isUsable) .map(\.nextOutboundHtlcLimitMsat) .reduce(0, +) / 1000 @@ -436,16 +432,6 @@ class LightningService { throw AppError(serviceError: .nodeNotSetup) } - // When geoblocked, verify we have external (non-LSP) peers - let isGeoblocked = GeoService.shared.isGeoBlocked - if isGeoblocked && !hasExternalPeers() { - Logger.error("Cannot send Lightning payment when geoblocked without external peers") - throw AppError( - message: "Lightning send unavailable", - debugMessage: "You need channels with non-Blocktank nodes to send Lightning payments." - ) - } - Logger.info("Paying bolt11: \(bolt11)") do { @@ -657,31 +643,6 @@ extension LightningService { try node.getAddressBalance(addressStr: address) } } - - /// Returns LSP (Blocktank) peer node IDs - func getLspPeerNodeIds() -> [String] { - return Env.trustedLnPeers.map(\.nodeId) - } - - /// Checks if there are connected peers other than LSP peers - /// Used for geoblocking to determine if Lightning operations can proceed - func hasExternalPeers() -> Bool { - guard let peers else { return false } - let lspNodeIds = Set(getLspPeerNodeIds()) - return peers.contains { peer in - !lspNodeIds.contains(peer.nodeId) - } - } - - /// Filters channels to exclude LSP channels - /// Used for geoblocking to only allow operations through non-Blocktank channels - func getNonLspChannels() -> [ChannelDetails] { - guard let channels else { return [] } - let lspNodeIds = Set(getLspPeerNodeIds()) - return channels.filter { channel in - !lspNodeIds.contains(channel.counterpartyNodeId) - } - } } // MARK: Events diff --git a/Bitkit/ViewModels/WalletViewModel.swift b/Bitkit/ViewModels/WalletViewModel.swift index c709eb11..f055dd20 100644 --- a/Bitkit/ViewModels/WalletViewModel.swift +++ b/Bitkit/ViewModels/WalletViewModel.swift @@ -544,31 +544,10 @@ class WalletViewModel: ObservableObject { return capacity } - /// Total inbound Lightning capacity excluding LSP (Blocktank) channels - /// Used when geoblocked to show only non-Blocktank receiving capacity - var totalNonLspInboundLightningSats: UInt64? { - let nonLspChannels = lightningService.getNonLspChannels() - guard !nonLspChannels.isEmpty else { - return nil - } - - var capacity: UInt64 = 0 - for channel in nonLspChannels { - capacity += channel.inboundCapacityMsat / 1000 - } - return capacity - } - var hasUsableChannels: Bool { return channels?.contains(where: \.isChannelReady) ?? false } - /// Check if there are non-LSP (non-Blocktank) channels available - /// Used for geoblocking to determine if Lightning operations can proceed - func hasNonLspChannels() -> Bool { - return !lightningService.getNonLspChannels().isEmpty - } - func refreshBip21(forceRefreshBolt11: Bool = false) async throws { // Get old payment ID and tags before refreshing (which may change payment ID) let oldPaymentId = await paymentId() diff --git a/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift b/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift index e6b63a2d..f2c02ba1 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveEdit.swift @@ -177,10 +177,7 @@ struct ReceiveEdit: View { private func needsAdditionalCjit() -> Bool { let isGeoBlocked = GeoService.shared.isGeoBlocked let minimumAmount = blocktank.minCjitSats ?? 0 - // When geoblocked, only count non-LSP inbound capacity - let inboundCapacity = isGeoBlocked - ? (wallet.totalNonLspInboundLightningSats ?? 0) - : (wallet.totalInboundLightningSats ?? 0) + let inboundCapacity = wallet.totalInboundLightningSats ?? 0 let invoiceAmount = amountViewModel.amountSats // Calculate maxClientBalance using TransferViewModel From 3c6bfd9f1e2448170546b464827c754a4c5a6acb Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 1 Jan 2026 10:08:54 -0300 Subject: [PATCH 4/4] refactor: clarify geoblocked cjit navigation --- Bitkit/Views/Wallets/Receive/ReceiveQr.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Bitkit/Views/Wallets/Receive/ReceiveQr.swift b/Bitkit/Views/Wallets/Receive/ReceiveQr.swift index 88eb3467..e7085213 100644 --- a/Bitkit/Views/Wallets/Receive/ReceiveQr.swift +++ b/Bitkit/Views/Wallets/Receive/ReceiveQr.swift @@ -96,10 +96,10 @@ struct ReceiveQr: View { .foregroundColor(.purpleAccent), isDisabled: wallet.nodeLifecycleState != .running ) { - if GeoService.shared.isGeoBlocked && !wallet.hasUsableChannels { - navigationPath.append(.cjitGeoBlocked) - } else { + if !wallet.hasUsableChannels && !GeoService.shared.isGeoBlocked { navigationPath.append(.cjitAmount) + } else if GeoService.shared.isGeoBlocked { + navigationPath.append(.cjitGeoBlocked) } } } else {