Skip to content

Commit a296d20

Browse files
authored
Merge pull request #608 from synonymdev/ovi/release-2.3.0
chore: release 2.3.1
2 parents 66e45ec + af90fe7 commit a296d20

6 files changed

Lines changed: 96 additions & 79 deletions

File tree

Bitkit.xcodeproj/project.pbxproj

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@
679679
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
680680
CODE_SIGN_ENTITLEMENTS = BitkitWidgetExtension.entitlements;
681681
CODE_SIGN_STYLE = Automatic;
682-
CURRENT_PROJECT_VERSION = 190;
682+
CURRENT_PROJECT_VERSION = 192;
683683
DEVELOPMENT_TEAM = KYH47R284B;
684684
GENERATE_INFOPLIST_FILE = YES;
685685
INFOPLIST_FILE = BitkitWidget/Info.plist;
@@ -691,7 +691,7 @@
691691
"@executable_path/Frameworks",
692692
"@executable_path/../../Frameworks",
693693
);
694-
MARKETING_VERSION = 2.3.0;
694+
MARKETING_VERSION = 2.3.1;
695695
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.widget;
696696
PRODUCT_NAME = "$(TARGET_NAME)";
697697
SDKROOT = iphoneos;
@@ -712,7 +712,7 @@
712712
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
713713
CODE_SIGN_ENTITLEMENTS = BitkitWidgetExtension.entitlements;
714714
CODE_SIGN_STYLE = Automatic;
715-
CURRENT_PROJECT_VERSION = 190;
715+
CURRENT_PROJECT_VERSION = 192;
716716
DEVELOPMENT_TEAM = KYH47R284B;
717717
GENERATE_INFOPLIST_FILE = YES;
718718
INFOPLIST_FILE = BitkitWidget/Info.plist;
@@ -724,7 +724,7 @@
724724
"@executable_path/Frameworks",
725725
"@executable_path/../../Frameworks",
726726
);
727-
MARKETING_VERSION = 2.3.0;
727+
MARKETING_VERSION = 2.3.1;
728728
PRODUCT_BUNDLE_IDENTIFIER = to.bitkit.widget;
729729
PRODUCT_NAME = "$(TARGET_NAME)";
730730
SDKROOT = iphoneos;
@@ -744,7 +744,7 @@
744744
buildSettings = {
745745
CODE_SIGN_ENTITLEMENTS = BitkitNotification/BitkitNotification.entitlements;
746746
CODE_SIGN_STYLE = Automatic;
747-
CURRENT_PROJECT_VERSION = 190;
747+
CURRENT_PROJECT_VERSION = 192;
748748
DEVELOPMENT_TEAM = KYH47R284B;
749749
GENERATE_INFOPLIST_FILE = YES;
750750
INFOPLIST_FILE = BitkitNotification/Info.plist;
@@ -756,7 +756,7 @@
756756
"@executable_path/Frameworks",
757757
"@executable_path/../../Frameworks",
758758
);
759-
MARKETING_VERSION = 2.3.0;
759+
MARKETING_VERSION = 2.3.1;
760760
OTHER_LDFLAGS = (
761761
"-framework",
762762
CoreBluetooth,
@@ -776,7 +776,7 @@
776776
buildSettings = {
777777
CODE_SIGN_ENTITLEMENTS = BitkitNotification/BitkitNotification.entitlements;
778778
CODE_SIGN_STYLE = Automatic;
779-
CURRENT_PROJECT_VERSION = 190;
779+
CURRENT_PROJECT_VERSION = 192;
780780
DEVELOPMENT_TEAM = KYH47R284B;
781781
GENERATE_INFOPLIST_FILE = YES;
782782
INFOPLIST_FILE = BitkitNotification/Info.plist;
@@ -788,7 +788,7 @@
788788
"@executable_path/Frameworks",
789789
"@executable_path/../../Frameworks",
790790
);
791-
MARKETING_VERSION = 2.3.0;
791+
MARKETING_VERSION = 2.3.1;
792792
OTHER_LDFLAGS = (
793793
"-framework",
794794
CoreBluetooth,
@@ -926,7 +926,7 @@
926926
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
927927
CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements;
928928
CODE_SIGN_STYLE = Automatic;
929-
CURRENT_PROJECT_VERSION = 190;
929+
CURRENT_PROJECT_VERSION = 192;
930930
DEVELOPMENT_ASSET_PATHS = "\"Bitkit/Preview Content\"";
931931
DEVELOPMENT_TEAM = KYH47R284B;
932932
ENABLE_HARDENED_RUNTIME = YES;
@@ -953,7 +953,7 @@
953953
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
954954
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
955955
MACOSX_DEPLOYMENT_TARGET = 14.0;
956-
MARKETING_VERSION = 2.3.0;
956+
MARKETING_VERSION = 2.3.1;
957957
OTHER_LDFLAGS = (
958958
"-framework",
959959
CoreBluetooth,
@@ -975,7 +975,7 @@
975975
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
976976
CODE_SIGN_ENTITLEMENTS = Bitkit/Bitkit.entitlements;
977977
CODE_SIGN_STYLE = Automatic;
978-
CURRENT_PROJECT_VERSION = 190;
978+
CURRENT_PROJECT_VERSION = 192;
979979
DEVELOPMENT_ASSET_PATHS = "\"Bitkit/Preview Content\"";
980980
DEVELOPMENT_TEAM = KYH47R284B;
981981
ENABLE_HARDENED_RUNTIME = YES;
@@ -1002,7 +1002,7 @@
10021002
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
10031003
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
10041004
MACOSX_DEPLOYMENT_TARGET = 14.0;
1005-
MARKETING_VERSION = 2.3.0;
1005+
MARKETING_VERSION = 2.3.1;
10061006
OTHER_LDFLAGS = (
10071007
"-framework",
10081008
CoreBluetooth,
@@ -1201,7 +1201,7 @@
12011201
repositoryURL = "https://github.com/synonymdev/bitkit-core";
12021202
requirement = {
12031203
kind = exactVersion;
1204-
version = 0.3.4;
1204+
version = 0.3.6;
12051205
};
12061206
};
12071207
96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {

Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Bitkit/Utilities/Lnurl.swift

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import BitkitCore
22
import Foundation
33

4-
// MARK: - Response Models
5-
6-
private struct LnurlPayResponse: Codable {
7-
let pr: String
8-
let routes: [String]
4+
struct LnurlPayInvoiceMismatchError: LocalizedError {
5+
var errorDescription: String? {
6+
return "The invoice did not match the requested payment. Payment cancelled."
7+
}
98
}
109

10+
// MARK: - Response Models
11+
1112
private struct LnurlWithdrawResponse: Codable {
1213
let status: String
1314
let reason: String?
@@ -116,37 +117,42 @@ private extension LnurlHelper {
116117
throw NSError(domain: "LNURL", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMessage])
117118
}
118119
}
120+
121+
static func mapLnurlPayInvoiceError(_ error: Error) -> Error {
122+
if let lnurlError = error as? LnurlError {
123+
switch lnurlError {
124+
case .InvalidAmount, .AmountMismatch:
125+
return LnurlPayInvoiceMismatchError()
126+
default:
127+
break
128+
}
129+
}
130+
131+
return error
132+
}
119133
}
120134

121135
@MainActor
122136
struct LnurlHelper {
123-
/// Fetches a Lightning invoice from an LNURL pay callback
137+
/// Fetches a Lightning invoice for an LNURL pay request
124138
/// - Parameters:
125-
/// - callbackUrl: The LNURL callback URL
139+
/// - data: The LNURL pay data
126140
/// - amountMsats: The amount in millisatoshis to pay
127141
/// - comment: Optional comment to include with the payment
128142
/// - Returns: The bolt11 invoice string
129143
/// - Throws: Network or parsing errors
130144
static func fetchLnurlInvoice(
131-
callbackUrl: String,
145+
data: LnurlPayData,
132146
amountMsats: UInt64,
133147
comment: String? = nil
134148
) async throws -> String {
135-
var queryItems = [
136-
URLQueryItem(name: "amount", value: String(amountMsats)),
137-
]
138-
139-
// Add comment if provided
140-
if let comment, !comment.isEmpty {
141-
queryItems.append(URLQueryItem(name: "comment", value: comment))
149+
do {
150+
let invoice = try await getLnurlInvoiceForPayData(data: data, amountMsats: amountMsats, comment: comment)
151+
Logger.debug("Fetched LNURL pay invoice")
152+
return invoice
153+
} catch {
154+
throw mapLnurlPayInvoiceError(error)
142155
}
143-
144-
let callbackURL = try buildUrl(baseUrl: callbackUrl, queryItems: queryItems)
145-
let responseString = try await makeHttpGetRequest(url: callbackURL)
146-
let lnurlResponse = try parseJsonResponse(responseString, as: LnurlPayResponse.self)
147-
148-
Logger.debug("Extracted bolt11 invoice: \(lnurlResponse.pr)")
149-
return lnurlResponse.pr
150156
}
151157

152158
/// Handles LNURL Withdraw Requests

Bitkit/Views/Wallets/Send/LnurlPayConfirm.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,19 +191,19 @@ struct LnurlPayConfirm: View {
191191
}
192192

193193
let amountMsats = lnurlPayData.callbackAmountMsats(userSats: wallet.sendAmountSats)
194-
195-
// Fetch the Lightning invoice from LNURL
196-
let bolt11 = try await LnurlHelper.fetchLnurlInvoice(
197-
callbackUrl: lnurlPayData.callback,
198-
amountMsats: amountMsats,
199-
comment: comment.isEmpty ? nil : comment
200-
)
201-
202-
let parsedInvoice = try Bolt11Invoice.fromStr(invoiceStr: bolt11)
203-
let paymentHash = String(describing: parsedInvoice.paymentHash())
204194
let contactPublicKey = app.contactPaymentContext?.publicKey
205195

206196
do {
197+
// Fetch the Lightning invoice from LNURL
198+
let bolt11 = try await LnurlHelper.fetchLnurlInvoice(
199+
data: lnurlPayData,
200+
amountMsats: amountMsats,
201+
comment: comment.isEmpty ? nil : comment
202+
)
203+
204+
let parsedInvoice = try Bolt11Invoice.fromStr(invoiceStr: bolt11)
205+
let paymentHash = String(describing: parsedInvoice.paymentHash())
206+
207207
// Perform the Lightning payment (10s timeout → navigate to pending for hold invoices)
208208
// LNURL server returns invoices with the amount baked in, so pass sats: nil
209209
// to let LDK use the invoice's native millisatoshi precision.

Bitkit/Views/Wallets/Send/SendQuickpay.swift

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,38 @@ struct SendQuickpay: View {
3434
.frame(maxWidth: .infinity, maxHeight: .infinity)
3535
.onAppear {
3636
Task {
37-
try await performPayment()
37+
await performPayment()
3838
}
3939
}
4040
}
4141

42-
private func performPayment() async throws {
43-
var bolt11Invoice: String?
44-
45-
// Handle LNURL Pay
46-
if let lnurlPayData = app.lnurlPayData {
47-
// Set the amount in sats for the success screen
48-
wallet.sendAmountSats = lnurlPayData.minSendableSat
42+
private func performPayment() async {
43+
do {
44+
var bolt11Invoice: String?
4945

50-
bolt11Invoice = try await LnurlHelper.fetchLnurlInvoice(
51-
callbackUrl: lnurlPayData.callback,
52-
amountMsats: lnurlPayData.callbackAmountMsats()
53-
)
54-
} else if let scannedInvoice = app.scannedLightningInvoice {
55-
wallet.sendAmountSats = scannedInvoice.amountSatoshis
56-
bolt11Invoice = scannedInvoice.bolt11
57-
}
46+
// Handle LNURL Pay
47+
if let lnurlPayData = app.lnurlPayData {
48+
// Set the amount in sats for the success screen
49+
wallet.sendAmountSats = lnurlPayData.minSendableSat
50+
51+
bolt11Invoice = try await LnurlHelper.fetchLnurlInvoice(
52+
data: lnurlPayData,
53+
amountMsats: lnurlPayData.callbackAmountMsats()
54+
)
55+
} else if let scannedInvoice = app.scannedLightningInvoice {
56+
wallet.sendAmountSats = scannedInvoice.amountSatoshis
57+
bolt11Invoice = scannedInvoice.bolt11
58+
}
5859

59-
guard let bolt11 = bolt11Invoice else {
60-
throw NSError(
61-
domain: "Payment", code: -1, userInfo: [NSLocalizedDescriptionKey: "No Lightning invoice found"]
62-
)
63-
}
60+
guard let bolt11 = bolt11Invoice else {
61+
throw NSError(
62+
domain: "Payment", code: -1, userInfo: [NSLocalizedDescriptionKey: "No Lightning invoice found"]
63+
)
64+
}
6465

65-
let parsedInvoice = try Bolt11Invoice.fromStr(invoiceStr: bolt11)
66-
let paymentHash = String(describing: parsedInvoice.paymentHash())
66+
let parsedInvoice = try Bolt11Invoice.fromStr(invoiceStr: bolt11)
67+
let paymentHash = String(describing: parsedInvoice.paymentHash())
6768

68-
do {
6969
// Quickpay only triggers for invoices with built-in amounts, so pass sats: nil
7070
// to let LDK use the invoice's native millisatoshi precision.
7171
try await wallet.sendWithTimeout(
@@ -82,15 +82,19 @@ struct SendQuickpay: View {
8282
// onTimeout callback already navigated to .pending; suppress throw
8383
return
8484
} catch {
85-
Logger.error("Quickpay payment failed: \(error)")
85+
handlePaymentError(error)
86+
}
87+
}
8688

87-
// TODO: remove toast and use failure screen instead
88-
app.toast(error)
89+
private func handlePaymentError(_ error: Error) {
90+
Logger.error("Quickpay payment failed: \(error)")
8991

90-
// TODO: this is a hack to make sure the navigation binding is ready
91-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
92-
navigationPath.append(.failure)
93-
}
92+
// TODO: remove toast and use failure screen instead
93+
app.toast(error)
94+
95+
// TODO: this is a hack to make sure the navigation binding is ready
96+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
97+
navigationPath.append(.failure)
9498
}
9599
}
96100
}

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.3.1] - 2026-06-26
11+
12+
### Fixed
13+
- Improved LNURL-pay invoice validation. #607
14+
- Improved LNURL-pay payment handling. #610
15+
1016
## [2.3.0] - 2026-06-04
1117

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

56-
[Unreleased]: https://github.com/synonymdev/bitkit-ios/compare/v2.3.0...HEAD
62+
[Unreleased]: https://github.com/synonymdev/bitkit-ios/compare/v2.3.1...HEAD
63+
[2.3.1]: https://github.com/synonymdev/bitkit-ios/compare/v2.3.0...v2.3.1
5764
[2.3.0]: https://github.com/synonymdev/bitkit-ios/compare/v2.2.1...v2.3.0
5865
[2.2.1]: https://github.com/synonymdev/bitkit-ios/compare/v2.2.0...v2.2.1
5966
[2.2.0]: https://github.com/synonymdev/bitkit-ios/compare/v2.1.2...v2.2.0

0 commit comments

Comments
 (0)