diff --git a/Cargo.lock b/Cargo.lock index 25bca7d..e24dca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "bitkitcore" -version = "0.3.5" +version = "0.3.6" dependencies = [ "android_logger", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 70ccfea..319ede8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitkitcore" -version = "0.3.5" +version = "0.3.6" edition = "2021" [lib] diff --git a/Package.swift b/Package.swift index fb81a2f..cf104b4 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.3.5" -let checksum = "bd1dcde687950d5dcaa5ea86d8fb8e3b8e68fd3cfdfca024e3bc4ee54746ea07" +let tag = "v0.3.6" +let checksum = "e94b57a1852219f3da9a49ff198fcf1c8c751ed8f262467a7796a47d4ef7a1ce" let url = "https://github.com/synonymdev/bitkit-core/releases/download/\(tag)/BitkitCore.xcframework.zip" let package = Package( diff --git a/bindings/android/gradle.properties b/bindings/android/gradle.properties index 2508c3a..9e9526e 100644 --- a/bindings/android/gradle.properties +++ b/bindings/android/gradle.properties @@ -3,4 +3,4 @@ android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official group=com.synonym -version=0.3.5 +version=0.3.6 diff --git a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so index aaa867d..861a2a8 100755 Binary files a/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/arm64-v8a/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so index d9eb362..2186ebf 100755 Binary files a/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/armeabi-v7a/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so index 7be9900..0a64dbb 100755 Binary files a/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/x86/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so index bce10ad..1ac618f 100755 Binary files a/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so and b/bindings/android/lib/src/main/jniLibs/x86_64/libbitkitcore.so differ diff --git a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt index ecf8074..e639b56 100644 --- a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt +++ b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.android.kt @@ -1489,6 +1489,8 @@ internal typealias UniffiVTableCallbackInterfaceTrezorUiCallbackUniffiByValue = + + @@ -1696,6 +1698,9 @@ internal object IntegrityCheckingUniffiLib : Library { if (uniffi_bitkitcore_checksum_func_get_lnurl_invoice() != 5475.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data() != 50807.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (uniffi_bitkitcore_checksum_func_get_min_zero_conf_tx_fee() != 6427.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2189,6 +2194,9 @@ internal object IntegrityCheckingUniffiLib : Library { external fun uniffi_bitkitcore_checksum_func_get_lnurl_invoice( ): Short @JvmStatic + external fun uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data( + ): Short + @JvmStatic external fun uniffi_bitkitcore_checksum_func_get_min_zero_conf_tx_fee( ): Short @JvmStatic @@ -2971,6 +2979,12 @@ internal object UniffiLib : Library { `amountSatoshis`: Long, ): Long @JvmStatic + external fun uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data( + `data`: RustBufferByValue, + `amountMsats`: Long, + `comment`: RustBufferByValue, + ): Long + @JvmStatic external fun uniffi_bitkitcore_fn_func_get_min_zero_conf_tx_fee( `orderId`: RustBufferByValue, ): Long @@ -9692,7 +9706,11 @@ public object FfiConverterTypeLnurlError : FfiConverterRustBuffer LnurlException.InvoiceCreationFailed( FfiConverterString.read(buf), ) - 7 -> LnurlException.AuthenticationFailed() + 7 -> LnurlException.AmountMismatch( + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + 8 -> LnurlException.AuthenticationFailed() else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } @@ -9727,6 +9745,12 @@ public object FfiConverterTypeLnurlError : FfiConverterRustBuffer ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + + FfiConverterULong.allocationSize(value.`requestedMsats`) + + FfiConverterULong.allocationSize(value.`invoiceMsats`) + ) is LnurlException.AuthenticationFailed -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL @@ -9764,8 +9788,14 @@ public object FfiConverterTypeLnurlError : FfiConverterRustBuffer { + is LnurlException.AmountMismatch -> { buf.putInt(7) + FfiConverterULong.write(value.`requestedMsats`, buf) + FfiConverterULong.write(value.`invoiceMsats`, buf) + Unit + } + is LnurlException.AuthenticationFailed -> { + buf.putInt(8) Unit } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } @@ -13881,6 +13911,25 @@ public suspend fun `getLnurlInvoice`(`address`: kotlin.String, `amountSatoshis`: ) } +@Throws(LnurlException::class, kotlin.coroutines.cancellation.CancellationException::class) +public suspend fun `getLnurlInvoiceForPayData`(`data`: LnurlPayData, `amountMsats`: kotlin.ULong, `comment`: kotlin.String?): kotlin.String { + return uniffiRustCallAsync( + UniffiLib.uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data( + FfiConverterTypeLnurlPayData.lower(`data`), + FfiConverterULong.lower(`amountMsats`), + FfiConverterOptionalString.lower(`comment`), + ), + { future, callback, continuation -> UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer(future, callback, continuation) }, + { future, continuation -> UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer(future, continuation) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer(future) }, + { future -> UniffiLib.ffi_bitkitcore_rust_future_cancel_rust_buffer(future) }, + // lift function + { FfiConverterString.lift(it) }, + // Error FFI converter + LnurlExceptionErrorHandler, + ) +} + @Throws(BlocktankException::class, kotlin.coroutines.cancellation.CancellationException::class) public suspend fun `getMinZeroConfTxFee`(`orderId`: kotlin.String): IBt0ConfMinTxFeeWindow { return uniffiRustCallAsync( diff --git a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt index 03db465..bb0f867 100644 --- a/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt +++ b/bindings/android/lib/src/main/kotlin/com/synonym/bitkitcore/bitkitcore.common.kt @@ -3517,6 +3517,14 @@ public sealed class LnurlException: kotlin.Exception() { get() = "errorDetails=${ `errorDetails` }" } + public class AmountMismatch( + public val `requestedMsats`: kotlin.ULong, + public val `invoiceMsats`: kotlin.ULong, + ) : LnurlException() { + override val message: String + get() = "requestedMsats=${ `requestedMsats` }, invoiceMsats=${ `invoiceMsats` }" + } + public class AuthenticationFailed( ) : LnurlException() { override val message: String diff --git a/bindings/ios/BitkitCore.xcframework.zip b/bindings/ios/BitkitCore.xcframework.zip index 288b6e8..6ce6b51 100644 Binary files a/bindings/ios/BitkitCore.xcframework.zip and b/bindings/ios/BitkitCore.xcframework.zip differ diff --git a/bindings/ios/BitkitCore.xcframework/Info.plist b/bindings/ios/BitkitCore.xcframework/Info.plist index b7357e0..478a88f 100644 --- a/bindings/ios/BitkitCore.xcframework/Info.plist +++ b/bindings/ios/BitkitCore.xcframework/Info.plist @@ -10,7 +10,7 @@ HeadersPath Headers LibraryIdentifier - ios-arm64-simulator + ios-arm64 LibraryPath libbitkitcore.a SupportedArchitectures @@ -19,8 +19,6 @@ SupportedPlatform ios - SupportedPlatformVariant - simulator BinaryPath @@ -28,7 +26,7 @@ HeadersPath Headers LibraryIdentifier - ios-arm64 + ios-arm64-simulator LibraryPath libbitkitcore.a SupportedArchitectures @@ -37,6 +35,8 @@ SupportedPlatform ios + SupportedPlatformVariant + simulator CFBundlePackageType diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h index 0821479..298f54f 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h @@ -787,6 +787,11 @@ uint64_t uniffi_bitkitcore_fn_func_get_info(RustBuffer refresh uint64_t uniffi_bitkitcore_fn_func_get_lnurl_invoice(RustBuffer address, uint64_t amount_satoshis ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +uint64_t uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data(RustBuffer data, uint64_t amount_msats, RustBuffer comment +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_MIN_ZERO_CONF_TX_FEE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_MIN_ZERO_CONF_TX_FEE uint64_t uniffi_bitkitcore_fn_func_get_min_zero_conf_tx_fee(RustBuffer order_id @@ -1882,6 +1887,12 @@ uint16_t uniffi_bitkitcore_checksum_func_get_info(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE uint16_t uniffi_bitkitcore_checksum_func_get_lnurl_invoice(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +uint16_t uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_MIN_ZERO_CONF_TX_FEE diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a index 205eaff..9be4062 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a differ diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h index 0821479..298f54f 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h @@ -787,6 +787,11 @@ uint64_t uniffi_bitkitcore_fn_func_get_info(RustBuffer refresh uint64_t uniffi_bitkitcore_fn_func_get_lnurl_invoice(RustBuffer address, uint64_t amount_satoshis ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +uint64_t uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data(RustBuffer data, uint64_t amount_msats, RustBuffer comment +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_MIN_ZERO_CONF_TX_FEE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_MIN_ZERO_CONF_TX_FEE uint64_t uniffi_bitkitcore_fn_func_get_min_zero_conf_tx_fee(RustBuffer order_id @@ -1882,6 +1887,12 @@ uint16_t uniffi_bitkitcore_checksum_func_get_info(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE uint16_t uniffi_bitkitcore_checksum_func_get_lnurl_invoice(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +uint16_t uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_MIN_ZERO_CONF_TX_FEE diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a index 53a1d7a..a22d0e8 100644 Binary files a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a and b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a differ diff --git a/bindings/ios/bitkitcore.swift b/bindings/ios/bitkitcore.swift index 78b190c..3a97647 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -16105,6 +16105,8 @@ public enum LnurlError: Swift.Error { ) case InvoiceCreationFailed(errorDetails: String ) + case AmountMismatch(requestedMsats: UInt64, invoiceMsats: UInt64 + ) case AuthenticationFailed } @@ -16134,7 +16136,11 @@ public struct FfiConverterTypeLnurlError: FfiConverterRustBuffer { case 6: return .InvoiceCreationFailed( errorDetails: try FfiConverterString.read(from: &buf) ) - case 7: return .AuthenticationFailed + case 7: return .AmountMismatch( + requestedMsats: try FfiConverterUInt64.read(from: &buf), + invoiceMsats: try FfiConverterUInt64.read(from: &buf) + ) + case 8: return .AuthenticationFailed default: throw UniffiInternalError.unexpectedEnumCase } @@ -16175,8 +16181,14 @@ public struct FfiConverterTypeLnurlError: FfiConverterRustBuffer { FfiConverterString.write(errorDetails, into: &buf) - case .AuthenticationFailed: + case let .AmountMismatch(requestedMsats,invoiceMsats): writeInt(&buf, Int32(7)) + FfiConverterUInt64.write(requestedMsats, into: &buf) + FfiConverterUInt64.write(invoiceMsats, into: &buf) + + + case .AuthenticationFailed: + writeInt(&buf, Int32(8)) } } @@ -21025,6 +21037,20 @@ public func getLnurlInvoice(address: String, amountSatoshis: UInt64)async throws errorHandler: FfiConverterTypeLnurlError_lift ) } +public func getLnurlInvoiceForPayData(data: LnurlPayData, amountMsats: UInt64, comment: String?)async throws -> String { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data(FfiConverterTypeLnurlPayData_lower(data),FfiConverterUInt64.lower(amountMsats),FfiConverterOptionString.lower(comment) + ) + }, + pollFunc: ffi_bitkitcore_rust_future_poll_rust_buffer, + completeFunc: ffi_bitkitcore_rust_future_complete_rust_buffer, + freeFunc: ffi_bitkitcore_rust_future_free_rust_buffer, + liftFunc: FfiConverterString.lift, + errorHandler: FfiConverterTypeLnurlError_lift + ) +} public func getMinZeroConfTxFee(orderId: String)async throws -> IBt0ConfMinTxFeeWindow { return try await uniffiRustCallAsync( @@ -22408,6 +22434,9 @@ private let initializationResult: InitializationResult = { if (uniffi_bitkitcore_checksum_func_get_lnurl_invoice() != 5475) { return InitializationResult.apiChecksumMismatch } + if (uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data() != 50807) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_bitkitcore_checksum_func_get_min_zero_conf_tx_fee() != 6427) { return InitializationResult.apiChecksumMismatch } diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index 0821479..298f54f 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -787,6 +787,11 @@ uint64_t uniffi_bitkitcore_fn_func_get_info(RustBuffer refresh uint64_t uniffi_bitkitcore_fn_func_get_lnurl_invoice(RustBuffer address, uint64_t amount_satoshis ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +uint64_t uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data(RustBuffer data, uint64_t amount_msats, RustBuffer comment +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_MIN_ZERO_CONF_TX_FEE #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_GET_MIN_ZERO_CONF_TX_FEE uint64_t uniffi_bitkitcore_fn_func_get_min_zero_conf_tx_fee(RustBuffer order_id @@ -1882,6 +1887,12 @@ uint16_t uniffi_bitkitcore_checksum_func_get_info(void #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE uint16_t uniffi_bitkitcore_checksum_func_get_lnurl_invoice(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +#define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_LNURL_INVOICE_FOR_PAY_DATA +uint16_t uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_CHECKSUM_FUNC_GET_MIN_ZERO_CONF_TX_FEE diff --git a/bindings/python/bitkitcore/bitkitcore.py b/bindings/python/bitkitcore/bitkitcore.py index e871328..6a1c908 100644 --- a/bindings/python/bitkitcore/bitkitcore.py +++ b/bindings/python/bitkitcore/bitkitcore.py @@ -571,6 +571,8 @@ def _uniffi_check_api_checksums(lib): raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_get_lnurl_invoice() != 5475: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data() != 50807: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_get_min_zero_conf_tx_fee() != 6427: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_bitkitcore_checksum_func_get_orders() != 47460: @@ -1397,6 +1399,12 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): ctypes.c_uint64, ) _UniffiLib.uniffi_bitkitcore_fn_func_get_lnurl_invoice.restype = ctypes.c_uint64 +_UniffiLib.uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data.argtypes = ( + _UniffiRustBuffer, + ctypes.c_uint64, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data.restype = ctypes.c_uint64 _UniffiLib.uniffi_bitkitcore_fn_func_get_min_zero_conf_tx_fee.argtypes = ( _UniffiRustBuffer, ) @@ -2303,6 +2311,9 @@ class _UniffiVTableCallbackInterfaceTrezorUiCallback(ctypes.Structure): _UniffiLib.uniffi_bitkitcore_checksum_func_get_lnurl_invoice.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_get_lnurl_invoice.restype = ctypes.c_uint16 +_UniffiLib.uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data.argtypes = ( +) +_UniffiLib.uniffi_bitkitcore_checksum_func_get_lnurl_invoice_for_pay_data.restype = ctypes.c_uint16 _UniffiLib.uniffi_bitkitcore_checksum_func_get_min_zero_conf_tx_fee.argtypes = ( ) _UniffiLib.uniffi_bitkitcore_checksum_func_get_min_zero_conf_tx_fee.restype = ctypes.c_uint16 @@ -12820,6 +12831,18 @@ def __init__(self, error_details): def __repr__(self): return "LnurlError.InvoiceCreationFailed({})".format(str(self)) _UniffiTempLnurlError.InvoiceCreationFailed = InvoiceCreationFailed # type: ignore + class AmountMismatch(_UniffiTempLnurlError): + def __init__(self, requested_msats, invoice_msats): + super().__init__(", ".join([ + "requested_msats={!r}".format(requested_msats), + "invoice_msats={!r}".format(invoice_msats), + ])) + self.requested_msats = requested_msats + self.invoice_msats = invoice_msats + + def __repr__(self): + return "LnurlError.AmountMismatch({})".format(str(self)) + _UniffiTempLnurlError.AmountMismatch = AmountMismatch # type: ignore class AuthenticationFailed(_UniffiTempLnurlError): def __init__(self): pass @@ -12859,6 +12882,11 @@ def read(buf): _UniffiConverterString.read(buf), ) if variant == 7: + return LnurlError.AmountMismatch( + _UniffiConverterUInt64.read(buf), + _UniffiConverterUInt64.read(buf), + ) + if variant == 8: return LnurlError.AuthenticationFailed( ) raise InternalError("Raw enum value doesn't match any cases") @@ -12881,6 +12909,10 @@ def check_lower(value): if isinstance(value, LnurlError.InvoiceCreationFailed): _UniffiConverterString.check_lower(value.error_details) return + if isinstance(value, LnurlError.AmountMismatch): + _UniffiConverterUInt64.check_lower(value.requested_msats) + _UniffiConverterUInt64.check_lower(value.invoice_msats) + return if isinstance(value, LnurlError.AuthenticationFailed): return @@ -12902,8 +12934,12 @@ def write(value, buf): if isinstance(value, LnurlError.InvoiceCreationFailed): buf.write_i32(6) _UniffiConverterString.write(value.error_details, buf) - if isinstance(value, LnurlError.AuthenticationFailed): + if isinstance(value, LnurlError.AmountMismatch): buf.write_i32(7) + _UniffiConverterUInt64.write(value.requested_msats, buf) + _UniffiConverterUInt64.write(value.invoice_msats, buf) + if isinstance(value, LnurlError.AuthenticationFailed): + buf.write_i32(8) @@ -19533,6 +19569,29 @@ async def get_lnurl_invoice(address: "str",amount_satoshis: "int") -> "str": # Error FFI converter _UniffiConverterTypeLnurlError, + ) +async def get_lnurl_invoice_for_pay_data(data: "LnurlPayData",amount_msats: "int",comment: "typing.Optional[str]") -> "str": + + _UniffiConverterTypeLnurlPayData.check_lower(data) + + _UniffiConverterUInt64.check_lower(amount_msats) + + _UniffiConverterOptionalString.check_lower(comment) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_bitkitcore_fn_func_get_lnurl_invoice_for_pay_data( + _UniffiConverterTypeLnurlPayData.lower(data), + _UniffiConverterUInt64.lower(amount_msats), + _UniffiConverterOptionalString.lower(comment)), + _UniffiLib.ffi_bitkitcore_rust_future_poll_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_complete_rust_buffer, + _UniffiLib.ffi_bitkitcore_rust_future_free_rust_buffer, + # lift function + _UniffiConverterString.lift, + + # Error FFI converter +_UniffiConverterTypeLnurlError, + ) async def get_min_zero_conf_tx_fee(order_id: "str") -> "IBt0ConfMinTxFeeWindow": @@ -21341,6 +21400,7 @@ def wipe_all_transaction_details() -> None: "get_gift", "get_info", "get_lnurl_invoice", + "get_lnurl_invoice_for_pay_data", "get_min_zero_conf_tx_fee", "get_orders", "get_payment", diff --git a/bindings/python/bitkitcore/libbitkitcore.dylib b/bindings/python/bitkitcore/libbitkitcore.dylib index 47e2ab3..1e2ab5d 100755 Binary files a/bindings/python/bitkitcore/libbitkitcore.dylib and b/bindings/python/bitkitcore/libbitkitcore.dylib differ diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 54b01c6..16e14fb 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -2,7 +2,7 @@ setup( name="bitkitcore", - version="0.3.5", + version="0.3.6", packages=find_packages(), package_data={ "bitkitcore": ["*.so", "*.dylib", "*.dll"], diff --git a/src/lib.rs b/src/lib.rs index 62e2232..f8d162c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ use crate::onchain::{ pub use modules::activity; pub use modules::lnurl; pub use modules::onchain; -pub use modules::scanner::{DecodingError, Scanner}; +pub use modules::scanner::{DecodingError, LnurlPayData, Scanner}; use bip39::Mnemonic; use bitcoin::bip32::Xpriv; @@ -117,6 +117,20 @@ pub async fn get_lnurl_invoice( .unwrap() } +#[uniffi::export] +pub async fn get_lnurl_invoice_for_pay_data( + data: LnurlPayData, + amount_msats: u64, + comment: Option, +) -> Result { + let rt = ensure_runtime(); + rt.spawn( + async move { lnurl::get_lnurl_invoice_for_pay_data(data, amount_msats, comment).await }, + ) + .await + .unwrap() +} + #[uniffi::export] pub fn create_channel_request_url( k1: String, diff --git a/src/modules/lnurl/errors.rs b/src/modules/lnurl/errors.rs index eae58e2..81ae18b 100644 --- a/src/modules/lnurl/errors.rs +++ b/src/modules/lnurl/errors.rs @@ -19,6 +19,11 @@ pub enum LnurlError { }, #[error("Failed to generate invoice: {error_details}")] InvoiceCreationFailed { error_details: String }, + #[error("Invoice amount mismatch")] + AmountMismatch { + requested_msats: u64, + invoice_msats: u64, + }, #[error("LNURL authentication failed")] AuthenticationFailed, } diff --git a/src/modules/lnurl/implementation.rs b/src/modules/lnurl/implementation.rs index b08c6e0..d20a6c3 100644 --- a/src/modules/lnurl/implementation.rs +++ b/src/modules/lnurl/implementation.rs @@ -1,12 +1,20 @@ use crate::lnurl::{ChannelRequestParams, LnurlAuthParams, LnurlError, WithdrawCallbackParams}; +use crate::modules::scanner::LnurlPayData; use bitcoin::bip32::Xpriv; use bitcoin::secp256k1::{Message, PublicKey, Secp256k1}; +use lightning_invoice::Bolt11Invoice; use lnurl::lightning_address::LightningAddress; use lnurl::lnurl::LnUrl; use lnurl::{get_derivation_path, AsyncClient, Builder, LnUrlResponse, Response}; +use serde::Deserialize; use std::str::FromStr; use url::Url; +#[derive(Deserialize)] +struct LnurlPayCallbackResponse { + pr: Option, +} + pub async fn get_lnurl_invoice(address: &str, amount_satoshis: u64) -> Result { let ln_addr = match parse_lightning_address(address) { Ok(addr) => addr, @@ -23,6 +31,33 @@ pub async fn get_lnurl_invoice(address: &str, amount_satoshis: u64) -> Result, +) -> Result { + validate_amount_msats(amount_msats, data.min_sendable, data.max_sendable)?; + + let callback_url = + build_lnurl_pay_callback_url(&data.callback, amount_msats, comment.as_deref())?; + + let response = reqwest::get(callback_url) + .await + .map_err(|_| LnurlError::RequestFailed)? + .error_for_status() + .map_err(|_| LnurlError::RequestFailed)?; + + let callback_response = response + .json::() + .await + .map_err(|_| LnurlError::InvalidResponse)?; + let pr = callback_response.pr.ok_or(LnurlError::InvalidResponse)?; + + validate_lnurl_pay_invoice(&pr, amount_msats)?; + + Ok(pr) +} + fn parse_lightning_address(address: &str) -> Result { LightningAddress::from_str(address).map_err(|_| LnurlError::InvalidAddress) } @@ -56,23 +91,69 @@ async fn generate_invoice( let amount_msats = amount_satoshis * 1000; - // Validate amount range - if amount_msats < pay.min_sendable || amount_msats > pay.max_sendable { - return Err(LnurlError::InvalidAmount { - amount_satoshis, - min: pay.min_sendable / 1000, - max: pay.max_sendable / 1000, - }); - } + validate_amount_msats(amount_msats, pay.min_sendable, pay.max_sendable)?; - // Generate invoice - client + let invoice = client .get_invoice(pay, amount_msats, None, None) .await - .map(|invoice| invoice.pr) .map_err(|e| LnurlError::InvoiceCreationFailed { error_details: e.to_string(), - }) + })?; + + validate_lnurl_pay_invoice(&invoice.pr, amount_msats)?; + + Ok(invoice.pr) +} + +fn validate_amount_msats(amount_msats: u64, min: u64, max: u64) -> Result<(), LnurlError> { + if amount_msats < min || amount_msats > max { + return Err(LnurlError::InvalidAmount { + amount_satoshis: amount_msats.div_ceil(1000), + min: min / 1000, + max: max / 1000, + }); + } + + Ok(()) +} + +pub(crate) fn build_lnurl_pay_callback_url( + callback: &str, + amount_msats: u64, + comment: Option<&str>, +) -> Result { + let mut url = Url::parse(callback).map_err(|_| LnurlError::InvalidAddress)?; + + { + let mut query_pairs = url.query_pairs_mut(); + query_pairs.append_pair("amount", &amount_msats.to_string()); + if let Some(comment) = comment { + if !comment.is_empty() { + query_pairs.append_pair("comment", comment); + } + } + } + + Ok(url) +} + +pub(crate) fn validate_lnurl_pay_invoice(pr: &str, amount_msats: u64) -> Result<(), LnurlError> { + let invoice = Bolt11Invoice::from_str(pr).map_err(|_| LnurlError::InvalidResponse)?; + let invoice_msats = invoice + .amount_milli_satoshis() + .ok_or(LnurlError::AmountMismatch { + requested_msats: amount_msats, + invoice_msats: 0, + })?; + + if invoice_msats != amount_msats { + return Err(LnurlError::AmountMismatch { + requested_msats: amount_msats, + invoice_msats, + }); + } + + Ok(()) } pub fn create_channel_request_url(params: ChannelRequestParams) -> Result { diff --git a/src/modules/lnurl/mod.rs b/src/modules/lnurl/mod.rs index 7050d65..0ddbcc7 100644 --- a/src/modules/lnurl/mod.rs +++ b/src/modules/lnurl/mod.rs @@ -8,7 +8,8 @@ mod tests; pub use errors::LnurlError; pub use implementation::{ - create_channel_request_url, create_withdraw_callback_url, get_lnurl_invoice, lnurl_auth, + create_channel_request_url, create_withdraw_callback_url, get_lnurl_invoice, + get_lnurl_invoice_for_pay_data, lnurl_auth, }; pub use types::{ ChannelRequestParams, LightningAddressInvoice, LnurlAuthParams, WithdrawCallbackParams, diff --git a/src/modules/lnurl/tests.rs b/src/modules/lnurl/tests.rs index 7a3b4bd..80c8baa 100644 --- a/src/modules/lnurl/tests.rs +++ b/src/modules/lnurl/tests.rs @@ -1,12 +1,58 @@ #[cfg(test)] mod tests { use crate::lnurl::implementation::{ - create_channel_request_url, create_withdraw_callback_url, lnurl_auth, + build_lnurl_pay_callback_url, create_channel_request_url, create_withdraw_callback_url, + get_lnurl_invoice_for_pay_data, lnurl_auth, validate_lnurl_pay_invoice, }; use crate::lnurl::{ChannelRequestParams, LnurlAuthParams, LnurlError, WithdrawCallbackParams}; + use crate::LnurlPayData; + use bitcoin::hashes::{sha256, Hash as _}; + use bitcoin::secp256k1::{Secp256k1, SecretKey}; + use lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret}; use lnurl::get_derivation_path; const TEST_MNEMONIC: &str = "stable inch effort skull suggest circle charge lemon amazing clean giant quantum party grow visa best rule icon gown disagree win drop smile love"; + const TEST_METADATA: &str = "[[\"text/plain\",\"test payment\"]]"; + const TEST_AMOUNT_MSATS: u64 = 12_345_000; + + fn create_test_invoice(amount_msats: Option, metadata: &str, hashed: bool) -> String { + let secp = Secp256k1::new(); + let secret_key = SecretKey::from_slice(&[0xab; 32]).unwrap(); + + let mut builder = InvoiceBuilder::new(Currency::Bitcoin) + .payment_hash(sha256::Hash::from_byte_array([1u8; 32])) + .payment_secret(PaymentSecret([2u8; 32])) + .current_timestamp() + .min_final_cltv_expiry_delta(144); + + if let Some(amount_msats) = amount_msats { + builder = builder.amount_milli_satoshis(amount_msats); + } + + let builder = if hashed { + builder.description_hash(sha256::Hash::hash(metadata.as_bytes())) + } else { + builder.description(metadata.to_string()) + }; + + builder + .build_signed(|hash| secp.sign_ecdsa_recoverable(hash, &secret_key)) + .unwrap() + .to_string() + } + + fn test_pay_data() -> LnurlPayData { + LnurlPayData { + uri: "lnurl1test".to_string(), + callback: "https://example.com/callback?existing=1".to_string(), + min_sendable: 1_000, + max_sendable: 20_000_000, + metadata_str: TEST_METADATA.to_string(), + comment_allowed: Some(100), + allows_nostr: false, + nostr_pubkey: None, + } + } #[test] fn test_create_channel_request_url() { @@ -136,6 +182,118 @@ mod tests { assert!(matches!(result, Err(LnurlError::InvalidAddress))); } + #[test] + fn test_lnurl_pay_callback_url_preserves_existing_params() { + let url = build_lnurl_pay_callback_url( + "https://example.com/callback?existing=param", + TEST_AMOUNT_MSATS, + Some("hello"), + ) + .unwrap(); + + assert_eq!(url.scheme(), "https"); + assert!(url.as_str().contains("existing=param")); + assert!(url.as_str().contains("amount=12345000")); + assert!(url.as_str().contains("comment=hello")); + } + + #[test] + fn test_validate_lnurl_pay_invoice_exact_match() { + let invoice = create_test_invoice(Some(TEST_AMOUNT_MSATS), TEST_METADATA, false); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(result.is_ok()); + } + + #[test] + fn test_validate_lnurl_pay_invoice_larger_mismatch() { + let invoice = create_test_invoice(Some(TEST_AMOUNT_MSATS + 1_000), TEST_METADATA, false); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(matches!( + result, + Err(LnurlError::AmountMismatch { + requested_msats: TEST_AMOUNT_MSATS, + invoice_msats + }) if invoice_msats == TEST_AMOUNT_MSATS + 1_000 + )); + } + + #[test] + fn test_validate_lnurl_pay_invoice_smaller_mismatch() { + let invoice = create_test_invoice(Some(TEST_AMOUNT_MSATS - 1_000), TEST_METADATA, false); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(matches!( + result, + Err(LnurlError::AmountMismatch { + requested_msats: TEST_AMOUNT_MSATS, + invoice_msats + }) if invoice_msats == TEST_AMOUNT_MSATS - 1_000 + )); + } + + #[test] + fn test_validate_lnurl_pay_invoice_amountless() { + let invoice = create_test_invoice(None, TEST_METADATA, false); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(matches!( + result, + Err(LnurlError::AmountMismatch { + requested_msats: TEST_AMOUNT_MSATS, + invoice_msats: 0 + }) + )); + } + + #[test] + fn test_validate_lnurl_pay_invoice_malformed() { + let result = validate_lnurl_pay_invoice("lnbc1malformed", TEST_AMOUNT_MSATS); + + assert!(matches!(result, Err(LnurlError::InvalidResponse))); + } + + #[tokio::test] + async fn test_get_lnurl_invoice_for_pay_data_amount_outside_range() { + let data = test_pay_data(); + + let result = get_lnurl_invoice_for_pay_data(data, 999, None).await; + + assert!(matches!(result, Err(LnurlError::InvalidAmount { .. }))); + } + + #[test] + fn test_validate_lnurl_pay_invoice_matching_amount_with_hash_description() { + let invoice = create_test_invoice(Some(TEST_AMOUNT_MSATS), TEST_METADATA, true); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(result.is_ok()); + } + + #[test] + fn test_validate_lnurl_pay_invoice_matching_amount_with_text_description() { + let invoice = create_test_invoice(Some(TEST_AMOUNT_MSATS), "test payment", false); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(result.is_ok()); + } + + #[test] + fn test_validate_lnurl_pay_invoice_matching_amount_with_different_description() { + let invoice = create_test_invoice(Some(TEST_AMOUNT_MSATS), "other metadata", false); + + let result = validate_lnurl_pay_invoice(&invoice, TEST_AMOUNT_MSATS); + + assert!(result.is_ok()); + } + #[test] fn test_get_derivation_path() { use url::Url; diff --git a/src/modules/scanner/errors.rs b/src/modules/scanner/errors.rs index 46f8198..7364b86 100644 --- a/src/modules/scanner/errors.rs +++ b/src/modules/scanner/errors.rs @@ -58,6 +58,7 @@ impl From for DecodingError { min, max, }, + LnurlError::AmountMismatch { .. } => DecodingError::InvalidResponse, LnurlError::AuthenticationFailed => DecodingError::InvalidResponse, } }