Add zero-amount Receive Chain Swap (#538)

Add support for Zero-Amount Receive Chain Swaps
This commit is contained in:
ok300
2024-12-09 16:10:52 +00:00
committed by GitHub
parent 790dfa91be
commit cfc883ae00
20 changed files with 571 additions and 60 deletions

4
cli/Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -609,7 +609,7 @@ dependencies = [
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/SatoshiPortal/boltz-rust?branch=trunk#19e01071f5722c21f80ae9b34edef5c3972e9e0b" source = "git+https://github.com/SatoshiPortal/boltz-rust?branch=trunk#68ff15b0f80abfa54d8dca4c11c8c2794e4b6921"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.31.2", "bitcoin 0.31.2",

View File

@@ -271,13 +271,21 @@ pub(crate) async fn handle_command(
}) })
.await?; .await?;
wait_confirmation!( let fees = prepare_response.fees_sat;
format!( let confirmation_msg = match payer_amount_sat {
"Fees: {} sat. Are the fees acceptable? (y/N) ", Some(_) => format!("Fees: {fees} sat. Are the fees acceptable? (y/N)"),
prepare_response.fees_sat None => {
), let min = prepare_response.min_payer_amount_sat;
"Payment receive halted" let max = prepare_response.max_payer_amount_sat;
); let service_feerate = prepare_response.service_feerate;
format!(
"Fees: {fees} sat + {service_feerate:?}% of the sent amount. \
Sender should send between {min:?} sat and {max:?} sat. \
Are the fees acceptable? (y/N)"
)
}
};
wait_confirmation!(confirmation_msg, "Payment receive halted");
let response = sdk let response = sdk
.receive_payment(&ReceivePaymentRequest { .receive_payment(&ReceivePaymentRequest {

4
lib/Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -723,7 +723,7 @@ dependencies = [
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/SatoshiPortal/boltz-rust?branch=trunk#19e01071f5722c21f80ae9b34edef5c3972e9e0b" source = "git+https://github.com/SatoshiPortal/boltz-rust?branch=trunk#68ff15b0f80abfa54d8dca4c11c8c2794e4b6921"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.31.2", "bitcoin 0.31.2",

View File

@@ -367,6 +367,9 @@ typedef struct wire_cst_prepare_receive_response {
int32_t payment_method; int32_t payment_method;
uint64_t *payer_amount_sat; uint64_t *payer_amount_sat;
uint64_t fees_sat; uint64_t fees_sat;
uint64_t *min_payer_amount_sat;
uint64_t *max_payer_amount_sat;
double *swapper_feerate;
} wire_cst_prepare_receive_response; } wire_cst_prepare_receive_response;
typedef struct wire_cst_receive_payment_request { typedef struct wire_cst_receive_payment_request {
@@ -1194,6 +1197,8 @@ struct wire_cst_check_message_request *frbgen_breez_liquid_cst_new_box_autoadd_c
struct wire_cst_connect_request *frbgen_breez_liquid_cst_new_box_autoadd_connect_request(void); struct wire_cst_connect_request *frbgen_breez_liquid_cst_new_box_autoadd_connect_request(void);
double *frbgen_breez_liquid_cst_new_box_autoadd_f_64(double value);
struct wire_cst_get_payment_request *frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request(void); struct wire_cst_get_payment_request *frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request(void);
int64_t *frbgen_breez_liquid_cst_new_box_autoadd_i_64(int64_t value); int64_t *frbgen_breez_liquid_cst_new_box_autoadd_i_64(int64_t value);
@@ -1306,6 +1311,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_buy_bitcoin_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_buy_bitcoin_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_check_message_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_check_message_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_connect_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_connect_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_f_64);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_i_64); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_i_64);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_liquid_address_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_liquid_address_data);

View File

@@ -431,9 +431,12 @@ dictionary PrepareReceiveRequest {
}; };
dictionary PrepareReceiveResponse { dictionary PrepareReceiveResponse {
u64? payer_amount_sat;
PaymentMethod payment_method; PaymentMethod payment_method;
u64 fees_sat; u64 fees_sat;
u64? payer_amount_sat;
u64? min_payer_amount_sat;
u64? max_payer_amount_sat;
f64? swapper_feerate;
}; };
dictionary ReceivePaymentRequest { dictionary ReceivePaymentRequest {

View File

@@ -436,6 +436,20 @@ impl ChainSwapHandler {
| ChainSwapStates::TransactionLockupFailed | ChainSwapStates::TransactionLockupFailed
| ChainSwapStates::TransactionRefunded | ChainSwapStates::TransactionRefunded
| ChainSwapStates::SwapExpired => { | ChainSwapStates::SwapExpired => {
// Zero-amount Receive Chain Swaps also get to TransactionLockupFailed when user locks up funds
let is_zero_amount = swap.payer_amount_sat == 0;
if matches!(swap_state, ChainSwapStates::TransactionLockupFailed) && is_zero_amount
{
match self.handle_amountless_update(swap).await {
Ok(_) => {
// We successfully accepted the quote, the swap should continue as normal
return Ok(()); // Break from TxLockupFailed branch
}
// In case of error, we continue and mark it as refundable
Err(e) => error!("Failed to accept the quote for swap {}: {e:?}", &swap.id),
}
}
match swap.refund_tx_id.clone() { match swap.refund_tx_id.clone() {
None => { None => {
warn!("Chain Swap {id} is in an unrecoverable state: {swap_state:?}"); warn!("Chain Swap {id} is in an unrecoverable state: {swap_state:?}");
@@ -453,7 +467,7 @@ impl ChainSwapHandler {
} }
} }
Some(refund_tx_id) => warn!( Some(refund_tx_id) => warn!(
"Refund tx for Chain Swap {id} was already broadcast: txid {refund_tx_id}" "Refund for Chain Swap {id} was already broadcast: txid {refund_tx_id}"
), ),
}; };
Ok(()) Ok(())
@@ -466,6 +480,82 @@ impl ChainSwapHandler {
} }
} }
async fn handle_amountless_update(&self, swap: &ChainSwap) -> Result<(), PaymentError> {
let quote = self
.swapper
.get_zero_amount_chain_swap_quote(&swap.id)
.map(|quote| quote.to_sat())?;
info!("Got quote of {quote} sat for swap {}", &swap.id);
self.validate_and_update_amountless_swap(swap, quote)
.await?;
self.swapper
.accept_zero_amount_chain_swap_quote(&swap.id, quote)
}
async fn validate_and_update_amountless_swap(
&self,
swap: &ChainSwap,
quote_server_lockup_amount_sat: u64,
) -> Result<(), PaymentError> {
debug!("Validating {swap:?}");
ensure_sdk!(
matches!(swap.direction, Direction::Incoming),
PaymentError::generic(&format!(
"Only an incoming chain swap can be a zero-amount swap. Swap ID: {}",
&swap.id
))
);
let script_pubkey = swap.get_receive_lockup_swap_script_pubkey(self.config.network)?;
let script_balance = self
.bitcoin_chain_service
.lock()
.await
.script_get_balance(script_pubkey.as_script())?;
debug!("Found lockup balance {script_balance:?}");
let user_lockup_amount_sat = match script_balance.confirmed > 0 {
true => script_balance.confirmed,
false => match script_balance.unconfirmed > 0 {
true => script_balance.unconfirmed.unsigned_abs(),
false => 0,
},
};
ensure_sdk!(
user_lockup_amount_sat > 0,
PaymentError::generic("Lockup address has no confirmed or unconfirmed balance")
);
let pair = swap.get_boltz_pair()?;
let swapper_service_feerate = pair.fees.percentage;
let swapper_server_fees_sat = pair.fees.server();
let service_fees_sat =
((swapper_service_feerate / 100.0) * user_lockup_amount_sat as f64).ceil() as u64;
let fees_sat = swapper_server_fees_sat + service_fees_sat;
ensure_sdk!(
user_lockup_amount_sat > fees_sat,
PaymentError::generic(&format!("Invalid quote: fees ({fees_sat} sat) are higher than user lockup ({user_lockup_amount_sat} sat)"))
);
let expected_server_lockup_amount_sat = user_lockup_amount_sat - fees_sat;
debug!("user_lockup_amount_sat = {}, service_fees_sat = {}, server_fees_sat = {}, expected_server_lockup_amount_sat = {}, quote_server_lockup_amount_sat = {}",
user_lockup_amount_sat, service_fees_sat, swapper_server_fees_sat, expected_server_lockup_amount_sat, quote_server_lockup_amount_sat);
ensure_sdk!(
expected_server_lockup_amount_sat <= quote_server_lockup_amount_sat,
PaymentError::generic(&format!("Invalid quote: expected at least {expected_server_lockup_amount_sat} sat, got {quote_server_lockup_amount_sat} sat"))
);
let receiver_amount_sat = quote_server_lockup_amount_sat - swap.claim_fees_sat;
self.persister.update_zero_amount_swap_values(
&swap.id,
user_lockup_amount_sat,
receiver_amount_sat,
)?;
Ok(())
}
async fn on_new_outgoing_status(&self, swap: &ChainSwap, update: &boltz::Update) -> Result<()> { async fn on_new_outgoing_status(&self, swap: &ChainSwap, update: &boltz::Update) -> Result<()> {
let id = &update.id; let id = &update.id;
let status = &update.status; let status = &update.status;

View File

@@ -3291,6 +3291,17 @@ impl SseDecode for Option<bool> {
} }
} }
impl SseDecode for Option<f64> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<f64>::sse_decode(deserializer));
} else {
return None;
}
}
}
impl SseDecode for Option<i64> { impl SseDecode for Option<i64> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -3747,10 +3758,16 @@ impl SseDecode for crate::model::PrepareReceiveResponse {
let mut var_paymentMethod = <crate::model::PaymentMethod>::sse_decode(deserializer); let mut var_paymentMethod = <crate::model::PaymentMethod>::sse_decode(deserializer);
let mut var_payerAmountSat = <Option<u64>>::sse_decode(deserializer); let mut var_payerAmountSat = <Option<u64>>::sse_decode(deserializer);
let mut var_feesSat = <u64>::sse_decode(deserializer); let mut var_feesSat = <u64>::sse_decode(deserializer);
let mut var_minPayerAmountSat = <Option<u64>>::sse_decode(deserializer);
let mut var_maxPayerAmountSat = <Option<u64>>::sse_decode(deserializer);
let mut var_swapperFeerate = <Option<f64>>::sse_decode(deserializer);
return crate::model::PrepareReceiveResponse { return crate::model::PrepareReceiveResponse {
payment_method: var_paymentMethod, payment_method: var_paymentMethod,
payer_amount_sat: var_payerAmountSat, payer_amount_sat: var_payerAmountSat,
fees_sat: var_feesSat, fees_sat: var_feesSat,
min_payer_amount_sat: var_minPayerAmountSat,
max_payer_amount_sat: var_maxPayerAmountSat,
swapper_feerate: var_swapperFeerate,
}; };
} }
} }
@@ -5807,6 +5824,9 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveResponse {
self.payment_method.into_into_dart().into_dart(), self.payment_method.into_into_dart().into_dart(),
self.payer_amount_sat.into_into_dart().into_dart(), self.payer_amount_sat.into_into_dart().into_dart(),
self.fees_sat.into_into_dart().into_dart(), self.fees_sat.into_into_dart().into_dart(),
self.min_payer_amount_sat.into_into_dart().into_dart(),
self.max_payer_amount_sat.into_into_dart().into_dart(),
self.swapper_feerate.into_into_dart().into_dart(),
] ]
.into_dart() .into_dart()
} }
@@ -7282,6 +7302,16 @@ impl SseEncode for Option<bool> {
} }
} }
impl SseEncode for Option<f64> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<f64>::sse_encode(value, serializer);
}
}
}
impl SseEncode for Option<i64> { impl SseEncode for Option<i64> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -7680,6 +7710,9 @@ impl SseEncode for crate::model::PrepareReceiveResponse {
<crate::model::PaymentMethod>::sse_encode(self.payment_method, serializer); <crate::model::PaymentMethod>::sse_encode(self.payment_method, serializer);
<Option<u64>>::sse_encode(self.payer_amount_sat, serializer); <Option<u64>>::sse_encode(self.payer_amount_sat, serializer);
<u64>::sse_encode(self.fees_sat, serializer); <u64>::sse_encode(self.fees_sat, serializer);
<Option<u64>>::sse_encode(self.min_payer_amount_sat, serializer);
<Option<u64>>::sse_encode(self.max_payer_amount_sat, serializer);
<Option<f64>>::sse_encode(self.swapper_feerate, serializer);
} }
} }
@@ -8290,6 +8323,12 @@ mod io {
CstDecode::<crate::model::ConnectRequest>::cst_decode(*wrap).into() CstDecode::<crate::model::ConnectRequest>::cst_decode(*wrap).into()
} }
} }
impl CstDecode<f64> for *mut f64 {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> f64 {
unsafe { *flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }
}
}
impl CstDecode<crate::model::GetPaymentRequest> for *mut wire_cst_get_payment_request { impl CstDecode<crate::model::GetPaymentRequest> for *mut wire_cst_get_payment_request {
// Codec=Cst (C-struct based), see doc to use other codecs // Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::GetPaymentRequest { fn cst_decode(self) -> crate::model::GetPaymentRequest {
@@ -9554,6 +9593,9 @@ mod io {
payment_method: self.payment_method.cst_decode(), payment_method: self.payment_method.cst_decode(),
payer_amount_sat: self.payer_amount_sat.cst_decode(), payer_amount_sat: self.payer_amount_sat.cst_decode(),
fees_sat: self.fees_sat.cst_decode(), fees_sat: self.fees_sat.cst_decode(),
min_payer_amount_sat: self.min_payer_amount_sat.cst_decode(),
max_payer_amount_sat: self.max_payer_amount_sat.cst_decode(),
swapper_feerate: self.swapper_feerate.cst_decode(),
} }
} }
} }
@@ -10702,6 +10744,9 @@ mod io {
payment_method: Default::default(), payment_method: Default::default(),
payer_amount_sat: core::ptr::null_mut(), payer_amount_sat: core::ptr::null_mut(),
fees_sat: Default::default(), fees_sat: Default::default(),
min_payer_amount_sat: core::ptr::null_mut(),
max_payer_amount_sat: core::ptr::null_mut(),
swapper_feerate: core::ptr::null_mut(),
} }
} }
} }
@@ -11481,6 +11526,11 @@ mod io {
) )
} }
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_f_64(value: f64) -> *mut f64 {
flutter_rust_bridge::for_generated::new_leak_box_ptr(value)
}
#[no_mangle] #[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request( pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request(
) -> *mut wire_cst_get_payment_request { ) -> *mut wire_cst_get_payment_request {
@@ -12840,6 +12890,9 @@ mod io {
payment_method: i32, payment_method: i32,
payer_amount_sat: *mut u64, payer_amount_sat: *mut u64,
fees_sat: u64, fees_sat: u64,
min_payer_amount_sat: *mut u64,
max_payer_amount_sat: *mut u64,
swapper_feerate: *mut f64,
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View File

@@ -2,6 +2,7 @@ use std::path::PathBuf;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use boltz_client::boltz::ChainPair;
use boltz_client::{ use boltz_client::{
bitcoin::ScriptBuf, bitcoin::ScriptBuf,
network::Chain, network::Chain,
@@ -302,7 +303,31 @@ pub struct PrepareReceiveRequest {
pub struct PrepareReceiveResponse { pub struct PrepareReceiveResponse {
pub payment_method: PaymentMethod, pub payment_method: PaymentMethod,
pub payer_amount_sat: Option<u64>, pub payer_amount_sat: Option<u64>,
/// Generally represents the total fees that would be paid to send or receive this payment.
///
/// In case of Zero-Amount Receive Chain swaps, the swapper service fee (`swapper_feerate` times
/// the amount) is paid in addition to `fees_sat`. The swapper service feerate is already known
/// in the beginning, but the exact swapper service fee will only be known when the
/// `payer_amount_sat` is known.
///
/// In all other types of swaps, the swapper service fee is included in `fees_sat`.
pub fees_sat: u64, pub fees_sat: u64,
/// The minimum amount the payer can send for this swap to succeed.
///
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
pub min_payer_amount_sat: Option<u64>,
/// The maximum amount the payer can send for this swap to succeed.
///
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
pub max_payer_amount_sat: Option<u64>,
/// The percentage of the sent amount that will count towards the service fee.
///
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
pub swapper_feerate: Option<f64>,
} }
/// An argument when calling [crate::sdk::LiquidSdk::receive_payment]. /// An argument when calling [crate::sdk::LiquidSdk::receive_payment].
@@ -671,6 +696,13 @@ impl ChainSwap {
}) })
} }
pub(crate) fn get_boltz_pair(&self) -> Result<ChainPair> {
let pair: ChainPair = serde_json::from_str(&self.pair_fees_json)
.map_err(|e| anyhow!("Failed to deserialize ChainPair: {e:?}"))?;
Ok(pair)
}
pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> { pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
let chain_swap_details = self.get_boltz_create_response()?.claim_details; let chain_swap_details = self.get_boltz_create_response()?.claim_details;
let our_pubkey = self.get_claim_keypair()?.public_key(); let our_pubkey = self.get_claim_keypair()?.public_key();

View File

@@ -267,6 +267,32 @@ impl Persister {
Ok(()) Ok(())
} }
/// Used for Zero-amount Receive Chain swaps, when we fetched the quote and we know how much
/// the sender locked up
pub(crate) fn update_zero_amount_swap_values(
&self,
swap_id: &str,
payer_amount_sat: u64,
receiver_amount_sat: u64,
) -> Result<(), PaymentError> {
log::info!("Updating chain swap {swap_id}: payer_amount_sat = {payer_amount_sat}, receiver_amount_sat = {receiver_amount_sat}");
let con: Connection = self.get_connection()?;
con.execute(
"UPDATE chain_swaps
SET
payer_amount_sat = :payer_amount_sat,
receiver_amount_sat = :receiver_amount_sat
WHERE
id = :id",
named_params! {
":id": swap_id,
":payer_amount_sat": payer_amount_sat,
":receiver_amount_sat": receiver_amount_sat,
},
)?;
Ok(())
}
// Only set the Chain Swap claim_tx_id if not set, otherwise return an error // Only set the Chain Swap claim_tx_id if not set, otherwise return an error
pub(crate) fn set_chain_swap_claim_tx_id( pub(crate) fn set_chain_swap_claim_tx_id(
&self, &self,

View File

@@ -706,10 +706,12 @@ impl LiquidSdk {
fn get_and_validate_chain_pair( fn get_and_validate_chain_pair(
&self, &self,
direction: Direction, direction: Direction,
user_lockup_amount_sat: u64, user_lockup_amount_sat: Option<u64>,
) -> Result<ChainPair, PaymentError> { ) -> Result<ChainPair, PaymentError> {
let pair = self.get_chain_pair(direction)?; let pair = self.get_chain_pair(direction)?;
self.validate_user_lockup_amount_for_chain_pair(&pair, user_lockup_amount_sat)?; if let Some(user_lockup_amount_sat) = user_lockup_amount_sat {
self.validate_user_lockup_amount_for_chain_pair(&pair, user_lockup_amount_sat)?;
}
Ok(pair) Ok(pair)
} }
@@ -1631,6 +1633,9 @@ impl LiquidSdk {
) -> Result<PrepareReceiveResponse, PaymentError> { ) -> Result<PrepareReceiveResponse, PaymentError> {
self.ensure_is_started().await?; self.ensure_is_started().await?;
let mut min_payer_amount_sat = None;
let mut max_payer_amount_sat = None;
let mut swapper_feerate = None;
let fees_sat; let fees_sat;
match req.payment_method { match req.payment_method {
PaymentMethod::Lightning => { PaymentMethod::Lightning => {
@@ -1651,29 +1656,35 @@ impl LiquidSdk {
.within(payer_amount_sat) .within(payer_amount_sat)
.map_err(|_| PaymentError::AmountOutOfRange)?; .map_err(|_| PaymentError::AmountOutOfRange)?;
min_payer_amount_sat = Some(reverse_pair.limits.minimal);
max_payer_amount_sat = Some(reverse_pair.limits.maximal);
swapper_feerate = Some(reverse_pair.fees.percentage);
debug!( debug!(
"Preparing Lightning Receive Swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat" "Preparing Lightning Receive Swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat"
); );
} }
PaymentMethod::BitcoinAddress => { PaymentMethod::BitcoinAddress => {
let Some(payer_amount_sat) = req.payer_amount_sat else { let payer_amount_sat = req.payer_amount_sat;
return Err(PaymentError::AmountMissing { err: "`payer_amount_sat` must be specified when `PaymentMethod::BitcoinAddress` is used.".to_string() });
};
let pair = let pair =
self.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)?; self.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)?;
let claim_fees_sat = pair.fees.claim_estimate(); let claim_fees_sat = pair.fees.claim_estimate();
let server_fees_sat = pair.fees.server(); let server_fees_sat = pair.fees.server();
fees_sat = pair.fees.boltz(payer_amount_sat) + claim_fees_sat + server_fees_sat; let service_fees_sat = payer_amount_sat
debug!( .map(|user_lockup_amount_sat| pair.fees.boltz(user_lockup_amount_sat))
"Preparing Chain Receive Swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat" .unwrap_or_default();
);
min_payer_amount_sat = Some(pair.limits.minimal);
max_payer_amount_sat = Some(pair.limits.maximal);
swapper_feerate = Some(pair.fees.percentage);
fees_sat = service_fees_sat + claim_fees_sat + server_fees_sat;
debug!("Preparing Chain Receive Swap with: payer_amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}");
} }
PaymentMethod::LiquidAddress => { PaymentMethod::LiquidAddress => {
fees_sat = 0; fees_sat = 0;
debug!( let payer_amount_sat = req.payer_amount_sat;
"Preparing Liquid Receive Swap with: amount_sat {:?} sat, fees_sat {fees_sat} sat", debug!("Preparing Liquid Receive Swap with: amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}");
req.payer_amount_sat
);
} }
}; };
@@ -1681,6 +1692,9 @@ impl LiquidSdk {
payer_amount_sat: req.payer_amount_sat, payer_amount_sat: req.payer_amount_sat,
fees_sat, fees_sat,
payment_method: req.payment_method.clone(), payment_method: req.payment_method.clone(),
min_payer_amount_sat,
max_payer_amount_sat,
swapper_feerate,
}) })
} }
@@ -1708,6 +1722,7 @@ impl LiquidSdk {
payment_method, payment_method,
payer_amount_sat: amount_sat, payer_amount_sat: amount_sat,
fees_sat, fees_sat,
..
} = &req.prepare_response; } = &req.prepare_response;
match payment_method { match payment_method {
@@ -1733,12 +1748,7 @@ impl LiquidSdk {
self.create_receive_swap(*amount_sat, *fees_sat, description, description_hash) self.create_receive_swap(*amount_sat, *fees_sat, description, description_hash)
.await .await
} }
PaymentMethod::BitcoinAddress => { PaymentMethod::BitcoinAddress => self.receive_onchain(*amount_sat, *fees_sat).await,
let Some(amount_sat) = amount_sat else {
return Err(PaymentError::AmountMissing { err: "`amount_sat` must be specified when `PaymentMethod::BitcoinAddress` is used.".to_string() });
};
self.receive_onchain(*amount_sat, *fees_sat).await
}
PaymentMethod::LiquidAddress => { PaymentMethod::LiquidAddress => {
let address = self.onchain_wallet.next_unused_address().await?.to_string(); let address = self.onchain_wallet.next_unused_address().await?.to_string();
@@ -1898,15 +1908,19 @@ impl LiquidSdk {
async fn create_receive_chain_swap( async fn create_receive_chain_swap(
&self, &self,
user_lockup_amount_sat: u64, user_lockup_amount_sat: Option<u64>,
fees_sat: u64, fees_sat: u64,
) -> Result<ChainSwap, PaymentError> { ) -> Result<ChainSwap, PaymentError> {
let pair = self.get_and_validate_chain_pair(Direction::Incoming, user_lockup_amount_sat)?; let pair = self.get_and_validate_chain_pair(Direction::Incoming, user_lockup_amount_sat)?;
let claim_fees_sat = pair.fees.claim_estimate(); let claim_fees_sat = pair.fees.claim_estimate();
let server_fees_sat = pair.fees.server(); let server_fees_sat = pair.fees.server();
// Service fees are 0 if this is a zero-amount swap
let service_fees_sat = user_lockup_amount_sat
.map(|user_lockup_amount_sat| pair.fees.boltz(user_lockup_amount_sat))
.unwrap_or_default();
ensure_sdk!( ensure_sdk!(
fees_sat == pair.fees.boltz(user_lockup_amount_sat) + claim_fees_sat + server_fees_sat, fees_sat == service_fees_sat + claim_fees_sat + server_fees_sat,
PaymentError::InvalidOrExpiredFees PaymentError::InvalidOrExpiredFees
); );
@@ -1938,7 +1952,7 @@ impl LiquidSdk {
preimage_hash: preimage.sha256, preimage_hash: preimage.sha256,
claim_public_key: Some(claim_public_key), claim_public_key: Some(claim_public_key),
refund_public_key: Some(refund_public_key), refund_public_key: Some(refund_public_key),
user_lock_amount: Some(user_lockup_amount_sat), user_lock_amount: user_lockup_amount_sat,
server_lock_amount: None, server_lock_amount: None,
pair_hash: Some(pair.hash.clone()), pair_hash: Some(pair.hash.clone()),
referral_id: None, referral_id: None,
@@ -1949,8 +1963,12 @@ impl LiquidSdk {
let create_response_json = let create_response_json =
ChainSwap::from_boltz_struct_to_json(&create_response, &swap_id)?; ChainSwap::from_boltz_struct_to_json(&create_response, &swap_id)?;
let accept_zero_conf = user_lockup_amount_sat <= pair.limits.maximal_zero_conf; let accept_zero_conf = user_lockup_amount_sat
let receiver_amount_sat = user_lockup_amount_sat - fees_sat; .map(|user_lockup_amount_sat| user_lockup_amount_sat <= pair.limits.maximal_zero_conf)
.unwrap_or(false);
let receiver_amount_sat = user_lockup_amount_sat
.map(|user_lockup_amount_sat| user_lockup_amount_sat - fees_sat)
.unwrap_or(0);
let swap = ChainSwap { let swap = ChainSwap {
id: swap_id.clone(), id: swap_id.clone(),
@@ -1960,7 +1978,7 @@ impl LiquidSdk {
timeout_block_height: create_response.lockup_details.timeout_block_height, timeout_block_height: create_response.lockup_details.timeout_block_height,
preimage: preimage_str, preimage: preimage_str,
description: Some("Bitcoin transfer".to_string()), description: Some("Bitcoin transfer".to_string()),
payer_amount_sat: user_lockup_amount_sat, payer_amount_sat: user_lockup_amount_sat.unwrap_or(0),
receiver_amount_sat, receiver_amount_sat,
claim_fees_sat, claim_fees_sat,
pair_fees_json: serde_json::to_string(&pair).map_err(|e| { pair_fees_json: serde_json::to_string(&pair).map_err(|e| {
@@ -1983,15 +2001,18 @@ impl LiquidSdk {
} }
/// Receive from a Bitcoin transaction via a chain swap. /// Receive from a Bitcoin transaction via a chain swap.
///
/// If no `user_lockup_amount_sat` is specified, this is an amountless swap and `fees_sat` exclude
/// the service fees.
async fn receive_onchain( async fn receive_onchain(
&self, &self,
payer_amount_sat: u64, user_lockup_amount_sat: Option<u64>,
fees_sat: u64, fees_sat: u64,
) -> Result<ReceivePaymentResponse, PaymentError> { ) -> Result<ReceivePaymentResponse, PaymentError> {
self.ensure_is_started().await?; self.ensure_is_started().await?;
let swap = self let swap = self
.create_receive_chain_swap(payer_amount_sat, fees_sat) .create_receive_chain_swap(user_lockup_amount_sat, fees_sat)
.await?; .await?;
let create_response = swap.get_boltz_create_response()?; let create_response = swap.get_boltz_create_response()?;
let address = create_response.lockup_details.lockup_address; let address = create_response.lockup_details.lockup_address;
@@ -2170,7 +2191,7 @@ impl LiquidSdk {
let swap = self let swap = self
.create_receive_chain_swap( .create_receive_chain_swap(
req.prepare_response.amount_sat, Some(req.prepare_response.amount_sat),
req.prepare_response.fees_sat, req.prepare_response.fees_sat,
) )
.await?; .await?;

View File

@@ -8,6 +8,7 @@ use boltz_client::{
elements::secp256k1_zkp::{MusigPartialSignature, MusigPubNonce}, elements::secp256k1_zkp::{MusigPartialSignature, MusigPubNonce},
network::{electrum::ElectrumConfig, Chain}, network::{electrum::ElectrumConfig, Chain},
util::secrets::Preimage, util::secrets::Preimage,
Amount,
}; };
use log::info; use log::info;
use url::Url; use url::Url;
@@ -179,6 +180,23 @@ impl Swapper for BoltzSwapper {
Ok((pair_outgoing, pair_incoming)) Ok((pair_outgoing, pair_incoming))
} }
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, PaymentError> {
self.client
.get_quote(swap_id)
.map(|r| Amount::from_sat(r.amount))
.map_err(Into::into)
}
fn accept_zero_amount_chain_swap_quote(
&self,
swap_id: &str,
server_lockup_sat: u64,
) -> Result<(), PaymentError> {
self.client
.accept_quote(swap_id, server_lockup_sat)
.map_err(Into::into)
}
/// Get a submarine pair information /// Get a submarine pair information
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> { fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> {
Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair()) Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair())

View File

@@ -8,6 +8,7 @@ use boltz_client::{
SubmarineClaimTxResponse, SubmarinePair, SubmarineClaimTxResponse, SubmarinePair,
}, },
network::Chain, network::Chain,
Amount,
}; };
use tokio::sync::{broadcast, watch}; use tokio::sync::{broadcast, watch};
@@ -40,6 +41,19 @@ pub trait Swapper: Send + Sync {
/// Get the current rate, limits and fees for both swap directions /// Get the current rate, limits and fees for both swap directions
fn get_chain_pairs(&self) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError>; fn get_chain_pairs(&self) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError>;
/// Get the quote for a Zero-Amount Receive Chain Swap.
///
/// If the user locked-up funds in the valid range this will return that amount. In all other
/// cases, this will return an error.
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, PaymentError>;
/// Accept a specific quote for a Zero-Amount Receive Chain Swap
fn accept_zero_amount_chain_swap_quote(
&self,
swap_id: &str,
server_lockup_sat: u64,
) -> Result<(), PaymentError>;
/// Get a submarine pair information /// Get a submarine pair information
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError>; fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError>;

View File

@@ -8,7 +8,7 @@ use boltz_client::{
SubmarinePair, SwapTree, SubmarinePair, SwapTree,
}, },
util::secrets::Preimage, util::secrets::Preimage,
PublicKey, Amount, PublicKey,
}; };
use sdk_common::invoice::parse_invoice; use sdk_common::invoice::parse_invoice;
@@ -313,4 +313,16 @@ impl Swapper for MockSwapper {
fn get_bolt12_invoice(&self, _offer: &str, _amount_sat: u64) -> Result<String, PaymentError> { fn get_bolt12_invoice(&self, _offer: &str, _amount_sat: u64) -> Result<String, PaymentError> {
unimplemented!() unimplemented!()
} }
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, PaymentError> {
unimplemented!()
}
fn accept_zero_amount_chain_swap_quote(
&self,
_swap_id: &str,
_server_lockup_sat: u64,
) -> Result<(), PaymentError> {
unimplemented!()
}
} }

View File

@@ -1411,6 +1411,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_connect_request(raw); return dco_decode_connect_request(raw);
} }
@protected
double dco_decode_box_autoadd_f_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as double;
}
@protected @protected
GetPaymentRequest dco_decode_box_autoadd_get_payment_request(dynamic raw) { GetPaymentRequest dco_decode_box_autoadd_get_payment_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2379,6 +2385,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_box_autoadd_bool(raw); return raw == null ? null : dco_decode_box_autoadd_bool(raw);
} }
@protected
double? dco_decode_opt_box_autoadd_f_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null ? null : dco_decode_box_autoadd_f_64(raw);
}
@protected @protected
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw) { PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@@ -2691,11 +2703,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
PrepareReceiveResponse dco_decode_prepare_receive_response(dynamic raw) { PrepareReceiveResponse dco_decode_prepare_receive_response(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>; final arr = raw as List<dynamic>;
if (arr.length != 3) throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
return PrepareReceiveResponse( return PrepareReceiveResponse(
paymentMethod: dco_decode_payment_method(arr[0]), paymentMethod: dco_decode_payment_method(arr[0]),
payerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[1]), payerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[1]),
feesSat: dco_decode_u_64(arr[2]), feesSat: dco_decode_u_64(arr[2]),
minPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[3]),
maxPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[4]),
swapperFeerate: dco_decode_opt_box_autoadd_f_64(arr[5]),
); );
} }
@@ -3291,6 +3306,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_connect_request(deserializer)); return (sse_decode_connect_request(deserializer));
} }
@protected
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_f_64(deserializer));
}
@protected @protected
GetPaymentRequest sse_decode_box_autoadd_get_payment_request(SseDeserializer deserializer) { GetPaymentRequest sse_decode_box_autoadd_get_payment_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -4303,6 +4324,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@protected
double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_box_autoadd_f_64(deserializer));
} else {
return null;
}
}
@protected @protected
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer) { PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -4664,8 +4696,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_paymentMethod = sse_decode_payment_method(deserializer); var var_paymentMethod = sse_decode_payment_method(deserializer);
var var_payerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); var var_payerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_feesSat = sse_decode_u_64(deserializer); var var_feesSat = sse_decode_u_64(deserializer);
var var_minPayerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_maxPayerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_swapperFeerate = sse_decode_opt_box_autoadd_f_64(deserializer);
return PrepareReceiveResponse( return PrepareReceiveResponse(
paymentMethod: var_paymentMethod, payerAmountSat: var_payerAmountSat, feesSat: var_feesSat); paymentMethod: var_paymentMethod,
payerAmountSat: var_payerAmountSat,
feesSat: var_feesSat,
minPayerAmountSat: var_minPayerAmountSat,
maxPayerAmountSat: var_maxPayerAmountSat,
swapperFeerate: var_swapperFeerate);
} }
@protected @protected
@@ -5308,6 +5348,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_connect_request(self, serializer); sse_encode_connect_request(self, serializer);
} }
@protected
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_f_64(self, serializer);
}
@protected @protected
void sse_encode_box_autoadd_get_payment_request(GetPaymentRequest self, SseSerializer serializer) { void sse_encode_box_autoadd_get_payment_request(GetPaymentRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -6175,6 +6221,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@protected
void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_box_autoadd_f_64(self, serializer);
}
}
@protected @protected
void sse_encode_opt_box_autoadd_i_64(PlatformInt64? self, SseSerializer serializer) { void sse_encode_opt_box_autoadd_i_64(PlatformInt64? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -6494,6 +6550,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_payment_method(self.paymentMethod, serializer); sse_encode_payment_method(self.paymentMethod, serializer);
sse_encode_opt_box_autoadd_u_64(self.payerAmountSat, serializer); sse_encode_opt_box_autoadd_u_64(self.payerAmountSat, serializer);
sse_encode_u_64(self.feesSat, serializer); sse_encode_u_64(self.feesSat, serializer);
sse_encode_opt_box_autoadd_u_64(self.minPayerAmountSat, serializer);
sse_encode_opt_box_autoadd_u_64(self.maxPayerAmountSat, serializer);
sse_encode_opt_box_autoadd_f_64(self.swapperFeerate, serializer);
} }
@protected @protected

