From 1a0127fcfc5dd617b73ae70144d21f5f0c9aacd9 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Tue, 25 Jun 2024 18:46:46 +0300 Subject: [PATCH] Use lowball fees only on lockup and cooperative refund --- lib/core/src/chain/liquid.rs | 64 +++++++++++++++++++++++------------- lib/core/src/model.rs | 7 ++++ lib/core/src/sdk.rs | 40 +++++++++++++--------- lib/core/src/send_swap.rs | 4 +-- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/lib/core/src/chain/liquid.rs b/lib/core/src/chain/liquid.rs index 8e3f2ef..cf3927e 100644 --- a/lib/core/src/chain/liquid.rs +++ b/lib/core/src/chain/liquid.rs @@ -11,7 +11,7 @@ use reqwest::Response; use serde::Deserialize; use std::str::FromStr; -use crate::model::Config; +use crate::model::{Config, LiquidNetwork}; const LIQUID_ESPLORA_URL: &str = "https://lq1.breez.technology/liquid/api"; @@ -44,13 +44,17 @@ struct Status { pub(crate) struct HybridLiquidChainService { electrum_client: ElectrumClient, + network: LiquidNetwork, } impl HybridLiquidChainService { pub(crate) fn new(config: Config) -> Result { let electrum_client = ElectrumClient::new(&ElectrumUrl::new(&config.liquid_electrum_url, true, true))?; - Ok(Self { electrum_client }) + Ok(Self { + electrum_client, + network: config.network, + }) } } @@ -61,17 +65,22 @@ impl LiquidChainService for HybridLiquidChainService { } async fn broadcast(&self, tx: &Transaction, swap_id: Option<&str>) -> Result { - let tx_bytes = tx.serialize(); - info!("Broadcasting Liquid tx: {}", tx_bytes.to_hex()); - let client = reqwest::Client::new(); - let response = client - .post(format!("{LIQUID_ESPLORA_URL}/tx")) - .header("Swap-ID", swap_id.unwrap_or_default()) - .body(tx_bytes.to_hex()) - .send() - .await?; - let txid = Txid::from_str(&response.text().await?)?; - Ok(txid) + match self.network { + LiquidNetwork::Mainnet => { + let tx_bytes = tx.serialize(); + info!("Broadcasting Liquid tx: {}", tx_bytes.to_hex()); + let client = reqwest::Client::new(); + let response = client + .post(format!("{LIQUID_ESPLORA_URL}/tx")) + .header("Swap-ID", swap_id.unwrap_or_default()) + .body(tx_bytes.to_hex()) + .send() + .await?; + let txid = Txid::from_str(&response.text().await?)?; + Ok(txid) + } + LiquidNetwork::Testnet => Ok(self.electrum_client.broadcast(tx)?), + } } async fn get_transactions(&self, txids: &[Txid]) -> Result> { @@ -79,17 +88,26 @@ impl LiquidChainService for HybridLiquidChainService { } async fn get_script_history(&self, script: &Script) -> Result> { - let script = lwk_wollet::elements::bitcoin::Script::from_bytes(script.as_bytes()); - let script_hash = sha256::Hash::hash(script.as_bytes()) - .to_byte_array() - .to_hex(); - let url = format!("{}/scripthash/{}/txs", LIQUID_ESPLORA_URL, script_hash); - // TODO must handle paging -> https://github.com/blockstream/esplora/blob/master/API.md#addresses - let response = get_with_retry(&url, 3).await?; - let json: Vec = response.json().await?; + match self.network { + LiquidNetwork::Mainnet => { + let script = lwk_wollet::elements::bitcoin::Script::from_bytes(script.as_bytes()); + let script_hash = sha256::Hash::hash(script.as_bytes()) + .to_byte_array() + .to_hex(); + let url = format!("{}/scripthash/{}/txs", LIQUID_ESPLORA_URL, script_hash); + // TODO must handle paging -> https://github.com/blockstream/esplora/blob/master/API.md#addresses + let response = get_with_retry(&url, 3).await?; + let json: Vec = response.json().await?; - let history: Vec = json.into_iter().map(Into::into).collect(); - Ok(history) + let history: Vec = json.into_iter().map(Into::into).collect(); + Ok(history) + } + LiquidNetwork::Testnet => { + let mut history_vec = self.electrum_client.get_scripts_history(&[script])?; + let h = history_vec.pop(); + Ok(h.unwrap_or(vec![])) + } + } } } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index de293a3..e414911 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -71,6 +71,13 @@ impl Config { self.zero_conf_max_amount_sat .unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT) } + + pub(crate) fn lowball_fee_rate(&self) -> Option { + match self.network { + LiquidNetwork::Mainnet => Some(LOWBALL_FEE_RATE_SAT_PER_VBYTE * 1000.0), + LiquidNetwork::Testnet => None, + } + } } /// Network chosen for this Liquid SDK instance. Note that it represents both the Liquid and the diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index b9410b0..0e21fce 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -582,14 +582,15 @@ impl LiquidSdk { } /// Estimate the onchain fee for sending the given amount to the given destination address - async fn estimate_onchain_tx_fee(&self, amount_sat: u64, address: &str) -> Result { + async fn estimate_onchain_tx_fee( + &self, + amount_sat: u64, + address: &str, + fee_rate: Option, + ) -> Result { Ok(self .onchain_wallet - .build_tx( - Some(LOWBALL_FEE_RATE_SAT_PER_VBYTE * 1000.0), - address, - amount_sat, - ) + .build_tx(fee_rate, address, amount_sat) .await? .all_fees() .values() @@ -604,7 +605,7 @@ impl LiquidSdk { LiquidNetwork::Testnet => "tlq1pq0wqu32e2xacxeyps22x8gjre4qk3u6r70pj4r62hzczxeyz8x3yxucrpn79zy28plc4x37aaf33kwt6dz2nn6gtkya6h02mwpzy4eh69zzexq7cf5y5" }; - self.estimate_onchain_tx_fee(amount_sat, temp_p2tr_addr) + self.estimate_onchain_tx_fee(amount_sat, temp_p2tr_addr, self.config.lowball_fee_rate()) .await } @@ -625,8 +626,12 @@ impl LiquidSdk { let fees_sat = match self.swapper.check_for_mrh(&req.invoice)? { Some((lbtc_address, _)) => { - self.estimate_onchain_tx_fee(receiver_amount_sat, &lbtc_address) - .await? + self.estimate_onchain_tx_fee( + receiver_amount_sat, + &lbtc_address, + self.config.lowball_fee_rate(), + ) + .await? } None => { let lockup_fees_sat = self.estimate_lockup_tx_fee(receiver_amount_sat).await?; @@ -675,17 +680,22 @@ impl LiquidSdk { async fn refund_send(&self, swap: &SendSwap) -> Result { let amount_sat = get_invoice_amount!(swap.invoice); let output_address = self.onchain_wallet.next_unused_address().await?.to_string(); - let refund_tx_fees_sat = self - .estimate_onchain_tx_fee(amount_sat, &output_address) + let cooperative_refund_tx_fees_sat = self + .estimate_onchain_tx_fee(amount_sat, &output_address, self.config.lowball_fee_rate()) .await?; - let refund_res = - self.swapper - .refund_send_swap_cooperative(swap, &output_address, refund_tx_fees_sat); + let refund_res = self.swapper.refund_send_swap_cooperative( + swap, + &output_address, + cooperative_refund_tx_fees_sat, + ); match refund_res { Ok(res) => Ok(res), Err(e) => { + let non_cooperative_refund_tx_fees_sat = self + .estimate_onchain_tx_fee(swap.receiver_amount_sat, &output_address, None) + .await?; warn!("Cooperative refund failed: {:?}", e); - self.refund_send_non_cooperative(swap, refund_tx_fees_sat) + self.refund_send_non_cooperative(swap, non_cooperative_refund_tx_fees_sat) .await } } diff --git a/lib/core/src/send_swap.rs b/lib/core/src/send_swap.rs index d95088b..299dcbc 100644 --- a/lib/core/src/send_swap.rs +++ b/lib/core/src/send_swap.rs @@ -13,7 +13,7 @@ use tokio::sync::{broadcast, Mutex}; use crate::chain::liquid::LiquidChainService; use crate::model::PaymentState::{Complete, Created, Failed, Pending, Refundable, TimedOut}; -use crate::model::{Config, SendSwap, LOWBALL_FEE_RATE_SAT_PER_VBYTE}; +use crate::model::{Config, SendSwap}; use crate::swapper::Swapper; use crate::wallet::OnchainWallet; use crate::{ensure_sdk, get_invoice_amount}; @@ -196,7 +196,7 @@ impl SendSwapStateHandler { let lockup_tx = self .onchain_wallet .build_tx( - Some(LOWBALL_FEE_RATE_SAT_PER_VBYTE * 1000.0), + self.config.lowball_fee_rate(), &create_response.address, create_response.expected_amount, )