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
26 changes: 13 additions & 13 deletions Bitkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = BitkitWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 192;
DEVELOPMENT_TEAM = KYH47R284B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BitkitWidget/Info.plist;
Expand All @@ -691,7 +691,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.3.0;
MARKETING_VERSION = 2.3.1;
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
Expand All @@ -712,7 +712,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = BitkitWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 192;
DEVELOPMENT_TEAM = KYH47R284B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BitkitWidget/Info.plist;
Expand All @@ -724,7 +724,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.3.0;
MARKETING_VERSION = 2.3.1;
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
Expand All @@ -744,7 +744,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = BitkitNotification/BitkitNotification.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 192;
DEVELOPMENT_TEAM = KYH47R284B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BitkitNotification/Info.plist;
Expand All @@ -756,7 +756,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.3.0;
MARKETING_VERSION = 2.3.1;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
Expand All @@ -776,7 +776,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = BitkitNotification/BitkitNotification.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 192;
DEVELOPMENT_TEAM = KYH47R284B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BitkitNotification/Info.plist;
Expand All @@ -788,7 +788,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.3.0;
MARKETING_VERSION = 2.3.1;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
Expand Down Expand Up @@ -926,7 +926,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 192;
DEVELOPMENT_ASSET_PATHS = "\"Bitkit/Preview Content\"";
DEVELOPMENT_TEAM = KYH47R284B;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -953,7 +953,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 2.3.0;
MARKETING_VERSION = 2.3.1;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
Expand All @@ -975,7 +975,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 190;
CURRENT_PROJECT_VERSION = 192;
DEVELOPMENT_ASSET_PATHS = "\"Bitkit/Preview Content\"";
DEVELOPMENT_TEAM = KYH47R284B;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1002,7 +1002,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 2.3.0;
MARKETING_VERSION = 2.3.1;
OTHER_LDFLAGS = (
"-framework",
CoreBluetooth,
Expand Down Expand Up @@ -1201,7 +1201,7 @@
repositoryURL = "https://github.com/synonymdev/bitkit-core";
requirement = {
kind = exactVersion;
version = 0.1.64;
version = 0.1.75;
};
};
96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 28 additions & 22 deletions Bitkit/Utilities/Lnurl.swift
Original file line number Diff line number Diff line change
@@ -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?
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Bitkit/ViewModels/Trezor/TrezorViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Comment on lines 196 to 200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Invoice-fetch errors escape the inner catch block

fetchLnurlInvoice is called before the do-catch that toasts the error and navigates to .failure. If core validation throws (e.g. AmountMismatch), the error propagates out of performPayment() to SwipeButton's closure; whether SwipeButton surfaces it to the user is not guaranteed. The inner do-catch already handles send failures with a toast + failure navigation — fetch failures should be treated the same way for consistent UX.

Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Views/Wallets/Send/SendQuickpay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct SendQuickpay: View {
wallet.sendAmountSats = lnurlPayData.minSendableSat

bolt11Invoice = try await LnurlHelper.fetchLnurlInvoice(
callbackUrl: lnurlPayData.callback,
data: lnurlPayData,
amountMsats: lnurlPayData.callbackAmountMsats()
Comment on lines +51 to 52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle LNURL validation failures in QuickPay

When a fixed-amount LNURL-pay endpoint returns an invoice that fails the new core amount/metadata validation, this call now throws before performPayment() reaches the do/catch that navigates to the failure state. In QuickPay it is invoked from Task { try await performPayment() } without any outer catch, so the payment is correctly cancelled but the user remains on the loader indefinitely instead of seeing the failure path; wrap the fetch/parse step in the existing error handling or catch at the task boundary.

Useful? React with 👍 / 👎.

)
} else if let scannedInvoice = app.scannedLightningInvoice {
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.3.1] - 2026-06-26

### Fixed
- Improved LNURL-pay invoice validation. #607
- Improved LNURL-pay payment handling. #610

## [2.3.0] - 2026-06-04

### Added
Expand Down Expand Up @@ -53,7 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix keyboard and UI issues in the calculator widget #513
- Preserve msat precision for LNURL pay, withdraw callbacks and bolt11 #512

[Unreleased]: https://github.com/synonymdev/bitkit-ios/compare/v2.3.0...HEAD
[Unreleased]: https://github.com/synonymdev/bitkit-ios/compare/v2.3.1...HEAD
[2.3.1]: https://github.com/synonymdev/bitkit-ios/compare/v2.3.0...v2.3.1
[2.3.0]: https://github.com/synonymdev/bitkit-ios/compare/v2.2.1...v2.3.0
[2.2.1]: https://github.com/synonymdev/bitkit-ios/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/synonymdev/bitkit-ios/compare/v2.1.2...v2.2.0