mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2026-01-06 07:34:24 +01:00
Add zero-amount Receive Chain Swap (#538)
Add support for Zero-Amount Receive Chain Swaps
This commit is contained in:
4
lib/Cargo.lock
generated
4
lib/Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
@@ -723,7 +723,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "boltz-client"
|
||||
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 = [
|
||||
"bip39",
|
||||
"bitcoin 0.31.2",
|
||||
|
||||
@@ -367,6 +367,9 @@ typedef struct wire_cst_prepare_receive_response {
|
||||
int32_t payment_method;
|
||||
uint64_t *payer_amount_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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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_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_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_i_64);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_liquid_address_data);
|
||||
|
||||
@@ -431,9 +431,12 @@ dictionary PrepareReceiveRequest {
|
||||
};
|
||||
|
||||
dictionary PrepareReceiveResponse {
|
||||
u64? payer_amount_sat;
|
||||
PaymentMethod payment_method;
|
||||
u64 fees_sat;
|
||||
u64? payer_amount_sat;
|
||||
u64? min_payer_amount_sat;
|
||||
u64? max_payer_amount_sat;
|
||||
f64? swapper_feerate;
|
||||
};
|
||||
|
||||
dictionary ReceivePaymentRequest {
|
||||
|
||||
@@ -436,6 +436,20 @@ impl ChainSwapHandler {
|
||||
| ChainSwapStates::TransactionLockupFailed
|
||||
| ChainSwapStates::TransactionRefunded
|
||||
| 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() {
|
||||
None => {
|
||||
warn!("Chain Swap {id} is in an unrecoverable state: {swap_state:?}");
|
||||
@@ -453,7 +467,7 @@ impl ChainSwapHandler {
|
||||
}
|
||||
}
|
||||
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(())
|
||||
@@ -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<()> {
|
||||
let id = &update.id;
|
||||
let status = &update.status;
|
||||
|
||||
@@ -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> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
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_payerAmountSat = <Option<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 {
|
||||
payment_method: var_paymentMethod,
|
||||
payer_amount_sat: var_payerAmountSat,
|
||||
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.payer_amount_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()
|
||||
}
|
||||
@@ -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> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
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);
|
||||
<Option<u64>>::sse_encode(self.payer_amount_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()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::GetPaymentRequest {
|
||||
@@ -9554,6 +9593,9 @@ mod io {
|
||||
payment_method: self.payment_method.cst_decode(),
|
||||
payer_amount_sat: self.payer_amount_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(),
|
||||
payer_amount_sat: core::ptr::null_mut(),
|
||||
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]
|
||||
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request(
|
||||
) -> *mut wire_cst_get_payment_request {
|
||||
@@ -12840,6 +12890,9 @@ mod io {
|
||||
payment_method: i32,
|
||||
payer_amount_sat: *mut u64,
|
||||
fees_sat: u64,
|
||||
min_payer_amount_sat: *mut u64,
|
||||
max_payer_amount_sat: *mut u64,
|
||||
swapper_feerate: *mut f64,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use boltz_client::boltz::ChainPair;
|
||||
use boltz_client::{
|
||||
bitcoin::ScriptBuf,
|
||||
network::Chain,
|
||||
@@ -302,7 +303,31 @@ pub struct PrepareReceiveRequest {
|
||||
pub struct PrepareReceiveResponse {
|
||||
pub payment_method: PaymentMethod,
|
||||
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,
|
||||
|
||||
/// 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].
|
||||
@@ -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> {
|
||||
let chain_swap_details = self.get_boltz_create_response()?.claim_details;
|
||||
let our_pubkey = self.get_claim_keypair()?.public_key();
|
||||
|
||||
@@ -267,6 +267,32 @@ impl Persister {
|
||||
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
|
||||
pub(crate) fn set_chain_swap_claim_tx_id(
|
||||
&self,
|
||||
|
||||
@@ -706,10 +706,12 @@ impl LiquidSdk {
|
||||
fn get_and_validate_chain_pair(
|
||||
&self,
|
||||
direction: Direction,
|
||||
user_lockup_amount_sat: u64,
|
||||
user_lockup_amount_sat: Option<u64>,
|
||||
) -> Result<ChainPair, PaymentError> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1631,6 +1633,9 @@ impl LiquidSdk {
|
||||
) -> Result<PrepareReceiveResponse, PaymentError> {
|
||||
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;
|
||||
match req.payment_method {
|
||||
PaymentMethod::Lightning => {
|
||||
@@ -1651,29 +1656,35 @@ impl LiquidSdk {
|
||||
.within(payer_amount_sat)
|
||||
.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!(
|
||||
"Preparing Lightning Receive Swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat"
|
||||
);
|
||||
}
|
||||
PaymentMethod::BitcoinAddress => {
|
||||
let Some(payer_amount_sat) = req.payer_amount_sat else {
|
||||
return Err(PaymentError::AmountMissing { err: "`payer_amount_sat` must be specified when `PaymentMethod::BitcoinAddress` is used.".to_string() });
|
||||
};
|
||||
let payer_amount_sat = req.payer_amount_sat;
|
||||
let pair =
|
||||
self.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)?;
|
||||
let claim_fees_sat = pair.fees.claim_estimate();
|
||||
let server_fees_sat = pair.fees.server();
|
||||
fees_sat = pair.fees.boltz(payer_amount_sat) + claim_fees_sat + server_fees_sat;
|
||||
debug!(
|
||||
"Preparing Chain Receive Swap with: payer_amount_sat {payer_amount_sat} sat, fees_sat {fees_sat} sat"
|
||||
);
|
||||
let service_fees_sat = payer_amount_sat
|
||||
.map(|user_lockup_amount_sat| pair.fees.boltz(user_lockup_amount_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 => {
|
||||
fees_sat = 0;
|
||||
debug!(
|
||||
"Preparing Liquid Receive Swap with: amount_sat {:?} sat, fees_sat {fees_sat} sat",
|
||||
req.payer_amount_sat
|
||||
);
|
||||
let payer_amount_sat = req.payer_amount_sat;
|
||||
debug!("Preparing Liquid Receive Swap with: amount_sat {payer_amount_sat:?}, fees_sat {fees_sat}");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1681,6 +1692,9 @@ impl LiquidSdk {
|
||||
payer_amount_sat: req.payer_amount_sat,
|
||||
fees_sat,
|
||||
payment_method: req.payment_method.clone(),
|
||||
min_payer_amount_sat,
|
||||
max_payer_amount_sat,
|
||||
swapper_feerate,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1708,6 +1722,7 @@ impl LiquidSdk {
|
||||
payment_method,
|
||||
payer_amount_sat: amount_sat,
|
||||
fees_sat,
|
||||
..
|
||||
} = &req.prepare_response;
|
||||
|
||||
match payment_method {
|
||||
@@ -1733,12 +1748,7 @@ impl LiquidSdk {
|
||||
self.create_receive_swap(*amount_sat, *fees_sat, description, description_hash)
|
||||
.await
|
||||
}
|
||||
PaymentMethod::BitcoinAddress => {
|
||||
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::BitcoinAddress => self.receive_onchain(*amount_sat, *fees_sat).await,
|
||||
PaymentMethod::LiquidAddress => {
|
||||
let address = self.onchain_wallet.next_unused_address().await?.to_string();
|
||||
|
||||
@@ -1898,15 +1908,19 @@ impl LiquidSdk {
|
||||
|
||||
async fn create_receive_chain_swap(
|
||||
&self,
|
||||
user_lockup_amount_sat: u64,
|
||||
user_lockup_amount_sat: Option<u64>,
|
||||
fees_sat: u64,
|
||||
) -> Result<ChainSwap, PaymentError> {
|
||||
let pair = self.get_and_validate_chain_pair(Direction::Incoming, user_lockup_amount_sat)?;
|
||||
let claim_fees_sat = pair.fees.claim_estimate();
|
||||
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!(
|
||||
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
|
||||
);
|
||||
|
||||
@@ -1938,7 +1952,7 @@ impl LiquidSdk {
|
||||
preimage_hash: preimage.sha256,
|
||||
claim_public_key: Some(claim_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,
|
||||
pair_hash: Some(pair.hash.clone()),
|
||||
referral_id: None,
|
||||
@@ -1949,8 +1963,12 @@ impl LiquidSdk {
|
||||
let create_response_json =
|
||||
ChainSwap::from_boltz_struct_to_json(&create_response, &swap_id)?;
|
||||
|
||||
let accept_zero_conf = user_lockup_amount_sat <= pair.limits.maximal_zero_conf;
|
||||
let receiver_amount_sat = user_lockup_amount_sat - fees_sat;
|
||||
let accept_zero_conf = user_lockup_amount_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 {
|
||||
id: swap_id.clone(),
|
||||
@@ -1960,7 +1978,7 @@ impl LiquidSdk {
|
||||
timeout_block_height: create_response.lockup_details.timeout_block_height,
|
||||
preimage: preimage_str,
|
||||
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,
|
||||
claim_fees_sat,
|
||||
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.
|
||||
///
|
||||
/// If no `user_lockup_amount_sat` is specified, this is an amountless swap and `fees_sat` exclude
|
||||
/// the service fees.
|
||||
async fn receive_onchain(
|
||||
&self,
|
||||
payer_amount_sat: u64,
|
||||
user_lockup_amount_sat: Option<u64>,
|
||||
fees_sat: u64,
|
||||
) -> Result<ReceivePaymentResponse, PaymentError> {
|
||||
self.ensure_is_started().await?;
|
||||
|
||||
let swap = self
|
||||
.create_receive_chain_swap(payer_amount_sat, fees_sat)
|
||||
.create_receive_chain_swap(user_lockup_amount_sat, fees_sat)
|
||||
.await?;
|
||||
let create_response = swap.get_boltz_create_response()?;
|
||||
let address = create_response.lockup_details.lockup_address;
|
||||
@@ -2170,7 +2191,7 @@ impl LiquidSdk {
|
||||
|
||||
let swap = self
|
||||
.create_receive_chain_swap(
|
||||
req.prepare_response.amount_sat,
|
||||
Some(req.prepare_response.amount_sat),
|
||||
req.prepare_response.fees_sat,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -8,6 +8,7 @@ use boltz_client::{
|
||||
elements::secp256k1_zkp::{MusigPartialSignature, MusigPubNonce},
|
||||
network::{electrum::ElectrumConfig, Chain},
|
||||
util::secrets::Preimage,
|
||||
Amount,
|
||||
};
|
||||
use log::info;
|
||||
use url::Url;
|
||||
@@ -179,6 +180,23 @@ impl Swapper for BoltzSwapper {
|
||||
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
|
||||
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> {
|
||||
Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair())
|
||||
|
||||
@@ -8,6 +8,7 @@ use boltz_client::{
|
||||
SubmarineClaimTxResponse, SubmarinePair,
|
||||
},
|
||||
network::Chain,
|
||||
Amount,
|
||||
};
|
||||
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
|
||||
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
|
||||
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError>;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use boltz_client::{
|
||||
SubmarinePair, SwapTree,
|
||||
},
|
||||
util::secrets::Preimage,
|
||||
PublicKey,
|
||||
Amount, PublicKey,
|
||||
};
|
||||
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> {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user