From 8fe46c8029f8dc3e24222895505866c17bcb81c0 Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 3 Dec 2025 19:48:49 -0500 Subject: [PATCH 1/2] Update LDK version to 0.7.0 --- .../xcshareddata/swiftpm/Package.resolved | 2 +- Bitkit/Services/LightningService.swift | 31 +++++++++++++---- Bitkit/Utilities/Errors.swift | 33 ++++++++++++++----- Bitkit/Utilities/StartupHandler.swift | 2 +- Bitkit/ViewModels/AppViewModel.swift | 7 +++- BitkitNotification/NotificationService.swift | 7 +++- 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 33891e89..51d6f81d 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -25,7 +25,7 @@ "location" : "https://github.com/synonymdev/ldk-node", "state" : { "branch" : "main", - "revision" : "90cab7b4839cc1108fafcfd95f5dadcdd0f8e745" + "revision" : "2a80f373f469a74ece77096dc30d2cf8093a2a48" } }, { diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 95f16369..569db19a 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -69,8 +69,6 @@ class LightningService { // Configure gossip source from current settings configureGossipSource(builder: builder, rgsServerUrl: rgsServerUrl) - builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase) - Logger.debug("Building node...") let storeId = try await VssStoreIdProvider.shared.getVssStoreId(walletIndex: walletIndex) @@ -79,9 +77,13 @@ class LightningService { Logger.debug("Building ldk-node with vssUrl: '\(vssUrl)'") Logger.debug("Building ldk-node with lnurlAuthServerUrl: '\(lnurlAuthServerUrl)'") + // Create NodeEntropy from mnemonic + let nodeEntropy = NodeEntropy.fromBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase) + try await ServiceQueue.background(.ldk) { if !lnurlAuthServerUrl.isEmpty { self.node = try builder.buildWithVssStore( + nodeEntropy: nodeEntropy, vssUrl: vssUrl, storeId: storeId, lnurlAuthServerUrl: lnurlAuthServerUrl, @@ -89,6 +91,7 @@ class LightningService { ) } else { self.node = try builder.buildWithVssStoreAndFixedHeaders( + nodeEntropy: nodeEntropy, vssUrl: vssUrl, storeId: storeId, fixedHeaders: [:] @@ -411,7 +414,7 @@ class LightningService { } } - func send(bolt11: String, sats: UInt64? = nil, params: SendingParameters? = nil) async throws -> PaymentHash { + func send(bolt11: String, sats: UInt64? = nil, params: RouteParametersConfig? = nil) async throws -> PaymentHash { guard let node else { throw AppError(serviceError: .nodeNotSetup) } @@ -432,10 +435,10 @@ class LightningService { return try await ServiceQueue.background(.ldk) { if let sats { try node.bolt11Payment().sendUsingAmount( - invoice: .fromStr(invoiceStr: bolt11), amountMsat: sats * 1000, sendingParameters: params + invoice: .fromStr(invoiceStr: bolt11), amountMsat: sats * 1000, routeParameters: params ) } else { - try node.bolt11Payment().send(invoice: .fromStr(invoiceStr: bolt11), sendingParameters: params) + try node.bolt11Payment().send(invoice: .fromStr(invoiceStr: bolt11), routeParameters: params) } } } catch { @@ -717,9 +720,9 @@ extension LightningService { "⏳ Channel pending: channelId: \(channelId) userChannelId: \(userChannelId) formerTemporaryChannelId: \(formerTemporaryChannelId) counterpartyNodeId: \(counterpartyNodeId) fundingTxo: \(fundingTxo)" ) await refreshChannelCache() - case let .channelReady(channelId, userChannelId, counterpartyNodeId): + case let .channelReady(channelId, userChannelId, counterpartyNodeId, fundingTxo): Logger.info( - "👐 Channel ready: channelId: \(channelId) userChannelId: \(userChannelId) counterpartyNodeId: \(counterpartyNodeId ?? "?")" + "👐 Channel ready: channelId: \(channelId) userChannelId: \(userChannelId) counterpartyNodeId: \(counterpartyNodeId ?? "?") fundingTxo: \(fundingTxo != nil ? "\(fundingTxo!.txid):\(fundingTxo!.vout)" : "nil")" ) await refreshChannelCache() case let .channelClosed(channelId, userChannelId, counterpartyNodeId, reason): @@ -815,6 +818,20 @@ extension LightningService { case let .balanceChanged(oldSpendableOnchain, newSpendableOnchain, oldTotalOnchain, newTotalOnchain, oldLightning, newLightning): Logger .info("💰 Balance changed: onchain=\(oldSpendableOnchain)->\(newSpendableOnchain) lightning=\(oldLightning)->\(newLightning)") + + // MARK: Splice Events + + case let .splicePending(channelId, userChannelId, counterpartyNodeId, newFundingTxo): + Logger + .info( + "🔀 Splice pending: channelId=\(channelId) userChannelId=\(userChannelId) counterpartyNodeId=\(counterpartyNodeId) newFundingTxo=\(newFundingTxo)" + ) + await refreshChannelCache() + case let .spliceFailed(channelId, userChannelId, counterpartyNodeId, abandonedFundingTxo): + Logger + .warn( + "❌ Splice failed: channelId=\(channelId) userChannelId=\(userChannelId) counterpartyNodeId=\(counterpartyNodeId) abandonedFundingTxo=\(abandonedFundingTxo != nil ? "\(abandonedFundingTxo!.txid):\(abandonedFundingTxo!.vout)" : "nil")" + ) } } } diff --git a/Bitkit/Utilities/Errors.swift b/Bitkit/Utilities/Errors.swift index d4fdf123..698e25b6 100644 --- a/Bitkit/Utilities/Errors.swift +++ b/Bitkit/Utilities/Errors.swift @@ -122,16 +122,13 @@ struct AppError: LocalizedError { private init(ldkBuildError: BuildError) { switch ldkBuildError as BuildError { + case let .InvalidSystemTime(message: ldkMessage): + message = "Invalid system time" + debugMessage = ldkMessage case let .InvalidChannelMonitor(message: ldkMessage): message = "Invalid channel monitor" debugMessage = ldkMessage - case let .InvalidSeedBytes(message: ldkMessage): - message = "Invalid seed bytes" - debugMessage = ldkMessage - case let .InvalidSeedFile(message: ldkMessage): - message = "Invalid seed file" - debugMessage = ldkMessage - case let .InvalidSystemTime(message: ldkMessage): + case let .InvalidListeningAddresses(message: ldkMessage): message = "Invalid system time" debugMessage = ldkMessage case let .InvalidListeningAddresses(message: ldkMessage): @@ -164,6 +161,12 @@ struct AppError: LocalizedError { case let .NetworkMismatch(message: ldkMessage): message = ldkMessage debugMessage = nil + case let .RuntimeSetupFailed(message: ldkMessage): + message = "Runtime setup failed" + debugMessage = ldkMessage + case let .AsyncPaymentsConfigMismatch(message: ldkMessage): + message = "Async payments config mismatch" + debugMessage = ldkMessage } } @@ -197,6 +200,9 @@ struct AppError: LocalizedError { // message = "Failed to send payment. \(ldkMessage)" message = ldkMessage debugMessage = ldkMessage + case let .InvalidCustomTlvs(message: ldkMessage): + message = "Invalid custom TLVs" + debugMessage = ldkMessage case let .ProbeSendingFailed(message: ldkMessage): message = "Failed to send probe" debugMessage = ldkMessage @@ -209,6 +215,9 @@ struct AppError: LocalizedError { case let .ChannelClosingFailed(message: ldkMessage): message = "Failed to close channel" debugMessage = ldkMessage + case let .ChannelSplicingFailed(message: ldkMessage): + message = "Failed to splice channel" + debugMessage = ldkMessage case let .ChannelConfigUpdateFailed(message: ldkMessage): message = "Failed to update channel config" debugMessage = ldkMessage @@ -245,6 +254,9 @@ struct AppError: LocalizedError { case let .LiquidityRequestFailed(message: ldkMessage): message = "Failed to request liquidity" debugMessage = ldkMessage + case let .UriParameterParsingFailed(message: ldkMessage): + message = "Failed to parse URI parameters" + debugMessage = ldkMessage case let .InvalidAddress(message: ldkMessage): message = "Invalid address" debugMessage = ldkMessage @@ -308,8 +320,11 @@ struct AppError: LocalizedError { case let .LiquidityFeeTooHigh(message: ldkMessage): message = "Liquidity fee too high" debugMessage = ldkMessage - case let .UriParameterParsingFailed(message: ldkMessage): - message = "Uri parameter parsing failed" + case let .InvalidBlindedPaths(message: ldkMessage): + message = "Invalid blinded paths" + debugMessage = ldkMessage + case let .AsyncPaymentServicesDisabled(message: ldkMessage): + message = "Async payment services disabled" debugMessage = ldkMessage case let .InvalidUri(message: ldkMessage): message = "Invalid URI" diff --git a/Bitkit/Utilities/StartupHandler.swift b/Bitkit/Utilities/StartupHandler.swift index f285cdfa..54135559 100644 --- a/Bitkit/Utilities/StartupHandler.swift +++ b/Bitkit/Utilities/StartupHandler.swift @@ -10,7 +10,7 @@ class StartupHandler { /// - walletIndex: wallet index, defaults to zero for first entry /// - Returns: The generated mnemonic static func createNewWallet(bip39Passphrase: String?, walletIndex: Int = 0) throws -> String { - let mnemonic = generateEntropyMnemonic() + let mnemonic = generateEntropyMnemonic(wordCount: .words12) try Keychain.saveString(key: .bip39Mnemonic(index: walletIndex), str: mnemonic) if let bip39Passphrase { diff --git a/Bitkit/ViewModels/AppViewModel.swift b/Bitkit/ViewModels/AppViewModel.swift index 79d352f4..8a5055b3 100644 --- a/Bitkit/ViewModels/AppViewModel.swift +++ b/Bitkit/ViewModels/AppViewModel.swift @@ -340,7 +340,7 @@ extension AppViewModel { case .channelPending(channelId: _, userChannelId: _, formerTemporaryChannelId: _, counterpartyNodeId: _, fundingTxo: _): // Only relevant for channels to external nodes break - case .channelReady(let channelId, userChannelId: _, counterpartyNodeId: _): + case .channelReady(let channelId, userChannelId: _, counterpartyNodeId: _, fundingTxo: _): if let channel = lightningService.channels?.first(where: { $0.channelId == channelId }) { Task { let cjitOrder = try await CoreService.shared.blocktank.getCjit(channel: channel) @@ -471,6 +471,11 @@ extension AppViewModel { } } + // MARK: Splice Events + + case .splicePending, .spliceFailed: + break + // MARK: Sync Events case let .syncProgress(syncType, progressPercent, currentBlockHeight, targetBlockHeight): diff --git a/BitkitNotification/NotificationService.swift b/BitkitNotification/NotificationService.swift index 6706a43e..4e41efbe 100644 --- a/BitkitNotification/NotificationService.swift +++ b/BitkitNotification/NotificationService.swift @@ -177,7 +177,7 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent?.title = "Spending Balance Ready" bestAttemptContent?.body = "Pending" // Don't deliver, give a chance for channelReady event to update the content if it's a turbo channel - case let .channelReady(channelId, _, _): + case let .channelReady(channelId, _, _, _): if notificationType == .cjitPaymentArrived { bestAttemptContent?.title = "Payment Received" bestAttemptContent?.body = "Your funds arrived in your spending balance" @@ -256,6 +256,11 @@ class NotificationService: UNNotificationServiceExtension { case .balanceChanged: // Balance changes are handled by other events, not critical for notifications break + + // MARK: Splice Events + + case .splicePending, .spliceFailed: + break } } From 84d87789ba0de8a5d6f60921ce02064a0368ade7 Mon Sep 17 00:00:00 2001 From: benk10 Date: Wed, 3 Dec 2025 19:49:12 -0500 Subject: [PATCH 2/2] Allow spending of unconfirmed incoming balances --- Bitkit/Services/LightningService.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Bitkit/Services/LightningService.swift b/Bitkit/Services/LightningService.swift index 569db19a..aec3b16e 100644 --- a/Bitkit/Services/LightningService.swift +++ b/Bitkit/Services/LightningService.swift @@ -46,6 +46,7 @@ class LightningService { trustedPeersNoReserve: Env.trustedLnPeers.map(\.nodeId), perChannelReserveSats: 1 ) + config.includeUntrustedPendingInSpendable = true let builder = Builder.fromConfig(config: config)