diff --git a/Cargo.lock b/Cargo.lock index 81a2442..bf56005 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "bitkitcore" -version = "0.1.67" +version = "0.1.75" dependencies = [ "android_logger", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 8aeeb16..e921f64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitkitcore" -version = "0.1.67" +version = "0.1.75" edition = "2021" [lib] diff --git a/Package.swift b/Package.swift index a8505a4..7fd8b8a 100644 --- a/Package.swift +++ b/Package.swift @@ -3,8 +3,8 @@ import PackageDescription -let tag = "v0.1.67" -let checksum = "96e05d5061ab9692f63ebfaccc980600cf11042b02c920459edd03e0a881fe0e" +let tag = "v0.1.75" +let checksum = "9e4c13246dee06e38491d4112029352b60032df54ac7ed885a64375186c6dc3b" 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 b60a220..a972522 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.1.67 +version=0.1.75 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 bce3418..ecbf43b 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 9c32e8e..3ba0e49 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 1fa8f52..5eed911 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 1cb60af..721b83f 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 03fb477..9c25c40 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 @@ -1477,6 +1477,8 @@ internal typealias UniffiVTableCallbackInterfaceTrezorUiCallbackUniffiByValue = + + @@ -1672,6 +1674,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") } @@ -2147,6 +2152,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 @@ -2895,6 +2903,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 @@ -9520,7 +9534,12 @@ public object FfiConverterTypeLnurlError : FfiConverterRustBuffer LnurlException.InvoiceCreationFailed( FfiConverterString.read(buf), ) - 7 -> LnurlException.AuthenticationFailed() + 7 -> LnurlException.AmountMismatch( + FfiConverterULong.read(buf), + FfiConverterULong.read(buf), + ) + 8 -> LnurlException.MetadataMismatch() + 9 -> LnurlException.AuthenticationFailed() else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } @@ -9555,6 +9574,16 @@ 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.MetadataMismatch -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4UL + ) is LnurlException.AuthenticationFailed -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields 4UL @@ -9592,8 +9621,18 @@ public object FfiConverterTypeLnurlError : FfiConverterRustBuffer { + is LnurlException.AmountMismatch -> { buf.putInt(7) + FfiConverterULong.write(value.`requestedMsats`, buf) + FfiConverterULong.write(value.`invoiceMsats`, buf) + Unit + } + is LnurlException.MetadataMismatch -> { + buf.putInt(8) + Unit + } + is LnurlException.AuthenticationFailed -> { + buf.putInt(9) Unit } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } @@ -13544,6 +13583,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 7f80bb1..806f1fa 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 @@ -120,7 +120,7 @@ public object NoPointer * from xpub watchers. */ public interface EventListener { - + /** * Called when a watcher event occurs. * @@ -128,7 +128,7 @@ public interface EventListener { * `event` is a typed enum — no JSON parsing needed. */ public fun `onEvent`(`watcherId`: kotlin.String, `event`: WatcherEvent) - + public companion object } @@ -3428,6 +3428,20 @@ 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 MetadataMismatch( + ) : LnurlException() { + override val message: String + get() = "" + } + public class AuthenticationFailed( ) : LnurlException() { override val message: String @@ -4354,6 +4368,5 @@ public enum class WordCount { - diff --git a/bindings/ios/BitkitCore.xcframework.zip b/bindings/ios/BitkitCore.xcframework.zip index 9614b20..fca4855 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 adc6b27..1765c8a 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/Headers/bitkitcoreFFI.h @@ -502,7 +502,7 @@ RustBuffer uniffi_bitkitcore_fn_method_trezoruicallback_on_passphrase_request(vo #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ACTIVITY_WIPE_ALL #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ACTIVITY_WIPE_ALL void uniffi_bitkitcore_fn_func_activity_wipe_all(RustCallStatus *_Nonnull out_status - + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ADD_PRE_ACTIVITY_METADATA @@ -765,6 +765,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 @@ -1824,6 +1829,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 @@ -2468,4 +2479,3 @@ uint32_t ffi_bitkitcore_uniffi_contract_version(void ); #endif - diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64-simulator/libbitkitcore.a index 0f64676..ad00e6f 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 adc6b27..1765c8a 100644 --- a/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h +++ b/bindings/ios/BitkitCore.xcframework/ios-arm64/Headers/bitkitcoreFFI.h @@ -502,7 +502,7 @@ RustBuffer uniffi_bitkitcore_fn_method_trezoruicallback_on_passphrase_request(vo #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ACTIVITY_WIPE_ALL #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ACTIVITY_WIPE_ALL void uniffi_bitkitcore_fn_func_activity_wipe_all(RustCallStatus *_Nonnull out_status - + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ADD_PRE_ACTIVITY_METADATA @@ -765,6 +765,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 @@ -1824,6 +1829,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 @@ -2468,4 +2479,3 @@ uint32_t ffi_bitkitcore_uniffi_contract_version(void ); #endif - diff --git a/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a b/bindings/ios/BitkitCore.xcframework/ios-arm64/libbitkitcore.a index c52bc3c..558f38f 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 c3e006b..7ad5c9c 100644 --- a/bindings/ios/bitkitcore.swift +++ b/bindings/ios/bitkitcore.swift @@ -15772,6 +15772,9 @@ public enum LnurlError: Swift.Error { ) case InvoiceCreationFailed(errorDetails: String ) + case AmountMismatch(requestedMsats: UInt64, invoiceMsats: UInt64 + ) + case MetadataMismatch case AuthenticationFailed } @@ -15801,7 +15804,12 @@ 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 .MetadataMismatch + case 9: return .AuthenticationFailed default: throw UniffiInternalError.unexpectedEnumCase } @@ -15835,15 +15843,25 @@ public struct FfiConverterTypeLnurlError: FfiConverterRustBuffer { FfiConverterUInt64.write(amountSatoshis, into: &buf) FfiConverterUInt64.write(min, into: &buf) FfiConverterUInt64.write(max, into: &buf) - - + + case let .InvoiceCreationFailed(errorDetails): writeInt(&buf, Int32(6)) 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 .MetadataMismatch: + writeInt(&buf, Int32(8)) + + + case .AuthenticationFailed: + writeInt(&buf, Int32(9)) } } @@ -20485,6 +20503,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( @@ -21816,6 +21848,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 } @@ -22154,4 +22189,4 @@ public func uniffiEnsureBitkitcoreInitialized() { } } -// swiftlint:enable all \ No newline at end of file +// swiftlint:enable all diff --git a/bindings/ios/bitkitcoreFFI.h b/bindings/ios/bitkitcoreFFI.h index adc6b27..1765c8a 100644 --- a/bindings/ios/bitkitcoreFFI.h +++ b/bindings/ios/bitkitcoreFFI.h @@ -502,7 +502,7 @@ RustBuffer uniffi_bitkitcore_fn_method_trezoruicallback_on_passphrase_request(vo #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ACTIVITY_WIPE_ALL #define UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ACTIVITY_WIPE_ALL void uniffi_bitkitcore_fn_func_activity_wipe_all(RustCallStatus *_Nonnull out_status - + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_BITKITCORE_FN_FUNC_ADD_PRE_ACTIVITY_METADATA @@ -765,6 +765,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 @@ -1824,6 +1829,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 @@ -2468,4 +2479,3 @@ uint32_t ffi_bitkitcore_uniffi_contract_version(void ); #endif - diff --git a/bindings/python/bitkitcore/bitkitcore.py b/bindings/python/bitkitcore/bitkitcore.py index a258db5..1724e7e 100644 --- a/bindings/python/bitkitcore/bitkitcore.py +++ b/bindings/python/bitkitcore/bitkitcore.py @@ -563,6 +563,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: @@ -1357,6 +1359,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, ) @@ -2237,6 +2245,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 @@ -10280,19 +10291,19 @@ class AccountType(enum.Enum): BIP44 legacy (P2PKH) — xpub/tpub prefix """ - + WRAPPED_SEGWIT = 1 """ BIP49 wrapped segwit (P2SH-P2WPKH) — ypub/upub prefix """ - + NATIVE_SEGWIT = 2 """ BIP84 native segwit (P2WPKH) — zpub/vpub prefix """ - + TAPROOT = 3 """ BIP86 taproot (P2TR) @@ -12524,6 +12535,25 @@ 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 MetadataMismatch(_UniffiTempLnurlError): + def __init__(self): + pass + + def __repr__(self): + return "LnurlError.MetadataMismatch({})".format(str(self)) + _UniffiTempLnurlError.MetadataMismatch = MetadataMismatch # type: ignore class AuthenticationFailed(_UniffiTempLnurlError): def __init__(self): pass @@ -12563,6 +12593,14 @@ def read(buf): _UniffiConverterString.read(buf), ) if variant == 7: + return LnurlError.AmountMismatch( + _UniffiConverterUInt64.read(buf), + _UniffiConverterUInt64.read(buf), + ) + if variant == 8: + return LnurlError.MetadataMismatch( + ) + if variant == 9: return LnurlError.AuthenticationFailed( ) raise InternalError("Raw enum value doesn't match any cases") @@ -12585,6 +12623,12 @@ 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.MetadataMismatch): + return if isinstance(value, LnurlError.AuthenticationFailed): return @@ -12606,8 +12650,14 @@ 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.MetadataMismatch): + buf.write_i32(8) + if isinstance(value, LnurlError.AuthenticationFailed): + buf.write_i32(9) @@ -17103,7 +17153,7 @@ def on_event(self, watcher_id: "str",event: "WatcherEvent") -> None: """ _UniffiConverterString.check_lower(watcher_id) - + _UniffiConverterTypeWatcherEvent.check_lower(event) _uniffi_rust_call(_UniffiLib.uniffi_bitkitcore_fn_method_eventlistener_on_event,self._uniffi_clone_pointer(), @@ -19027,6 +19077,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": @@ -20774,6 +20847,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", @@ -20870,4 +20944,3 @@ def wipe_all_transaction_details() -> None: "TrezorTransportCallback", "TrezorUiCallback", ] - diff --git a/bindings/python/bitkitcore/libbitkitcore.dylib b/bindings/python/bitkitcore/libbitkitcore.dylib index 4d2b0ca..07ff79a 100755 Binary files a/bindings/python/bitkitcore/libbitkitcore.dylib and b/bindings/python/bitkitcore/libbitkitcore.dylib differ diff --git a/src/lib.rs b/src/lib.rs index d6255c1..ea699b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,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; @@ -115,6 +115,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..16c828b 100644 --- a/src/modules/lnurl/errors.rs +++ b/src/modules/lnurl/errors.rs @@ -19,6 +19,13 @@ 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("Invoice metadata mismatch")] + MetadataMismatch, #[error("LNURL authentication failed")] AuthenticationFailed, } diff --git a/src/modules/lnurl/implementation.rs b/src/modules/lnurl/implementation.rs index b08c6e0..85baa38 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, &data.metadata_str)?; + + Ok(pr) +} + fn parse_lightning_address(address: &str) -> Result { LightningAddress::from_str(address).map_err(|_| LnurlError::InvalidAddress) } @@ -56,23 +91,73 @@ 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, &pay.metadata)?; + + 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, + _metadata: &str, +) -> 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..96e7278 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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, TEST_METADATA); + + 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..5a966f3 100644 --- a/src/modules/scanner/errors.rs +++ b/src/modules/scanner/errors.rs @@ -58,6 +58,8 @@ impl From for DecodingError { min, max, }, + LnurlError::AmountMismatch { .. } => DecodingError::InvalidResponse, + LnurlError::MetadataMismatch => DecodingError::InvalidResponse, LnurlError::AuthenticationFailed => DecodingError::InvalidResponse, } }