Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions Bitkit/Services/LightningService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -368,12 +368,8 @@
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
Expand Down Expand Up @@ -436,16 +432,6 @@
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 {
Expand Down Expand Up @@ -488,7 +474,7 @@
}

func closeChannel(_ channel: ChannelDetails, force: Bool = false, forceCloseReason: String? = nil) async throws {
guard let node else {

Check warning on line 477 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

value 'node' was defined but never used; consider replacing with boolean test

Check warning on line 477 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

value 'node' was defined but never used; consider replacing with boolean test
throw AppError(serviceError: .nodeNotStarted)
}

Expand Down Expand Up @@ -657,31 +643,6 @@
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
Expand All @@ -706,7 +667,7 @@
onEvent?(event)

switch event {
case let .paymentSuccessful(paymentId, paymentHash, paymentPreimage, feePaidMsat):

Check warning on line 670 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'paymentPreimage' was never used; consider replacing with '_' or removing it

Check warning on line 670 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

immutable value 'paymentPreimage' was never used; consider replacing with '_' or removing it
Logger.info("✅ Payment successful: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) feePaidMsat: \(feePaidMsat ?? 0)")
Task {
let hash = paymentId ?? paymentHash
Expand All @@ -731,7 +692,7 @@
Logger.warn("No paymentId or paymentHash available for failed payment", context: "LightningService")
}
}
case let .paymentReceived(paymentId, paymentHash, amountMsat, feePaidMsat):

Check warning on line 695 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'feePaidMsat' was never used; consider replacing with '_' or removing it
Logger.info("🤑 Payment received: paymentId: \(paymentId ?? "?") paymentHash: \(paymentHash) amountMsat: \(amountMsat)")
Task {
let hash = paymentId ?? paymentHash
Expand All @@ -741,7 +702,7 @@
Logger.error("Failed to handle payment received for \(hash): \(error)", context: "LightningService")
}
}
case let .paymentClaimable(paymentId, paymentHash, claimableAmountMsat, claimDeadline, customRecords):

Check warning on line 705 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

immutable value 'claimDeadline' was never used; consider replacing with '_' or removing it
Logger.info(
"🫰 Payment claimable: paymentId: \(paymentId) paymentHash: \(paymentHash) claimableAmountMsat: \(claimableAmountMsat)"
)
Expand Down Expand Up @@ -770,7 +731,7 @@

if let channel {
await registerClosedChannel(channel: channel, reason: reasonString)
await MainActor.run {

Check warning on line 734 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Tests

result of call to 'run(resultType:body:)' is unused

Check warning on line 734 in Bitkit/Services/LightningService.swift

View workflow job for this annotation

GitHub Actions / Run Integration Tests

result of call to 'run(resultType:body:)' is unused
channelCache.removeValue(forKey: channelIdString)
}
} else {
Expand Down
29 changes: 2 additions & 27 deletions Bitkit/ViewModels/WalletViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -544,25 +544,8 @@ 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
}

/// 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
var hasUsableChannels: Bool {
return channels?.contains(where: \.isChannelReady) ?? false
}

func refreshBip21(forceRefreshBolt11: Bool = false) async throws {
Expand Down Expand Up @@ -591,14 +574,6 @@ 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
}

if hasUsableChannels {
if forceRefreshBolt11 || bolt11.isEmpty {
bolt11 = try await createInvoice(amountSats: amountSats, note: invoiceNote)
Expand Down
5 changes: 1 addition & 4 deletions Bitkit/Views/Wallets/Receive/ReceiveEdit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 6 additions & 19 deletions Bitkit/Views/Wallets/Receive/ReceiveQr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,9 @@ struct ReceiveQr: View {
}
}

private var hasUsableChannels: Bool {
if GeoService.shared.isGeoBlocked {
return wallet.hasNonLspChannels()
} else {
return wallet.channelCount != 0
}
}

private var availableTabItems: [TabItem<ReceiveTab>] {
// Only show unified tab if there are usable channels
if hasUsableChannels {
if wallet.hasUsableChannels {
return [
TabItem(.savings),
TabItem(.unified, activeColor: .white),
Expand All @@ -67,12 +59,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 !wallet.hasUsableChannels && cjitInvoice == nil && selectedTab == .spending
}

var body: some View {
Expand All @@ -88,7 +75,7 @@ struct ReceiveQr: View {
TabView(selection: $selectedTab) {
tabContent(for: .savings)

if hasUsableChannels {
if wallet.hasUsableChannels {
tabContent(for: .unified)
}

Expand All @@ -109,10 +96,10 @@ struct ReceiveQr: View {
.foregroundColor(.purpleAccent),
isDisabled: wallet.nodeLifecycleState != .running
) {
if GeoService.shared.isGeoBlocked && !wallet.hasNonLspChannels() {
navigationPath.append(.cjitGeoBlocked)
} else {
if !wallet.hasUsableChannels && !GeoService.shared.isGeoBlocked {
navigationPath.append(.cjitAmount)
} else if GeoService.shared.isGeoBlocked {
navigationPath.append(.cjitGeoBlocked)
}
}
} else {
Expand Down
Loading