diff --git a/libs/sdk-bindings/src/breez_sdk.udl b/libs/sdk-bindings/src/breez_sdk.udl index d4beb967d..ecbcdd2ce 100644 --- a/libs/sdk-bindings/src/breez_sdk.udl +++ b/libs/sdk-bindings/src/breez_sdk.udl @@ -601,7 +601,7 @@ dictionary BitcoinAddressData { dictionary LnUrlPaySuccessData { SuccessActionProcessed? success_action; - string payment_hash; + Payment payment; }; dictionary LnUrlErrorData { @@ -617,6 +617,7 @@ dictionary LnUrlPayRequest { LnUrlPayRequestData data; u64 amount_msat; string? comment = null; + string? payment_label = null; }; dictionary LnUrlPayRequestData { @@ -746,12 +747,14 @@ dictionary RedeemOnchainFundsResponse { dictionary SendPaymentRequest { string bolt11; u64? amount_msat = null; + string? label = null; }; dictionary SendSpontaneousPaymentRequest { string node_id; u64 amount_msat; sequence? extra_tlvs = null; + string? label = null; }; dictionary SendPaymentResponse { diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs index c3ecb71cc..b7238cf2d 100644 --- a/libs/sdk-core/src/breez_services.rs +++ b/libs/sdk-core/src/breez_services.rs @@ -313,10 +313,10 @@ impl BreezServices { { Some(_) => Err(SendPaymentError::AlreadyPaid), None => { - self.persist_pending_payment(&parsed_invoice, amount_msat)?; + self.persist_pending_payment(&parsed_invoice, amount_msat, req.label.clone())?; let payment_res = self .node_api - .send_payment(parsed_invoice.bolt11.clone(), req.amount_msat) + .send_payment(parsed_invoice.bolt11.clone(), req.amount_msat, req.label) .map_err(Into::into) .await; let payment = self @@ -339,7 +339,12 @@ impl BreezServices { self.start_node().await?; let payment_res = self .node_api - .send_spontaneous_payment(req.node_id.clone(), req.amount_msat, req.extra_tlvs) + .send_spontaneous_payment( + req.node_id.clone(), + req.amount_msat, + req.extra_tlvs, + req.label, + ) .map_err(Into::into) .await; let payment = self @@ -372,6 +377,7 @@ impl BreezServices { let pay_req = SendPaymentRequest { bolt11: cb.pr.clone(), amount_msat: None, + label: req.payment_label, }; let invoice = parse_invoice(cb.pr.as_str())?; @@ -445,7 +451,7 @@ impl BreezServices { Ok(LnUrlPayResult::EndpointSuccess { data: LnUrlPaySuccessData { - payment_hash: details.payment_hash.clone(), + payment, success_action: maybe_sa_processed, }, }) @@ -1158,6 +1164,7 @@ impl BreezServices { &self, invoice: &LNInvoice, amount_msat: u64, + label: Option, ) -> Result<(), SendPaymentError> { self.persister.insert_or_update_payments( &[Payment { @@ -1172,7 +1179,7 @@ impl BreezServices { details: PaymentDetails::Ln { data: LnPaymentDetails { payment_hash: invoice.payment_hash.clone(), - label: String::new(), + label: label.unwrap_or_default(), destination_pubkey: invoice.payee_pubkey.clone(), payment_preimage: String::new(), keysend: false, diff --git a/libs/sdk-core/src/bridge_generated.io.rs b/libs/sdk-core/src/bridge_generated.io.rs index cf08f26d3..0f9c7c608 100644 --- a/libs/sdk-core/src/bridge_generated.io.rs +++ b/libs/sdk-core/src/bridge_generated.io.rs @@ -858,6 +858,7 @@ impl Wire2Api for wire_LnUrlPayRequest { data: self.data.wire2api(), amount_msat: self.amount_msat.wire2api(), comment: self.comment.wire2api(), + payment_label: self.payment_label.wire2api(), } } } @@ -1070,6 +1071,7 @@ impl Wire2Api for wire_SendPaymentRequest { SendPaymentRequest { bolt11: self.bolt11.wire2api(), amount_msat: self.amount_msat.wire2api(), + label: self.label.wire2api(), } } } @@ -1079,6 +1081,7 @@ impl Wire2Api for wire_SendSpontaneousPaymentRequ node_id: self.node_id.wire2api(), amount_msat: self.amount_msat.wire2api(), extra_tlvs: self.extra_tlvs.wire2api(), + label: self.label.wire2api(), } } } @@ -1223,6 +1226,7 @@ pub struct wire_LnUrlPayRequest { data: wire_LnUrlPayRequestData, amount_msat: u64, comment: *mut wire_uint_8_list, + payment_label: *mut wire_uint_8_list, } #[repr(C)] @@ -1385,6 +1389,7 @@ pub struct wire_SendOnchainRequest { pub struct wire_SendPaymentRequest { bolt11: *mut wire_uint_8_list, amount_msat: *mut u64, + label: *mut wire_uint_8_list, } #[repr(C)] @@ -1393,6 +1398,7 @@ pub struct wire_SendSpontaneousPaymentRequest { node_id: *mut wire_uint_8_list, amount_msat: u64, extra_tlvs: *mut wire_list_tlv_entry, + label: *mut wire_uint_8_list, } #[repr(C)] @@ -1627,6 +1633,7 @@ impl NewWithNullPtr for wire_LnUrlPayRequest { data: Default::default(), amount_msat: Default::default(), comment: core::ptr::null_mut(), + payment_label: core::ptr::null_mut(), } } } @@ -1989,6 +1996,7 @@ impl NewWithNullPtr for wire_SendPaymentRequest { Self { bolt11: core::ptr::null_mut(), amount_msat: core::ptr::null_mut(), + label: core::ptr::null_mut(), } } } @@ -2005,6 +2013,7 @@ impl NewWithNullPtr for wire_SendSpontaneousPaymentRequest { node_id: core::ptr::null_mut(), amount_msat: Default::default(), extra_tlvs: core::ptr::null_mut(), + label: core::ptr::null_mut(), } } } diff --git a/libs/sdk-core/src/bridge_generated.rs b/libs/sdk-core/src/bridge_generated.rs index e7b3fcef8..aff06860c 100644 --- a/libs/sdk-core/src/bridge_generated.rs +++ b/libs/sdk-core/src/bridge_generated.rs @@ -1510,7 +1510,7 @@ impl rust2dart::IntoIntoDart for LnUrlPayResult { impl support::IntoDart for LnUrlPaySuccessData { fn into_dart(self) -> support::DartAbi { vec![ - self.payment_hash.into_into_dart().into_dart(), + self.payment.into_into_dart().into_dart(), self.success_action.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 71ac5f584..64bfe65a4 100644 --- a/libs/sdk-core/src/greenlight/node_api.rs +++ b/libs/sdk-core/src/greenlight/node_api.rs @@ -1059,7 +1059,12 @@ impl NodeAPI for Greenlight { }) } - async fn send_payment(&self, bolt11: String, amount_msat: Option) -> NodeResult { + async fn send_payment( + &self, + bolt11: String, + amount_msat: Option, + label: Option, + ) -> NodeResult { let mut description = None; if !bolt11.is_empty() { let invoice = parse_invoice(&bolt11)?; @@ -1073,7 +1078,7 @@ impl NodeAPI for Greenlight { amount_msat: amount_msat.map(|amt| cln::Amount { msat: amt }), maxfeepercent: Some(self.sdk_config.maxfee_percent), retry_for: Some(self.sdk_config.payment_timeout_sec), - label: None, + label, maxdelay: None, riskfactor: None, localinvreqid: None, @@ -1097,15 +1102,16 @@ impl NodeAPI for Greenlight { node_id: String, amount_msat: u64, extra_tlvs: Option>, + label: Option, ) -> NodeResult { let mut client: node::ClnClient = self.get_node_client().await?; let request = cln::KeysendRequest { destination: hex::decode(node_id)?, amount_msat: Some(cln::Amount { msat: amount_msat }), - label: Some(format!( + label: label.or(Some(format!( "breez-{}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() - )), + ))), extratlvs: extra_tlvs.map(|tlvs| cln::TlvStream { entries: tlvs .into_iter() @@ -1997,7 +2003,7 @@ impl TryFrom for Payment { details: PaymentDetails::Ln { data: LnPaymentDetails { payment_hash: hex::encode(payment.payment_hash), - label: "".to_string(), + label: payment.label.unwrap_or_default(), destination_pubkey: payment.destination.map(hex::encode).unwrap_or_default(), payment_preimage: payment.preimage.map(hex::encode).unwrap_or_default(), keysend: payment.bolt11.is_none(), diff --git a/libs/sdk-core/src/lnurl/pay.rs b/libs/sdk-core/src/lnurl/pay.rs index 2a77bce61..c18baf504 100644 --- a/libs/sdk-core/src/lnurl/pay.rs +++ b/libs/sdk-core/src/lnurl/pay.rs @@ -121,7 +121,7 @@ fn validate_invoice(user_amount_msat: u64, bolt11: &str, network: Network) -> Ln pub(crate) mod model { use crate::lnurl::error::{LnUrlError, LnUrlResult}; use crate::lnurl::pay::{Aes256CbcDec, Aes256CbcEnc}; - use crate::{ensure_sdk, input_parser::*}; + use crate::{ensure_sdk, input_parser::*, Payment}; use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use anyhow::{anyhow, Result}; @@ -144,6 +144,7 @@ pub(crate) mod model { /// * `PayError` indicates that an error occurred while trying to pay the invoice from the LNURL endpoint. /// This includes the payment hash of the failed invoice and the failure reason. #[derive(Debug, Serialize, Deserialize)] + #[allow(clippy::large_enum_variant)] pub enum LnUrlPayResult { EndpointSuccess { data: LnUrlPaySuccessData }, EndpointError { data: LnUrlErrorData }, @@ -158,7 +159,7 @@ pub(crate) mod model { #[derive(Serialize, Deserialize, Debug)] pub struct LnUrlPaySuccessData { - pub payment_hash: String, + pub payment: Payment, pub success_action: Option, } @@ -875,8 +876,8 @@ mod tests { #[tokio::test] async fn test_lnurl_pay_no_success_action() -> Result<()> { - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); let inv = rand_invoice_with_description_hash(temp_desc)?; let user_amount_msat = inv.amount_milli_satoshis().unwrap(); @@ -895,6 +896,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await? { @@ -916,13 +918,13 @@ mod tests { } } - static COMMENT_LENGHT: u16 = 10; + static COMMENT_LENGTH: u16 = 10; #[tokio::test] async fn test_lnurl_pay_unsupported_success_action() -> Result<()> { let user_amount_msat = 11000; - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let _m = mock_lnurl_pay_callback_endpoint_unsupported_success_action(LnurlPayCallbackParams { pay_req: &pay_req, @@ -938,6 +940,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await; // An unsupported Success Action results in an error @@ -948,8 +951,8 @@ mod tests { #[tokio::test] async fn test_lnurl_pay_success_payment_hash() -> Result<()> { - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); let inv = rand_invoice_with_description_hash(temp_desc)?; let user_amount_msat = inv.amount_milli_satoshis().unwrap(); @@ -967,10 +970,11 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await? { - LnUrlPayResult::EndpointSuccess { data } => match data.payment_hash { + LnUrlPayResult::EndpointSuccess { data } => match data.payment.id { s if s == inv.payment_hash().to_hex() => Ok(()), _ => Err(anyhow!("Unexpected payment hash")), }, @@ -980,8 +984,8 @@ mod tests { #[tokio::test] async fn test_lnurl_pay_msg_success_action() -> Result<()> { - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); let inv = rand_invoice_with_description_hash(temp_desc)?; let user_amount_msat = inv.amount_milli_satoshis().unwrap(); @@ -999,6 +1003,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await? { @@ -1027,8 +1032,8 @@ mod tests { #[tokio::test] async fn test_lnurl_pay_msg_success_action_incorrect_amount() -> Result<()> { - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); let inv = rand_invoice_with_description_hash(temp_desc)?; let user_amount_msat = inv.amount_milli_satoshis().unwrap() + 1000; @@ -1045,7 +1050,8 @@ mod tests { .lnurl_pay(LnUrlPayRequest { data: pay_req, amount_msat: user_amount_msat, - comment: Some(comment) + comment: Some(comment), + payment_label: None, }) .await .is_err()); @@ -1055,8 +1061,8 @@ mod tests { #[tokio::test] async fn test_lnurl_pay_msg_success_action_error_from_endpoint() -> Result<()> { - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); let inv = rand_invoice_with_description_hash(temp_desc)?; let user_amount_msat = inv.amount_milli_satoshis().unwrap(); @@ -1075,6 +1081,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await; assert!(matches!(res, Ok(LnUrlPayResult::EndpointError { data: _ }))); @@ -1092,8 +1099,8 @@ mod tests { #[tokio::test] async fn test_lnurl_pay_url_success_action() -> Result<()> { - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); let inv = rand_invoice_with_description_hash(temp_desc)?; let user_amount_msat = inv.amount_milli_satoshis().unwrap(); @@ -1111,6 +1118,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await? { @@ -1159,8 +1167,8 @@ mod tests { // Generate preimage let preimage = sha256::Hash::hash(&rand_vec_u8(10)); - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); // The invoice (served by LNURL-pay endpoint, matching preimage and description hash) @@ -1195,6 +1203,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await? { @@ -1235,8 +1244,8 @@ mod tests { // Generate preimage let preimage = sha256::Hash::hash(&rand_vec_u8(10)); - let comment = rand_string(COMMENT_LENGHT as usize); - let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGHT); + let comment = rand_string(COMMENT_LENGTH as usize); + let pay_req = get_test_pay_req_data(0, 100_000, COMMENT_LENGTH); let temp_desc = pay_req.metadata_str.clone(); // The invoice (served by LNURL-pay endpoint, matching preimage and description hash) @@ -1278,6 +1287,7 @@ mod tests { data: pay_req, amount_msat: user_amount_msat, comment: Some(comment), + payment_label: None, }) .await? { diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs index 9d3717f57..dcb440053 100644 --- a/libs/sdk-core/src/models.rs +++ b/libs/sdk-core/src/models.rs @@ -877,6 +877,8 @@ pub struct SendPaymentRequest { pub bolt11: String, /// The amount to pay in millisatoshis. Should only be set when `bolt11` is a zero-amount invoice. pub amount_msat: Option, + /// The external label or identifier of the [Payment] + pub label: Option, } /// Represents a TLV entry for a keysend payment. @@ -897,6 +899,8 @@ pub struct SendSpontaneousPaymentRequest { pub amount_msat: u64, // Optional extra TLVs pub extra_tlvs: Option>, + /// The external label or identifier of the [Payment] + pub label: Option, } /// Represents a send payment response. @@ -1566,6 +1570,8 @@ pub struct LnUrlPayRequest { pub amount_msat: u64, /// An optional comment for this payment pub comment: Option, + /// The external label or identifier of the [Payment] + pub payment_label: Option, } /// [LnUrlCallbackStatus] specific to LNURL-withdraw, where the success case contains the invoice. diff --git a/libs/sdk-core/src/node_api.rs b/libs/sdk-core/src/node_api.rs index 251bcba75..0aa102ba8 100644 --- a/libs/sdk-core/src/node_api.rs +++ b/libs/sdk-core/src/node_api.rs @@ -85,12 +85,18 @@ pub trait NodeAPI: Send + Sync { balance_changed: bool, ) -> NodeResult; /// As per the `pb::PayRequest` docs, `amount_msat` is only needed when the invoice doesn't specify an amount - async fn send_payment(&self, bolt11: String, amount_msat: Option) -> NodeResult; + async fn send_payment( + &self, + bolt11: String, + amount_msat: Option, + label: Option, + ) -> NodeResult; async fn send_spontaneous_payment( &self, node_id: String, amount_msat: u64, extra_tlvs: Option>, + label: Option, ) -> NodeResult; async fn start(&self) -> NodeResult; diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs index 2fe502a75..6fc0ab97c 100644 --- a/libs/sdk-core/src/test_utils.rs +++ b/libs/sdk-core/src/test_utils.rs @@ -372,7 +372,12 @@ impl NodeAPI for MockNodeAPI { Err(NodeError::Generic(anyhow!("Not implemented"))) } - async fn send_payment(&self, bolt11: String, _amount_msat: Option) -> NodeResult { + async fn send_payment( + &self, + bolt11: String, + _amount_msat: Option, + _label: Option, + ) -> NodeResult { let payment = self.add_dummy_payment_for(bolt11, None, None).await?; Ok(payment) } @@ -382,6 +387,7 @@ impl NodeAPI for MockNodeAPI { _node_id: String, _amount_msat: u64, _extra_tlvs: Option>, + _label: Option, ) -> NodeResult { let payment = self.add_dummy_payment_rand().await?; Ok(payment) diff --git a/libs/sdk-flutter/ios/Classes/bridge_generated.h b/libs/sdk-flutter/ios/Classes/bridge_generated.h index 2e4d72344..9a7da71f3 100644 --- a/libs/sdk-flutter/ios/Classes/bridge_generated.h +++ b/libs/sdk-flutter/ios/Classes/bridge_generated.h @@ -111,6 +111,7 @@ typedef struct wire_ListPaymentsRequest { typedef struct wire_SendPaymentRequest { struct wire_uint_8_list *bolt11; uint64_t *amount_msat; + struct wire_uint_8_list *label; } wire_SendPaymentRequest; typedef struct wire_TlvEntry { @@ -127,6 +128,7 @@ typedef struct wire_SendSpontaneousPaymentRequest { struct wire_uint_8_list *node_id; uint64_t amount_msat; struct wire_list_tlv_entry *extra_tlvs; + struct wire_uint_8_list *label; } wire_SendSpontaneousPaymentRequest; typedef struct wire_OpeningFeeParams { @@ -164,6 +166,7 @@ typedef struct wire_LnUrlPayRequest { struct wire_LnUrlPayRequestData data; uint64_t amount_msat; struct wire_uint_8_list *comment; + struct wire_uint_8_list *payment_label; } wire_LnUrlPayRequest; typedef struct wire_LnUrlWithdrawRequestData { diff --git a/libs/sdk-flutter/lib/bridge_generated.dart b/libs/sdk-flutter/lib/bridge_generated.dart index 42f592637..94a3cec76 100644 --- a/libs/sdk-flutter/lib/bridge_generated.dart +++ b/libs/sdk-flutter/lib/bridge_generated.dart @@ -890,10 +890,14 @@ class LnUrlPayRequest { /// An optional comment for this payment final String? comment; + /// The external label or identifier of the [Payment] + final String? paymentLabel; + const LnUrlPayRequest({ required this.data, required this.amountMsat, this.comment, + this.paymentLabel, }); } @@ -968,11 +972,11 @@ sealed class LnUrlPayResult with _$LnUrlPayResult { } class LnUrlPaySuccessData { - final String paymentHash; + final Payment payment; final SuccessActionProcessed? successAction; const LnUrlPaySuccessData({ - required this.paymentHash, + required this.payment, this.successAction, }); } @@ -1787,9 +1791,13 @@ class SendPaymentRequest { /// The amount to pay in millisatoshis. Should only be set when `bolt11` is a zero-amount invoice. final int? amountMsat; + /// The external label or identifier of the [Payment] + final String? label; + const SendPaymentRequest({ required this.bolt11, this.amountMsat, + this.label, }); } @@ -1811,10 +1819,14 @@ class SendSpontaneousPaymentRequest { final int amountMsat; final List? extraTlvs; + /// The external label or identifier of the [Payment] + final String? label; + const SendSpontaneousPaymentRequest({ required this.nodeId, required this.amountMsat, this.extraTlvs, + this.label, }); } @@ -3650,7 +3662,7 @@ class BreezSdkCoreImpl implements BreezSdkCore { final arr = raw as List; if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); return LnUrlPaySuccessData( - paymentHash: _wire2api_String(arr[0]), + payment: _wire2api_payment(arr[0]), successAction: _wire2api_opt_box_autoadd_success_action_processed(arr[1]), ); } @@ -4870,6 +4882,7 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { _api_fill_to_wire_ln_url_pay_request_data(apiObj.data, wireObj.data); wireObj.amount_msat = api2wire_u64(apiObj.amountMsat); wireObj.comment = api2wire_opt_String(apiObj.comment); + wireObj.payment_label = api2wire_opt_String(apiObj.paymentLabel); } void _api_fill_to_wire_ln_url_pay_request_data( @@ -5027,6 +5040,7 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { void _api_fill_to_wire_send_payment_request(SendPaymentRequest apiObj, wire_SendPaymentRequest wireObj) { wireObj.bolt11 = api2wire_String(apiObj.bolt11); wireObj.amount_msat = api2wire_opt_box_autoadd_u64(apiObj.amountMsat); + wireObj.label = api2wire_opt_String(apiObj.label); } void _api_fill_to_wire_send_spontaneous_payment_request( @@ -5034,6 +5048,7 @@ class BreezSdkCorePlatform extends FlutterRustBridgeBase { wireObj.node_id = api2wire_String(apiObj.nodeId); wireObj.amount_msat = api2wire_u64(apiObj.amountMsat); wireObj.extra_tlvs = api2wire_opt_list_tlv_entry(apiObj.extraTlvs); + wireObj.label = api2wire_opt_String(apiObj.label); } void _api_fill_to_wire_sign_message_request(SignMessageRequest apiObj, wire_SignMessageRequest wireObj) { @@ -6570,6 +6585,8 @@ final class wire_SendPaymentRequest extends ffi.Struct { external ffi.Pointer bolt11; external ffi.Pointer amount_msat; + + external ffi.Pointer label; } final class wire_TlvEntry extends ffi.Struct { @@ -6593,6 +6610,8 @@ final class wire_SendSpontaneousPaymentRequest extends ffi.Struct { external int amount_msat; external ffi.Pointer extra_tlvs; + + external ffi.Pointer label; } final class wire_OpeningFeeParams extends ffi.Struct { @@ -6661,6 +6680,8 @@ final class wire_LnUrlPayRequest extends ffi.Struct { external int amount_msat; external ffi.Pointer comment; + + external ffi.Pointer payment_label; } final class wire_LnUrlWithdrawRequestData extends ffi.Struct { 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 c42011879..88906d411 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 @@ -1155,10 +1155,12 @@ fun asLnUrlPayRequest(lnUrlPayRequest: ReadableMap): LnUrlPayRequest? { val data = lnUrlPayRequest.getMap("data")?.let { asLnUrlPayRequestData(it) }!! val amountMsat = lnUrlPayRequest.getDouble("amountMsat").toULong() val comment = if (hasNonNullKey(lnUrlPayRequest, "comment")) lnUrlPayRequest.getString("comment") else null + val paymentLabel = if (hasNonNullKey(lnUrlPayRequest, "paymentLabel")) lnUrlPayRequest.getString("paymentLabel") else null return LnUrlPayRequest( data, amountMsat, comment, + paymentLabel, ) } @@ -1167,6 +1169,7 @@ fun readableMapOf(lnUrlPayRequest: LnUrlPayRequest): ReadableMap { "data" to readableMapOf(lnUrlPayRequest.data), "amountMsat" to lnUrlPayRequest.amountMsat, "comment" to lnUrlPayRequest.comment, + "paymentLabel" to lnUrlPayRequest.paymentLabel, ) } @@ -1248,7 +1251,7 @@ fun asLnUrlPaySuccessData(lnUrlPaySuccessData: ReadableMap): LnUrlPaySuccessData if (!validateMandatoryFields( lnUrlPaySuccessData, arrayOf( - "paymentHash", + "payment", ), ) ) { @@ -1262,17 +1265,17 @@ fun asLnUrlPaySuccessData(lnUrlPaySuccessData: ReadableMap): LnUrlPaySuccessData } else { null } - val paymentHash = lnUrlPaySuccessData.getString("paymentHash")!! + val payment = lnUrlPaySuccessData.getMap("payment")?.let { asPayment(it) }!! return LnUrlPaySuccessData( successAction, - paymentHash, + payment, ) } fun readableMapOf(lnUrlPaySuccessData: LnUrlPaySuccessData): ReadableMap { return readableMapOf( "successAction" to lnUrlPaySuccessData.successAction?.let { readableMapOf(it) }, - "paymentHash" to lnUrlPaySuccessData.paymentHash, + "payment" to readableMapOf(lnUrlPaySuccessData.payment), ) } @@ -3224,9 +3227,11 @@ fun asSendPaymentRequest(sendPaymentRequest: ReadableMap): SendPaymentRequest? { } val bolt11 = sendPaymentRequest.getString("bolt11")!! val amountMsat = if (hasNonNullKey(sendPaymentRequest, "amountMsat")) sendPaymentRequest.getDouble("amountMsat").toULong() else null + val label = if (hasNonNullKey(sendPaymentRequest, "label")) sendPaymentRequest.getString("label") else null return SendPaymentRequest( bolt11, amountMsat, + label, ) } @@ -3234,6 +3239,7 @@ fun readableMapOf(sendPaymentRequest: SendPaymentRequest): ReadableMap { return readableMapOf( "bolt11" to sendPaymentRequest.bolt11, "amountMsat" to sendPaymentRequest.amountMsat, + "label" to sendPaymentRequest.label, ) } @@ -3306,10 +3312,12 @@ fun asSendSpontaneousPaymentRequest(sendSpontaneousPaymentRequest: ReadableMap): } else { null } + val label = if (hasNonNullKey(sendSpontaneousPaymentRequest, "label")) sendSpontaneousPaymentRequest.getString("label") else null return SendSpontaneousPaymentRequest( nodeId, amountMsat, extraTlvs, + label, ) } @@ -3318,6 +3326,7 @@ fun readableMapOf(sendSpontaneousPaymentRequest: SendSpontaneousPaymentRequest): "nodeId" to sendSpontaneousPaymentRequest.nodeId, "amountMsat" to sendSpontaneousPaymentRequest.amountMsat, "extraTlvs" to sendSpontaneousPaymentRequest.extraTlvs?.let { readableArrayOf(it) }, + "label" to sendSpontaneousPaymentRequest.label, ) } diff --git a/libs/sdk-react-native/ios/BreezSDKMapper.swift b/libs/sdk-react-native/ios/BreezSDKMapper.swift index 690ca118d..cbc424406 100644 --- a/libs/sdk-react-native/ios/BreezSDKMapper.swift +++ b/libs/sdk-react-native/ios/BreezSDKMapper.swift @@ -1278,11 +1278,19 @@ enum BreezSDKMapper { } comment = commentTmp } + var paymentLabel: String? + if hasNonNilKey(data: lnUrlPayRequest, key: "paymentLabel") { + guard let paymentLabelTmp = lnUrlPayRequest["paymentLabel"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "paymentLabel")) + } + paymentLabel = paymentLabelTmp + } return LnUrlPayRequest( data: data, amountMsat: amountMsat, - comment: comment + comment: comment, + paymentLabel: paymentLabel ) } @@ -1291,6 +1299,7 @@ enum BreezSDKMapper { "data": dictionaryOf(lnUrlPayRequestData: lnUrlPayRequest.data), "amountMsat": lnUrlPayRequest.amountMsat, "comment": lnUrlPayRequest.comment == nil ? nil : lnUrlPayRequest.comment, + "paymentLabel": lnUrlPayRequest.paymentLabel == nil ? nil : lnUrlPayRequest.paymentLabel, ] } @@ -1398,20 +1407,21 @@ enum BreezSDKMapper { successAction = try asSuccessActionProcessed(successActionProcessed: successActionTmp) } - guard let paymentHash = lnUrlPaySuccessData["paymentHash"] as? String else { - throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentHash", typeName: "LnUrlPaySuccessData")) + guard let paymentTmp = lnUrlPaySuccessData["payment"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "payment", typeName: "LnUrlPaySuccessData")) } + let payment = try asPayment(payment: paymentTmp) return LnUrlPaySuccessData( successAction: successAction, - paymentHash: paymentHash + payment: payment ) } static func dictionaryOf(lnUrlPaySuccessData: LnUrlPaySuccessData) -> [String: Any?] { return [ "successAction": lnUrlPaySuccessData.successAction == nil ? nil : dictionaryOf(successActionProcessed: lnUrlPaySuccessData.successAction!), - "paymentHash": lnUrlPaySuccessData.paymentHash, + "payment": dictionaryOf(payment: lnUrlPaySuccessData.payment), ] } @@ -3519,10 +3529,18 @@ enum BreezSDKMapper { } amountMsat = amountMsatTmp } + var label: String? + if hasNonNilKey(data: sendPaymentRequest, key: "label") { + guard let labelTmp = sendPaymentRequest["label"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "label")) + } + label = labelTmp + } return SendPaymentRequest( bolt11: bolt11, - amountMsat: amountMsat + amountMsat: amountMsat, + label: label ) } @@ -3530,6 +3548,7 @@ enum BreezSDKMapper { return [ "bolt11": sendPaymentRequest.bolt11, "amountMsat": sendPaymentRequest.amountMsat == nil ? nil : sendPaymentRequest.amountMsat, + "label": sendPaymentRequest.label == nil ? nil : sendPaymentRequest.label, ] } @@ -3595,10 +3614,19 @@ enum BreezSDKMapper { extraTlvs = try asTlvEntryList(arr: extraTlvsTmp) } + var label: String? + if hasNonNilKey(data: sendSpontaneousPaymentRequest, key: "label") { + guard let labelTmp = sendSpontaneousPaymentRequest["label"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "label")) + } + label = labelTmp + } + return SendSpontaneousPaymentRequest( nodeId: nodeId, amountMsat: amountMsat, - extraTlvs: extraTlvs + extraTlvs: extraTlvs, + label: label ) } @@ -3607,6 +3635,7 @@ enum BreezSDKMapper { "nodeId": sendSpontaneousPaymentRequest.nodeId, "amountMsat": sendSpontaneousPaymentRequest.amountMsat, "extraTlvs": sendSpontaneousPaymentRequest.extraTlvs == nil ? nil : arrayOf(tlvEntryList: sendSpontaneousPaymentRequest.extraTlvs!), + "label": sendSpontaneousPaymentRequest.label == nil ? nil : sendSpontaneousPaymentRequest.label, ] } diff --git a/libs/sdk-react-native/src/index.ts b/libs/sdk-react-native/src/index.ts index 40f3e8858..b5d5ddcde 100644 --- a/libs/sdk-react-native/src/index.ts +++ b/libs/sdk-react-native/src/index.ts @@ -186,6 +186,7 @@ export type LnUrlPayRequest = { data: LnUrlPayRequestData amountMsat: number comment?: string + paymentLabel?: string } export type LnUrlPayRequestData = { @@ -202,7 +203,7 @@ export type LnUrlPayRequestData = { export type LnUrlPaySuccessData = { successAction?: SuccessActionProcessed - paymentHash: string + payment: Payment } export type LnUrlWithdrawRequest = { @@ -488,6 +489,7 @@ export type SendOnchainResponse = { export type SendPaymentRequest = { bolt11: string amountMsat?: number + label?: string } export type SendPaymentResponse = { @@ -498,6 +500,7 @@ export type SendSpontaneousPaymentRequest = { nodeId: string amountMsat: number extraTlvs?: TlvEntry[] + label?: string } export type ServiceHealthCheckResponse = { diff --git a/tools/sdk-cli/src/command_handlers.rs b/tools/sdk-cli/src/command_handlers.rs index 07ef89477..f81c24a9f 100644 --- a/tools/sdk-cli/src/command_handlers.rs +++ b/tools/sdk-cli/src/command_handlers.rs @@ -242,11 +242,13 @@ pub(crate) async fn handle_command( Commands::SendPayment { bolt11, amount_msat, + label, } => { let payment = sdk()? .send_payment(SendPaymentRequest { bolt11, amount_msat, + label, }) .await?; serde_json::to_string_pretty(&payment).map_err(|e| e.into()) @@ -254,12 +256,14 @@ pub(crate) async fn handle_command( Commands::SendSpontaneousPayment { node_id, amount_msat, + label, } => { let response = sdk()? .send_spontaneous_payment(SendSpontaneousPaymentRequest { node_id, amount_msat, extra_tlvs: None, + label, }) .await?; serde_json::to_string_pretty(&response.payment).map_err(|e| e.into()) @@ -460,7 +464,7 @@ pub(crate) async fn handle_command( let res = sdk()?.check_message(req).await?; Ok(format!("Message was signed by node: {}", res.is_valid)) } - Commands::LnurlPay { lnurl } => match parse(&lnurl).await? { + Commands::LnurlPay { lnurl, label } => match parse(&lnurl).await? { LnUrlPay { data: pd } => { let prompt = format!( "Amount to pay in millisatoshi (min {} msat, max {} msat: ", @@ -473,6 +477,7 @@ pub(crate) async fn handle_command( data: pd, amount_msat: amount_msat.parse::()?, comment: None, + payment_label: label, }) .await?; //show_results(pay_res); diff --git a/tools/sdk-cli/src/commands.rs b/tools/sdk-cli/src/commands.rs index bc25ef416..d9a4fa20f 100644 --- a/tools/sdk-cli/src/commands.rs +++ b/tools/sdk-cli/src/commands.rs @@ -46,12 +46,20 @@ pub(crate) enum Commands { #[clap(name = "amount_msat", short = 'a', long = "amt")] amount_msat: Option, + + /// The external label or identifier of the payment + #[clap(name = "label", short = 'l', long = "label")] + label: Option, }, /// [pay] Send a spontaneous (keysend) payment SendSpontaneousPayment { node_id: String, amount_msat: u64, + + /// The external label or identifier of the payment + #[clap(name = "label", short = 'l', long = "label")] + label: Option, }, /// [pay] Generate a bolt11 invoice @@ -81,6 +89,10 @@ pub(crate) enum Commands { /// [lnurl] Pay using lnurl pay LnurlPay { lnurl: String, + + /// The external label or identifier of the payment + #[clap(name = "label", short = 'l', long = "label")] + label: Option, }, /// [lnurl] Withdraw using lnurl withdraw