Merge pull request #332 from breez/lowbal-fees-fixes

Use lowball fees only on lockup and cooperative refund
This commit is contained in:
Roei Erez
2024-06-25 20:03:14 +03:00
committed by GitHub
4 changed files with 75 additions and 40 deletions

View File

@@ -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<Self> {
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<Txid> {
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<Vec<Transaction>> {
@@ -79,17 +88,26 @@ impl LiquidChainService for HybridLiquidChainService {
}
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
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<EsploraTx> = 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<EsploraTx> = response.json().await?;
let history: Vec<History> = json.into_iter().map(Into::into).collect();
Ok(history)
let history: Vec<History> = 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![]))
}
}
}
}

View File

@@ -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<f32> {
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

View File

@@ -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<u64> {
async fn estimate_onchain_tx_fee(
&self,
amount_sat: u64,
address: &str,
fee_rate: Option<f32>,
) -> Result<u64> {
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<String, PaymentError> {
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
}
}

View File

@@ -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,
)