diff --git a/cli/src/commands.rs b/cli/src/commands.rs index f119f8c..ec341e7 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -405,24 +405,9 @@ pub(crate) async fn handle_command( drain, delay, } => { - let destination = match (invoice, offer, address) { - (Some(invoice), None, None) => Ok(invoice), - (None, Some(offer), None) => match amount_sat { - Some(_) => Ok(offer), - None => Err(anyhow!( - "Must specify an amount for a BOLT12 offer." - )) - }, - (None, None, Some(address)) => Ok(address), - (Some(_), _, Some(_)) => { - Err(anyhow::anyhow!( - "Cannot specify both invoice and address at the same time." - )) - } - _ => Err(anyhow!( - "Must specify either a BOLT11 invoice, a BOLT12 offer or a direct/BIP21 address." - )) - }?; + let destination = invoice.or(offer.or(address)).ok_or(anyhow!( + "Must specify either a BOLT11 invoice, a BOLT12 offer or a direct/BIP21 address." + ))?; let amount = match (asset_id, amount, amount_sat, drain.unwrap_or(false)) { (Some(asset_id), Some(receiver_amount), _, _) => Some(PayAmount::Asset { asset_id, 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 3ca6eec..b3680e2 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 @@ -285,6 +285,26 @@ typedef struct wire_cst_ln_url_pay_request_data { struct wire_cst_list_prim_u_8_strict *ln_address; } wire_cst_ln_url_pay_request_data; +typedef struct wire_cst_PayAmount_Bitcoin { + uint64_t receiver_amount_sat; +} wire_cst_PayAmount_Bitcoin; + +typedef struct wire_cst_PayAmount_Asset { + struct wire_cst_list_prim_u_8_strict *asset_id; + double receiver_amount; + bool *estimate_asset_fees; +} wire_cst_PayAmount_Asset; + +typedef union PayAmountKind { + struct wire_cst_PayAmount_Bitcoin Bitcoin; + struct wire_cst_PayAmount_Asset Asset; +} PayAmountKind; + +typedef struct wire_cst_pay_amount { + int32_t tag; + union PayAmountKind kind; +} wire_cst_pay_amount; + 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; @@ -328,6 +348,7 @@ 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_pay_amount amount; struct wire_cst_list_prim_u_8_strict *comment; struct wire_cst_success_action *success_action; } wire_cst_prepare_ln_url_pay_response; @@ -366,26 +387,6 @@ typedef struct wire_cst_prepare_buy_bitcoin_request { uint64_t amount_sat; } wire_cst_prepare_buy_bitcoin_request; -typedef struct wire_cst_PayAmount_Bitcoin { - uint64_t receiver_amount_sat; -} wire_cst_PayAmount_Bitcoin; - -typedef struct wire_cst_PayAmount_Asset { - struct wire_cst_list_prim_u_8_strict *asset_id; - double receiver_amount; - bool *estimate_asset_fees; -} wire_cst_PayAmount_Asset; - -typedef union PayAmountKind { - struct wire_cst_PayAmount_Bitcoin Bitcoin; - struct wire_cst_PayAmount_Asset Asset; -} PayAmountKind; - -typedef struct wire_cst_pay_amount { - int32_t tag; - union PayAmountKind kind; -} wire_cst_pay_amount; - typedef struct wire_cst_prepare_ln_url_pay_request { struct wire_cst_ln_url_pay_request_data data; struct wire_cst_pay_amount amount; @@ -461,6 +462,7 @@ typedef struct wire_cst_restore_request { typedef struct wire_cst_prepare_send_response { struct wire_cst_send_destination destination; + struct wire_cst_pay_amount *amount; uint64_t *fees_sat; double *estimated_asset_fees; } wire_cst_prepare_send_response; diff --git a/lib/bindings/src/breez_sdk_liquid.udl b/lib/bindings/src/breez_sdk_liquid.udl index d754b54..a2700de 100644 --- a/lib/bindings/src/breez_sdk_liquid.udl +++ b/lib/bindings/src/breez_sdk_liquid.udl @@ -423,6 +423,7 @@ dictionary PrepareLnUrlPayResponse { SendDestination destination; u64 fees_sat; LnUrlPayRequestData data; + PayAmount amount; string? comment = null; SuccessAction? success_action = null; }; @@ -445,6 +446,7 @@ interface SendDestination { dictionary PrepareSendResponse { SendDestination destination; + PayAmount? amount; u64? fees_sat; f64? estimated_asset_fees; }; diff --git a/lib/core/src/frb_generated.rs b/lib/core/src/frb_generated.rs index 55741a4..ec8e308 100644 --- a/lib/core/src/frb_generated.rs +++ b/lib/core/src/frb_generated.rs @@ -4297,6 +4297,7 @@ impl SseDecode for crate::model::PrepareLnUrlPayResponse { let mut var_destination = ::sse_decode(deserializer); let mut var_feesSat = ::sse_decode(deserializer); let mut var_data = ::sse_decode(deserializer); + let mut var_amount = ::sse_decode(deserializer); let mut var_comment = >::sse_decode(deserializer); let mut var_successAction = >::sse_decode(deserializer); @@ -4304,6 +4305,7 @@ impl SseDecode for crate::model::PrepareLnUrlPayResponse { destination: var_destination, fees_sat: var_feesSat, data: var_data, + amount: var_amount, comment: var_comment, success_action: var_successAction, }; @@ -4412,10 +4414,12 @@ impl SseDecode for crate::model::PrepareSendResponse { // 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_destination = ::sse_decode(deserializer); + let mut var_amount = >::sse_decode(deserializer); let mut var_feesSat = >::sse_decode(deserializer); let mut var_estimatedAssetFees = >::sse_decode(deserializer); return crate::model::PrepareSendResponse { destination: var_destination, + amount: var_amount, fees_sat: var_feesSat, estimated_asset_fees: var_estimatedAssetFees, }; @@ -6745,6 +6749,7 @@ 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.amount.into_into_dart().into_dart(), self.comment.into_into_dart().into_dart(), self.success_action.into_into_dart().into_dart(), ] @@ -6921,6 +6926,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareSendResponse { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ self.destination.into_into_dart().into_dart(), + self.amount.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(), self.estimated_asset_fees.into_into_dart().into_dart(), ] @@ -9085,6 +9091,7 @@ impl SseEncode for crate::model::PrepareLnUrlPayResponse { ::sse_encode(self.destination, serializer); ::sse_encode(self.fees_sat, serializer); ::sse_encode(self.data, serializer); + ::sse_encode(self.amount, serializer); >::sse_encode(self.comment, serializer); >::sse_encode(self.success_action, serializer); } @@ -9157,6 +9164,7 @@ impl SseEncode for crate::model::PrepareSendResponse { // 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.destination, serializer); + >::sse_encode(self.amount, serializer); >::sse_encode(self.fees_sat, serializer); >::sse_encode(self.estimated_asset_fees, serializer); } @@ -11324,6 +11332,7 @@ mod io { destination: self.destination.cst_decode(), fees_sat: self.fees_sat.cst_decode(), data: self.data.cst_decode(), + amount: self.amount.cst_decode(), comment: self.comment.cst_decode(), success_action: self.success_action.cst_decode(), } @@ -11404,6 +11413,7 @@ mod io { fn cst_decode(self) -> crate::model::PrepareSendResponse { crate::model::PrepareSendResponse { destination: self.destination.cst_decode(), + amount: self.amount.cst_decode(), fees_sat: self.fees_sat.cst_decode(), estimated_asset_fees: self.estimated_asset_fees.cst_decode(), } @@ -12696,6 +12706,7 @@ mod io { destination: Default::default(), fees_sat: Default::default(), data: Default::default(), + amount: Default::default(), comment: core::ptr::null_mut(), success_action: core::ptr::null_mut(), } @@ -12808,6 +12819,7 @@ mod io { fn new_with_null_ptr() -> Self { Self { destination: Default::default(), + amount: core::ptr::null_mut(), fees_sat: core::ptr::null_mut(), estimated_asset_fees: core::ptr::null_mut(), } @@ -15213,6 +15225,7 @@ mod io { destination: wire_cst_send_destination, fees_sat: u64, data: wire_cst_ln_url_pay_request_data, + amount: wire_cst_pay_amount, comment: *mut wire_cst_list_prim_u_8_strict, success_action: *mut wire_cst_success_action, } @@ -15269,6 +15282,7 @@ mod io { #[derive(Clone, Copy)] pub struct wire_cst_prepare_send_response { destination: wire_cst_send_destination, + amount: *mut wire_cst_pay_amount, fees_sat: *mut u64, estimated_asset_fees: *mut f64, } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 45debf5..8bedb72 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -729,6 +729,8 @@ pub enum SendDestination { #[derive(Debug, Serialize, Clone)] pub struct PrepareSendResponse { pub destination: SendDestination, + /// The optional amount to be sent in either Bitcoin or another asset + pub amount: Option, /// The optional estimated fee in satoshi. Is set when there is Bitcoin available /// to pay fees. When not set, there are asset fees available to pay fees. pub fees_sat: Option, @@ -2296,6 +2298,8 @@ pub struct PrepareLnUrlPayResponse { pub fees_sat: u64, /// The [LnUrlPayRequestData] returned by [parse] pub data: LnUrlPayRequestData, + /// The amount to send + pub amount: PayAmount, /// An optional comment for this payment pub comment: Option, /// The unprocessed LUD-09 success action. This will be processed and decrypted if diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index bd65111..ac8ccee 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -1219,6 +1219,7 @@ impl LiquidSdk { /// # Returns /// Returns a [PrepareSendResponse] containing: /// * `destination` - the parsed destination, of type [SendDestination] + /// * `amount` - the optional [PayAmount] to be sent in either Bitcoin or another asset /// * `fees_sat` - the optional estimated fee in satoshi. Is set when there is Bitcoin /// available to pay fees. When not set, there are asset fees available to pay fees. /// * `estimated_asset_fees` - the optional estimated fee in the asset. Is set when @@ -1399,76 +1400,105 @@ impl LiquidSdk { .map(|(address, _)| address); asset_id = self.config.lbtc_asset_id(); estimated_asset_fees = None; - (receiver_amount_sat, fees_sat, payment_destination) = - match (mrh_address.clone(), req.amount.clone()) { - (Some(lbtc_address), Some(PayAmount::Drain)) => { - // The BOLT11 invoice has an MRH and it is requested that the wallet balance is to be drained, - // therefore we use the MRH address and drain the balance (overpaying the invoice if neccessary) - let drain_fees_sat = self - .estimate_drain_tx_fee(None, Some(&lbtc_address)) - .await?; - let drain_amount_sat = - get_info_res.wallet_info.balance_sat - drain_fees_sat; - let payment_destination = SendDestination::LiquidAddress { - address_data: LiquidAddressData { - address: lbtc_address, - asset_id: Some(asset_id.clone()), - amount: None, - amount_sat: Some(drain_amount_sat), - network: self.config.network.into(), - label: None, - message: None, - }, - bip353_address: None, - }; - (drain_amount_sat, Some(drain_fees_sat), payment_destination) - } - (Some(lbtc_address), _) => { - // The BOLT11 invoice has an MRH but no drain is requested, - // so we calculate the fees of a direct Liquid transaction - let fees_sat = self - .estimate_onchain_tx_or_drain_tx_fee( - invoice_amount_sat, - &lbtc_address, - &asset_id, - ) - .await?; - ( + (receiver_amount_sat, fees_sat) = match (mrh_address.clone(), req.amount.clone()) { + (Some(lbtc_address), Some(PayAmount::Drain)) => { + // The BOLT11 invoice has an MRH and it is requested that the + // wallet balance is to be drained, so we calculate the fees of + // a direct Liquid drain transaction + let drain_fees_sat = self + .estimate_drain_tx_fee(None, Some(&lbtc_address)) + .await?; + let drain_amount_sat = + get_info_res.wallet_info.balance_sat - drain_fees_sat; + (drain_amount_sat, Some(drain_fees_sat)) + } + (Some(lbtc_address), _) => { + // The BOLT11 invoice has an MRH but no drain is requested, + // so we calculate the fees of a direct Liquid transaction + let fees_sat = self + .estimate_onchain_tx_or_drain_tx_fee( invoice_amount_sat, - Some(fees_sat), - SendDestination::Bolt11 { - invoice, - bip353_address: None, - }, + &lbtc_address, + &asset_id, ) - } - (None, _) => { - // The BOLT11 invoice has no MRH, so we calculate the fees using a swap - let boltz_fees_total = lbtc_pair.fees.total(invoice_amount_sat); - let user_lockup_amount_sat = invoice_amount_sat + boltz_fees_total; - let lockup_fees_sat = self - .estimate_lockup_tx_or_drain_tx_fee(user_lockup_amount_sat) - .await?; - let fees_sat = boltz_fees_total + lockup_fees_sat; - ( - invoice_amount_sat, - Some(fees_sat), - SendDestination::Bolt11 { - invoice, - bip353_address: None, - }, - ) - } - }; + .await?; + (invoice_amount_sat, Some(fees_sat)) + } + (None, _) => { + // The BOLT11 invoice has no MRH, so we calculate the fees using a swap + let boltz_fees_total = lbtc_pair.fees.total(invoice_amount_sat); + let user_lockup_amount_sat = invoice_amount_sat + boltz_fees_total; + let lockup_fees_sat = self + .estimate_lockup_tx_or_drain_tx_fee(user_lockup_amount_sat) + .await?; + let fees_sat = boltz_fees_total + lockup_fees_sat; + (invoice_amount_sat, Some(fees_sat)) + } + }; + + payment_destination = SendDestination::Bolt11 { + invoice, + bip353_address: None, + }; } Ok(InputType::Bolt12Offer { offer, bip353_address, }) => { - receiver_amount_sat = match req.amount { + asset_id = self.config.lbtc_asset_id(); + estimated_asset_fees = None; + (receiver_amount_sat, fees_sat) = match req.amount { + Some(PayAmount::Drain) => { + ensure_sdk!( + get_info_res.wallet_info.pending_receive_sat == 0 + && get_info_res.wallet_info.pending_send_sat == 0, + PaymentError::Generic { + err: "Cannot drain while there are pending payments".to_string(), + } + ); + let lbtc_pair = self + .swapper + .get_submarine_pairs() + .await? + .ok_or(PaymentError::PairsNotFound)?; + let drain_fees_sat = self.estimate_drain_tx_fee(None, None).await?; + let drain_amount_sat = + get_info_res.wallet_info.balance_sat - drain_fees_sat; + // Get the inverse receiver amount by calculating a dummy amount then increment up to the drain amount + let dummy_fees_sat = lbtc_pair.fees.total(drain_amount_sat); + let dummy_amount_sat = drain_amount_sat - dummy_fees_sat; + let receiver_amount_sat = + utils::increment_receiver_amount_up_to_drain_amount( + dummy_amount_sat, + &lbtc_pair, + drain_amount_sat, + ); + lbtc_pair.limits.within(receiver_amount_sat)?; + // Validate if we can actually drain the wallet with a swap + let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat); + ensure_sdk!( + receiver_amount_sat + boltz_fees_total == drain_amount_sat, + PaymentError::Generic { + err: "Cannot drain without leaving a remainder".to_string(), + } + ); + let fees_sat = Some(boltz_fees_total + drain_fees_sat); + info!("Drain amount: {receiver_amount_sat} sat"); + Ok((receiver_amount_sat, fees_sat)) + } Some(PayAmount::Bitcoin { - receiver_amount_sat: amount_sat, - }) => Ok(amount_sat), + receiver_amount_sat, + }) => { + let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat).await?; + let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat); + let lockup_fees_sat = self + .estimate_lockup_tx_or_drain_tx_fee( + receiver_amount_sat + boltz_fees_total, + ) + .await?; + let fees_sat = Some(boltz_fees_total + lockup_fees_sat); + Ok((receiver_amount_sat, fees_sat)) + } _ => Err(PaymentError::amount_missing( "Expected PayAmount of type Receiver when processing a Bolt12 offer", )), @@ -1482,16 +1512,6 @@ impl LiquidSdk { ); } - let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat).await?; - - let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat); - let lockup_fees_sat = self - .estimate_lockup_tx_or_drain_tx_fee(receiver_amount_sat + boltz_fees_total) - .await?; - asset_id = self.config.lbtc_asset_id(); - fees_sat = Some(boltz_fees_total + lockup_fees_sat); - estimated_asset_fees = None; - payment_destination = SendDestination::Bolt12 { offer, receiver_amount_sat, @@ -1514,6 +1534,7 @@ impl LiquidSdk { destination: payment_destination, fees_sat, estimated_asset_fees, + amount: req.amount.clone(), }) } @@ -1548,8 +1569,10 @@ impl LiquidSdk { let PrepareSendResponse { fees_sat, destination: payment_destination, + amount, .. } = &req.prepare_response; + let is_drain = matches!(amount, Some(PayAmount::Drain)); match payment_destination { SendDestination::LiquidAddress { @@ -1606,7 +1629,9 @@ impl LiquidSdk { bip353_address, } => { let fees_sat = fees_sat.ok_or(PaymentError::InsufficientFunds)?; - let mut response = self.pay_bolt11_invoice(&invoice.bolt11, fees_sat).await?; + let mut response = self + .pay_bolt11_invoice(&invoice.bolt11, fees_sat, is_drain) + .await?; self.insert_bip353_payment_details(bip353_address, &mut response)?; Ok(response) } @@ -1621,7 +1646,13 @@ impl LiquidSdk { .get_bolt12_info(&offer.offer, *receiver_amount_sat) .await?; let mut response = self - .pay_bolt12_invoice(offer, *receiver_amount_sat, bolt12_info, fees_sat) + .pay_bolt12_invoice( + offer, + *receiver_amount_sat, + bolt12_info, + fees_sat, + is_drain, + ) .await?; self.insert_bip353_payment_details(bip353_address, &mut response)?; Ok(response) @@ -1660,6 +1691,7 @@ impl LiquidSdk { &self, invoice: &str, fees_sat: u64, + is_drain: bool, ) -> Result { self.ensure_send_is_not_self_transfer(invoice)?; let bolt11_invoice = self.validate_bolt11_invoice(invoice)?; @@ -1671,8 +1703,9 @@ impl LiquidSdk { err: "Invoice amount is missing".to_string(), })?; let payer_amount_sat = amount_sat + fees_sat; + let get_info_response = self.get_info().await?; ensure_sdk!( - payer_amount_sat <= self.get_info().await?.wallet_info.balance_sat, + payer_amount_sat <= get_info_response.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1685,6 +1718,16 @@ impl LiquidSdk { // If we find a valid MRH, extract the BIP21 address and pay to it via onchain tx Some((address, _)) => { info!("Found MRH for L-BTC address {address}, invoice amount_sat {amount_sat}"); + let (amount_sat, fees_sat) = if is_drain { + let drain_fees_sat = self.estimate_drain_tx_fee(None, Some(&address)).await?; + let drain_amount_sat = + get_info_response.wallet_info.balance_sat - drain_fees_sat; + info!("Drain amount: {drain_amount_sat} sat"); + (drain_amount_sat, drain_fees_sat) + } else { + (amount_sat, fees_sat) + }; + self.pay_liquid( LiquidAddressData { address, @@ -1723,6 +1766,7 @@ impl LiquidSdk { user_specified_receiver_amount_sat: u64, bolt12_info: GetBolt12FetchResponse, fees_sat: u64, + is_drain: bool, ) -> Result { let invoice = self.validate_bolt12_invoice( offer, @@ -1732,8 +1776,9 @@ impl LiquidSdk { let receiver_amount_sat = invoice.amount_msats() / 1_000; let payer_amount_sat = receiver_amount_sat + fees_sat; + let get_info_response = self.get_info().await?; ensure_sdk!( - payer_amount_sat <= self.get_info().await?.wallet_info.balance_sat, + payer_amount_sat <= get_info_response.wallet_info.balance_sat, PaymentError::InsufficientFunds ); @@ -1745,6 +1790,15 @@ impl LiquidSdk { ); let signing_pubkey = invoice.signing_pubkey().to_string(); let (_, address, _, _) = verify_mrh_signature(&bip21, &signing_pubkey, &signature)?; + let (receiver_amount_sat, fees_sat) = if is_drain { + let drain_fees_sat = self.estimate_drain_tx_fee(None, Some(&address)).await?; + let drain_amount_sat = + get_info_response.wallet_info.balance_sat - drain_fees_sat; + info!("Drain amount: {drain_amount_sat} sat"); + (drain_amount_sat, drain_fees_sat) + } else { + (receiver_amount_sat, fees_sat) + }; self.pay_liquid( LiquidAddressData { @@ -4020,10 +4074,10 @@ impl LiquidSdk { /// /// * `req` - the [PrepareLnUrlPayRequest] containing: /// * `data` - the [LnUrlPayRequestData] returned by [LiquidSdk::parse] - /// * `amount` - The optional amount of type [PayAmount]. - /// - [PayAmount::Drain] which uses all funds - /// - [PayAmount::Receiver] which sets the amount the receiver should receive - /// * `bip353_address` - A BIP353 address, in case one was used in order to fetch the LNURL + /// * `amount` - the [PayAmount] to send: + /// - [PayAmount::Drain] which uses all Bitcoin funds + /// - [PayAmount::Bitcoin] which sets the amount in satoshi that will be received + /// * `bip353_address` - a BIP353 address, in case one was used in order to fetch the LNURL /// Pay request data. Returned by [parse]. /// * `comment` - an optional comment for this payment /// * `validate_success_action_url` - validates that, if there is a URL success action, the URL domain matches @@ -4032,9 +4086,10 @@ impl LiquidSdk { /// # Returns /// Returns a [PrepareLnUrlPayResponse] containing: /// * `destination` - the destination of the payment - /// * `fees_sat` - The fees in satoshis to send the payment - /// * `data` - The [LnUrlPayRequestData] returned by [parse] - /// * `comment` - An optional comment for this payment + /// * `amount` - the [PayAmount] to send + /// * `fees_sat` - the fees in satoshis to send the payment + /// * `data` - the [LnUrlPayRequestData] returned by [parse] + /// * `comment` - an optional comment for this payment /// * `success_action` - the optional unprocessed LUD-09 success action pub async fn prepare_lnurl_pay( &self, @@ -4060,28 +4115,28 @@ impl LiquidSdk { .ok_or(PaymentError::PairsNotFound)?; let drain_fees_sat = self.estimate_drain_tx_fee(None, None).await?; let drain_amount_sat = get_info_res.wallet_info.balance_sat - drain_fees_sat; - // Get the inverse invoice amount by calculating a dummy amount then increment up to the drain amount + // Get the inverse receiver amount by calculating a dummy amount then increment up to the drain amount let dummy_fees_sat = lbtc_pair.fees.total(drain_amount_sat); let dummy_amount_sat = drain_amount_sat - dummy_fees_sat; - let invoice_amount_sat = utils::increment_invoice_amount_up_to_drain_amount( + let receiver_amount_sat = utils::increment_receiver_amount_up_to_drain_amount( dummy_amount_sat, &lbtc_pair, drain_amount_sat, ); lbtc_pair .limits - .within(invoice_amount_sat) + .within(receiver_amount_sat) .map_err(|e| LnUrlPayError::Generic { err: e.message() })?; // Validate if we can actually drain the wallet with a swap - let pair_fees_sat = lbtc_pair.fees.total(invoice_amount_sat); + let pair_fees_sat = lbtc_pair.fees.total(receiver_amount_sat); ensure_sdk!( - invoice_amount_sat + pair_fees_sat == drain_amount_sat, + receiver_amount_sat + pair_fees_sat == drain_amount_sat, LnUrlPayError::Generic { err: "Cannot drain without leaving a remainder".to_string(), } ); - invoice_amount_sat * 1000 + receiver_amount_sat * 1000 } PayAmount::Bitcoin { receiver_amount_sat, @@ -4110,7 +4165,7 @@ impl LiquidSdk { let prepare_response = self .prepare_send_payment(&PrepareSendRequest { destination: data.pr.clone(), - amount: Some(req.amount), + amount: Some(req.amount.clone()), }) .await .map_err(|e| LnUrlPayError::Generic { err: e.to_string() })?; @@ -4136,6 +4191,7 @@ impl LiquidSdk { destination, fees_sat, data: req.data, + amount: req.amount, comment: req.comment, success_action: data.success_action, }) @@ -4166,6 +4222,7 @@ impl LiquidSdk { destination: prepare_response.destination.clone(), fees_sat: Some(prepare_response.fees_sat), estimated_asset_fees: None, + amount: Some(prepare_response.amount), }, use_asset_fees: None, }) diff --git a/lib/core/src/utils/mod.rs b/lib/core/src/utils/mod.rs index 1542209..627ee16 100644 --- a/lib/core/src/utils/mod.rs +++ b/lib/core/src/utils/mod.rs @@ -151,23 +151,23 @@ pub(crate) fn lbtc_asset_id(network: LiquidNetwork) -> AssetId { } } -/// Increments the inversely calculated invoice amount up to the maximum drainable amount, -/// as calculating the inverse invoice amount in some cases has rounding down errors -pub(crate) fn increment_invoice_amount_up_to_drain_amount( - invoice_amount_sat: u64, +/// Increments the inversely calculated receiver amount up to the maximum drainable amount, +/// as calculating the inverse receiver amount in some cases has rounding down errors +pub(crate) fn increment_receiver_amount_up_to_drain_amount( + receiver_amount_sat: u64, lbtc_pair: &SubmarinePair, drain_amount_sat: u64, ) -> u64 { - let incremented_amount_sat = invoice_amount_sat + 1; + let incremented_amount_sat = receiver_amount_sat + 1; let fees_sat = lbtc_pair.fees.total(incremented_amount_sat); if incremented_amount_sat + fees_sat <= drain_amount_sat { - increment_invoice_amount_up_to_drain_amount( + increment_receiver_amount_up_to_drain_amount( incremented_amount_sat, lbtc_pair, drain_amount_sat, ) } else { - invoice_amount_sat + receiver_amount_sat } } diff --git a/lib/wasm/src/model.rs b/lib/wasm/src/model.rs index 7b8047a..50eb7ed 100644 --- a/lib/wasm/src/model.rs +++ b/lib/wasm/src/model.rs @@ -457,6 +457,7 @@ pub enum SendDestination { #[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::PrepareSendResponse)] pub struct PrepareSendResponse { pub destination: SendDestination, + pub amount: Option, pub fees_sat: Option, pub estimated_asset_fees: Option, } @@ -789,6 +790,7 @@ pub struct PrepareLnUrlPayResponse { pub destination: SendDestination, pub fees_sat: u64, pub data: LnUrlPayRequestData, + pub amount: PayAmount, pub comment: Option, pub success_action: Option, } diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index e5ed684..4cf1aa2 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -2986,13 +2986,14 @@ 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 != 5) throw Exception('unexpected arr length: expect 5 but see ${arr.length}'); + if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); return PrepareLnUrlPayResponse( destination: dco_decode_send_destination(arr[0]), feesSat: dco_decode_u_64(arr[1]), 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]), + amount: dco_decode_pay_amount(arr[3]), + comment: dco_decode_opt_String(arr[4]), + successAction: dco_decode_opt_box_autoadd_success_action(arr[5]), ); } @@ -3084,11 +3085,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareSendResponse dco_decode_prepare_send_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 != 4) throw Exception('unexpected arr length: expect 4 but see ${arr.length}'); return PrepareSendResponse( destination: dco_decode_send_destination(arr[0]), - feesSat: dco_decode_opt_box_autoadd_u_64(arr[1]), - estimatedAssetFees: dco_decode_opt_box_autoadd_f_64(arr[2]), + amount: dco_decode_opt_box_autoadd_pay_amount(arr[1]), + feesSat: dco_decode_opt_box_autoadd_u_64(arr[2]), + estimatedAssetFees: dco_decode_opt_box_autoadd_f_64(arr[3]), ); } @@ -5404,12 +5406,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { 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_amount = sse_decode_pay_amount(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, data: var_data, + amount: var_amount, comment: var_comment, successAction: var_successAction, ); @@ -5501,10 +5505,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { PrepareSendResponse sse_decode_prepare_send_response(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs var var_destination = sse_decode_send_destination(deserializer); + var var_amount = sse_decode_opt_box_autoadd_pay_amount(deserializer); var var_feesSat = sse_decode_opt_box_autoadd_u_64(deserializer); var var_estimatedAssetFees = sse_decode_opt_box_autoadd_f_64(deserializer); return PrepareSendResponse( destination: var_destination, + amount: var_amount, feesSat: var_feesSat, estimatedAssetFees: var_estimatedAssetFees, ); @@ -7683,6 +7689,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { 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_pay_amount(self.amount, serializer); sse_encode_opt_String(self.comment, serializer); sse_encode_opt_box_autoadd_success_action(self.successAction, serializer); } @@ -7747,6 +7754,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { void sse_encode_prepare_send_response(PrepareSendResponse self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs sse_encode_send_destination(self.destination, serializer); + sse_encode_opt_box_autoadd_pay_amount(self.amount, serializer); sse_encode_opt_box_autoadd_u_64(self.feesSat, serializer); sse_encode_opt_box_autoadd_f_64(self.estimatedAssetFees, serializer); } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index e4c9d7b..66f6fd0 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -3654,6 +3654,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { 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); + cst_api_fill_to_wire_pay_amount(apiObj.amount, wireObj.amount); wireObj.comment = cst_encode_opt_String(apiObj.comment); wireObj.success_action = cst_encode_opt_box_autoadd_success_action(apiObj.successAction); } @@ -3734,6 +3735,7 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wire_cst_prepare_send_response wireObj, ) { cst_api_fill_to_wire_send_destination(apiObj.destination, wireObj.destination); + wireObj.amount = cst_encode_opt_box_autoadd_pay_amount(apiObj.amount); wireObj.fees_sat = cst_encode_opt_box_autoadd_u_64(apiObj.feesSat); wireObj.estimated_asset_fees = cst_encode_opt_box_autoadd_f_64(apiObj.estimatedAssetFees); } @@ -6701,6 +6703,33 @@ final class wire_cst_ln_url_pay_request_data extends ffi.Struct { external ffi.Pointer ln_address; } +final class wire_cst_PayAmount_Bitcoin extends ffi.Struct { + @ffi.Uint64() + external int receiver_amount_sat; +} + +final class wire_cst_PayAmount_Asset extends ffi.Struct { + external ffi.Pointer asset_id; + + @ffi.Double() + external double receiver_amount; + + external ffi.Pointer estimate_asset_fees; +} + +final class PayAmountKind extends ffi.Union { + external wire_cst_PayAmount_Bitcoin Bitcoin; + + external wire_cst_PayAmount_Asset Asset; +} + +final class wire_cst_pay_amount extends ffi.Struct { + @ffi.Int32() + external int tag; + + external PayAmountKind kind; +} + final class wire_cst_aes_success_action_data extends ffi.Struct { external ffi.Pointer description; @@ -6757,6 +6786,8 @@ final class wire_cst_prepare_ln_url_pay_response extends ffi.Struct { external wire_cst_ln_url_pay_request_data data; + external wire_cst_pay_amount amount; + external ffi.Pointer comment; external ffi.Pointer success_action; @@ -6814,33 +6845,6 @@ final class wire_cst_prepare_buy_bitcoin_request extends ffi.Struct { external int amount_sat; } -final class wire_cst_PayAmount_Bitcoin extends ffi.Struct { - @ffi.Uint64() - external int receiver_amount_sat; -} - -final class wire_cst_PayAmount_Asset extends ffi.Struct { - external ffi.Pointer asset_id; - - @ffi.Double() - external double receiver_amount; - - external ffi.Pointer estimate_asset_fees; -} - -final class PayAmountKind extends ffi.Union { - external wire_cst_PayAmount_Bitcoin Bitcoin; - - external wire_cst_PayAmount_Asset Asset; -} - -final class wire_cst_pay_amount extends ffi.Struct { - @ffi.Int32() - external int tag; - - external PayAmountKind kind; -} - final class wire_cst_prepare_ln_url_pay_request extends ffi.Struct { external wire_cst_ln_url_pay_request_data data; @@ -6945,6 +6949,8 @@ final class wire_cst_restore_request extends ffi.Struct { final class wire_cst_prepare_send_response extends ffi.Struct { external wire_cst_send_destination destination; + external ffi.Pointer amount; + external ffi.Pointer fees_sat; external ffi.Pointer estimated_asset_fees; diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 85be8ed..1f4fc4a 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -1187,6 +1187,9 @@ class PrepareLnUrlPayResponse { /// The [LnUrlPayRequestData] returned by [parse] final LnUrlPayRequestData data; + /// The amount to send + final PayAmount amount; + /// An optional comment for this payment final String? comment; @@ -1198,13 +1201,19 @@ class PrepareLnUrlPayResponse { required this.destination, required this.feesSat, required this.data, + required this.amount, this.comment, this.successAction, }); @override int get hashCode => - destination.hashCode ^ feesSat.hashCode ^ data.hashCode ^ comment.hashCode ^ successAction.hashCode; + destination.hashCode ^ + feesSat.hashCode ^ + data.hashCode ^ + amount.hashCode ^ + comment.hashCode ^ + successAction.hashCode; @override bool operator ==(Object other) => @@ -1214,6 +1223,7 @@ class PrepareLnUrlPayResponse { destination == other.destination && feesSat == other.feesSat && data == other.data && + amount == other.amount && comment == other.comment && successAction == other.successAction; } @@ -1430,6 +1440,9 @@ class PrepareSendRequest { class PrepareSendResponse { final SendDestination destination; + /// The optional amount to be sent in either Bitcoin or another asset + final PayAmount? amount; + /// The optional estimated fee in satoshi. Is set when there is Bitcoin available /// to pay fees. When not set, there are asset fees available to pay fees. final BigInt? feesSat; @@ -1439,10 +1452,10 @@ class PrepareSendResponse { /// are funds available in this asset to pay fees. final double? estimatedAssetFees; - const PrepareSendResponse({required this.destination, this.feesSat, this.estimatedAssetFees}); + const PrepareSendResponse({required this.destination, this.amount, this.feesSat, this.estimatedAssetFees}); @override - int get hashCode => destination.hashCode ^ feesSat.hashCode ^ estimatedAssetFees.hashCode; + int get hashCode => destination.hashCode ^ amount.hashCode ^ feesSat.hashCode ^ estimatedAssetFees.hashCode; @override bool operator ==(Object other) => @@ -1450,6 +1463,7 @@ class PrepareSendResponse { other is PrepareSendResponse && runtimeType == other.runtimeType && destination == other.destination && + amount == other.amount && feesSat == other.feesSat && estimatedAssetFees == other.estimatedAssetFees; } diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index a862e19..e2003bb 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -4550,6 +4550,33 @@ final class wire_cst_ln_url_pay_request_data extends ffi.Struct { external ffi.Pointer ln_address; } +final class wire_cst_PayAmount_Bitcoin extends ffi.Struct { + @ffi.Uint64() + external int receiver_amount_sat; +} + +final class wire_cst_PayAmount_Asset extends ffi.Struct { + external ffi.Pointer asset_id; + + @ffi.Double() + external double receiver_amount; + + external ffi.Pointer estimate_asset_fees; +} + +final class PayAmountKind extends ffi.Union { + external wire_cst_PayAmount_Bitcoin Bitcoin; + + external wire_cst_PayAmount_Asset Asset; +} + +final class wire_cst_pay_amount extends ffi.Struct { + @ffi.Int32() + external int tag; + + external PayAmountKind kind; +} + final class wire_cst_aes_success_action_data extends ffi.Struct { external ffi.Pointer description; @@ -4606,6 +4633,8 @@ final class wire_cst_prepare_ln_url_pay_response extends ffi.Struct { external wire_cst_ln_url_pay_request_data data; + external wire_cst_pay_amount amount; + external ffi.Pointer comment; external ffi.Pointer success_action; @@ -4663,33 +4692,6 @@ final class wire_cst_prepare_buy_bitcoin_request extends ffi.Struct { external int amount_sat; } -final class wire_cst_PayAmount_Bitcoin extends ffi.Struct { - @ffi.Uint64() - external int receiver_amount_sat; -} - -final class wire_cst_PayAmount_Asset extends ffi.Struct { - external ffi.Pointer asset_id; - - @ffi.Double() - external double receiver_amount; - - external ffi.Pointer estimate_asset_fees; -} - -final class PayAmountKind extends ffi.Union { - external wire_cst_PayAmount_Bitcoin Bitcoin; - - external wire_cst_PayAmount_Asset Asset; -} - -final class wire_cst_pay_amount extends ffi.Struct { - @ffi.Int32() - external int tag; - - external PayAmountKind kind; -} - final class wire_cst_prepare_ln_url_pay_request extends ffi.Struct { external wire_cst_ln_url_pay_request_data data; @@ -4794,6 +4796,8 @@ final class wire_cst_restore_request extends ffi.Struct { final class wire_cst_prepare_send_response extends ffi.Struct { external wire_cst_send_destination destination; + external ffi.Pointer amount; + external ffi.Pointer fees_sat; external ffi.Pointer estimated_asset_fees; 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 95eead5..016750f 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 @@ -2003,6 +2003,7 @@ fun asPrepareLnUrlPayResponse(prepareLnUrlPayResponse: ReadableMap): PrepareLnUr "destination", "feesSat", "data", + "amount", ), ) ) { @@ -2011,6 +2012,7 @@ 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 amount = prepareLnUrlPayResponse.getMap("amount")?.let { asPayAmount(it) }!! val comment = if (hasNonNullKey(prepareLnUrlPayResponse, "comment")) prepareLnUrlPayResponse.getString("comment") else null val successAction = if (hasNonNullKey(prepareLnUrlPayResponse, "successAction")) { @@ -2020,7 +2022,7 @@ fun asPrepareLnUrlPayResponse(prepareLnUrlPayResponse: ReadableMap): PrepareLnUr } else { null } - return PrepareLnUrlPayResponse(destination, feesSat, data, comment, successAction) + return PrepareLnUrlPayResponse(destination, feesSat, data, amount, comment, successAction) } fun readableMapOf(prepareLnUrlPayResponse: PrepareLnUrlPayResponse): ReadableMap = @@ -2028,6 +2030,7 @@ fun readableMapOf(prepareLnUrlPayResponse: PrepareLnUrlPayResponse): ReadableMap "destination" to readableMapOf(prepareLnUrlPayResponse.destination), "feesSat" to prepareLnUrlPayResponse.feesSat, "data" to readableMapOf(prepareLnUrlPayResponse.data), + "amount" to readableMapOf(prepareLnUrlPayResponse.amount), "comment" to prepareLnUrlPayResponse.comment, "successAction" to prepareLnUrlPayResponse.successAction?.let { readableMapOf(it) }, ) @@ -2357,6 +2360,7 @@ fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse return null } val destination = prepareSendResponse.getMap("destination")?.let { asSendDestination(it) }!! + val amount = if (hasNonNullKey(prepareSendResponse, "amount")) prepareSendResponse.getMap("amount")?.let { asPayAmount(it) } else null val feesSat = if (hasNonNullKey(prepareSendResponse, "feesSat")) prepareSendResponse.getDouble("feesSat").toULong() else null val estimatedAssetFees = if (hasNonNullKey( @@ -2368,12 +2372,13 @@ fun asPrepareSendResponse(prepareSendResponse: ReadableMap): PrepareSendResponse } else { null } - return PrepareSendResponse(destination, feesSat, estimatedAssetFees) + return PrepareSendResponse(destination, amount, feesSat, estimatedAssetFees) } fun readableMapOf(prepareSendResponse: PrepareSendResponse): ReadableMap = readableMapOf( "destination" to readableMapOf(prepareSendResponse.destination), + "amount" to prepareSendResponse.amount?.let { readableMapOf(it) }, "feesSat" to prepareSendResponse.feesSat, "estimatedAssetFees" to prepareSendResponse.estimatedAssetFees, ) diff --git a/packages/react-native/ios/BreezSDKLiquidMapper.swift b/packages/react-native/ios/BreezSDKLiquidMapper.swift index 5e76f67..8797671 100644 --- a/packages/react-native/ios/BreezSDKLiquidMapper.swift +++ b/packages/react-native/ios/BreezSDKLiquidMapper.swift @@ -2392,6 +2392,11 @@ enum BreezSDKLiquidMapper { } let data = try asLnUrlPayRequestData(lnUrlPayRequestData: dataTmp) + guard let amountTmp = prepareLnUrlPayResponse["amount"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amount", typeName: "PrepareLnUrlPayResponse")) + } + let amount = try asPayAmount(payAmount: amountTmp) + var comment: String? if hasNonNilKey(data: prepareLnUrlPayResponse, key: "comment") { guard let commentTmp = prepareLnUrlPayResponse["comment"] as? String else { @@ -2404,7 +2409,7 @@ enum BreezSDKLiquidMapper { successAction = try asSuccessAction(successAction: successActionTmp) } - return PrepareLnUrlPayResponse(destination: destination, feesSat: feesSat, data: data, comment: comment, successAction: successAction) + return PrepareLnUrlPayResponse(destination: destination, feesSat: feesSat, data: data, amount: amount, comment: comment, successAction: successAction) } static func dictionaryOf(prepareLnUrlPayResponse: PrepareLnUrlPayResponse) -> [String: Any?] { @@ -2412,6 +2417,7 @@ enum BreezSDKLiquidMapper { "destination": dictionaryOf(sendDestination: prepareLnUrlPayResponse.destination), "feesSat": prepareLnUrlPayResponse.feesSat, "data": dictionaryOf(lnUrlPayRequestData: prepareLnUrlPayResponse.data), + "amount": dictionaryOf(payAmount: prepareLnUrlPayResponse.amount), "comment": prepareLnUrlPayResponse.comment == nil ? nil : prepareLnUrlPayResponse.comment, "successAction": prepareLnUrlPayResponse.successAction == nil ? nil : dictionaryOf(successAction: prepareLnUrlPayResponse.successAction!), ] @@ -2743,6 +2749,11 @@ enum BreezSDKLiquidMapper { } let destination = try asSendDestination(sendDestination: destinationTmp) + var amount: PayAmount? + if let amountTmp = prepareSendResponse["amount"] as? [String: Any?] { + amount = try asPayAmount(payAmount: amountTmp) + } + var feesSat: UInt64? if hasNonNilKey(data: prepareSendResponse, key: "feesSat") { guard let feesSatTmp = prepareSendResponse["feesSat"] as? UInt64 else { @@ -2758,12 +2769,13 @@ enum BreezSDKLiquidMapper { estimatedAssetFees = estimatedAssetFeesTmp } - return PrepareSendResponse(destination: destination, feesSat: feesSat, estimatedAssetFees: estimatedAssetFees) + return PrepareSendResponse(destination: destination, amount: amount, feesSat: feesSat, estimatedAssetFees: estimatedAssetFees) } static func dictionaryOf(prepareSendResponse: PrepareSendResponse) -> [String: Any?] { return [ "destination": dictionaryOf(sendDestination: prepareSendResponse.destination), + "amount": prepareSendResponse.amount == nil ? nil : dictionaryOf(payAmount: prepareSendResponse.amount!), "feesSat": prepareSendResponse.feesSat == nil ? nil : prepareSendResponse.feesSat, "estimatedAssetFees": prepareSendResponse.estimatedAssetFees == nil ? nil : prepareSendResponse.estimatedAssetFees, ] diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 36b5320..b3cb609 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -356,6 +356,7 @@ export interface PrepareLnUrlPayResponse { destination: SendDestination feesSat: number data: LnUrlPayRequestData + amount: PayAmount comment?: string successAction?: SuccessAction } @@ -404,6 +405,7 @@ export interface PrepareSendRequest { export interface PrepareSendResponse { destination: SendDestination + amount?: PayAmount feesSat?: number estimatedAssetFees?: number }