From c522b5cc3cc223bc87e0158cfe749129eac83844 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Fri, 26 Jun 2026 05:04:59 +0200 Subject: [PATCH] fix: use core LNURL-pay validation --- Bitkit.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- Bitkit/Utilities/Lnurl.swift | 50 +++++++++++-------- .../ViewModels/Trezor/TrezorViewModel.swift | 2 + .../Views/Wallets/Send/LnurlPayConfirm.swift | 2 +- Bitkit/Views/Wallets/Send/SendQuickpay.swift | 2 +- changelog.d/hotfix/607.fixed.md | 1 + 7 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 changelog.d/hotfix/607.fixed.md diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index 9519ceb31..da49763f5 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -1201,7 +1201,7 @@ repositoryURL = "https://github.com/synonymdev/bitkit-core"; requirement = { kind = exactVersion; - version = 0.1.64; + version = 0.1.74; }; }; 96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = { diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1347ccc14..da128ae92 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/synonymdev/bitkit-core", "state" : { - "revision" : "a7577cc4572d581a0ab1d84f2792a1e6198110ef", - "version" : "0.1.64" + "revision" : "201e37b1951d9ec68e7c46d2534314afe5ccac3c", + "version" : "0.1.74" } }, { diff --git a/Bitkit/Utilities/Lnurl.swift b/Bitkit/Utilities/Lnurl.swift index bdff50594..bf8a1598a 100644 --- a/Bitkit/Utilities/Lnurl.swift +++ b/Bitkit/Utilities/Lnurl.swift @@ -1,13 +1,14 @@ import BitkitCore import Foundation -// MARK: - Response Models - -private struct LnurlPayResponse: Codable { - let pr: String - let routes: [String] +struct LnurlPayInvoiceMismatchError: LocalizedError { + var errorDescription: String? { + return "The invoice did not match the requested payment. Payment cancelled." + } } +// MARK: - Response Models + private struct LnurlWithdrawResponse: Codable { let status: String let reason: String? @@ -116,37 +117,42 @@ private extension LnurlHelper { throw NSError(domain: "LNURL", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMessage]) } } + + static func mapLnurlPayInvoiceError(_ error: Error) -> Error { + if let lnurlError = error as? LnurlError { + switch lnurlError { + case .InvalidAmount, .AmountMismatch, .MetadataMismatch: + return LnurlPayInvoiceMismatchError() + default: + break + } + } + + return error + } } @MainActor struct LnurlHelper { - /// Fetches a Lightning invoice from an LNURL pay callback + /// Fetches a Lightning invoice for an LNURL pay request /// - Parameters: - /// - callbackUrl: The LNURL callback URL + /// - data: The LNURL pay data /// - amountMsats: The amount in millisatoshis to pay /// - comment: Optional comment to include with the payment /// - Returns: The bolt11 invoice string /// - Throws: Network or parsing errors static func fetchLnurlInvoice( - callbackUrl: String, + data: LnurlPayData, amountMsats: UInt64, comment: String? = nil ) async throws -> String { - var queryItems = [ - URLQueryItem(name: "amount", value: String(amountMsats)), - ] - - // Add comment if provided - if let comment, !comment.isEmpty { - queryItems.append(URLQueryItem(name: "comment", value: comment)) + do { + let invoice = try await getLnurlInvoiceForPayData(data: data, amountMsats: amountMsats, comment: comment) + Logger.debug("Fetched LNURL pay invoice") + return invoice + } catch { + throw mapLnurlPayInvoiceError(error) } - - let callbackURL = try buildUrl(baseUrl: callbackUrl, queryItems: queryItems) - let responseString = try await makeHttpGetRequest(url: callbackURL) - let lnurlResponse = try parseJsonResponse(responseString, as: LnurlPayResponse.self) - - Logger.debug("Extracted bolt11 invoice: \(lnurlResponse.pr)") - return lnurlResponse.pr } /// Handles LNURL Withdraw Requests diff --git a/Bitkit/ViewModels/Trezor/TrezorViewModel.swift b/Bitkit/ViewModels/Trezor/TrezorViewModel.swift index 3f47ea5bb..832f70b11 100644 --- a/Bitkit/ViewModels/Trezor/TrezorViewModel.swift +++ b/Bitkit/ViewModels/Trezor/TrezorViewModel.swift @@ -951,6 +951,8 @@ class TrezorViewModel { return "Invalid transaction ID: \(errorDetails)" case let .TransactionNotFound(errorDetails): return "Transaction not found: \(errorDetails)" + case let .WatcherError(errorDetails): + return "Watcher error: \(errorDetails)" } } if let appError = error as? AppError, diff --git a/Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift b/Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift index 2161ed0da..727a200b2 100644 --- a/Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift +++ b/Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift @@ -194,7 +194,7 @@ struct LnurlPayConfirm: View { // Fetch the Lightning invoice from LNURL let bolt11 = try await LnurlHelper.fetchLnurlInvoice( - callbackUrl: lnurlPayData.callback, + data: lnurlPayData, amountMsats: amountMsats, comment: comment.isEmpty ? nil : comment ) diff --git a/Bitkit/Views/Wallets/Send/SendQuickpay.swift b/Bitkit/Views/Wallets/Send/SendQuickpay.swift index d6b5c78e0..361e98ed0 100644 --- a/Bitkit/Views/Wallets/Send/SendQuickpay.swift +++ b/Bitkit/Views/Wallets/Send/SendQuickpay.swift @@ -48,7 +48,7 @@ struct SendQuickpay: View { wallet.sendAmountSats = lnurlPayData.minSendableSat bolt11Invoice = try await LnurlHelper.fetchLnurlInvoice( - callbackUrl: lnurlPayData.callback, + data: lnurlPayData, amountMsats: lnurlPayData.callbackAmountMsats() ) } else if let scannedInvoice = app.scannedLightningInvoice { diff --git a/changelog.d/hotfix/607.fixed.md b/changelog.d/hotfix/607.fixed.md new file mode 100644 index 000000000..3a4d4d29f --- /dev/null +++ b/changelog.d/hotfix/607.fixed.md @@ -0,0 +1 @@ +Improved LNURL-pay invoice validation.