View File

@@ -107,6 +107,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
ConnectRequest dco_decode_box_autoadd_connect_request(dynamic raw); ConnectRequest dco_decode_box_autoadd_connect_request(dynamic raw);
@protected
double dco_decode_box_autoadd_f_64(dynamic raw);
@protected @protected
GetPaymentRequest dco_decode_box_autoadd_get_payment_request(dynamic raw); GetPaymentRequest dco_decode_box_autoadd_get_payment_request(dynamic raw);
@@ -398,6 +401,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
bool? dco_decode_opt_box_autoadd_bool(dynamic raw); bool? dco_decode_opt_box_autoadd_bool(dynamic raw);
@protected
double? dco_decode_opt_box_autoadd_f_64(dynamic raw);
@protected @protected
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw); PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
@@ -654,6 +660,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
ConnectRequest sse_decode_box_autoadd_connect_request(SseDeserializer deserializer); ConnectRequest sse_decode_box_autoadd_connect_request(SseDeserializer deserializer);
@protected
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer);
@protected @protected
GetPaymentRequest sse_decode_box_autoadd_get_payment_request(SseDeserializer deserializer); GetPaymentRequest sse_decode_box_autoadd_get_payment_request(SseDeserializer deserializer);
@@ -945,6 +954,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer); bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer);
@protected
double? sse_decode_opt_box_autoadd_f_64(SseDeserializer deserializer);
@protected @protected
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer); PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
@@ -1243,6 +1255,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return ptr; return ptr;
} }
@protected
ffi.Pointer<ffi.Double> cst_encode_box_autoadd_f_64(double raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return wire.cst_new_box_autoadd_f_64(cst_encode_f_64(raw));
}
@protected @protected
ffi.Pointer<wire_cst_get_payment_request> cst_encode_box_autoadd_get_payment_request( ffi.Pointer<wire_cst_get_payment_request> cst_encode_box_autoadd_get_payment_request(
GetPaymentRequest raw) { GetPaymentRequest raw) {
@@ -1708,6 +1726,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return raw == null ? ffi.nullptr : cst_encode_box_autoadd_bool(raw); return raw == null ? ffi.nullptr : cst_encode_box_autoadd_bool(raw);
} }
@protected
ffi.Pointer<ffi.Double> cst_encode_opt_box_autoadd_f_64(double? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw == null ? ffi.nullptr : cst_encode_box_autoadd_f_64(raw);
}
@protected @protected
ffi.Pointer<ffi.Int64> cst_encode_opt_box_autoadd_i_64(PlatformInt64? raw) { ffi.Pointer<ffi.Int64> cst_encode_opt_box_autoadd_i_64(PlatformInt64? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs // Codec=Cst (C-struct based), see doc to use other codecs
@@ -2895,6 +2919,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.payment_method = cst_encode_payment_method(apiObj.paymentMethod); wireObj.payment_method = cst_encode_payment_method(apiObj.paymentMethod);
wireObj.payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.payerAmountSat); wireObj.payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.payerAmountSat);
wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat); wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat);
wireObj.min_payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.minPayerAmountSat);
wireObj.max_payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.maxPayerAmountSat);
wireObj.swapper_feerate = cst_encode_opt_box_autoadd_f_64(apiObj.swapperFeerate);
} }
@protected @protected
@@ -3308,6 +3335,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_box_autoadd_connect_request(ConnectRequest self, SseSerializer serializer); void sse_encode_box_autoadd_connect_request(ConnectRequest self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer);
@protected @protected
void sse_encode_box_autoadd_get_payment_request(GetPaymentRequest self, SseSerializer serializer); void sse_encode_box_autoadd_get_payment_request(GetPaymentRequest self, SseSerializer serializer);
@@ -3607,6 +3637,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer); void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_f_64(double? self, SseSerializer serializer);
@protected @protected
void sse_encode_opt_box_autoadd_i_64(PlatformInt64? self, SseSerializer serializer); void sse_encode_opt_box_autoadd_i_64(PlatformInt64? self, SseSerializer serializer);
@@ -4689,6 +4722,20 @@ class RustLibWire implements BaseWire {
late final _cst_new_box_autoadd_connect_request = late final _cst_new_box_autoadd_connect_request =
_cst_new_box_autoadd_connect_requestPtr.asFunction<ffi.Pointer<wire_cst_connect_request> Function()>(); _cst_new_box_autoadd_connect_requestPtr.asFunction<ffi.Pointer<wire_cst_connect_request> Function()>();
ffi.Pointer<ffi.Double> cst_new_box_autoadd_f_64(
double value,
) {
return _cst_new_box_autoadd_f_64(
value,
);
}
late final _cst_new_box_autoadd_f_64Ptr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Double> Function(ffi.Double)>>(
'frbgen_breez_liquid_cst_new_box_autoadd_f_64');
late final _cst_new_box_autoadd_f_64 =
_cst_new_box_autoadd_f_64Ptr.asFunction<ffi.Pointer<ffi.Double> Function(double)>();
ffi.Pointer<wire_cst_get_payment_request> cst_new_box_autoadd_get_payment_request() { ffi.Pointer<wire_cst_get_payment_request> cst_new_box_autoadd_get_payment_request() {
return _cst_new_box_autoadd_get_payment_request(); return _cst_new_box_autoadd_get_payment_request();
} }
@@ -5744,6 +5791,12 @@ final class wire_cst_prepare_receive_response extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int fees_sat; external int fees_sat;
external ffi.Pointer<ffi.Uint64> min_payer_amount_sat;
external ffi.Pointer<ffi.Uint64> max_payer_amount_sat;
external ffi.Pointer<ffi.Double> swapper_feerate;
} }
final class wire_cst_receive_payment_request extends ffi.Struct { final class wire_cst_receive_payment_request extends ffi.Struct {

View File

@@ -936,16 +936,49 @@ class PrepareReceiveRequest {
class PrepareReceiveResponse { class PrepareReceiveResponse {
final PaymentMethod paymentMethod; final PaymentMethod paymentMethod;
final BigInt? payerAmountSat; final BigInt? payerAmountSat;
/// Generally represents the total fees that would be paid to send or receive this payment.
///
/// In case of Zero-Amount Receive Chain swaps, the swapper service fee (`swapper_feerate` times
/// the amount) is paid in addition to `fees_sat`. The swapper service feerate is already known
/// in the beginning, but the exact swapper service fee will only be known when the
/// `payer_amount_sat` is known.
///
/// In all other types of swaps, the swapper service fee is included in `fees_sat`.
final BigInt feesSat; final BigInt feesSat;
/// The minimum amount the payer can send for this swap to succeed.
///
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
final BigInt? minPayerAmountSat;
/// The maximum amount the payer can send for this swap to succeed.
///
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
final BigInt? maxPayerAmountSat;
/// The percentage of the sent amount that will count towards the service fee.
///
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
final double? swapperFeerate;
const PrepareReceiveResponse({ const PrepareReceiveResponse({
required this.paymentMethod, required this.paymentMethod,
this.payerAmountSat, this.payerAmountSat,
required this.feesSat, required this.feesSat,
this.minPayerAmountSat,
this.maxPayerAmountSat,
this.swapperFeerate,
}); });
@override @override
int get hashCode => paymentMethod.hashCode ^ payerAmountSat.hashCode ^ feesSat.hashCode; int get hashCode =>
paymentMethod.hashCode ^
payerAmountSat.hashCode ^
feesSat.hashCode ^
minPayerAmountSat.hashCode ^
maxPayerAmountSat.hashCode ^
swapperFeerate.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -954,7 +987,10 @@ class PrepareReceiveResponse {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
paymentMethod == other.paymentMethod && paymentMethod == other.paymentMethod &&
payerAmountSat == other.payerAmountSat && payerAmountSat == other.payerAmountSat &&
feesSat == other.feesSat; feesSat == other.feesSat &&
minPayerAmountSat == other.minPayerAmountSat &&
maxPayerAmountSat == other.maxPayerAmountSat &&
swapperFeerate == other.swapperFeerate;
} }
/// An argument when calling [crate::sdk::LiquidSdk::prepare_refund]. /// An argument when calling [crate::sdk::LiquidSdk::prepare_refund].

View File

@@ -939,6 +939,20 @@ class FlutterBreezLiquidBindings {
_frbgen_breez_liquid_cst_new_box_autoadd_connect_requestPtr _frbgen_breez_liquid_cst_new_box_autoadd_connect_requestPtr
.asFunction<ffi.Pointer<wire_cst_connect_request> Function()>(); .asFunction<ffi.Pointer<wire_cst_connect_request> Function()>();
ffi.Pointer<ffi.Double> frbgen_breez_liquid_cst_new_box_autoadd_f_64(
double value,
) {
return _frbgen_breez_liquid_cst_new_box_autoadd_f_64(
value,
);
}
late final _frbgen_breez_liquid_cst_new_box_autoadd_f_64Ptr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Double> Function(ffi.Double)>>(
'frbgen_breez_liquid_cst_new_box_autoadd_f_64');
late final _frbgen_breez_liquid_cst_new_box_autoadd_f_64 =
_frbgen_breez_liquid_cst_new_box_autoadd_f_64Ptr.asFunction<ffi.Pointer<ffi.Double> Function(double)>();
ffi.Pointer<wire_cst_get_payment_request> frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request() { ffi.Pointer<wire_cst_get_payment_request> frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request() {
return _frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request(); return _frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request();
} }
@@ -4327,6 +4341,12 @@ final class wire_cst_prepare_receive_response extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int fees_sat; external int fees_sat;
external ffi.Pointer<ffi.Uint64> min_payer_amount_sat;
external ffi.Pointer<ffi.Uint64> max_payer_amount_sat;
external ffi.Pointer<ffi.Double> swapper_feerate;
} }
final class wire_cst_receive_payment_request extends ffi.Struct { final class wire_cst_receive_payment_request extends ffi.Struct {

View File

@@ -1687,6 +1687,8 @@ fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiv
) { ) {
return null return null
} }
val paymentMethod = prepareReceiveResponse.getString("paymentMethod")?.let { asPaymentMethod(it) }!!
val feesSat = prepareReceiveResponse.getDouble("feesSat").toULong()
val payerAmountSat = val payerAmountSat =
if (hasNonNullKey( if (hasNonNullKey(
prepareReceiveResponse, prepareReceiveResponse,
@@ -1697,16 +1699,47 @@ fun asPrepareReceiveResponse(prepareReceiveResponse: ReadableMap): PrepareReceiv
} else { } else {
null null
} }
val paymentMethod = prepareReceiveResponse.getString("paymentMethod")?.let { asPaymentMethod(it) }!! val minPayerAmountSat =
val feesSat = prepareReceiveResponse.getDouble("feesSat").toULong() if (hasNonNullKey(
return PrepareReceiveResponse(payerAmountSat, paymentMethod, feesSat) prepareReceiveResponse,
"minPayerAmountSat",
)
) {
prepareReceiveResponse.getDouble("minPayerAmountSat").toULong()
} else {
null
}
val maxPayerAmountSat =
if (hasNonNullKey(
prepareReceiveResponse,
"maxPayerAmountSat",
)
) {
prepareReceiveResponse.getDouble("maxPayerAmountSat").toULong()
} else {
null
}
val swapperFeerate =
if (hasNonNullKey(
prepareReceiveResponse,
"swapperFeerate",
)
) {
prepareReceiveResponse.getDouble("swapperFeerate")
} else {
null
}
return PrepareReceiveResponse(paymentMethod, feesSat, payerAmountSat, minPayerAmountSat, maxPayerAmountSat, swapperFeerate)
} }
fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap = fun readableMapOf(prepareReceiveResponse: PrepareReceiveResponse): ReadableMap =
readableMapOf( readableMapOf(
"payerAmountSat" to prepareReceiveResponse.payerAmountSat,
"paymentMethod" to prepareReceiveResponse.paymentMethod.name.lowercase(), "paymentMethod" to prepareReceiveResponse.paymentMethod.name.lowercase(),
"feesSat" to prepareReceiveResponse.feesSat, "feesSat" to prepareReceiveResponse.feesSat,
"payerAmountSat" to prepareReceiveResponse.payerAmountSat,
"minPayerAmountSat" to prepareReceiveResponse.minPayerAmountSat,
"maxPayerAmountSat" to prepareReceiveResponse.maxPayerAmountSat,
"swapperFeerate" to prepareReceiveResponse.swapperFeerate,
) )
fun asPrepareReceiveResponseList(arr: ReadableArray): List<PrepareReceiveResponse> { fun asPrepareReceiveResponseList(arr: ReadableArray): List<PrepareReceiveResponse> {

View File

@@ -1972,13 +1972,6 @@ enum BreezSDKLiquidMapper {
} }
static func asPrepareReceiveResponse(prepareReceiveResponse: [String: Any?]) throws -> PrepareReceiveResponse { static func asPrepareReceiveResponse(prepareReceiveResponse: [String: Any?]) throws -> PrepareReceiveResponse {
var payerAmountSat: UInt64?
if hasNonNilKey(data: prepareReceiveResponse, key: "payerAmountSat") {
guard let payerAmountSatTmp = prepareReceiveResponse["payerAmountSat"] as? UInt64 else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "payerAmountSat"))
}
payerAmountSat = payerAmountSatTmp
}
guard let paymentMethodTmp = prepareReceiveResponse["paymentMethod"] as? String else { guard let paymentMethodTmp = prepareReceiveResponse["paymentMethod"] as? String else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentMethod", typeName: "PrepareReceiveResponse")) throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "paymentMethod", typeName: "PrepareReceiveResponse"))
} }
@@ -1987,15 +1980,46 @@ enum BreezSDKLiquidMapper {
guard let feesSat = prepareReceiveResponse["feesSat"] as? UInt64 else { guard let feesSat = prepareReceiveResponse["feesSat"] as? UInt64 else {
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PrepareReceiveResponse")) throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PrepareReceiveResponse"))
} }
var payerAmountSat: UInt64?
if hasNonNilKey(data: prepareReceiveResponse, key: "payerAmountSat") {
guard let payerAmountSatTmp = prepareReceiveResponse["payerAmountSat"] as? UInt64 else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "payerAmountSat"))
}
payerAmountSat = payerAmountSatTmp
}
var minPayerAmountSat: UInt64?
if hasNonNilKey(data: prepareReceiveResponse, key: "minPayerAmountSat") {
guard let minPayerAmountSatTmp = prepareReceiveResponse["minPayerAmountSat"] as? UInt64 else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "minPayerAmountSat"))
}
minPayerAmountSat = minPayerAmountSatTmp
}
var maxPayerAmountSat: UInt64?
if hasNonNilKey(data: prepareReceiveResponse, key: "maxPayerAmountSat") {
guard let maxPayerAmountSatTmp = prepareReceiveResponse["maxPayerAmountSat"] as? UInt64 else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "maxPayerAmountSat"))
}
maxPayerAmountSat = maxPayerAmountSatTmp
}
var swapperFeerate: Double?
if hasNonNilKey(data: prepareReceiveResponse, key: "swapperFeerate") {
guard let swapperFeerateTmp = prepareReceiveResponse["swapperFeerate"] as? Double else {
throw SdkError.Generic(message: errUnexpectedValue(fieldName: "swapperFeerate"))
}
swapperFeerate = swapperFeerateTmp
}
return PrepareReceiveResponse(payerAmountSat: payerAmountSat, paymentMethod: paymentMethod, feesSat: feesSat) return PrepareReceiveResponse(paymentMethod: paymentMethod, feesSat: feesSat, payerAmountSat: payerAmountSat, minPayerAmountSat: minPayerAmountSat, maxPayerAmountSat: maxPayerAmountSat, swapperFeerate: swapperFeerate)
} }
static func dictionaryOf(prepareReceiveResponse: PrepareReceiveResponse) -> [String: Any?] { static func dictionaryOf(prepareReceiveResponse: PrepareReceiveResponse) -> [String: Any?] {
return [ return [
"payerAmountSat": prepareReceiveResponse.payerAmountSat == nil ? nil : prepareReceiveResponse.payerAmountSat,
"paymentMethod": valueOf(paymentMethod: prepareReceiveResponse.paymentMethod), "paymentMethod": valueOf(paymentMethod: prepareReceiveResponse.paymentMethod),
"feesSat": prepareReceiveResponse.feesSat, "feesSat": prepareReceiveResponse.feesSat,
"payerAmountSat": prepareReceiveResponse.payerAmountSat == nil ? nil : prepareReceiveResponse.payerAmountSat,
"minPayerAmountSat": prepareReceiveResponse.minPayerAmountSat == nil ? nil : prepareReceiveResponse.minPayerAmountSat,
"maxPayerAmountSat": prepareReceiveResponse.maxPayerAmountSat == nil ? nil : prepareReceiveResponse.maxPayerAmountSat,
"swapperFeerate": prepareReceiveResponse.swapperFeerate == nil ? nil : prepareReceiveResponse.swapperFeerate,
] ]
} }

View File

@@ -299,9 +299,12 @@ export interface PrepareReceiveRequest {
} }
export interface PrepareReceiveResponse { export interface PrepareReceiveResponse {
payerAmountSat?: number
paymentMethod: PaymentMethod paymentMethod: PaymentMethod
feesSat: number feesSat: number
payerAmountSat?: number
minPayerAmountSat?: number
maxPayerAmountSat?: number
swapperFeerate?: number
} }
export interface PrepareRefundRequest { export interface PrepareRefundRequest {