From 71f265b66fbd2781ed2d3eaaa824790491617a48 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 28 Oct 2025 09:29:36 -0500 Subject: [PATCH 1/6] chore: megalith lsp default --- .../LightningNodeService.swift | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift b/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift index 9d0790d..76b284e 100644 --- a/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift +++ b/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift @@ -60,14 +60,24 @@ class LightningNodeService { fatalError("Configuration error: No Esplora servers available for \(network)") } self.server = server - self.lsp = - LightningServiceProvider.getByNodeId( - backupInfo.lspNodeId ?? LightningServiceProvider.see_signet.nodeId - ) ?? .see_signet + let resolvedLspNodeId = + backupInfo.lspNodeId ?? LightningServiceProvider.megalith_signet.nodeId + if let resolvedLsp = LightningServiceProvider.getByNodeId(resolvedLspNodeId) { + print( + "LightningNodeService: using LSP \(resolvedLsp.name) (\(resolvedLsp.nodeId))" + ) + self.lsp = resolvedLsp + } else { + print( + "LightningNodeService: missing LSP for node id \(resolvedLspNodeId); defaulting to Megalith" + ) + self.lsp = .megalith_signet + } } else { self.network = .signet - self.server = .mutiny_signet - self.lsp = .see_signet + self.server = .mutiny_signet //.mutiny_signet + print("LightningNodeService: no backup info; defaulting to Megalith LSP") + self.lsp = .megalith_signet //.see_signet } self.keyService = keyService From eb9009cbfe9650495e5b4a9cdc37e27e2a328b48 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 28 Oct 2025 09:34:51 -0500 Subject: [PATCH 2/6] fix: ui still saying see --- LDKNodeMonday/App/WalletClient.swift | 3 ++- .../Service/Lightning Service/LightningNodeService.swift | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/LDKNodeMonday/App/WalletClient.swift b/LDKNodeMonday/App/WalletClient.swift index 4bb464f..7929b37 100644 --- a/LDKNodeMonday/App/WalletClient.swift +++ b/LDKNodeMonday/App/WalletClient.swift @@ -16,7 +16,7 @@ public class WalletClient { public var lightningClient: LightningNodeClient public var network = Network.signet public var server = EsploraServer.mutiny_signet - public var lsp = LightningServiceProvider.see_signet + public var lsp = LightningServiceProvider.megalith_signet public var appMode: AppMode public var appState = AppState.loading public var appError: Error? @@ -61,6 +61,7 @@ public class WalletClient { await MainActor.run { self.network = lightningClient.getNetwork() self.server = lightningClient.getServer() + self.lsp = lightningClient.getLsp() // Load LSP from keychain if available if let savedLSPNodeId = try? self.keyClient.getLSP(), diff --git a/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift b/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift index 76b284e..435ceb0 100644 --- a/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift +++ b/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift @@ -437,6 +437,10 @@ class LightningNodeService { return payments } + func currentLsp() -> LightningServiceProvider { + return lsp + } + func status() -> NodeStatus { let status = ldkNode.status() return status @@ -526,6 +530,7 @@ public struct LightningNodeClient { let listPeers: () -> [PeerDetails] let listChannels: () -> [ChannelDetails] let listPayments: () -> [PaymentDetails] + let getLsp: () -> LightningServiceProvider let status: () -> NodeStatus let deleteWallet: () throws -> Void let getBackupInfo: () throws -> BackupInfo @@ -607,6 +612,7 @@ extension LightningNodeClient { listPeers: { LightningNodeService.shared.listPeers() }, listChannels: { LightningNodeService.shared.listChannels() }, listPayments: { LightningNodeService.shared.listPayments() }, + getLsp: { LightningNodeService.shared.currentLsp() }, status: { LightningNodeService.shared.status() }, deleteWallet: { try LightningNodeService.shared.deleteWallet() }, getBackupInfo: { try LightningNodeService.shared.getBackupInfo() }, @@ -653,6 +659,7 @@ extension LightningNodeClient { listPeers: { [] }, listChannels: { [] }, listPayments: { mockPayments }, + getLsp: { .megalith_signet }, status: { NodeStatus( isRunning: true, From 8415477e8e7479223b51658454c2e7e53a09338e Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 28 Oct 2025 14:18:34 -0500 Subject: [PATCH 3/6] fix: ldknodeerror24 (no dupes, dont request jit when just onchain) --- .../Home/Receive/ReceiveViewModel.swift | 57 +++++++++++++------ .../View/Home/Receive/ReceiveView.swift | 13 +++++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/LDKNodeMonday/View Model/Home/Receive/ReceiveViewModel.swift b/LDKNodeMonday/View Model/Home/Receive/ReceiveViewModel.swift index 3a33760..b4e1ad1 100644 --- a/LDKNodeMonday/View Model/Home/Receive/ReceiveViewModel.swift +++ b/LDKNodeMonday/View Model/Home/Receive/ReceiveViewModel.swift @@ -15,6 +15,7 @@ class ReceiveViewModel: ObservableObject { @Published var paymentAddresses: [PaymentAddress?] = [] @Published var addressGenerationStatus = AddressGenerationStatus.generating @Published var receiveViewError: MondayError? + @Published var lightningWarning: MondayError? @Published var networkColor = Color.gray @Published var amountSat: UInt64 = 0 @Published var message: String = "" @@ -33,6 +34,8 @@ class ReceiveViewModel: ObservableObject { func receivePayment(amountSat: UInt64, message: String, expirySecs: UInt32) async { await MainActor.run { self.addressGenerationStatus = .generating + self.receiveViewError = nil + self.lightningWarning = nil } let receiveCapacity = maxReceiveCapacity() let needsJIT = @@ -109,23 +112,45 @@ class ReceiveViewModel: ObservableObject { let needsJIT = amountSat.satsAsMsats > receiveCapacity || (amountSat == 0 && receiveCapacity == 0) - // Always try to generate bolt11 invoice - // The needsJIT flag will handle JIT channel creation when capacity is insufficient - do { - let bolt11Invoice = try await lightningClient.bolt11Payment( - amountSat.satsAsMsats, - Bolt11InvoiceDescription.direct(description: message), //message, - expirySecs, - nil, - needsJIT - ) - let bolt11InvoiceString = bolt11Invoice.description - bolt11PaymentAddress = PaymentAddress( - type: needsJIT ? .bolt11Jit : .bolt11, - address: bolt11InvoiceString + // Always try to generate bolt11 invoice unless we know it will fail (e.g., zero amount with no inbound) + if amountSat == 0 && needsJIT { + debugPrint( + "Skipping Bolt11 generation: zero-amount request with no inbound capacity (needsJIT=true)" ) - } catch { - debugPrint("Error generating Bolt11:", error.localizedDescription) + } else { + do { + let bolt11Invoice = try await lightningClient.bolt11Payment( + amountSat.satsAsMsats, + Bolt11InvoiceDescription.direct(description: message), + expirySecs, + nil, + needsJIT + ) + let bolt11InvoiceString = bolt11Invoice.description + bolt11PaymentAddress = PaymentAddress( + type: needsJIT ? .bolt11Jit : .bolt11, + address: bolt11InvoiceString + ) + } catch let nodeError as NodeError { + let mondayError = handleNodeError(nodeError) + let pendingInbound = lightningClient.listPayments() + .filter { $0.direction == .inbound && $0.status == .pending } + let pendingSummary = pendingInbound.map { + "id=\($0.id), amountMsat=\($0.amountMsat), kind=\(String(describing: $0.kind))" + } + debugPrint( + """ + Error generating Bolt11 (needsJIT=\(needsJIT), amountMsat=\(amountSat.satsAsMsats), message=\(message)): + \(nodeError) + Pending inbound payments: \(pendingSummary) + """ + ) + await MainActor.run { + self.lightningWarning = mondayError + } + } catch { + debugPrint("Error generating Bolt11:", error.localizedDescription) + } } // Unified diff --git a/LDKNodeMonday/View/Home/Receive/ReceiveView.swift b/LDKNodeMonday/View/Home/Receive/ReceiveView.swift index e05bc6e..f9f230f 100644 --- a/LDKNodeMonday/View/Home/Receive/ReceiveView.swift +++ b/LDKNodeMonday/View/Home/Receive/ReceiveView.swift @@ -37,6 +37,19 @@ struct ReceiveView: View { selectedPaymentAddress: selectedPaymentAddress, addressArray: viewModel.paymentAddresses.compactMap { $0 } ) + if let warning = viewModel.lightningWarning { + VStack(spacing: 4) { + Text(warning.title) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(.secondary) + Text(warning.detail) + .font(.caption) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + } + .padding(.top, 16) + .padding(.horizontal, 32) + } } Spacer() From 7407b2b9353a34891bb3548d4df957ca380fdc34 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 28 Oct 2025 14:42:08 -0500 Subject: [PATCH 4/6] fix: mock words --- .../Profile/Danger/SeedViewModel.swift | 35 ++++++++++++++----- .../View/Settings/Danger/SeedView.swift | 13 +++++-- .../View/Settings/SettingsView.swift | 3 +- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/LDKNodeMonday/View Model/Profile/Danger/SeedViewModel.swift b/LDKNodeMonday/View Model/Profile/Danger/SeedViewModel.swift index fd69d77..5ffdff3 100644 --- a/LDKNodeMonday/View Model/Profile/Danger/SeedViewModel.swift +++ b/LDKNodeMonday/View Model/Profile/Danger/SeedViewModel.swift @@ -16,25 +16,44 @@ class SeedViewModel: ObservableObject { serverURL: EsploraServer.mutiny_signet.url ) @Published var seedViewError: MondayError? - private let lightningClient: LightningNodeClient + private let keyClient: KeyClient + private let lightningClient: LightningNodeClient? - init(lightningClient: LightningNodeClient) { + init(keyClient: KeyClient, lightningClient: LightningNodeClient? = nil) { + self.keyClient = keyClient self.lightningClient = lightningClient } func getSeed() { do { - let seed = try lightningClient.getBackupInfo() - self.seed = seed - } catch let error as NodeError { - let errorString = handleNodeError(error) + let seed = try keyClient.getBackupInfo() DispatchQueue.main.async { - self.seedViewError = .init(title: errorString.title, detail: errorString.detail) + self.seed = seed } } catch { + if let lightningClient { + do { + let seed = try lightningClient.getBackupInfo() + DispatchQueue.main.async { + self.seed = seed + } + return + } catch let nodeError as NodeError { + let errorString = handleNodeError(nodeError) + DispatchQueue.main.async { + self.seedViewError = .init( + title: errorString.title, + detail: errorString.detail + ) + } + return + } catch { + // Fall through to generic error handling below. + } + } DispatchQueue.main.async { self.seedViewError = .init( - title: "Unexpected error", + title: "Recovery phrase unavailable", detail: error.localizedDescription ) } diff --git a/LDKNodeMonday/View/Settings/Danger/SeedView.swift b/LDKNodeMonday/View/Settings/Danger/SeedView.swift index a8c3bb5..e16e8e9 100644 --- a/LDKNodeMonday/View/Settings/Danger/SeedView.swift +++ b/LDKNodeMonday/View/Settings/Danger/SeedView.swift @@ -10,13 +10,19 @@ import LDKNode import SwiftUI struct SeedView: View { - @ObservedObject var viewModel: SeedViewModel + @StateObject private var viewModel: SeedViewModel @State private var showAlert = false @State private var showRecoveryPhrase = false @State private var isCopied = false @State private var showCheckmark = false @State private var showingSeedViewErrorAlert = false + init(keyClient: KeyClient, lightningClient: LightningNodeClient) { + _viewModel = StateObject( + wrappedValue: SeedViewModel(keyClient: keyClient, lightningClient: lightningClient) + ) + } + var body: some View { VStack(alignment: .center) { @@ -46,7 +52,8 @@ struct SeedView: View { preferredWordsPerRow: 2, usePaging: true, wordsPerPage: 12 - ).padding() + ) + .padding() HStack { Button( @@ -98,6 +105,6 @@ struct SeedView: View { #if DEBUG #Preview { - SeedView(viewModel: .init(lightningClient: .mock)) + SeedView(keyClient: .mock, lightningClient: .mock) } #endif diff --git a/LDKNodeMonday/View/Settings/SettingsView.swift b/LDKNodeMonday/View/Settings/SettingsView.swift index c474960..18ba2c3 100644 --- a/LDKNodeMonday/View/Settings/SettingsView.swift +++ b/LDKNodeMonday/View/Settings/SettingsView.swift @@ -25,7 +25,8 @@ struct SettingsView: View { Section { NavigationLink( destination: SeedView( - viewModel: .init(lightningClient: viewModel.lightningClient) + keyClient: viewModel.keyClient, + lightningClient: viewModel.lightningClient ) ) { Label("Recovery Phrase", systemImage: "lock") From 210f501897704d2b3886f0eca700e06e96544ae1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 28 Oct 2025 14:54:34 -0500 Subject: [PATCH 5/6] fix: surface fee rate error --- LDKNodeMonday/App/WalletClient.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/LDKNodeMonday/App/WalletClient.swift b/LDKNodeMonday/App/WalletClient.swift index 7929b37..e2f87b4 100644 --- a/LDKNodeMonday/App/WalletClient.swift +++ b/LDKNodeMonday/App/WalletClient.swift @@ -74,6 +74,20 @@ public class WalletClient { self.appState = .wallet } } catch let error { + if let nodeError = error as? NodeError, + case .FeerateEstimationUpdateTimeout = nodeError + { + let warning = handleNodeError(nodeError) + await MainActor.run { + self.appError = NSError( + domain: warning.title, + code: nodeError.hashValue, + userInfo: [NSLocalizedDescriptionKey: warning.detail] + ) + self.appState = .error + } + return + } await MainActor.run { self.appError = error self.appState = .error From 078bfe31382e592fa181f99e7a4a326c657a83c0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 28 Oct 2025 14:57:03 -0500 Subject: [PATCH 6/6] lsp: see --- LDKNodeMonday/App/WalletClient.swift | 2 +- .../LightningNodeService.swift | 22 +++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/LDKNodeMonday/App/WalletClient.swift b/LDKNodeMonday/App/WalletClient.swift index e2f87b4..bed27b6 100644 --- a/LDKNodeMonday/App/WalletClient.swift +++ b/LDKNodeMonday/App/WalletClient.swift @@ -16,7 +16,7 @@ public class WalletClient { public var lightningClient: LightningNodeClient public var network = Network.signet public var server = EsploraServer.mutiny_signet - public var lsp = LightningServiceProvider.megalith_signet + public var lsp = LightningServiceProvider.see_signet public var appMode: AppMode public var appState = AppState.loading public var appError: Error? diff --git a/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift b/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift index 435ceb0..6b3c56f 100644 --- a/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift +++ b/LDKNodeMonday/Service/Lightning Service/LightningNodeService.swift @@ -61,23 +61,13 @@ class LightningNodeService { } self.server = server let resolvedLspNodeId = - backupInfo.lspNodeId ?? LightningServiceProvider.megalith_signet.nodeId - if let resolvedLsp = LightningServiceProvider.getByNodeId(resolvedLspNodeId) { - print( - "LightningNodeService: using LSP \(resolvedLsp.name) (\(resolvedLsp.nodeId))" - ) - self.lsp = resolvedLsp - } else { - print( - "LightningNodeService: missing LSP for node id \(resolvedLspNodeId); defaulting to Megalith" - ) - self.lsp = .megalith_signet - } + backupInfo.lspNodeId ?? LightningServiceProvider.see_signet.nodeId + self.lsp = + LightningServiceProvider.getByNodeId(resolvedLspNodeId) ?? .see_signet } else { self.network = .signet - self.server = .mutiny_signet //.mutiny_signet - print("LightningNodeService: no backup info; defaulting to Megalith LSP") - self.lsp = .megalith_signet //.see_signet + self.server = .mutiny_signet + self.lsp = .see_signet } self.keyService = keyService @@ -659,7 +649,7 @@ extension LightningNodeClient { listPeers: { [] }, listChannels: { [] }, listPayments: { mockPayments }, - getLsp: { .megalith_signet }, + getLsp: { .see_signet }, status: { NodeStatus( isRunning: true,