From 8f8f7ad36fab6564607af7d4473f7c052d839c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Granh=C3=A3o?= Date: Mon, 6 Nov 2023 15:33:09 +0000 Subject: [PATCH] Include `SwapInfo` in `Payment` resulting from swap --- libs/sdk-bindings/src/breez_sdk.udl | 1 + libs/sdk-core/src/breez_services.rs | 77 +++++++++++++++++- libs/sdk-core/src/bridge_generated.rs | 1 + libs/sdk-core/src/greenlight/node_api.rs | 5 ++ libs/sdk-core/src/models.rs | 4 + libs/sdk-core/src/persist/transactions.rs | 80 +++++++++++++++++-- libs/sdk-core/src/swap_in/swap.rs | 1 + libs/sdk-flutter/lib/bridge_generated.dart | 7 +- .../main/java/com/breezsdk/BreezSDKMapper.kt | 3 + .../sdk-react-native/ios/BreezSDKMapper.swift | 8 +- libs/sdk-react-native/src/index.ts | 1 + 11 files changed, 178 insertions(+), 10 deletions(-) diff --git a/libs/sdk-bindings/src/breez_sdk.udl b/libs/sdk-bindings/src/breez_sdk.udl index c22b24d71..dd55fa37c 100644 --- a/libs/sdk-bindings/src/breez_sdk.udl +++ b/libs/sdk-bindings/src/breez_sdk.udl @@ -261,6 +261,7 @@ dictionary LnPaymentDetails { string? lnurl_metadata; string? ln_address; string? lnurl_withdraw_endpoint; + SwapInfo? swap_info; }; dictionary ClosedChannelPaymentDetails { diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs index c1aa30bf3..84c6fb961 100644 --- a/libs/sdk-core/src/breez_services.rs +++ b/libs/sdk-core/src/breez_services.rs @@ -1941,7 +1941,8 @@ pub(crate) mod tests { use crate::PaymentType; use crate::{ input_parser, parse_short_channel_id, test_utils::*, BuyBitcoinProvider, BuyBitcoinRequest, - InputType, ListPaymentsRequest, PaymentStatus, ReceivePaymentRequest, + InputType, ListPaymentsRequest, OpeningFeeParams, PaymentStatus, ReceivePaymentRequest, + SwapInfo, SwapStatus, }; use super::{PaymentReceiver, Receiver}; @@ -1964,6 +1965,37 @@ pub(crate) mod tests { let payment_hash_lnurl_withdraw = "2222"; let payment_hash_with_lnurl_success_action = "3333"; + let payment_hash_swap: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; + let swap_info = SwapInfo { + bitcoin_address: "123".to_string(), + created_at: 12345678, + lock_height: 654321, + payment_hash: payment_hash_swap.clone(), + preimage: vec![], + private_key: vec![], + public_key: vec![], + swapper_public_key: vec![], + script: vec![], + bolt11: Some("312".into()), + paid_sats: 1, + confirmed_sats: 1, + unconfirmed_sats: 0, + status: SwapStatus::Expired, + refund_tx_ids: vec![], + unconfirmed_tx_ids: vec![], + confirmed_tx_ids: vec![], + min_allowed_deposit: 5_000, + max_allowed_deposit: 1_000_000, + last_redeem_error: None, + channel_opening_fees: Some(OpeningFeeParams { + min_msat: 5_000_000, + proportional: 50, + valid_until: "date".to_string(), + max_idle_time: 12345, + max_client_to_self_delay: 234, + promise: "promise".to_string(), + }), + }; let dummy_transactions = vec![ Payment { id: "1111".to_string(), @@ -1985,6 +2017,7 @@ pub(crate) mod tests { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }, @@ -2008,6 +2041,7 @@ pub(crate) mod tests { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: Some(test_lnurl_withdraw_endpoint.to_string()), + swap_info: None, }, }, }, @@ -2031,6 +2065,31 @@ pub(crate) mod tests { lnurl_metadata: Some(lnurl_metadata.to_string()), ln_address: Some(test_ln_address.to_string()), lnurl_withdraw_endpoint: None, + swap_info: None, + }, + }, + }, + Payment { + id: hex::encode(payment_hash_swap.clone()), + payment_type: PaymentType::Received, + payment_time: 250000, + amount_msat: 1_000, + fee_msat: 0, + status: PaymentStatus::Complete, + description: Some("test receive".to_string()), + details: PaymentDetails::Ln { + data: LnPaymentDetails { + payment_hash: hex::encode(payment_hash_swap), + label: "".to_string(), + destination_pubkey: "321".to_string(), + payment_preimage: "5555".to_string(), + keysend: false, + bolt11: "312".to_string(), + lnurl_success_action: None, + lnurl_metadata: None, + ln_address: None, + lnurl_withdraw_endpoint: None, + swap_info: Some(swap_info.clone()), }, }, }, @@ -2055,6 +2114,11 @@ pub(crate) mod tests { None, Some(test_lnurl_withdraw_endpoint.to_string()), )?; + persister.insert_swap(swap_info.clone())?; + persister.update_swap_bolt11( + swap_info.bitcoin_address.clone(), + swap_info.bolt11.clone().unwrap(), + )?; let mut builder = BreezServicesBuilder::new(test_config.clone()); let breez_services = builder @@ -2085,7 +2149,10 @@ pub(crate) mod tests { ..Default::default() }) .await?; - assert_eq!(received, vec![cloned[1].clone(), cloned[0].clone()]); + assert_eq!( + received, + vec![cloned[3].clone(), cloned[1].clone(), cloned[0].clone()] + ); let sent = breez_services .list_payments(ListPaymentsRequest { @@ -2106,9 +2173,13 @@ pub(crate) mod tests { PaymentDetails::Ln {data: LnPaymentDetails {ln_address, ..}} if ln_address == &Some(test_ln_address.to_string()))); assert!(matches!( - &received[0].details, + &received[1].details, PaymentDetails::Ln {data: LnPaymentDetails {lnurl_withdraw_endpoint, ..}} if lnurl_withdraw_endpoint == &Some(test_lnurl_withdraw_endpoint.to_string()))); + assert!(matches!( + &received[0].details, + PaymentDetails::Ln {data: LnPaymentDetails {swap_info: swap, ..}} + if swap == &Some(swap_info))); Ok(()) } diff --git a/libs/sdk-core/src/bridge_generated.rs b/libs/sdk-core/src/bridge_generated.rs index 26d4ae3fa..876b4a40f 100644 --- a/libs/sdk-core/src/bridge_generated.rs +++ b/libs/sdk-core/src/bridge_generated.rs @@ -1139,6 +1139,7 @@ impl support::IntoDart for LnPaymentDetails { self.ln_address.into_dart(), self.lnurl_metadata.into_dart(), self.lnurl_withdraw_endpoint.into_dart(), + self.swap_info.into_dart(), ] .into_dart() } diff --git a/libs/sdk-core/src/greenlight/node_api.rs b/libs/sdk-core/src/greenlight/node_api.rs index 7860df49c..58ab4d5ff 100644 --- a/libs/sdk-core/src/greenlight/node_api.rs +++ b/libs/sdk-core/src/greenlight/node_api.rs @@ -1088,6 +1088,7 @@ impl TryFrom for Payment { lnurl_metadata: None, // For received payments, this is None ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }) @@ -1123,6 +1124,7 @@ impl TryFrom for Payment { lnurl_metadata: None, // For received payments, this is None ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }) @@ -1173,6 +1175,7 @@ impl TryFrom for Payment { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }) @@ -1212,6 +1215,7 @@ impl TryFrom for Payment { lnurl_metadata: None, // For received payments, this is None ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }) @@ -1274,6 +1278,7 @@ impl TryFrom for Payment { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }) diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs index 4c91072b8..6010fd9d1 100644 --- a/libs/sdk-core/src/models.rs +++ b/libs/sdk-core/src/models.rs @@ -630,6 +630,7 @@ pub struct PaymentResponse { } /// Wrapper for the different types of payments +#[allow(clippy::large_enum_variant)] #[derive(PartialEq, Eq, Debug, Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum PaymentDetails { @@ -665,6 +666,9 @@ pub struct LnPaymentDetails { /// Only set for [PaymentType::Received] payments that were received as part of LNURL-withdraw pub lnurl_withdraw_endpoint: Option, + + /// Only set for [PaymentType::Received] payments that were received in the context of a swap + pub swap_info: Option, } /// Represents the funds that were on the user side of the channel at the time it was closed. diff --git a/libs/sdk-core/src/persist/transactions.rs b/libs/sdk-core/src/persist/transactions.rs index d8669ab7b..3a82d7f37 100644 --- a/libs/sdk-core/src/persist/transactions.rs +++ b/libs/sdk-core/src/persist/transactions.rs @@ -249,6 +249,9 @@ impl SqliteStorage { data.lnurl_metadata = row.get(9)?; data.ln_address = row.get(10)?; data.lnurl_withdraw_endpoint = row.get(11)?; + data.swap_info = self + .get_swap_info_by_hash(&hex::decode(&payment.id).unwrap_or_default()) + .unwrap_or(None) } // In case we have a record of the open channel fee, let's use it. @@ -277,7 +280,7 @@ fn filter_to_where_clause( where_clause.push(format!("payment_time <= {t}")); }; if !with_failures { - where_clause.push(format!("status != {}", PaymentStatus::Failed as i64)); + where_clause.push(format!("p.status != {}", PaymentStatus::Failed as i64)); }; if let Some(filters) = type_filters { @@ -381,7 +384,38 @@ fn test_ln_transactions() -> PersistResult<(), Box> { let payment_hash_with_lnurl_success_action = "123"; let payment_hash_with_lnurl_withdraw = "124"; + let payment_hash_with_swap_info: Vec = vec![234, 12, 53, 124]; let lnurl_withdraw_url = "https://test.lnurl.withdraw.link"; + let swap_info = SwapInfo { + bitcoin_address: "123".to_string(), + created_at: 1234567, + lock_height: 7654321, + payment_hash: payment_hash_with_swap_info.clone(), + preimage: vec![1, 2, 3], + private_key: vec![3, 2, 1], + public_key: vec![1, 3, 2], + swapper_public_key: vec![2, 1, 3], + script: vec![2, 3, 1], + bolt11: Some("swap_bolt11".into()), + paid_sats: 50, + confirmed_sats: 50, + unconfirmed_sats: 0, + status: SwapStatus::Expired, + refund_tx_ids: vec![], + unconfirmed_tx_ids: vec![], + confirmed_tx_ids: vec![], + min_allowed_deposit: 5_000, + max_allowed_deposit: 1_000_000, + last_redeem_error: None, + channel_opening_fees: Some(OpeningFeeParams { + min_msat: 5_000_000, + proportional: 50, + valid_until: "date".to_string(), + max_idle_time: 12345, + max_client_to_self_delay: 234, + promise: "promise".to_string(), + }), + }; let txs = [ Payment { id: payment_hash_with_lnurl_success_action.to_string(), @@ -403,6 +437,7 @@ fn test_ln_transactions() -> PersistResult<(), Box> { lnurl_metadata: Some(lnurl_metadata.to_string()), ln_address: Some(test_ln_address.to_string()), lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }, @@ -426,6 +461,31 @@ fn test_ln_transactions() -> PersistResult<(), Box> { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: Some(lnurl_withdraw_url.to_string()), + swap_info: None, + }, + }, + }, + Payment { + id: hex::encode(payment_hash_with_swap_info.clone()), + payment_type: PaymentType::Received, + payment_time: 999, + amount_msat: 50_000, + fee_msat: 20, + status: PaymentStatus::Complete, + description: Some("desc".to_string()), + details: PaymentDetails::Ln { + data: LnPaymentDetails { + payment_hash: hex::encode(payment_hash_with_swap_info.clone()), + label: "label".to_string(), + destination_pubkey: "pubkey".to_string(), + payment_preimage: "payment_preimage".to_string(), + keysend: false, + bolt11: "swap_bolt11".to_string(), + lnurl_success_action: None, + lnurl_metadata: None, + ln_address: None, + lnurl_withdraw_endpoint: None, + swap_info: Some(swap_info.clone()), }, }, }, @@ -450,6 +510,7 @@ fn test_ln_transactions() -> PersistResult<(), Box> { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }]; @@ -471,10 +532,15 @@ fn test_ln_transactions() -> PersistResult<(), Box> { None, Some(lnurl_withdraw_url.to_string()), )?; + storage.insert_swap(swap_info.clone())?; + storage.update_swap_bolt11( + swap_info.bitcoin_address.clone(), + swap_info.bolt11.clone().unwrap(), + )?; // retrieve all let retrieve_txs = storage.list_payments(ListPaymentsRequest::default())?; - assert_eq!(retrieve_txs.len(), 2); + assert_eq!(retrieve_txs.len(), 3); assert_eq!(retrieve_txs, txs); //test only sent @@ -499,15 +565,19 @@ fn test_ln_transactions() -> PersistResult<(), Box> { filters: Some(vec![PaymentTypeFilter::Received]), ..Default::default() })?; - assert_eq!(retrieve_txs.len(), 1); + assert_eq!(retrieve_txs.len(), 2); assert_eq!(retrieve_txs[0], txs[1]); + assert_eq!(retrieve_txs[1], txs[2]); + assert!( + matches!( &retrieve_txs[1].details, PaymentDetails::Ln {data: LnPaymentDetails {swap_info: swap, ..}} if swap == &Some(swap_info)) + ); let max_ts = storage.last_payment_timestamp()?; assert_eq!(max_ts, 2000); storage.insert_or_update_payments(&txs)?; let retrieve_txs = storage.list_payments(ListPaymentsRequest::default())?; - assert_eq!(retrieve_txs.len(), 2); + assert_eq!(retrieve_txs.len(), 3); assert_eq!(retrieve_txs, txs); storage.insert_open_channel_payment_info("123", 150)?; @@ -519,7 +589,7 @@ fn test_ln_transactions() -> PersistResult<(), Box> { include_failures: Some(true), ..Default::default() })?; - assert_eq!(retrieve_txs.len(), 3); + assert_eq!(retrieve_txs.len(), 4); // test sent with failures let retrieve_txs = storage.list_payments(ListPaymentsRequest { diff --git a/libs/sdk-core/src/swap_in/swap.rs b/libs/sdk-core/src/swap_in/swap.rs index 07aabf435..160161157 100644 --- a/libs/sdk-core/src/swap_in/swap.rs +++ b/libs/sdk-core/src/swap_in/swap.rs @@ -870,6 +870,7 @@ mod tests { lnurl_metadata: None, ln_address: None, lnurl_withdraw_endpoint: None, + swap_info: None, }, }, }; diff --git a/libs/sdk-flutter/lib/bridge_generated.dart b/libs/sdk-flutter/lib/bridge_generated.dart index dd299d82e..8de208b9c 100644 --- a/libs/sdk-flutter/lib/bridge_generated.dart +++ b/libs/sdk-flutter/lib/bridge_generated.dart @@ -653,6 +653,9 @@ class LnPaymentDetails { /// Only set for [PaymentType::Received] payments that were received as part of LNURL-withdraw final String? lnurlWithdrawEndpoint; + /// Only set for [PaymentType::Received] payments that were received in the context of a swap + final SwapInfo? swapInfo; + const LnPaymentDetails({ required this.paymentHash, required this.label, @@ -664,6 +667,7 @@ class LnPaymentDetails { this.lnAddress, this.lnurlMetadata, this.lnurlWithdrawEndpoint, + this.swapInfo, }); } @@ -2790,7 +2794,7 @@ class BreezSdkCoreImpl implements BreezSdkCore { LnPaymentDetails _wire2api_ln_payment_details(dynamic raw) { final arr = raw as List; - if (arr.length != 10) throw Exception('unexpected arr length: expect 10 but see ${arr.length}'); + if (arr.length != 11) throw Exception('unexpected arr length: expect 11 but see ${arr.length}'); return LnPaymentDetails( paymentHash: _wire2api_String(arr[0]), label: _wire2api_String(arr[1]), @@ -2802,6 +2806,7 @@ class BreezSdkCoreImpl implements BreezSdkCore { lnAddress: _wire2api_opt_String(arr[7]), lnurlMetadata: _wire2api_opt_String(arr[8]), lnurlWithdrawEndpoint: _wire2api_opt_String(arr[9]), + swapInfo: _wire2api_opt_box_autoadd_swap_info(arr[10]), ); } diff --git a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt index 36526cc34..f770fa331 100644 --- a/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt +++ b/libs/sdk-react-native/android/src/main/java/com/breezsdk/BreezSDKMapper.kt @@ -835,6 +835,7 @@ fun asLnPaymentDetails(lnPaymentDetails: ReadableMap): LnPaymentDetails? { } else { null } + val swapInfo = if (hasNonNullKey(lnPaymentDetails, "swapInfo")) lnPaymentDetails.getMap("swapInfo")?.let { asSwapInfo(it) } else null return LnPaymentDetails( paymentHash, label, @@ -846,6 +847,7 @@ fun asLnPaymentDetails(lnPaymentDetails: ReadableMap): LnPaymentDetails? { lnurlMetadata, lnAddress, lnurlWithdrawEndpoint, + swapInfo, ) } @@ -861,6 +863,7 @@ fun readableMapOf(lnPaymentDetails: LnPaymentDetails): ReadableMap { "lnurlMetadata" to lnPaymentDetails.lnurlMetadata, "lnAddress" to lnPaymentDetails.lnAddress, "lnurlWithdrawEndpoint" to lnPaymentDetails.lnurlWithdrawEndpoint, + "swapInfo" to lnPaymentDetails.swapInfo?.let { readableMapOf(it) }, ) } diff --git a/libs/sdk-react-native/ios/BreezSDKMapper.swift b/libs/sdk-react-native/ios/BreezSDKMapper.swift index 224905288..d2d3f338d 100644 --- a/libs/sdk-react-native/ios/BreezSDKMapper.swift +++ b/libs/sdk-react-native/ios/BreezSDKMapper.swift @@ -721,6 +721,10 @@ class BreezSDKMapper { let lnurlMetadata = lnPaymentDetails["lnurlMetadata"] as? String let lnAddress = lnPaymentDetails["lnAddress"] as? String let lnurlWithdrawEndpoint = lnPaymentDetails["lnurlWithdrawEndpoint"] as? String + var swapInfo: SwapInfo? + if let swapInfoTmp = lnPaymentDetails["swapInfo"] as? [String: Any?] { + swapInfo = try asSwapInfo(swapInfo: swapInfoTmp) + } return LnPaymentDetails( paymentHash: paymentHash, @@ -732,7 +736,8 @@ class BreezSDKMapper { lnurlSuccessAction: lnurlSuccessAction, lnurlMetadata: lnurlMetadata, lnAddress: lnAddress, - lnurlWithdrawEndpoint: lnurlWithdrawEndpoint + lnurlWithdrawEndpoint: lnurlWithdrawEndpoint, + swapInfo: swapInfo ) } @@ -748,6 +753,7 @@ class BreezSDKMapper { "lnurlMetadata": lnPaymentDetails.lnurlMetadata == nil ? nil : lnPaymentDetails.lnurlMetadata, "lnAddress": lnPaymentDetails.lnAddress == nil ? nil : lnPaymentDetails.lnAddress, "lnurlWithdrawEndpoint": lnPaymentDetails.lnurlWithdrawEndpoint == nil ? nil : lnPaymentDetails.lnurlWithdrawEndpoint, + "swapInfo": lnPaymentDetails.swapInfo == nil ? nil : dictionaryOf(swapInfo: lnPaymentDetails.swapInfo!), ] } diff --git a/libs/sdk-react-native/src/index.ts b/libs/sdk-react-native/src/index.ts index 9596a02e3..da30f6584 100644 --- a/libs/sdk-react-native/src/index.ts +++ b/libs/sdk-react-native/src/index.ts @@ -144,6 +144,7 @@ export type LnPaymentDetails = { lnurlMetadata?: string lnAddress?: string lnurlWithdrawEndpoint?: string + swapInfo?: SwapInfo } export type LnUrlAuthRequestData = {