diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 7b3921380..f27bc6475 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -3436,7 +3436,7 @@ dependencies = [ [[package]] name = "sdk-common" version = "0.6.2" -source = "git+https://github.com/breez/breez-sdk?rev=e537feb8ed134bc3c7af5196e10a0a189dd36ac7#e537feb8ed134bc3c7af5196e10a0a189dd36ac7" +source = "git+https://github.com/breez/breez-sdk?rev=f77208acd34d74b571388889e856444908c59a85#f77208acd34d74b571388889e856444908c59a85" dependencies = [ "aes 0.8.4", "anyhow", diff --git a/lib/Cargo.lock b/lib/Cargo.lock index e83aefc6b..557adfdbb 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -3530,7 +3530,7 @@ dependencies = [ [[package]] name = "sdk-common" version = "0.6.2" -source = "git+https://github.com/breez/breez-sdk?rev=e537feb8ed134bc3c7af5196e10a0a189dd36ac7#e537feb8ed134bc3c7af5196e10a0a189dd36ac7" +source = "git+https://github.com/breez/breez-sdk?rev=f77208acd34d74b571388889e856444908c59a85#f77208acd34d74b571388889e856444908c59a85" dependencies = [ "aes 0.8.4", "anyhow", diff --git a/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h b/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h index ced162528..2280a07d3 100644 --- a/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h +++ b/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h @@ -237,6 +237,18 @@ typedef struct wire_cst_send_destination { union SendDestinationKind kind; } wire_cst_send_destination; +typedef struct wire_cst_ln_url_pay_request_data { + struct wire_cst_list_prim_u_8_strict *callback; + uint64_t min_sendable; + uint64_t max_sendable; + struct wire_cst_list_prim_u_8_strict *metadata_str; + uint16_t comment_allowed; + struct wire_cst_list_prim_u_8_strict *domain; + bool allows_nostr; + struct wire_cst_list_prim_u_8_strict *nostr_pubkey; + struct wire_cst_list_prim_u_8_strict *ln_address; +} wire_cst_ln_url_pay_request_data; + typedef struct wire_cst_aes_success_action_data { struct wire_cst_list_prim_u_8_strict *description; struct wire_cst_list_prim_u_8_strict *ciphertext; @@ -279,6 +291,8 @@ typedef struct wire_cst_success_action { typedef struct wire_cst_prepare_ln_url_pay_response { struct wire_cst_send_destination destination; uint64_t fees_sat; + struct wire_cst_ln_url_pay_request_data data; + struct wire_cst_list_prim_u_8_strict *comment; struct wire_cst_success_action *success_action; } wire_cst_prepare_ln_url_pay_response; @@ -316,18 +330,6 @@ typedef struct wire_cst_prepare_buy_bitcoin_request { uint64_t amount_sat; } wire_cst_prepare_buy_bitcoin_request; -typedef struct wire_cst_ln_url_pay_request_data { - struct wire_cst_list_prim_u_8_strict *callback; - uint64_t min_sendable; - uint64_t max_sendable; - struct wire_cst_list_prim_u_8_strict *metadata_str; - uint16_t comment_allowed; - struct wire_cst_list_prim_u_8_strict *domain; - bool allows_nostr; - struct wire_cst_list_prim_u_8_strict *nostr_pubkey; - struct wire_cst_list_prim_u_8_strict *ln_address; -} wire_cst_ln_url_pay_request_data; - typedef struct wire_cst_prepare_ln_url_pay_request { struct wire_cst_ln_url_pay_request_data data; uint64_t amount_msat; @@ -411,6 +413,62 @@ typedef struct wire_cst_binding_event_listener { struct wire_cst_list_prim_u_8_strict *stream; } wire_cst_binding_event_listener; +typedef struct wire_cst_aes_success_action_data_decrypted { + struct wire_cst_list_prim_u_8_strict *description; + struct wire_cst_list_prim_u_8_strict *plaintext; +} wire_cst_aes_success_action_data_decrypted; + +typedef struct wire_cst_AesSuccessActionDataResult_Decrypted { + struct wire_cst_aes_success_action_data_decrypted *data; +} wire_cst_AesSuccessActionDataResult_Decrypted; + +typedef struct wire_cst_AesSuccessActionDataResult_ErrorStatus { + struct wire_cst_list_prim_u_8_strict *reason; +} wire_cst_AesSuccessActionDataResult_ErrorStatus; + +typedef union AesSuccessActionDataResultKind { + struct wire_cst_AesSuccessActionDataResult_Decrypted Decrypted; + struct wire_cst_AesSuccessActionDataResult_ErrorStatus ErrorStatus; +} AesSuccessActionDataResultKind; + +typedef struct wire_cst_aes_success_action_data_result { + int32_t tag; + union AesSuccessActionDataResultKind kind; +} wire_cst_aes_success_action_data_result; + +typedef struct wire_cst_SuccessActionProcessed_Aes { + struct wire_cst_aes_success_action_data_result *result; +} wire_cst_SuccessActionProcessed_Aes; + +typedef struct wire_cst_SuccessActionProcessed_Message { + struct wire_cst_message_success_action_data *data; +} wire_cst_SuccessActionProcessed_Message; + +typedef struct wire_cst_SuccessActionProcessed_Url { + struct wire_cst_url_success_action_data *data; +} wire_cst_SuccessActionProcessed_Url; + +typedef union SuccessActionProcessedKind { + struct wire_cst_SuccessActionProcessed_Aes Aes; + struct wire_cst_SuccessActionProcessed_Message Message; + struct wire_cst_SuccessActionProcessed_Url Url; +} SuccessActionProcessedKind; + +typedef struct wire_cst_success_action_processed { + int32_t tag; + union SuccessActionProcessedKind kind; +} wire_cst_success_action_processed; + +typedef struct wire_cst_ln_url_info { + struct wire_cst_list_prim_u_8_strict *ln_address; + struct wire_cst_list_prim_u_8_strict *lnurl_pay_comment; + struct wire_cst_list_prim_u_8_strict *lnurl_pay_domain; + struct wire_cst_list_prim_u_8_strict *lnurl_pay_metadata; + struct wire_cst_success_action_processed *lnurl_pay_success_action; + struct wire_cst_success_action *lnurl_pay_unprocessed_success_action; + struct wire_cst_list_prim_u_8_strict *lnurl_withdraw_endpoint; +} wire_cst_ln_url_info; + typedef struct wire_cst_PaymentDetails_Lightning { struct wire_cst_list_prim_u_8_strict *swap_id; struct wire_cst_list_prim_u_8_strict *description; @@ -418,6 +476,7 @@ typedef struct wire_cst_PaymentDetails_Lightning { struct wire_cst_list_prim_u_8_strict *bolt11; struct wire_cst_list_prim_u_8_strict *bolt12_offer; struct wire_cst_list_prim_u_8_strict *payment_hash; + struct wire_cst_ln_url_info *lnurl_info; struct wire_cst_list_prim_u_8_strict *refund_tx_id; uint64_t *refund_tx_amount_sat; } wire_cst_PaymentDetails_Lightning; @@ -526,29 +585,6 @@ typedef struct wire_cst_connect_request { struct wire_cst_list_prim_u_8_strict *mnemonic; } wire_cst_connect_request; -typedef struct wire_cst_aes_success_action_data_decrypted { - struct wire_cst_list_prim_u_8_strict *description; - struct wire_cst_list_prim_u_8_strict *plaintext; -} wire_cst_aes_success_action_data_decrypted; - -typedef struct wire_cst_AesSuccessActionDataResult_Decrypted { - struct wire_cst_aes_success_action_data_decrypted *data; -} wire_cst_AesSuccessActionDataResult_Decrypted; - -typedef struct wire_cst_AesSuccessActionDataResult_ErrorStatus { - struct wire_cst_list_prim_u_8_strict *reason; -} wire_cst_AesSuccessActionDataResult_ErrorStatus; - -typedef union AesSuccessActionDataResultKind { - struct wire_cst_AesSuccessActionDataResult_Decrypted Decrypted; - struct wire_cst_AesSuccessActionDataResult_ErrorStatus ErrorStatus; -} AesSuccessActionDataResultKind; - -typedef struct wire_cst_aes_success_action_data_result { - int32_t tag; - union AesSuccessActionDataResultKind kind; -} wire_cst_aes_success_action_data_result; - typedef struct wire_cst_bitcoin_address_data { struct wire_cst_list_prim_u_8_strict *address; int32_t network; @@ -566,29 +602,6 @@ typedef struct wire_cst_ln_url_pay_error_data { struct wire_cst_list_prim_u_8_strict *reason; } wire_cst_ln_url_pay_error_data; -typedef struct wire_cst_SuccessActionProcessed_Aes { - struct wire_cst_aes_success_action_data_result *result; -} wire_cst_SuccessActionProcessed_Aes; - -typedef struct wire_cst_SuccessActionProcessed_Message { - struct wire_cst_message_success_action_data *data; -} wire_cst_SuccessActionProcessed_Message; - -typedef struct wire_cst_SuccessActionProcessed_Url { - struct wire_cst_url_success_action_data *data; -} wire_cst_SuccessActionProcessed_Url; - -typedef union SuccessActionProcessedKind { - struct wire_cst_SuccessActionProcessed_Aes Aes; - struct wire_cst_SuccessActionProcessed_Message Message; - struct wire_cst_SuccessActionProcessed_Url Url; -} SuccessActionProcessedKind; - -typedef struct wire_cst_success_action_processed { - int32_t tag; - union SuccessActionProcessedKind kind; -} wire_cst_success_action_processed; - typedef struct wire_cst_ln_url_pay_success_data { struct wire_cst_payment payment; struct wire_cst_success_action_processed *success_action; @@ -1237,6 +1250,8 @@ struct wire_cst_ln_url_auth_request_data *frbgen_breez_liquid_cst_new_box_autoad struct wire_cst_ln_url_error_data *frbgen_breez_liquid_cst_new_box_autoadd_ln_url_error_data(void); +struct wire_cst_ln_url_info *frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info(void); + struct wire_cst_ln_url_pay_error_data *frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_error_data(void); struct wire_cst_ln_url_pay_request *frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_request(void); @@ -1345,6 +1360,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_offer); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_auth_request_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_error_data); + dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_error_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_request_data); diff --git a/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift b/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift index 49ea38709..fd70f715b 100644 --- a/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift +++ b/lib/bindings/langs/swift/Sources/BreezSDKLiquid/Task/SwapUpdated.swift @@ -54,7 +54,7 @@ class SwapUpdatedTask : TaskProtocol { switch details { case let .bitcoin(swapId, _, _, _): return swapId - case let .lightning(swapId, _, _, _, _, _, _, _): + case let .lightning(swapId, _, _, _, _, _, _, _, _): return swapId default: break diff --git a/lib/bindings/src/breez_sdk_liquid.udl b/lib/bindings/src/breez_sdk_liquid.udl index d9674047a..7b0f69f00 100644 --- a/lib/bindings/src/breez_sdk_liquid.udl +++ b/lib/bindings/src/breez_sdk_liquid.udl @@ -389,6 +389,8 @@ dictionary PrepareLnUrlPayRequest { dictionary PrepareLnUrlPayResponse { SendDestination destination; u64 fees_sat; + LnUrlPayRequestData data; + string? comment = null; SuccessAction? success_action = null; }; @@ -538,9 +540,19 @@ interface GetPaymentRequest { Lightning(string payment_hash); }; +dictionary LnUrlInfo { + string? ln_address; + string? lnurl_pay_comment; + string? lnurl_pay_domain; + string? lnurl_pay_metadata; + SuccessActionProcessed? lnurl_pay_success_action; + SuccessAction? lnurl_pay_unprocessed_success_action; + string? lnurl_withdraw_endpoint; +}; + [Enum] interface PaymentDetails { - Lightning(string swap_id, string description, string? preimage, string? bolt11, string? bolt12_offer, string? payment_hash, string? refund_tx_id, u64? refund_tx_amount_sat); + Lightning(string swap_id, string description, string? preimage, string? bolt11, string? bolt12_offer, string? payment_hash, LnUrlInfo? lnurl_info, string? refund_tx_id, u64? refund_tx_amount_sat); Liquid(string destination, string description); Bitcoin(string swap_id, string description, string? refund_tx_id, u64? refund_tx_amount_sat); }; diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index f97c6ea3e..26af69232 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -32,7 +32,7 @@ lwk_wollet = { git = "https://github.com/dangeross/lwk", branch = "savage-full-s #lwk_wollet = "0.7.0" rusqlite = { version = "0.31", features = ["backup", "bundled"] } rusqlite_migration = "1.0" -sdk-common = { git = "https://github.com/breez/breez-sdk", rev = "e537feb8ed134bc3c7af5196e10a0a189dd36ac7", features = ["liquid"] } +sdk-common = { git = "https://github.com/breez/breez-sdk", rev = "f77208acd34d74b571388889e856444908c59a85", features = ["liquid"] } serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.116" strum = "0.25" diff --git a/lib/core/src/chain_swap.rs b/lib/core/src/chain_swap.rs index 59a7704bf..09b95195b 100644 --- a/lib/core/src/chain_swap.rs +++ b/lib/core/src/chain_swap.rs @@ -485,7 +485,7 @@ impl ChainSwapHandler { fees_sat: lockup_tx_fees_sat + swap.claim_fees_sat, payment_type: PaymentType::Send, is_confirmed: false, - }, None, None)?; + }, None, false)?; self.update_swap_info(&ChainSwapUpdate { swap_id: id, @@ -840,7 +840,7 @@ impl ChainSwapHandler { is_confirmed: false, }, None, - None, + false, )?; Some(claim_tx_id.clone()) } @@ -1320,9 +1320,8 @@ impl ChainSwapHandler { #[cfg(test)] mod tests { - use std::collections::{HashMap, HashSet}; - use anyhow::Result; + use std::collections::{HashMap, HashSet}; use crate::{ model::{ diff --git a/lib/core/src/frb_generated.rs b/lib/core/src/frb_generated.rs index 620e3867e..7c4a00c1e 100644 --- a/lib/core/src/frb_generated.rs +++ b/lib/core/src/frb_generated.rs @@ -2989,6 +2989,30 @@ impl SseDecode for crate::bindings::LnUrlErrorData { } } +impl SseDecode for crate::model::LnUrlInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_lnAddress = >::sse_decode(deserializer); + let mut var_lnurlPayComment = >::sse_decode(deserializer); + let mut var_lnurlPayDomain = >::sse_decode(deserializer); + let mut var_lnurlPayMetadata = >::sse_decode(deserializer); + let mut var_lnurlPaySuccessAction = + >::sse_decode(deserializer); + let mut var_lnurlPayUnprocessedSuccessAction = + >::sse_decode(deserializer); + let mut var_lnurlWithdrawEndpoint = >::sse_decode(deserializer); + return crate::model::LnUrlInfo { + ln_address: var_lnAddress, + lnurl_pay_comment: var_lnurlPayComment, + lnurl_pay_domain: var_lnurlPayDomain, + lnurl_pay_metadata: var_lnurlPayMetadata, + lnurl_pay_success_action: var_lnurlPaySuccessAction, + lnurl_pay_unprocessed_success_action: var_lnurlPayUnprocessedSuccessAction, + lnurl_withdraw_endpoint: var_lnurlWithdrawEndpoint, + }; + } +} + impl SseDecode for crate::bindings::duplicates::LnUrlPayError { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -3400,6 +3424,17 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -3586,6 +3621,7 @@ impl SseDecode for crate::model::PaymentDetails { let mut var_bolt11 = >::sse_decode(deserializer); let mut var_bolt12Offer = >::sse_decode(deserializer); let mut var_paymentHash = >::sse_decode(deserializer); + let mut var_lnurlInfo = >::sse_decode(deserializer); let mut var_refundTxId = >::sse_decode(deserializer); let mut var_refundTxAmountSat = >::sse_decode(deserializer); return crate::model::PaymentDetails::Lightning { @@ -3595,6 +3631,7 @@ impl SseDecode for crate::model::PaymentDetails { bolt11: var_bolt11, bolt12_offer: var_bolt12Offer, payment_hash: var_paymentHash, + lnurl_info: var_lnurlInfo, refund_tx_id: var_refundTxId, refund_tx_amount_sat: var_refundTxAmountSat, }; @@ -3804,11 +3841,15 @@ impl SseDecode for crate::model::PrepareLnUrlPayResponse { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut var_destination = ::sse_decode(deserializer); let mut var_feesSat = ::sse_decode(deserializer); + let mut var_data = ::sse_decode(deserializer); + let mut var_comment = >::sse_decode(deserializer); let mut var_successAction = >::sse_decode(deserializer); return crate::model::PrepareLnUrlPayResponse { destination: var_destination, fees_sat: var_feesSat, + data: var_data, + comment: var_comment, success_action: var_successAction, }; } @@ -5130,6 +5171,29 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.ln_address.into_into_dart().into_dart(), + self.lnurl_pay_comment.into_into_dart().into_dart(), + self.lnurl_pay_domain.into_into_dart().into_dart(), + self.lnurl_pay_metadata.into_into_dart().into_dart(), + self.lnurl_pay_success_action.into_into_dart().into_dart(), + self.lnurl_pay_unprocessed_success_action + .into_into_dart() + .into_dart(), + self.lnurl_withdraw_endpoint.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::model::LnUrlInfo {} +impl flutter_rust_bridge::IntoIntoDart for crate::model::LnUrlInfo { + fn into_into_dart(self) -> crate::model::LnUrlInfo { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::bindings::duplicates::LnUrlPayError { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { @@ -5622,6 +5686,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentDetails { bolt11, bolt12_offer, payment_hash, + lnurl_info, refund_tx_id, refund_tx_amount_sat, } => [ @@ -5632,6 +5697,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentDetails { bolt11.into_into_dart().into_dart(), bolt12_offer.into_into_dart().into_dart(), payment_hash.into_into_dart().into_dart(), + lnurl_info.into_into_dart().into_dart(), refund_tx_id.into_into_dart().into_dart(), refund_tx_amount_sat.into_into_dart().into_dart(), ] @@ -5862,6 +5928,8 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareLnUrlPayResponse { [ self.destination.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(), + self.data.into_into_dart().into_dart(), + self.comment.into_into_dart().into_dart(), self.success_action.into_into_dart().into_dart(), ] .into_dart() @@ -7163,6 +7231,25 @@ impl SseEncode for crate::bindings::LnUrlErrorData { } } +impl SseEncode for crate::model::LnUrlInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.ln_address, serializer); + >::sse_encode(self.lnurl_pay_comment, serializer); + >::sse_encode(self.lnurl_pay_domain, serializer); + >::sse_encode(self.lnurl_pay_metadata, serializer); + >::sse_encode( + self.lnurl_pay_success_action, + serializer, + ); + >::sse_encode( + self.lnurl_pay_unprocessed_success_action, + serializer, + ); + >::sse_encode(self.lnurl_withdraw_endpoint, serializer); + } +} + impl SseEncode for crate::bindings::duplicates::LnUrlPayError { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -7492,6 +7579,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -7644,6 +7741,7 @@ impl SseEncode for crate::model::PaymentDetails { bolt11, bolt12_offer, payment_hash, + lnurl_info, refund_tx_id, refund_tx_amount_sat, } => { @@ -7654,6 +7752,7 @@ impl SseEncode for crate::model::PaymentDetails { >::sse_encode(bolt11, serializer); >::sse_encode(bolt12_offer, serializer); >::sse_encode(payment_hash, serializer); + >::sse_encode(lnurl_info, serializer); >::sse_encode(refund_tx_id, serializer); >::sse_encode(refund_tx_amount_sat, serializer); } @@ -7855,6 +7954,8 @@ impl SseEncode for crate::model::PrepareLnUrlPayResponse { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { ::sse_encode(self.destination, serializer); ::sse_encode(self.fees_sat, serializer); + ::sse_encode(self.data, serializer); + >::sse_encode(self.comment, serializer); >::sse_encode(self.success_action, serializer); } } @@ -8571,6 +8672,13 @@ mod io { CstDecode::::cst_decode(*wrap).into() } } + impl CstDecode for *mut wire_cst_ln_url_info { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::LnUrlInfo { + let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }; + CstDecode::::cst_decode(*wrap).into() + } + } impl CstDecode for *mut wire_cst_ln_url_pay_error_data { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::bindings::LnUrlPayErrorData { @@ -9280,6 +9388,22 @@ mod io { } } } + impl CstDecode for wire_cst_ln_url_info { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::model::LnUrlInfo { + crate::model::LnUrlInfo { + ln_address: self.ln_address.cst_decode(), + lnurl_pay_comment: self.lnurl_pay_comment.cst_decode(), + lnurl_pay_domain: self.lnurl_pay_domain.cst_decode(), + lnurl_pay_metadata: self.lnurl_pay_metadata.cst_decode(), + lnurl_pay_success_action: self.lnurl_pay_success_action.cst_decode(), + lnurl_pay_unprocessed_success_action: self + .lnurl_pay_unprocessed_success_action + .cst_decode(), + lnurl_withdraw_endpoint: self.lnurl_withdraw_endpoint.cst_decode(), + } + } + } impl CstDecode for wire_cst_ln_url_pay_error { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::bindings::duplicates::LnUrlPayError { @@ -9629,6 +9753,7 @@ mod io { bolt11: ans.bolt11.cst_decode(), bolt12_offer: ans.bolt12_offer.cst_decode(), payment_hash: ans.payment_hash.cst_decode(), + lnurl_info: ans.lnurl_info.cst_decode(), refund_tx_id: ans.refund_tx_id.cst_decode(), refund_tx_amount_sat: ans.refund_tx_amount_sat.cst_decode(), } @@ -9769,6 +9894,8 @@ mod io { crate::model::PrepareLnUrlPayResponse { destination: self.destination.cst_decode(), fees_sat: self.fees_sat.cst_decode(), + data: self.data.cst_decode(), + comment: self.comment.cst_decode(), success_action: self.success_action.cst_decode(), } } @@ -10585,6 +10712,24 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_ln_url_info { + fn new_with_null_ptr() -> Self { + Self { + ln_address: core::ptr::null_mut(), + lnurl_pay_comment: core::ptr::null_mut(), + lnurl_pay_domain: core::ptr::null_mut(), + lnurl_pay_metadata: core::ptr::null_mut(), + lnurl_pay_success_action: core::ptr::null_mut(), + lnurl_pay_unprocessed_success_action: core::ptr::null_mut(), + lnurl_withdraw_endpoint: core::ptr::null_mut(), + } + } + } + impl Default for wire_cst_ln_url_info { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_ln_url_pay_error { fn new_with_null_ptr() -> Self { Self { @@ -10921,6 +11066,8 @@ mod io { Self { destination: Default::default(), fees_sat: Default::default(), + data: Default::default(), + comment: core::ptr::null_mut(), success_action: core::ptr::null_mut(), } } @@ -11830,6 +11977,14 @@ mod io { ) } + #[no_mangle] + pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info( + ) -> *mut wire_cst_ln_url_info { + flutter_rust_bridge::for_generated::new_leak_box_ptr( + wire_cst_ln_url_info::new_with_null_ptr(), + ) + } + #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_error_data( ) -> *mut wire_cst_ln_url_pay_error_data { @@ -12708,6 +12863,17 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_ln_url_info { + ln_address: *mut wire_cst_list_prim_u_8_strict, + lnurl_pay_comment: *mut wire_cst_list_prim_u_8_strict, + lnurl_pay_domain: *mut wire_cst_list_prim_u_8_strict, + lnurl_pay_metadata: *mut wire_cst_list_prim_u_8_strict, + lnurl_pay_success_action: *mut wire_cst_success_action_processed, + lnurl_pay_unprocessed_success_action: *mut wire_cst_success_action, + lnurl_withdraw_endpoint: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_ln_url_pay_error { tag: i32, kind: LnUrlPayErrorKind, @@ -13028,6 +13194,7 @@ mod io { bolt11: *mut wire_cst_list_prim_u_8_strict, bolt12_offer: *mut wire_cst_list_prim_u_8_strict, payment_hash: *mut wire_cst_list_prim_u_8_strict, + lnurl_info: *mut wire_cst_ln_url_info, refund_tx_id: *mut wire_cst_list_prim_u_8_strict, refund_tx_amount_sat: *mut u64, } @@ -13143,6 +13310,8 @@ mod io { pub struct wire_cst_prepare_ln_url_pay_response { destination: wire_cst_send_destination, fees_sat: u64, + data: wire_cst_ln_url_pay_request_data, + comment: *mut wire_cst_list_prim_u_8_strict, success_action: *mut wire_cst_success_action, } #[repr(C)] diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 499c1a108..a7659e759 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -1240,8 +1240,21 @@ pub struct PaymentSwapData { pub status: PaymentState, } +/// Represents the payment LNURL info +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct LnUrlInfo { + pub ln_address: Option, + pub lnurl_pay_comment: Option, + pub lnurl_pay_domain: Option, + pub lnurl_pay_metadata: Option, + pub lnurl_pay_success_action: Option, + pub lnurl_pay_unprocessed_success_action: Option, + pub lnurl_withdraw_endpoint: Option, +} + /// The specific details of a payment, depending on its type #[derive(Debug, Clone, PartialEq, Serialize)] +#[allow(clippy::large_enum_variant)] pub enum PaymentDetails { /// Swapping to or from Lightning Lightning { @@ -1263,6 +1276,9 @@ pub enum PaymentDetails { /// The payment hash of the invoice payment_hash: Option, + /// The payment LNURL info + lnurl_info: Option, + /// For a Send swap which was refunded, this is the refund tx id refund_tx_id: Option, @@ -1396,6 +1412,7 @@ impl Payment { bolt12_offer: swap.bolt12_offer, payment_hash: swap.payment_hash, description: swap.description, + lnurl_info: None, refund_tx_id: swap.refund_tx_id, refund_tx_amount_sat: swap.refund_tx_amount_sat, }, @@ -1585,6 +1602,10 @@ pub struct PrepareLnUrlPayResponse { pub destination: SendDestination, /// The fees in satoshis to send the payment pub fees_sat: u64, + /// The [LnUrlPayRequestData] returned by [crate::input_parser::parse] + pub data: LnUrlPayRequestData, + /// An optional comment for this payment + pub comment: Option, /// The unprocessed LUD-09 success action. This will be processed and decrypted if /// needed after calling [crate::sdk::LiquidSdk::lnurl_pay] pub success_action: Option, diff --git a/lib/core/src/persist/migrations.rs b/lib/core/src/persist/migrations.rs index 6e0d8708b..0b9bb500d 100644 --- a/lib/core/src/persist/migrations.rs +++ b/lib/core/src/persist/migrations.rs @@ -213,5 +213,6 @@ pub(crate) fn current_migrations() -> Vec<&'static str> { data BLOB NOT NULL ) STRICT;", "ALTER TABLE receive_swaps DROP COLUMN mrh_script_pubkey;", + "ALTER TABLE payment_details ADD COLUMN lnurl_info_json TEXT;", ] } diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 9dfbca831..9ade6ba02 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -3,22 +3,27 @@ mod backup; pub(crate) mod cache; pub(crate) mod chain; mod migrations; +pub(crate) mod model; pub(crate) mod receive; pub(crate) mod send; pub(crate) mod sync; use std::collections::{HashMap, HashSet}; +use std::ops::Not; use std::{fs::create_dir_all, path::PathBuf, str::FromStr}; -use crate::error::PaymentError; use crate::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription}; use crate::model::*; +use crate::sync::model::RecordType; use crate::{get_invoice_description, utils}; use anyhow::{anyhow, Result}; use boltz_client::boltz::{ChainPair, ReversePair, SubmarinePair}; use lwk_wollet::WalletTx; use migrations::current_migrations; -use rusqlite::{params, params_from_iter, Connection, OptionalExtension, Row, ToSql}; +use model::PaymentTxDetails; +use rusqlite::{ + params, params_from_iter, Connection, OptionalExtension, Row, ToSql, TransactionBehavior, +}; use rusqlite_migration::{Migrations, M}; use sdk_common::bitcoin::hashes::hex::ToHex; use tokio::sync::mpsc::Sender; @@ -96,10 +101,7 @@ impl Persister { } } - pub(crate) fn insert_or_update_payment_with_wallet_tx( - &self, - tx: &WalletTx, - ) -> Result<(), PaymentError> { + pub(crate) fn insert_or_update_payment_with_wallet_tx(&self, tx: &WalletTx) -> Result<()> { let tx_id = tx.txid.to_string(); let is_tx_confirmed = tx.height.is_some(); let amount_sat = tx.balance.values().sum::(); @@ -120,8 +122,12 @@ impl Persister { }, is_confirmed: is_tx_confirmed, }, - maybe_script_pubkey, - None, + maybe_script_pubkey.map(|destination| PaymentTxDetails { + tx_id, + destination, + ..Default::default() + }), + true, ) } @@ -156,11 +162,12 @@ impl Persister { pub(crate) fn insert_or_update_payment( &self, ptx: PaymentTxData, - destination: Option, - description: Option, - ) -> Result<(), PaymentError> { - let con = self.get_connection()?; - con.execute( + payment_tx_details: Option, + from_wallet_tx_data: bool, + ) -> Result<()> { + let mut con = self.get_connection()?; + let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?; + tx.execute( "INSERT INTO payment_tx_data ( tx_id, timestamp, @@ -187,26 +194,117 @@ impl Persister { ), )?; - if let Some(destination) = destination { - // Only store the destination if there is no payment_details entry else - // the destination is overwritten by the tx script_pubkey - con.execute( - "INSERT INTO payment_details ( - tx_id, - destination, - description - ) - VALUES (?1, ?2, ?3) - ON CONFLICT (tx_id) - DO UPDATE SET description = COALESCE(?3, description) - ", - (ptx.tx_id, destination, description), + let mut trigger_sync = false; + if let Some(ref payment_tx_details) = payment_tx_details { + // If the update comes from the wallet tx: + // - Skip updating the destination from the script_pubkey + // - Skip syncing the payment_tx_details + Self::insert_or_update_payment_details_inner( + &tx, + payment_tx_details, + from_wallet_tx_data, )?; + if !from_wallet_tx_data { + self.commit_outgoing( + &tx, + &payment_tx_details.tx_id, + RecordType::PaymentDetails, + None, + )?; + trigger_sync = true; + } + } + + tx.commit()?; + + if trigger_sync { + self.sync_trigger.try_send(())?; } Ok(()) } + fn insert_or_update_payment_details_inner( + con: &Connection, + payment_tx_details: &PaymentTxDetails, + skip_destination_update: bool, + ) -> Result<()> { + let destination_update = skip_destination_update + .not() + .then_some("destination = excluded.destination,") + .unwrap_or_default(); + con.execute( + &format!( + "INSERT INTO payment_details ( + tx_id, + destination, + description, + lnurl_info_json + ) + VALUES (?, ?, ?, ?) + ON CONFLICT (tx_id) + DO UPDATE SET + {destination_update} + description = COALESCE(excluded.description, description), + lnurl_info_json = COALESCE(excluded.lnurl_info_json, lnurl_info_json) + " + ), + ( + &payment_tx_details.tx_id, + &payment_tx_details.destination, + &payment_tx_details.description, + payment_tx_details + .lnurl_info + .as_ref() + .map(|info| serde_json::to_string(&info).ok()), + ), + )?; + Ok(()) + } + + pub(crate) fn insert_or_update_payment_details( + &self, + payment_tx_details: PaymentTxDetails, + ) -> Result<()> { + let mut con = self.get_connection()?; + let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?; + + Self::insert_or_update_payment_details_inner(&tx, &payment_tx_details, false)?; + self.commit_outgoing( + &tx, + &payment_tx_details.tx_id, + RecordType::PaymentDetails, + None, + )?; + tx.commit()?; + + self.sync_trigger.try_send(())?; + + Ok(()) + } + + pub(crate) fn get_payment_details(&self, tx_id: &str) -> Result> { + let con = self.get_connection()?; + let mut stmt = con.prepare( + "SELECT destination, description, lnurl_info_json + FROM payment_details + WHERE tx_id = ?", + )?; + let res = stmt.query_row([tx_id], |row| { + let destination = row.get(0)?; + let description = row.get(1)?; + let maybe_lnurl_info_json: Option = row.get(2)?; + Ok(PaymentTxDetails { + tx_id: tx_id.to_string(), + destination, + description, + lnurl_info: maybe_lnurl_info_json + .and_then(|info| serde_json::from_str::(&info).ok()), + }) + }); + Ok(res.ok()) + } + pub(crate) fn list_ongoing_swaps(&self) -> Result> { let ongoing_send_swaps: Vec = self .list_ongoing_send_swaps()? @@ -281,7 +379,8 @@ impl Persister { cs.pair_fees_json, rtx.amount_sat, pd.destination, - pd.description + pd.description, + pd.lnurl_info_json FROM payment_tx_data AS ptx -- Payment tx (each tx results in a Payment) FULL JOIN ( SELECT * FROM receive_swaps @@ -372,6 +471,9 @@ impl Persister { let maybe_payment_details_destination: Option = row.get(40)?; let maybe_payment_details_description: Option = row.get(41)?; + let maybe_payment_details_lnurl_info_json: Option = row.get(42)?; + let maybe_payment_details_lnurl_info: Option = + maybe_payment_details_lnurl_info_json.and_then(|info| serde_json::from_str(&info).ok()); let (swap, payment_type) = match maybe_receive_swap_id { Some(receive_swap_id) => { @@ -503,6 +605,7 @@ impl Persister { bolt11, bolt12_offer, payment_hash, + lnurl_info: maybe_payment_details_lnurl_info, refund_tx_id, refund_tx_amount_sat, description: description.unwrap_or("Lightning transfer".to_string()), @@ -678,6 +781,7 @@ mod tests { use anyhow::Result; use crate::{ + persist::PaymentTxDetails, prelude::ListPaymentsRequest, test_utils::persist::{ create_persister, new_payment_tx_data, new_receive_swap, new_send_swap, @@ -693,8 +797,11 @@ mod tests { let payment_tx_data = new_payment_tx_data(PaymentType::Send); storage.insert_or_update_payment( payment_tx_data.clone(), - Some("mock-address".to_string()), - None, + Some(PaymentTxDetails { + destination: "mock-address".to_string(), + ..Default::default() + }), + false, )?; assert!(storage diff --git a/lib/core/src/persist/model.rs b/lib/core/src/persist/model.rs new file mode 100644 index 000000000..309d72578 --- /dev/null +++ b/lib/core/src/persist/model.rs @@ -0,0 +1,9 @@ +use super::LnUrlInfo; + +#[derive(Clone, Debug, Default)] +pub(crate) struct PaymentTxDetails { + pub(crate) tx_id: String, + pub(crate) destination: String, + pub(crate) description: Option, + pub(crate) lnurl_info: Option, +} diff --git a/lib/core/src/persist/sync.rs b/lib/core/src/persist/sync.rs index 73afd8bc1..f9d27b49c 100644 --- a/lib/core/src/persist/sync.rs +++ b/lib/core/src/persist/sync.rs @@ -5,7 +5,7 @@ use rusqlite::{ named_params, Connection, OptionalExtension, Row, Statement, Transaction, TransactionBehavior, }; -use super::{cache::KEY_LAST_DERIVATION_INDEX, Persister, Swap}; +use super::{cache::KEY_LAST_DERIVATION_INDEX, PaymentTxDetails, Persister, Swap}; use crate::{ sync::model::{ data::LAST_DERIVATION_INDEX_DATA_ID, sync::Record, RecordType, SyncOutgoingChanges, @@ -466,4 +466,31 @@ impl Persister { Ok(()) } + + pub(crate) fn commit_incoming_payment_details( + &self, + payment_tx_details: PaymentTxDetails, + sync_state: &SyncState, + last_commit_time: Option, + ) -> Result<()> { + let mut con = self.get_connection()?; + let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?; + + if let Some(last_commit_time) = last_commit_time { + Self::check_commit_update(&tx, &sync_state.record_id, last_commit_time)?; + } + + Self::insert_or_update_payment_details_inner(&tx, &payment_tx_details, false)?; + + Self::set_sync_state_stmt(&tx)?.execute(named_params! { + ":data_id": &sync_state.data_id, + ":record_id": &sync_state.record_id, + ":record_revision": &sync_state.record_revision, + ":is_local": &sync_state.is_local, + })?; + + tx.commit()?; + + Ok(()) + } } diff --git a/lib/core/src/receive_swap.rs b/lib/core/src/receive_swap.rs index 2d82315b0..88c783793 100644 --- a/lib/core/src/receive_swap.rs +++ b/lib/core/src/receive_swap.rs @@ -356,7 +356,7 @@ impl ReceiveSwapHandler { is_confirmed: false, }, None, - None, + false, )?; info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}"); diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index af0d88788..b279555e9 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -19,6 +19,7 @@ use lwk_wollet::elements_miniscript::elements::bitcoin::bip32::Xpub; use lwk_wollet::hashes::{sha256, Hash}; use lwk_wollet::secp256k1::ThirtyTwoByteHash; use lwk_wollet::ElementsNetwork; +use persist::model::PaymentTxDetails; use recover::recoverer::Recoverer; use sdk_common::bitcoin::hashes::hex::ToHex; use sdk_common::input_parser::InputType; @@ -1221,8 +1222,13 @@ impl LiquidSdk { self.persister.insert_or_update_payment( tx_data.clone(), - Some(destination.clone()), - description.clone(), + Some(PaymentTxDetails { + tx_id: tx_id.clone(), + destination: destination.clone(), + description: description.clone(), + ..Default::default() + }), + false, )?; self.emit_payment_updated(Some(tx_id)).await?; // Emit Pending event @@ -1341,7 +1347,7 @@ impl LiquidSdk { .try_lockup(&swap, &create_response) .await?; - self.wait_for_payment(Swap::Send(swap), create_response.accept_zero_conf) + self.wait_for_payment_with_timeout(Swap::Send(swap), create_response.accept_zero_conf) .await .map(|payment| SendPaymentResponse { payment }) } @@ -1619,12 +1625,12 @@ impl LiquidSdk { self.persister.insert_or_update_chain_swap(&swap)?; self.status_stream.track_swap_id(&swap_id)?; - self.wait_for_payment(Swap::Chain(swap), accept_zero_conf) + self.wait_for_payment_with_timeout(Swap::Chain(swap), accept_zero_conf) .await .map(|payment| SendPaymentResponse { payment }) } - async fn wait_for_payment( + async fn wait_for_payment_with_timeout( &self, swap: Swap, accept_zero_conf: bool, @@ -2634,6 +2640,8 @@ impl LiquidSdk { Ok(PrepareLnUrlPayResponse { destination: prepare_response.destination, fees_sat: prepare_response.fees_sat, + data: req.data, + comment: req.comment, success_action: data.success_action, }) } @@ -2670,42 +2678,81 @@ impl LiquidSdk { let maybe_sa_processed: Option = match prepare_response .success_action + .clone() { Some(sa) => { - let processed_sa = match sa { - // For AES, we decrypt the contents on the fly + match sa { + // For AES, we decrypt the contents if the preimage is available SuccessAction::Aes { data } => { - let PaymentDetails::Lightning { preimage, .. } = &payment.details else { + let PaymentDetails::Lightning { + swap_id, preimage, .. + } = &payment.details + else { return Err(LnUrlPayError::Generic { - err: format!("Invalid payment type: expected type `PaymentDetails::Lightning`, got payment details {:?}.", payment.details), - }); + err: format!("Invalid payment type: expected type `PaymentDetails::Lightning`, got payment details {:?}.", payment.details), + }); }; - let preimage_str = preimage.clone().ok_or(LnUrlPayError::Generic { - err: "Payment successful but no preimage found".to_string(), - })?; - let preimage = sha256::Hash::from_str(&preimage_str).map_err(|_| { - LnUrlPayError::Generic { - err: "Invalid preimage".to_string(), + match preimage { + Some(preimage_str) => { + debug!( + "Decrypting AES success action with preimage for Send Swap {}", + swap_id + ); + let preimage = + sha256::Hash::from_str(preimage_str).map_err(|_| { + LnUrlPayError::Generic { + err: "Invalid preimage".to_string(), + } + })?; + let preimage_arr: [u8; 32] = preimage.into_32(); + let result = match (data, &preimage_arr).try_into() { + Ok(data) => AesSuccessActionDataResult::Decrypted { data }, + Err(e) => AesSuccessActionDataResult::ErrorStatus { + reason: e.to_string(), + }, + }; + Some(SuccessActionProcessed::Aes { result }) } - })?; - let preimage_arr: [u8; 32] = preimage.into_32(); - let result = match (data, &preimage_arr).try_into() { - Ok(data) => AesSuccessActionDataResult::Decrypted { data }, - Err(e) => AesSuccessActionDataResult::ErrorStatus { - reason: e.to_string(), - }, - }; - SuccessActionProcessed::Aes { result } + None => { + debug!("Preimage not yet available to decrypt AES success action for Send Swap {}", swap_id); + None + } + } } - SuccessAction::Message { data } => SuccessActionProcessed::Message { data }, - SuccessAction::Url { data } => SuccessActionProcessed::Url { data }, - }; - Some(processed_sa) + SuccessAction::Message { data } => { + Some(SuccessActionProcessed::Message { data }) + } + SuccessAction::Url { data } => Some(SuccessActionProcessed::Url { data }), + } } None => None, }; + let lnurl_pay_domain = match prepare_response.data.ln_address { + Some(_) => None, + None => Some(prepare_response.data.domain), + }; + if let (Some(tx_id), Some(destination)) = + (payment.tx_id.clone(), payment.destination.clone()) + { + self.persister + .insert_or_update_payment_details(PaymentTxDetails { + tx_id, + destination, + description: prepare_response.comment.clone(), + lnurl_info: Some(LnUrlInfo { + ln_address: prepare_response.data.ln_address, + lnurl_pay_comment: prepare_response.comment, + lnurl_pay_domain, + lnurl_pay_metadata: Some(prepare_response.data.metadata_str), + lnurl_pay_success_action: maybe_sa_processed.clone(), + lnurl_pay_unprocessed_success_action: prepare_response.success_action, + lnurl_withdraw_endpoint: None, + }), + })?; + } + Ok(LnUrlPayResult::EndpointSuccess { data: model::LnUrlPaySuccessData { payment, @@ -2735,19 +2782,39 @@ impl LiquidSdk { let receive_res = self .receive_payment(&ReceivePaymentRequest { prepare_response, - description: None, + description: req.description.clone(), use_description_hash: Some(false), }) .await?; - if let Ok(invoice) = parse_invoice(&receive_res.destination) { - let res = validate_lnurl_withdraw(req.data, invoice).await?; - Ok(res) - } else { - Err(LnUrlWithdrawError::Generic { + let Ok(invoice) = parse_invoice(&receive_res.destination) else { + return Err(LnUrlWithdrawError::Generic { err: "Received unexpected output from receive request".to_string(), - }) + }); + }; + + let res = validate_lnurl_withdraw(req.data.clone(), invoice.clone()).await?; + if let LnUrlWithdrawResult::Ok { data: _ } = res { + if let Some(ReceiveSwap { + claim_tx_id: Some(tx_id), + .. + }) = self + .persister + .fetch_receive_swap_by_invoice(&invoice.bolt11)? + { + self.persister + .insert_or_update_payment_details(PaymentTxDetails { + tx_id, + destination: receive_res.destination, + description: req.description, + lnurl_info: Some(LnUrlInfo { + lnurl_withdraw_endpoint: Some(req.data.callback), + ..Default::default() + }), + })?; + } } + Ok(res) } /// Third and last step of LNURL-auth. The first step is [parse], which also validates the LNURL destination diff --git a/lib/core/src/send_swap.rs b/lib/core/src/send_swap.rs index 63d071dba..2dd680570 100644 --- a/lib/core/src/send_swap.rs +++ b/lib/core/src/send_swap.rs @@ -10,10 +10,14 @@ use boltz_client::Bolt11Invoice; use futures_util::TryFutureExt; use log::{debug, error, info, warn}; use lwk_wollet::elements::{LockTime, Transaction}; +use lwk_wollet::hashes::sha256; +use lwk_wollet::secp256k1::ThirtyTwoByteHash; +use sdk_common::prelude::{AesSuccessActionDataResult, SuccessAction, SuccessActionProcessed}; use tokio::sync::{broadcast, Mutex}; use crate::chain::liquid::LiquidChainService; use crate::model::{BlockListener, Config, PaymentState::*, SendSwap}; +use crate::persist::model::PaymentTxDetails; use crate::prelude::{PaymentTxData, PaymentType, Swap}; use crate::recover::recoverer::Recoverer; use crate::swapper::Swapper; @@ -233,7 +237,7 @@ impl SendSwapHandler { is_confirmed: false, }, None, - None, + false, )?; self.update_swap_info(swap_id, Pending, None, Some(&lockup_tx_id), None)?; @@ -268,7 +272,8 @@ impl SendSwapHandler { // Updates the swap without state transition validation pub(crate) fn update_swap(&self, updated_swap: SendSwap) -> Result<(), PaymentError> { let swap = self.fetch_send_swap_by_id(&updated_swap.id)?; - if updated_swap != swap { + let lnurl_info_updated = self.update_swap_lnurl_info(&swap, &updated_swap)?; + if updated_swap != swap || lnurl_info_updated { info!( "Updating Send swap {} to {:?} (lockup_tx_id = {:?}, refund_tx_id = {:?})", updated_swap.id, @@ -282,6 +287,56 @@ impl SendSwapHandler { Ok(()) } + pub(crate) fn update_swap_lnurl_info( + &self, + swap: &SendSwap, + updated_swap: &SendSwap, + ) -> Result { + if swap.preimage.is_none() { + let Some(tx_id) = updated_swap.lockup_tx_id.clone() else { + return Ok(false); + }; + let Some(ref preimage_str) = updated_swap.preimage.clone() else { + return Ok(false); + }; + if let Some(PaymentTxDetails { + destination, + description, + lnurl_info: Some(mut lnurl_info), + .. + }) = self.persister.get_payment_details(&tx_id)? + { + if let Some(SuccessAction::Aes { data }) = + lnurl_info.lnurl_pay_unprocessed_success_action.clone() + { + debug!( + "Decrypting AES success action with preimage for Send Swap {}", + swap.id + ); + let preimage = sha256::Hash::from_str(preimage_str)?; + let preimage_arr: [u8; 32] = preimage.into_32(); + let result = match (data, &preimage_arr).try_into() { + Ok(data) => AesSuccessActionDataResult::Decrypted { data }, + Err(e) => AesSuccessActionDataResult::ErrorStatus { + reason: e.to_string(), + }, + }; + lnurl_info.lnurl_pay_success_action = + Some(SuccessActionProcessed::Aes { result }); + self.persister + .insert_or_update_payment_details(PaymentTxDetails { + tx_id, + destination, + description, + lnurl_info: Some(lnurl_info), + })?; + return Ok(true); + } + } + } + Ok(false) + } + // Updates the swap state with validation pub(crate) fn update_swap_info( &self, @@ -305,7 +360,8 @@ impl SendSwapHandler { refund_tx_id, )?; let updated_swap = self.fetch_send_swap_by_id(swap_id)?; - if updated_swap != swap { + let lnurl_info_updated = self.update_swap_lnurl_info(&swap, &updated_swap)?; + if updated_swap != swap || lnurl_info_updated { self.notify_swap_changes(swap, updated_swap)?; } Ok(()) diff --git a/lib/core/src/sync/mod.rs b/lib/core/src/sync/mod.rs index a64c77e05..e32511a4d 100644 --- a/lib/core/src/sync/mod.rs +++ b/lib/core/src/sync/mod.rs @@ -4,6 +4,7 @@ use std::time::Duration; use anyhow::{anyhow, Result}; use futures_util::TryFutureExt; +use model::data::PaymentDetailsSyncData; use tokio::sync::mpsc::Receiver; use tokio::sync::{watch, Mutex}; @@ -134,6 +135,13 @@ impl SyncService { *last_commit_time, ) } + SyncData::PaymentDetails(payment_details_sync_data) => { + self.persister.commit_incoming_payment_details( + payment_details_sync_data.into(), + new_sync_state, + *last_commit_time, + ) + } } } @@ -169,6 +177,14 @@ impl SyncService { .ok_or(anyhow!("Could not find last derivation index"))? .parse()?, ), + RecordType::PaymentDetails => { + let payment_details_data: PaymentDetailsSyncData = self + .persister + .get_payment_details(data_id)? + .ok_or(anyhow!("Could not find Payment Details {data_id}"))? + .into(); + SyncData::PaymentDetails(payment_details_data) + } }; Ok(data) } diff --git a/lib/core/src/sync/model/data.rs b/lib/core/src/sync/model/data.rs index 308d9157f..cdd681884 100644 --- a/lib/core/src/sync/model/data.rs +++ b/lib/core/src/sync/model/data.rs @@ -1,6 +1,9 @@ use serde::{Deserialize, Serialize}; -use crate::prelude::{ChainSwap, Direction, PaymentState, ReceiveSwap, SendSwap, Swap}; +use crate::{ + persist::model::PaymentTxDetails, + prelude::{ChainSwap, Direction, LnUrlInfo, PaymentState, ReceiveSwap, SendSwap, Swap}, +}; pub(crate) const LAST_DERIVATION_INDEX_DATA_ID: &str = "last-derivation-index"; @@ -215,6 +218,49 @@ impl From for ReceiveSwap { } } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub(crate) struct PaymentDetailsSyncData { + pub(crate) tx_id: String, + pub(crate) destination: String, + pub(crate) description: Option, + pub(crate) lnurl_info: Option, +} + +impl PaymentDetailsSyncData { + pub(crate) fn merge(&mut self, other: &Self, updated_fields: &[String]) { + for field in updated_fields { + match field.as_str() { + "destination" => self.destination = other.destination.clone(), + "description" => clone_if_set(&mut self.description, &other.description), + "lnurl_info" => clone_if_set(&mut self.lnurl_info, &other.lnurl_info), + _ => continue, + } + } + } +} + +impl From for PaymentDetailsSyncData { + fn from(value: PaymentTxDetails) -> Self { + Self { + tx_id: value.tx_id, + destination: value.destination, + description: value.description, + lnurl_info: value.lnurl_info, + } + } +} + +impl From for PaymentTxDetails { + fn from(val: PaymentDetailsSyncData) -> Self { + PaymentTxDetails { + tx_id: val.tx_id, + destination: val.destination, + description: val.description, + lnurl_info: val.lnurl_info, + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(tag = "data_type", content = "data")] pub(crate) enum SyncData { @@ -222,6 +268,7 @@ pub(crate) enum SyncData { Send(SendSyncData), Receive(ReceiveSyncData), LastDerivationIndex(u32), + PaymentDetails(PaymentDetailsSyncData), } impl SyncData { @@ -231,6 +278,7 @@ impl SyncData { SyncData::Send(send_data) => &send_data.swap_id, SyncData::Receive(receive_data) => &receive_data.swap_id, SyncData::LastDerivationIndex(_) => LAST_DERIVATION_INDEX_DATA_ID, + SyncData::PaymentDetails(payment_details) => &payment_details.tx_id, } } @@ -241,7 +289,7 @@ impl SyncData { /// Whether the data is a swap pub(crate) fn is_swap(&self) -> bool { match self { - SyncData::LastDerivationIndex(_) => false, + SyncData::LastDerivationIndex(_) | SyncData::PaymentDetails(_) => false, SyncData::Chain(_) | SyncData::Send(_) | SyncData::Receive(_) => true, } } @@ -263,6 +311,9 @@ impl SyncData { ) => { *our_index = std::cmp::max(*their_index, *our_index); } + (SyncData::PaymentDetails(ref mut base), SyncData::PaymentDetails(other)) => { + base.merge(other, updated_fields) + } _ => return Err(anyhow::anyhow!("Cannot merge data from two separate types")), }; Ok(()) diff --git a/lib/core/src/sync/model/mod.rs b/lib/core/src/sync/model/mod.rs index 61d795b5b..7038cc4e5 100644 --- a/lib/core/src/sync/model/mod.rs +++ b/lib/core/src/sync/model/mod.rs @@ -27,6 +27,7 @@ pub(crate) enum RecordType { Send = 1, Chain = 2, LastDerivationIndex = 3, + PaymentDetails = 4, } impl ToSql for RecordType { @@ -43,6 +44,7 @@ impl FromSql for RecordType { 1 => Ok(Self::Send), 2 => Ok(Self::Chain), 3 => Ok(Self::LastDerivationIndex), + 4 => Ok(Self::PaymentDetails), _ => Err(FromSqlError::OutOfRange(i)), }, _ => Err(FromSqlError::InvalidType), @@ -154,6 +156,7 @@ impl Record { SyncData::Send(_) => "send-swap", SyncData::Receive(_) => "receive-swap", SyncData::LastDerivationIndex(_) => "derivation-index", + SyncData::PaymentDetails(_) => "payment-details", } .to_string(); Self::id(prefix, data.id()) @@ -165,6 +168,7 @@ impl Record { RecordType::Send => "send-swap", RecordType::Receive => "receive-swap", RecordType::LastDerivationIndex => "derivation-index", + RecordType::PaymentDetails => "payment-details", } .to_string(); Self::id(prefix, data_id) diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 71cfa2eaa..a36f152ee 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -1476,6 +1476,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_ln_url_error_data(raw); } + @protected + LnUrlInfo dco_decode_box_autoadd_ln_url_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_ln_url_info(raw); + } + @protected LnUrlPayErrorData dco_decode_box_autoadd_ln_url_pay_error_data(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2122,6 +2128,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + LnUrlInfo dco_decode_ln_url_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); + return LnUrlInfo( + lnAddress: dco_decode_opt_String(arr[0]), + lnurlPayComment: dco_decode_opt_String(arr[1]), + lnurlPayDomain: dco_decode_opt_String(arr[2]), + lnurlPayMetadata: dco_decode_opt_String(arr[3]), + lnurlPaySuccessAction: dco_decode_opt_box_autoadd_success_action_processed(arr[4]), + lnurlPayUnprocessedSuccessAction: dco_decode_opt_box_autoadd_success_action(arr[5]), + lnurlWithdrawEndpoint: dco_decode_opt_String(arr[6]), + ); + } + @protected LnUrlPayError dco_decode_ln_url_pay_error(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2435,6 +2457,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_box_autoadd_list_payment_details(raw); } + @protected + LnUrlInfo? dco_decode_opt_box_autoadd_ln_url_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_ln_url_info(raw); + } + @protected PayAmount? dco_decode_opt_box_autoadd_pay_amount(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2551,8 +2579,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { bolt11: dco_decode_opt_String(raw[4]), bolt12Offer: dco_decode_opt_String(raw[5]), paymentHash: dco_decode_opt_String(raw[6]), - refundTxId: dco_decode_opt_String(raw[7]), - refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[8]), + lnurlInfo: dco_decode_opt_box_autoadd_ln_url_info(raw[7]), + refundTxId: dco_decode_opt_String(raw[8]), + refundTxAmountSat: dco_decode_opt_box_autoadd_u_64(raw[9]), ); case 1: return PaymentDetails_Liquid( @@ -2701,11 +2730,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareLnUrlPayResponse dco_decode_prepare_ln_url_pay_response(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs final arr = raw as List; - if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + if (arr.length != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); return PrepareLnUrlPayResponse( destination: dco_decode_send_destination(arr[0]), feesSat: dco_decode_u_64(arr[1]), - successAction: dco_decode_opt_box_autoadd_success_action(arr[2]), + data: dco_decode_ln_url_pay_request_data(arr[2]), + comment: dco_decode_opt_String(arr[3]), + successAction: dco_decode_opt_box_autoadd_success_action(arr[4]), ); } @@ -3410,6 +3441,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_ln_url_error_data(deserializer)); } + @protected + LnUrlInfo sse_decode_box_autoadd_ln_url_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_ln_url_info(deserializer)); + } + @protected LnUrlPayErrorData sse_decode_box_autoadd_ln_url_pay_error_data(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4134,6 +4171,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return LnUrlErrorData(reason: var_reason); } + @protected + LnUrlInfo sse_decode_ln_url_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_lnAddress = sse_decode_opt_String(deserializer); + var var_lnurlPayComment = sse_decode_opt_String(deserializer); + var var_lnurlPayDomain = sse_decode_opt_String(deserializer); + var var_lnurlPayMetadata = sse_decode_opt_String(deserializer); + var var_lnurlPaySuccessAction = sse_decode_opt_box_autoadd_success_action_processed(deserializer); + var var_lnurlPayUnprocessedSuccessAction = sse_decode_opt_box_autoadd_success_action(deserializer); + var var_lnurlWithdrawEndpoint = sse_decode_opt_String(deserializer); + return LnUrlInfo( + lnAddress: var_lnAddress, + lnurlPayComment: var_lnurlPayComment, + lnurlPayDomain: var_lnurlPayDomain, + lnurlPayMetadata: var_lnurlPayMetadata, + lnurlPaySuccessAction: var_lnurlPaySuccessAction, + lnurlPayUnprocessedSuccessAction: var_lnurlPayUnprocessedSuccessAction, + lnurlWithdrawEndpoint: var_lnurlWithdrawEndpoint); + } + @protected LnUrlPayError sse_decode_ln_url_pay_error(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4441,6 +4498,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + LnUrlInfo? sse_decode_opt_box_autoadd_ln_url_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_ln_url_info(deserializer)); + } else { + return null; + } + } + @protected PayAmount? sse_decode_opt_box_autoadd_pay_amount(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4612,6 +4680,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var var_bolt11 = sse_decode_opt_String(deserializer); var var_bolt12Offer = sse_decode_opt_String(deserializer); var var_paymentHash = sse_decode_opt_String(deserializer); + var var_lnurlInfo = sse_decode_opt_box_autoadd_ln_url_info(deserializer); var var_refundTxId = sse_decode_opt_String(deserializer); var var_refundTxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); return PaymentDetails_Lightning( @@ -4621,6 +4690,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { bolt11: var_bolt11, bolt12Offer: var_bolt12Offer, paymentHash: var_paymentHash, + lnurlInfo: var_lnurlInfo, refundTxId: var_refundTxId, refundTxAmountSat: var_refundTxAmountSat); case 1: @@ -4763,9 +4833,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs var var_destination = sse_decode_send_destination(deserializer); var var_feesSat = sse_decode_u_64(deserializer); + var var_data = sse_decode_ln_url_pay_request_data(deserializer); + var var_comment = sse_decode_opt_String(deserializer); var var_successAction = sse_decode_opt_box_autoadd_success_action(deserializer); return PrepareLnUrlPayResponse( - destination: var_destination, feesSat: var_feesSat, successAction: var_successAction); + destination: var_destination, + feesSat: var_feesSat, + data: var_data, + comment: var_comment, + successAction: var_successAction); } @protected @@ -5514,6 +5590,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_ln_url_error_data(self, serializer); } + @protected + void sse_encode_box_autoadd_ln_url_info(LnUrlInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_ln_url_info(self, serializer); + } + @protected void sse_encode_box_autoadd_ln_url_pay_error_data(LnUrlPayErrorData self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6119,6 +6201,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(self.reason, serializer); } + @protected + void sse_encode_ln_url_info(LnUrlInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_String(self.lnAddress, serializer); + sse_encode_opt_String(self.lnurlPayComment, serializer); + sse_encode_opt_String(self.lnurlPayDomain, serializer); + sse_encode_opt_String(self.lnurlPayMetadata, serializer); + sse_encode_opt_box_autoadd_success_action_processed(self.lnurlPaySuccessAction, serializer); + sse_encode_opt_box_autoadd_success_action(self.lnurlPayUnprocessedSuccessAction, serializer); + sse_encode_opt_String(self.lnurlWithdrawEndpoint, serializer); + } + @protected void sse_encode_ln_url_pay_error(LnUrlPayError self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6386,6 +6480,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_ln_url_info(LnUrlInfo? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_ln_url_info(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_pay_amount(PayAmount? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6533,6 +6637,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { bolt11: final bolt11, bolt12Offer: final bolt12Offer, paymentHash: final paymentHash, + lnurlInfo: final lnurlInfo, refundTxId: final refundTxId, refundTxAmountSat: final refundTxAmountSat ): @@ -6543,6 +6648,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_opt_String(bolt11, serializer); sse_encode_opt_String(bolt12Offer, serializer); sse_encode_opt_String(paymentHash, serializer); + sse_encode_opt_box_autoadd_ln_url_info(lnurlInfo, serializer); sse_encode_opt_String(refundTxId, serializer); sse_encode_opt_box_autoadd_u_64(refundTxAmountSat, serializer); case PaymentDetails_Liquid(destination: final destination, description: final description): @@ -6674,6 +6780,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_send_destination(self.destination, serializer); sse_encode_u_64(self.feesSat, serializer); + sse_encode_ln_url_pay_request_data(self.data, serializer); + sse_encode_opt_String(self.comment, serializer); sse_encode_opt_box_autoadd_success_action(self.successAction, serializer); } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 8a213b160..539e6824d 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -137,6 +137,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LnUrlErrorData dco_decode_box_autoadd_ln_url_error_data(dynamic raw); + @protected + LnUrlInfo dco_decode_box_autoadd_ln_url_info(dynamic raw); + @protected LnUrlPayErrorData dco_decode_box_autoadd_ln_url_pay_error_data(dynamic raw); @@ -350,6 +353,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LnUrlErrorData dco_decode_ln_url_error_data(dynamic raw); + @protected + LnUrlInfo dco_decode_ln_url_info(dynamic raw); + @protected LnUrlPayError dco_decode_ln_url_pay_error(dynamic raw); @@ -419,6 +425,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ListPaymentDetails? dco_decode_opt_box_autoadd_list_payment_details(dynamic raw); + @protected + LnUrlInfo? dco_decode_opt_box_autoadd_ln_url_info(dynamic raw); + @protected PayAmount? dco_decode_opt_box_autoadd_pay_amount(dynamic raw); @@ -705,6 +714,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LnUrlErrorData sse_decode_box_autoadd_ln_url_error_data(SseDeserializer deserializer); + @protected + LnUrlInfo sse_decode_box_autoadd_ln_url_info(SseDeserializer deserializer); + @protected LnUrlPayErrorData sse_decode_box_autoadd_ln_url_pay_error_data(SseDeserializer deserializer); @@ -918,6 +930,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LnUrlErrorData sse_decode_ln_url_error_data(SseDeserializer deserializer); + @protected + LnUrlInfo sse_decode_ln_url_info(SseDeserializer deserializer); + @protected LnUrlPayError sse_decode_ln_url_pay_error(SseDeserializer deserializer); @@ -987,6 +1002,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ListPaymentDetails? sse_decode_opt_box_autoadd_list_payment_details(SseDeserializer deserializer); + @protected + LnUrlInfo? sse_decode_opt_box_autoadd_ln_url_info(SseDeserializer deserializer); + @protected PayAmount? sse_decode_opt_box_autoadd_pay_amount(SseDeserializer deserializer); @@ -1366,6 +1384,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ptr; } + @protected + ffi.Pointer cst_encode_box_autoadd_ln_url_info(LnUrlInfo raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ptr = wire.cst_new_box_autoadd_ln_url_info(); + cst_api_fill_to_wire_ln_url_info(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer cst_encode_box_autoadd_ln_url_pay_error_data( LnUrlPayErrorData raw) { @@ -1796,6 +1822,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw == null ? ffi.nullptr : cst_encode_box_autoadd_list_payment_details(raw); } + @protected + ffi.Pointer cst_encode_opt_box_autoadd_ln_url_info(LnUrlInfo? raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + return raw == null ? ffi.nullptr : cst_encode_box_autoadd_ln_url_info(raw); + } + @protected ffi.Pointer cst_encode_opt_box_autoadd_pay_amount(PayAmount? raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -2047,6 +2079,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { cst_api_fill_to_wire_ln_url_error_data(apiObj, wireObj.ref); } + @protected + void cst_api_fill_to_wire_box_autoadd_ln_url_info( + LnUrlInfo apiObj, ffi.Pointer wireObj) { + cst_api_fill_to_wire_ln_url_info(apiObj, wireObj.ref); + } + @protected void cst_api_fill_to_wire_box_autoadd_ln_url_pay_error_data( LnUrlPayErrorData apiObj, ffi.Pointer wireObj) { @@ -2497,6 +2535,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.reason = cst_encode_String(apiObj.reason); } + @protected + void cst_api_fill_to_wire_ln_url_info(LnUrlInfo apiObj, wire_cst_ln_url_info wireObj) { + wireObj.ln_address = cst_encode_opt_String(apiObj.lnAddress); + wireObj.lnurl_pay_comment = cst_encode_opt_String(apiObj.lnurlPayComment); + wireObj.lnurl_pay_domain = cst_encode_opt_String(apiObj.lnurlPayDomain); + wireObj.lnurl_pay_metadata = cst_encode_opt_String(apiObj.lnurlPayMetadata); + wireObj.lnurl_pay_success_action = + cst_encode_opt_box_autoadd_success_action_processed(apiObj.lnurlPaySuccessAction); + wireObj.lnurl_pay_unprocessed_success_action = + cst_encode_opt_box_autoadd_success_action(apiObj.lnurlPayUnprocessedSuccessAction); + wireObj.lnurl_withdraw_endpoint = cst_encode_opt_String(apiObj.lnurlWithdrawEndpoint); + } + @protected void cst_api_fill_to_wire_ln_url_pay_error(LnUrlPayError apiObj, wire_cst_ln_url_pay_error wireObj) { if (apiObj is LnUrlPayError_AlreadyPaid) { @@ -2789,6 +2840,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { var pre_bolt11 = cst_encode_opt_String(apiObj.bolt11); var pre_bolt12_offer = cst_encode_opt_String(apiObj.bolt12Offer); var pre_payment_hash = cst_encode_opt_String(apiObj.paymentHash); + var pre_lnurl_info = cst_encode_opt_box_autoadd_ln_url_info(apiObj.lnurlInfo); var pre_refund_tx_id = cst_encode_opt_String(apiObj.refundTxId); var pre_refund_tx_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.refundTxAmountSat); wireObj.tag = 0; @@ -2798,6 +2850,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.kind.Lightning.bolt11 = pre_bolt11; wireObj.kind.Lightning.bolt12_offer = pre_bolt12_offer; wireObj.kind.Lightning.payment_hash = pre_payment_hash; + wireObj.kind.Lightning.lnurl_info = pre_lnurl_info; wireObj.kind.Lightning.refund_tx_id = pre_refund_tx_id; wireObj.kind.Lightning.refund_tx_amount_sat = pre_refund_tx_amount_sat; return; @@ -2963,6 +3016,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { PrepareLnUrlPayResponse apiObj, wire_cst_prepare_ln_url_pay_response wireObj) { cst_api_fill_to_wire_send_destination(apiObj.destination, wireObj.destination); wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); + cst_api_fill_to_wire_ln_url_pay_request_data(apiObj.data, wireObj.data); + wireObj.comment = cst_encode_opt_String(apiObj.comment); wireObj.success_action = cst_encode_opt_box_autoadd_success_action(apiObj.successAction); } @@ -3440,6 +3495,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_ln_url_error_data(LnUrlErrorData self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_ln_url_info(LnUrlInfo self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_ln_url_pay_error_data(LnUrlPayErrorData self, SseSerializer serializer); @@ -3660,6 +3718,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_ln_url_error_data(LnUrlErrorData self, SseSerializer serializer); + @protected + void sse_encode_ln_url_info(LnUrlInfo self, SseSerializer serializer); + @protected void sse_encode_ln_url_pay_error(LnUrlPayError self, SseSerializer serializer); @@ -3730,6 +3791,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_opt_box_autoadd_list_payment_details(ListPaymentDetails? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_ln_url_info(LnUrlInfo? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_pay_amount(PayAmount? self, SseSerializer serializer); @@ -4924,6 +4988,16 @@ class RustLibWire implements BaseWire { late final _cst_new_box_autoadd_ln_url_error_data = _cst_new_box_autoadd_ln_url_error_dataPtr .asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_ln_url_info() { + return _cst_new_box_autoadd_ln_url_info(); + } + + late final _cst_new_box_autoadd_ln_url_infoPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info'); + late final _cst_new_box_autoadd_ln_url_info = + _cst_new_box_autoadd_ln_url_infoPtr.asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_ln_url_pay_error_data() { return _cst_new_box_autoadd_ln_url_pay_error_data(); } @@ -5726,6 +5800,30 @@ final class wire_cst_send_destination extends ffi.Struct { external SendDestinationKind kind; } +final class wire_cst_ln_url_pay_request_data extends ffi.Struct { + external ffi.Pointer callback; + + @ffi.Uint64() + external int min_sendable; + + @ffi.Uint64() + external int max_sendable; + + external ffi.Pointer metadata_str; + + @ffi.Uint16() + external int comment_allowed; + + external ffi.Pointer domain; + + @ffi.Bool() + external bool allows_nostr; + + external ffi.Pointer nostr_pubkey; + + external ffi.Pointer ln_address; +} + final class wire_cst_aes_success_action_data extends ffi.Struct { external ffi.Pointer description; @@ -5780,6 +5878,10 @@ final class wire_cst_prepare_ln_url_pay_response extends ffi.Struct { @ffi.Uint64() external int fees_sat; + external wire_cst_ln_url_pay_request_data data; + + external ffi.Pointer comment; + external ffi.Pointer success_action; } @@ -5835,30 +5937,6 @@ final class wire_cst_prepare_buy_bitcoin_request extends ffi.Struct { external int amount_sat; } -final class wire_cst_ln_url_pay_request_data extends ffi.Struct { - external ffi.Pointer callback; - - @ffi.Uint64() - external int min_sendable; - - @ffi.Uint64() - external int max_sendable; - - external ffi.Pointer metadata_str; - - @ffi.Uint16() - external int comment_allowed; - - external ffi.Pointer domain; - - @ffi.Bool() - external bool allows_nostr; - - external ffi.Pointer nostr_pubkey; - - external ffi.Pointer ln_address; -} - final class wire_cst_prepare_ln_url_pay_request extends ffi.Struct { external wire_cst_ln_url_pay_request_data data; @@ -5970,6 +6048,76 @@ final class wire_cst_binding_event_listener extends ffi.Struct { external ffi.Pointer stream; } +final class wire_cst_aes_success_action_data_decrypted extends ffi.Struct { + external ffi.Pointer description; + + external ffi.Pointer plaintext; +} + +final class wire_cst_AesSuccessActionDataResult_Decrypted extends ffi.Struct { + external ffi.Pointer data; +} + +final class wire_cst_AesSuccessActionDataResult_ErrorStatus extends ffi.Struct { + external ffi.Pointer reason; +} + +final class AesSuccessActionDataResultKind extends ffi.Union { + external wire_cst_AesSuccessActionDataResult_Decrypted Decrypted; + + external wire_cst_AesSuccessActionDataResult_ErrorStatus ErrorStatus; +} + +final class wire_cst_aes_success_action_data_result extends ffi.Struct { + @ffi.Int32() + external int tag; + + external AesSuccessActionDataResultKind kind; +} + +final class wire_cst_SuccessActionProcessed_Aes extends ffi.Struct { + external ffi.Pointer result; +} + +final class wire_cst_SuccessActionProcessed_Message extends ffi.Struct { + external ffi.Pointer data; +} + +final class wire_cst_SuccessActionProcessed_Url extends ffi.Struct { + external ffi.Pointer data; +} + +final class SuccessActionProcessedKind extends ffi.Union { + external wire_cst_SuccessActionProcessed_Aes Aes; + + external wire_cst_SuccessActionProcessed_Message Message; + + external wire_cst_SuccessActionProcessed_Url Url; +} + +final class wire_cst_success_action_processed extends ffi.Struct { + @ffi.Int32() + external int tag; + + external SuccessActionProcessedKind kind; +} + +final class wire_cst_ln_url_info extends ffi.Struct { + external ffi.Pointer ln_address; + + external ffi.Pointer lnurl_pay_comment; + + external ffi.Pointer lnurl_pay_domain; + + external ffi.Pointer lnurl_pay_metadata; + + external ffi.Pointer lnurl_pay_success_action; + + external ffi.Pointer lnurl_pay_unprocessed_success_action; + + external ffi.Pointer lnurl_withdraw_endpoint; +} + final class wire_cst_PaymentDetails_Lightning extends ffi.Struct { external ffi.Pointer swap_id; @@ -5983,6 +6131,8 @@ final class wire_cst_PaymentDetails_Lightning extends ffi.Struct { external ffi.Pointer payment_hash; + external ffi.Pointer lnurl_info; + external ffi.Pointer refund_tx_id; external ffi.Pointer refund_tx_amount_sat; @@ -6139,33 +6289,6 @@ final class wire_cst_connect_request extends ffi.Struct { external ffi.Pointer mnemonic; } -final class wire_cst_aes_success_action_data_decrypted extends ffi.Struct { - external ffi.Pointer description; - - external ffi.Pointer plaintext; -} - -final class wire_cst_AesSuccessActionDataResult_Decrypted extends ffi.Struct { - external ffi.Pointer data; -} - -final class wire_cst_AesSuccessActionDataResult_ErrorStatus extends ffi.Struct { - external ffi.Pointer reason; -} - -final class AesSuccessActionDataResultKind extends ffi.Union { - external wire_cst_AesSuccessActionDataResult_Decrypted Decrypted; - - external wire_cst_AesSuccessActionDataResult_ErrorStatus ErrorStatus; -} - -final class wire_cst_aes_success_action_data_result extends ffi.Struct { - @ffi.Int32() - external int tag; - - external AesSuccessActionDataResultKind kind; -} - final class wire_cst_bitcoin_address_data extends ffi.Struct { external ffi.Pointer address; @@ -6189,33 +6312,6 @@ final class wire_cst_ln_url_pay_error_data extends ffi.Struct { external ffi.Pointer reason; } -final class wire_cst_SuccessActionProcessed_Aes extends ffi.Struct { - external ffi.Pointer result; -} - -final class wire_cst_SuccessActionProcessed_Message extends ffi.Struct { - external ffi.Pointer data; -} - -final class wire_cst_SuccessActionProcessed_Url extends ffi.Struct { - external ffi.Pointer data; -} - -final class SuccessActionProcessedKind extends ffi.Union { - external wire_cst_SuccessActionProcessed_Aes Aes; - - external wire_cst_SuccessActionProcessed_Message Message; - - external wire_cst_SuccessActionProcessed_Url Url; -} - -final class wire_cst_success_action_processed extends ffi.Struct { - @ffi.Int32() - external int tag; - - external SuccessActionProcessedKind kind; -} - final class wire_cst_ln_url_pay_success_data extends ffi.Struct { external wire_cst_payment payment; diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index c7c8a70bd..e0c60f2b3 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -399,6 +399,50 @@ class ListPaymentsRequest { details == other.details; } +/// Represents the payment LNURL info +class LnUrlInfo { + final String? lnAddress; + final String? lnurlPayComment; + final String? lnurlPayDomain; + final String? lnurlPayMetadata; + final SuccessActionProcessed? lnurlPaySuccessAction; + final SuccessAction? lnurlPayUnprocessedSuccessAction; + final String? lnurlWithdrawEndpoint; + + const LnUrlInfo({ + this.lnAddress, + this.lnurlPayComment, + this.lnurlPayDomain, + this.lnurlPayMetadata, + this.lnurlPaySuccessAction, + this.lnurlPayUnprocessedSuccessAction, + this.lnurlWithdrawEndpoint, + }); + + @override + int get hashCode => + lnAddress.hashCode ^ + lnurlPayComment.hashCode ^ + lnurlPayDomain.hashCode ^ + lnurlPayMetadata.hashCode ^ + lnurlPaySuccessAction.hashCode ^ + lnurlPayUnprocessedSuccessAction.hashCode ^ + lnurlWithdrawEndpoint.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LnUrlInfo && + runtimeType == other.runtimeType && + lnAddress == other.lnAddress && + lnurlPayComment == other.lnurlPayComment && + lnurlPayDomain == other.lnurlPayDomain && + lnurlPayMetadata == other.lnurlPayMetadata && + lnurlPaySuccessAction == other.lnurlPaySuccessAction && + lnurlPayUnprocessedSuccessAction == other.lnurlPayUnprocessedSuccessAction && + lnurlWithdrawEndpoint == other.lnurlWithdrawEndpoint; +} + /// An argument when calling [crate::sdk::LiquidSdk::lnurl_pay]. class LnUrlPayRequest { /// The response from calling [crate::sdk::LiquidSdk::prepare_lnurl_pay] @@ -651,6 +695,9 @@ sealed class PaymentDetails with _$PaymentDetails { /// The payment hash of the invoice String? paymentHash, + /// The payment LNURL info + LnUrlInfo? lnurlInfo, + /// For a Send swap which was refunded, this is the refund tx id String? refundTxId, @@ -856,6 +903,12 @@ class PrepareLnUrlPayResponse { /// The fees in satoshis to send the payment final BigInt feesSat; + /// The [LnUrlPayRequestData] returned by [crate::input_parser::parse] + final LnUrlPayRequestData data; + + /// An optional comment for this payment + final String? comment; + /// The unprocessed LUD-09 success action. This will be processed and decrypted if /// needed after calling [crate::sdk::LiquidSdk::lnurl_pay] final SuccessAction? successAction; @@ -863,11 +916,14 @@ class PrepareLnUrlPayResponse { const PrepareLnUrlPayResponse({ required this.destination, required this.feesSat, + required this.data, + this.comment, this.successAction, }); @override - int get hashCode => destination.hashCode ^ feesSat.hashCode ^ successAction.hashCode; + int get hashCode => + destination.hashCode ^ feesSat.hashCode ^ data.hashCode ^ comment.hashCode ^ successAction.hashCode; @override bool operator ==(Object other) => @@ -876,6 +932,8 @@ class PrepareLnUrlPayResponse { runtimeType == other.runtimeType && destination == other.destination && feesSat == other.feesSat && + data == other.data && + comment == other.comment && successAction == other.successAction; } diff --git a/packages/dart/lib/src/model.freezed.dart b/packages/dart/lib/src/model.freezed.dart index c511a99f3..a712d5559 100644 --- a/packages/dart/lib/src/model.freezed.dart +++ b/packages/dart/lib/src/model.freezed.dart @@ -795,6 +795,7 @@ abstract class _$$PaymentDetails_LightningImplCopyWith<$Res> implements $Payment String? bolt11, String? bolt12Offer, String? paymentHash, + LnUrlInfo? lnurlInfo, String? refundTxId, BigInt? refundTxAmountSat}); } @@ -818,6 +819,7 @@ class __$$PaymentDetails_LightningImplCopyWithImpl<$Res> Object? bolt11 = freezed, Object? bolt12Offer = freezed, Object? paymentHash = freezed, + Object? lnurlInfo = freezed, Object? refundTxId = freezed, Object? refundTxAmountSat = freezed, }) { @@ -846,6 +848,10 @@ class __$$PaymentDetails_LightningImplCopyWithImpl<$Res> ? _value.paymentHash : paymentHash // ignore: cast_nullable_to_non_nullable as String?, + lnurlInfo: freezed == lnurlInfo + ? _value.lnurlInfo + : lnurlInfo // ignore: cast_nullable_to_non_nullable + as LnUrlInfo?, refundTxId: freezed == refundTxId ? _value.refundTxId : refundTxId // ignore: cast_nullable_to_non_nullable @@ -868,6 +874,7 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { this.bolt11, this.bolt12Offer, this.paymentHash, + this.lnurlInfo, this.refundTxId, this.refundTxAmountSat}) : super._(); @@ -895,6 +902,10 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { @override final String? paymentHash; + /// The payment LNURL info + @override + final LnUrlInfo? lnurlInfo; + /// For a Send swap which was refunded, this is the refund tx id @override final String? refundTxId; @@ -905,7 +916,7 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { @override String toString() { - return 'PaymentDetails.lightning(swapId: $swapId, description: $description, preimage: $preimage, bolt11: $bolt11, bolt12Offer: $bolt12Offer, paymentHash: $paymentHash, refundTxId: $refundTxId, refundTxAmountSat: $refundTxAmountSat)'; + return 'PaymentDetails.lightning(swapId: $swapId, description: $description, preimage: $preimage, bolt11: $bolt11, bolt12Offer: $bolt12Offer, paymentHash: $paymentHash, lnurlInfo: $lnurlInfo, refundTxId: $refundTxId, refundTxAmountSat: $refundTxAmountSat)'; } @override @@ -919,6 +930,7 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { (identical(other.bolt11, bolt11) || other.bolt11 == bolt11) && (identical(other.bolt12Offer, bolt12Offer) || other.bolt12Offer == bolt12Offer) && (identical(other.paymentHash, paymentHash) || other.paymentHash == paymentHash) && + (identical(other.lnurlInfo, lnurlInfo) || other.lnurlInfo == lnurlInfo) && (identical(other.refundTxId, refundTxId) || other.refundTxId == refundTxId) && (identical(other.refundTxAmountSat, refundTxAmountSat) || other.refundTxAmountSat == refundTxAmountSat)); @@ -926,7 +938,7 @@ class _$PaymentDetails_LightningImpl extends PaymentDetails_Lightning { @override int get hashCode => Object.hash(runtimeType, swapId, description, preimage, bolt11, bolt12Offer, - paymentHash, refundTxId, refundTxAmountSat); + paymentHash, lnurlInfo, refundTxId, refundTxAmountSat); /// Create a copy of PaymentDetails /// with the given fields replaced by the non-null parameter values. @@ -945,6 +957,7 @@ abstract class PaymentDetails_Lightning extends PaymentDetails { final String? bolt11, final String? bolt12Offer, final String? paymentHash, + final LnUrlInfo? lnurlInfo, final String? refundTxId, final BigInt? refundTxAmountSat}) = _$PaymentDetails_LightningImpl; const PaymentDetails_Lightning._() : super._(); @@ -967,6 +980,9 @@ abstract class PaymentDetails_Lightning extends PaymentDetails { /// The payment hash of the invoice String? get paymentHash; + /// The payment LNURL info + LnUrlInfo? get lnurlInfo; + /// For a Send swap which was refunded, this is the refund tx id String? get refundTxId; diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index b0b40994b..9e3f09ed5 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -1060,6 +1060,17 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_error_dataPtr .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info() { + return _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info(); + } + + late final _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_infoPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info'); + late final _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_info = + _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_infoPtr + .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_error_data() { return _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_error_data(); @@ -4211,6 +4222,30 @@ final class wire_cst_send_destination extends ffi.Struct { external SendDestinationKind kind; } +final class wire_cst_ln_url_pay_request_data extends ffi.Struct { + external ffi.Pointer callback; + + @ffi.Uint64() + external int min_sendable; + + @ffi.Uint64() + external int max_sendable; + + external ffi.Pointer metadata_str; + + @ffi.Uint16() + external int comment_allowed; + + external ffi.Pointer domain; + + @ffi.Bool() + external bool allows_nostr; + + external ffi.Pointer nostr_pubkey; + + external ffi.Pointer ln_address; +} + final class wire_cst_aes_success_action_data extends ffi.Struct { external ffi.Pointer description; @@ -4265,6 +4300,10 @@ final class wire_cst_prepare_ln_url_pay_response extends ffi.Struct { @ffi.Uint64() external int fees_sat; + external wire_cst_ln_url_pay_request_data data; + + external ffi.Pointer comment; + external ffi.Pointer success_action; } @@ -4320,30 +4359,6 @@ final class wire_cst_prepare_buy_bitcoin_request extends ffi.Struct { external int amount_sat; } -final class wire_cst_ln_url_pay_request_data extends ffi.Struct { - external ffi.Pointer callback; - - @ffi.Uint64() - external int min_sendable; - - @ffi.Uint64() - external int max_sendable; - - external ffi.Pointer metadata_str; - - @ffi.Uint16() - external int comment_allowed; - - external ffi.Pointer domain; - - @ffi.Bool() - external bool allows_nostr; - - external ffi.Pointer nostr_pubkey; - - external ffi.Pointer ln_address; -} - final class wire_cst_prepare_ln_url_pay_request extends ffi.Struct { external wire_cst_ln_url_pay_request_data data; @@ -4455,6 +4470,76 @@ final class wire_cst_binding_event_listener extends ffi.Struct { external ffi.Pointer stream; } +final class wire_cst_aes_success_action_data_decrypted extends ffi.Struct { + external ffi.Pointer description; + + external ffi.Pointer plaintext; +} + +final class wire_cst_AesSuccessActionDataResult_Decrypted extends ffi.Struct { + external ffi.Pointer data; +} + +final class wire_cst_AesSuccessActionDataResult_ErrorStatus extends ffi.Struct { + external ffi.Pointer reason; +} + +final class AesSuccessActionDataResultKind extends ffi.Union { + external wire_cst_AesSuccessActionDataResult_Decrypted Decrypted; + + external wire_cst_AesSuccessActionDataResult_ErrorStatus ErrorStatus; +} + +final class wire_cst_aes_success_action_data_result extends ffi.Struct { + @ffi.Int32() + external int tag; + + external AesSuccessActionDataResultKind kind; +} + +final class wire_cst_SuccessActionProcessed_Aes extends ffi.Struct { + external ffi.Pointer result; +} + +final class wire_cst_SuccessActionProcessed_Message extends ffi.Struct { + external ffi.Pointer data; +} + +final class wire_cst_SuccessActionProcessed_Url extends ffi.Struct { + external ffi.Pointer data; +} + +final class SuccessActionProcessedKind extends ffi.Union { + external wire_cst_SuccessActionProcessed_Aes Aes; + + external wire_cst_SuccessActionProcessed_Message Message; + + external wire_cst_SuccessActionProcessed_Url Url; +} + +final class wire_cst_success_action_processed extends ffi.Struct { + @ffi.Int32() + external int tag; + + external SuccessActionProcessedKind kind; +} + +final class wire_cst_ln_url_info extends ffi.Struct { + external ffi.Pointer ln_address; + + external ffi.Pointer lnurl_pay_comment; + + external ffi.Pointer lnurl_pay_domain; + + external ffi.Pointer lnurl_pay_metadata; + + external ffi.Pointer lnurl_pay_success_action; + + external ffi.Pointer lnurl_pay_unprocessed_success_action; + + external ffi.Pointer lnurl_withdraw_endpoint; +} + final class wire_cst_PaymentDetails_Lightning extends ffi.Struct { external ffi.Pointer swap_id; @@ -4468,6 +4553,8 @@ final class wire_cst_PaymentDetails_Lightning extends ffi.Struct { external ffi.Pointer payment_hash; + external ffi.Pointer lnurl_info; + external ffi.Pointer refund_tx_id; external ffi.Pointer refund_tx_amount_sat; @@ -4624,33 +4711,6 @@ final class wire_cst_connect_request extends ffi.Struct { external ffi.Pointer mnemonic; } -final class wire_cst_aes_success_action_data_decrypted extends ffi.Struct { - external ffi.Pointer description; - - external ffi.Pointer plaintext; -} - -final class wire_cst_AesSuccessActionDataResult_Decrypted extends ffi.Struct { - external ffi.Pointer data; -} - -final class wire_cst_AesSuccessActionDataResult_ErrorStatus extends ffi.Struct { - external ffi.Pointer reason; -} - -final class AesSuccessActionDataResultKind extends ffi.Union { - external wire_cst_AesSuccessActionDataResult_Decrypted Decrypted; - - external wire_cst_AesSuccessActionDataResult_ErrorStatus ErrorStatus; -} - -final class wire_cst_aes_success_action_data_result extends ffi.Struct { - @ffi.Int32() - external int tag; - - external AesSuccessActionDataResultKind kind; -} - final class wire_cst_bitcoin_address_data extends ffi.Struct { external ffi.Pointer address; @@ -4674,33 +4734,6 @@ final class wire_cst_ln_url_pay_error_data extends ffi.Struct { external ffi.Pointer reason; } -final class wire_cst_SuccessActionProcessed_Aes extends ffi.Struct { - external ffi.Pointer result; -} - -final class wire_cst_SuccessActionProcessed_Message extends ffi.Struct { - external ffi.Pointer data; -} - -final class wire_cst_SuccessActionProcessed_Url extends ffi.Struct { - external ffi.Pointer data; -} - -final class SuccessActionProcessedKind extends ffi.Union { - external wire_cst_SuccessActionProcessed_Aes Aes; - - external wire_cst_SuccessActionProcessed_Message Message; - - external wire_cst_SuccessActionProcessed_Url Url; -} - -final class wire_cst_success_action_processed extends ffi.Struct { - @ffi.Int32() - external int tag; - - external SuccessActionProcessedKind kind; -} - final class wire_cst_ln_url_pay_success_data extends ffi.Struct { external wire_cst_payment payment; diff --git a/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt b/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt index d9c6f6c01..63be33ddc 100644 --- a/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt @@ -941,6 +941,81 @@ fun asLnUrlErrorDataList(arr: ReadableArray): List { return list } +fun asLnUrlInfo(lnUrlInfo: ReadableMap): LnUrlInfo? { + if (!validateMandatoryFields( + lnUrlInfo, + arrayOf(), + ) + ) { + return null + } + val lnAddress = if (hasNonNullKey(lnUrlInfo, "lnAddress")) lnUrlInfo.getString("lnAddress") else null + val lnurlPayComment = if (hasNonNullKey(lnUrlInfo, "lnurlPayComment")) lnUrlInfo.getString("lnurlPayComment") else null + val lnurlPayDomain = if (hasNonNullKey(lnUrlInfo, "lnurlPayDomain")) lnUrlInfo.getString("lnurlPayDomain") else null + val lnurlPayMetadata = if (hasNonNullKey(lnUrlInfo, "lnurlPayMetadata")) lnUrlInfo.getString("lnurlPayMetadata") else null + val lnurlPaySuccessAction = + if (hasNonNullKey(lnUrlInfo, "lnurlPaySuccessAction")) { + lnUrlInfo.getMap("lnurlPaySuccessAction")?.let { + asSuccessActionProcessed(it) + } + } else { + null + } + val lnurlPayUnprocessedSuccessAction = + if (hasNonNullKey( + lnUrlInfo, + "lnurlPayUnprocessedSuccessAction", + ) + ) { + lnUrlInfo.getMap("lnurlPayUnprocessedSuccessAction")?.let { + asSuccessAction(it) + } + } else { + null + } + val lnurlWithdrawEndpoint = + if (hasNonNullKey( + lnUrlInfo, + "lnurlWithdrawEndpoint", + ) + ) { + lnUrlInfo.getString("lnurlWithdrawEndpoint") + } else { + null + } + return LnUrlInfo( + lnAddress, + lnurlPayComment, + lnurlPayDomain, + lnurlPayMetadata, + lnurlPaySuccessAction, + lnurlPayUnprocessedSuccessAction, + lnurlWithdrawEndpoint, + ) +} + +fun readableMapOf(lnUrlInfo: LnUrlInfo): ReadableMap = + readableMapOf( + "lnAddress" to lnUrlInfo.lnAddress, + "lnurlPayComment" to lnUrlInfo.lnurlPayComment, + "lnurlPayDomain" to lnUrlInfo.lnurlPayDomain, + "lnurlPayMetadata" to lnUrlInfo.lnurlPayMetadata, + "lnurlPaySuccessAction" to lnUrlInfo.lnurlPaySuccessAction?.let { readableMapOf(it) }, + "lnurlPayUnprocessedSuccessAction" to lnUrlInfo.lnurlPayUnprocessedSuccessAction?.let { readableMapOf(it) }, + "lnurlWithdrawEndpoint" to lnUrlInfo.lnurlWithdrawEndpoint, + ) + +fun asLnUrlInfoList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asLnUrlInfo(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asLnUrlPayErrorData(lnUrlPayErrorData: ReadableMap): LnUrlPayErrorData? { if (!validateMandatoryFields( lnUrlPayErrorData, @@ -1581,6 +1656,7 @@ fun asPrepareLnUrlPayResponse(prepareLnUrlPayResponse: ReadableMap): PrepareLnUr arrayOf( "destination", "feesSat", + "data", ), ) ) { @@ -1588,6 +1664,8 @@ fun asPrepareLnUrlPayResponse(prepareLnUrlPayResponse: ReadableMap): PrepareLnUr } val destination = prepareLnUrlPayResponse.getMap("destination")?.let { asSendDestination(it) }!! val feesSat = prepareLnUrlPayResponse.getDouble("feesSat").toULong() + val data = prepareLnUrlPayResponse.getMap("data")?.let { asLnUrlPayRequestData(it) }!! + val comment = if (hasNonNullKey(prepareLnUrlPayResponse, "comment")) prepareLnUrlPayResponse.getString("comment") else null val successAction = if (hasNonNullKey(prepareLnUrlPayResponse, "successAction")) { prepareLnUrlPayResponse.getMap("successAction")?.let { @@ -1596,13 +1674,15 @@ fun asPrepareLnUrlPayResponse(prepareLnUrlPayResponse: ReadableMap): PrepareLnUr } else { null } - return PrepareLnUrlPayResponse(destination, feesSat, successAction) + return PrepareLnUrlPayResponse(destination, feesSat, data, comment, successAction) } fun readableMapOf(prepareLnUrlPayResponse: PrepareLnUrlPayResponse): ReadableMap = readableMapOf( "destination" to readableMapOf(prepareLnUrlPayResponse.destination), "feesSat" to prepareLnUrlPayResponse.feesSat, + "data" to readableMapOf(prepareLnUrlPayResponse.data), + "comment" to prepareLnUrlPayResponse.comment, "successAction" to prepareLnUrlPayResponse.successAction?.let { readableMapOf(it) }, ) @@ -2979,6 +3059,16 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { val bolt11 = if (hasNonNullKey(paymentDetails, "bolt11")) paymentDetails.getString("bolt11") else null val bolt12Offer = if (hasNonNullKey(paymentDetails, "bolt12Offer")) paymentDetails.getString("bolt12Offer") else null val paymentHash = if (hasNonNullKey(paymentDetails, "paymentHash")) paymentDetails.getString("paymentHash") else null + val lnurlInfo = + if (hasNonNullKey( + paymentDetails, + "lnurlInfo", + ) + ) { + paymentDetails.getMap("lnurlInfo")?.let { asLnUrlInfo(it) } + } else { + null + } val refundTxId = if (hasNonNullKey(paymentDetails, "refundTxId")) paymentDetails.getString("refundTxId") else null val refundTxAmountSat = if (hasNonNullKey( @@ -2990,7 +3080,17 @@ fun asPaymentDetails(paymentDetails: ReadableMap): PaymentDetails? { } else { null } - return PaymentDetails.Lightning(swapId, description, preimage, bolt11, bolt12Offer, paymentHash, refundTxId, refundTxAmountSat) + return PaymentDetails.Lightning( + swapId, + description, + preimage, + bolt11, + bolt12Offer, + paymentHash, + lnurlInfo, + refundTxId, + refundTxAmountSat, + ) } if (type == "liquid") { val destination = paymentDetails.getString("destination")!! @@ -3027,6 +3127,7 @@ fun readableMapOf(paymentDetails: PaymentDetails): ReadableMap? { pushToMap(map, "bolt11", paymentDetails.bolt11) pushToMap(map, "bolt12Offer", paymentDetails.bolt12Offer) pushToMap(map, "paymentHash", paymentDetails.paymentHash) + pushToMap(map, "lnurlInfo", paymentDetails.lnurlInfo?.let { readableMapOf(it) }) pushToMap(map, "refundTxId", paymentDetails.refundTxId) pushToMap(map, "refundTxAmountSat", paymentDetails.refundTxAmountSat) } diff --git a/packages/react-native/ios/BreezSDKLiquidMapper.swift b/packages/react-native/ios/BreezSDKLiquidMapper.swift index 1cc59314c..5b333e6c9 100644 --- a/packages/react-native/ios/BreezSDKLiquidMapper.swift +++ b/packages/react-native/ios/BreezSDKLiquidMapper.swift @@ -1131,6 +1131,85 @@ enum BreezSDKLiquidMapper { return lnUrlErrorDataList.map { v -> [String: Any?] in return dictionaryOf(lnUrlErrorData: v) } } + static func asLnUrlInfo(lnUrlInfo: [String: Any?]) throws -> LnUrlInfo { + var lnAddress: String? + if hasNonNilKey(data: lnUrlInfo, key: "lnAddress") { + guard let lnAddressTmp = lnUrlInfo["lnAddress"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnAddress")) + } + lnAddress = lnAddressTmp + } + var lnurlPayComment: String? + if hasNonNilKey(data: lnUrlInfo, key: "lnurlPayComment") { + guard let lnurlPayCommentTmp = lnUrlInfo["lnurlPayComment"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlPayComment")) + } + lnurlPayComment = lnurlPayCommentTmp + } + var lnurlPayDomain: String? + if hasNonNilKey(data: lnUrlInfo, key: "lnurlPayDomain") { + guard let lnurlPayDomainTmp = lnUrlInfo["lnurlPayDomain"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlPayDomain")) + } + lnurlPayDomain = lnurlPayDomainTmp + } + var lnurlPayMetadata: String? + if hasNonNilKey(data: lnUrlInfo, key: "lnurlPayMetadata") { + guard let lnurlPayMetadataTmp = lnUrlInfo["lnurlPayMetadata"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlPayMetadata")) + } + lnurlPayMetadata = lnurlPayMetadataTmp + } + var lnurlPaySuccessAction: SuccessActionProcessed? + if let lnurlPaySuccessActionTmp = lnUrlInfo["lnurlPaySuccessAction"] as? [String: Any?] { + lnurlPaySuccessAction = try asSuccessActionProcessed(successActionProcessed: lnurlPaySuccessActionTmp) + } + + var lnurlPayUnprocessedSuccessAction: SuccessAction? + if let lnurlPayUnprocessedSuccessActionTmp = lnUrlInfo["lnurlPayUnprocessedSuccessAction"] as? [String: Any?] { + lnurlPayUnprocessedSuccessAction = try asSuccessAction(successAction: lnurlPayUnprocessedSuccessActionTmp) + } + + var lnurlWithdrawEndpoint: String? + if hasNonNilKey(data: lnUrlInfo, key: "lnurlWithdrawEndpoint") { + guard let lnurlWithdrawEndpointTmp = lnUrlInfo["lnurlWithdrawEndpoint"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "lnurlWithdrawEndpoint")) + } + lnurlWithdrawEndpoint = lnurlWithdrawEndpointTmp + } + + return LnUrlInfo(lnAddress: lnAddress, lnurlPayComment: lnurlPayComment, lnurlPayDomain: lnurlPayDomain, lnurlPayMetadata: lnurlPayMetadata, lnurlPaySuccessAction: lnurlPaySuccessAction, lnurlPayUnprocessedSuccessAction: lnurlPayUnprocessedSuccessAction, lnurlWithdrawEndpoint: lnurlWithdrawEndpoint) + } + + static func dictionaryOf(lnUrlInfo: LnUrlInfo) -> [String: Any?] { + return [ + "lnAddress": lnUrlInfo.lnAddress == nil ? nil : lnUrlInfo.lnAddress, + "lnurlPayComment": lnUrlInfo.lnurlPayComment == nil ? nil : lnUrlInfo.lnurlPayComment, + "lnurlPayDomain": lnUrlInfo.lnurlPayDomain == nil ? nil : lnUrlInfo.lnurlPayDomain, + "lnurlPayMetadata": lnUrlInfo.lnurlPayMetadata == nil ? nil : lnUrlInfo.lnurlPayMetadata, + "lnurlPaySuccessAction": lnUrlInfo.lnurlPaySuccessAction == nil ? nil : dictionaryOf(successActionProcessed: lnUrlInfo.lnurlPaySuccessAction!), + "lnurlPayUnprocessedSuccessAction": lnUrlInfo.lnurlPayUnprocessedSuccessAction == nil ? nil : dictionaryOf(successAction: lnUrlInfo.lnurlPayUnprocessedSuccessAction!), + "lnurlWithdrawEndpoint": lnUrlInfo.lnurlWithdrawEndpoint == nil ? nil : lnUrlInfo.lnurlWithdrawEndpoint, + ] + } + + static func asLnUrlInfoList(arr: [Any]) throws -> [LnUrlInfo] { + var list = [LnUrlInfo]() + for value in arr { + if let val = value as? [String: Any?] { + var lnUrlInfo = try asLnUrlInfo(lnUrlInfo: val) + list.append(lnUrlInfo) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "LnUrlInfo")) + } + } + return list + } + + static func arrayOf(lnUrlInfoList: [LnUrlInfo]) -> [Any] { + return lnUrlInfoList.map { v -> [String: Any?] in return dictionaryOf(lnUrlInfo: v) } + } + static func asLnUrlPayErrorData(lnUrlPayErrorData: [String: Any?]) throws -> LnUrlPayErrorData { guard let paymentHash = lnUrlPayErrorData["paymentHash"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentHash", typeName: "LnUrlPayErrorData")) @@ -1871,18 +1950,32 @@ enum BreezSDKLiquidMapper { guard let feesSat = prepareLnUrlPayResponse["feesSat"] as? UInt64 else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PrepareLnUrlPayResponse")) } + guard let dataTmp = prepareLnUrlPayResponse["data"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "data", typeName: "PrepareLnUrlPayResponse")) + } + let data = try asLnUrlPayRequestData(lnUrlPayRequestData: dataTmp) + + var comment: String? + if hasNonNilKey(data: prepareLnUrlPayResponse, key: "comment") { + guard let commentTmp = prepareLnUrlPayResponse["comment"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "comment")) + } + comment = commentTmp + } var successAction: SuccessAction? if let successActionTmp = prepareLnUrlPayResponse["successAction"] as? [String: Any?] { successAction = try asSuccessAction(successAction: successActionTmp) } - return PrepareLnUrlPayResponse(destination: destination, feesSat: feesSat, successAction: successAction) + return PrepareLnUrlPayResponse(destination: destination, feesSat: feesSat, data: data, comment: comment, successAction: successAction) } static func dictionaryOf(prepareLnUrlPayResponse: PrepareLnUrlPayResponse) -> [String: Any?] { return [ "destination": dictionaryOf(sendDestination: prepareLnUrlPayResponse.destination), "feesSat": prepareLnUrlPayResponse.feesSat, + "data": dictionaryOf(lnUrlPayRequestData: prepareLnUrlPayResponse.data), + "comment": prepareLnUrlPayResponse.comment == nil ? nil : prepareLnUrlPayResponse.comment, "successAction": prepareLnUrlPayResponse.successAction == nil ? nil : dictionaryOf(successAction: prepareLnUrlPayResponse.successAction!), ] } @@ -3659,11 +3752,16 @@ enum BreezSDKLiquidMapper { let _paymentHash = paymentDetails["paymentHash"] as? String + var _lnurlInfo: LnUrlInfo? + if let lnurlInfoTmp = paymentDetails["lnurlInfo"] as? [String: Any?] { + _lnurlInfo = try asLnUrlInfo(lnUrlInfo: lnurlInfoTmp) + } + let _refundTxId = paymentDetails["refundTxId"] as? String let _refundTxAmountSat = paymentDetails["refundTxAmountSat"] as? UInt64 - return PaymentDetails.lightning(swapId: _swapId, description: _description, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) + return PaymentDetails.lightning(swapId: _swapId, description: _description, preimage: _preimage, bolt11: _bolt11, bolt12Offer: _bolt12Offer, paymentHash: _paymentHash, lnurlInfo: _lnurlInfo, refundTxId: _refundTxId, refundTxAmountSat: _refundTxAmountSat) } if type == "liquid" { guard let _destination = paymentDetails["destination"] as? String else { @@ -3694,7 +3792,7 @@ enum BreezSDKLiquidMapper { static func dictionaryOf(paymentDetails: PaymentDetails) -> [String: Any?] { switch paymentDetails { case let .lightning( - swapId, description, preimage, bolt11, bolt12Offer, paymentHash, refundTxId, refundTxAmountSat + swapId, description, preimage, bolt11, bolt12Offer, paymentHash, lnurlInfo, refundTxId, refundTxAmountSat ): return [ "type": "lightning", @@ -3704,6 +3802,7 @@ enum BreezSDKLiquidMapper { "bolt11": bolt11 == nil ? nil : bolt11, "bolt12Offer": bolt12Offer == nil ? nil : bolt12Offer, "paymentHash": paymentHash == nil ? nil : paymentHash, + "lnurlInfo": lnurlInfo == nil ? nil : dictionaryOf(lnUrlInfo: lnurlInfo!), "refundTxId": refundTxId == nil ? nil : refundTxId, "refundTxAmountSat": refundTxAmountSat == nil ? nil : refundTxAmountSat, ] diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 28367051c..4fa9798a7 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -181,6 +181,16 @@ export interface LnUrlErrorData { reason: string } +export interface LnUrlInfo { + lnAddress?: string + lnurlPayComment?: string + lnurlPayDomain?: string + lnurlPayMetadata?: string + lnurlPaySuccessAction?: SuccessActionProcessed + lnurlPayUnprocessedSuccessAction?: SuccessAction + lnurlWithdrawEndpoint?: string +} + export interface LnUrlPayErrorData { paymentHash: string reason: string @@ -288,6 +298,8 @@ export interface PrepareLnUrlPayRequest { export interface PrepareLnUrlPayResponse { destination: SendDestination feesSat: number + data: LnUrlPayRequestData + comment?: string successAction?: SuccessAction } @@ -606,6 +618,7 @@ export type PaymentDetails = { bolt11?: string bolt12Offer?: string paymentHash?: string + lnurlInfo?: LnUrlInfo refundTxId?: string refundTxAmountSat?: number } | {