feat: remove swapper initialization at startup (#712)

This commit is contained in:
yse
2025-02-06 14:24:55 +01:00
committed by GitHub
parent 0056429c0d
commit e7a7adaf8a
18 changed files with 610 additions and 396 deletions

View File

@@ -402,6 +402,7 @@ impl ChainSwapHandler {
let quote = self
.swapper
.get_zero_amount_chain_swap_quote(&id)
.await
.map(|quote| quote.to_sat())?;
info!("Got quote of {quote} sat for swap {}", &id);
@@ -420,7 +421,8 @@ impl ChainSwapHandler {
.inspect_err(|e| {
error!("Failed to accept zero-amount swap {id} quote: {e} - trying to erase the accepted receiver amount...");
let _ = self.persister.update_accepted_receiver_amount(&id, None);
})?;
})
.await?;
self.persister.set_chain_swap_auto_accepted_fees(&id)
}
ValidateAmountlessSwapResult::RequiresUserAction {
@@ -828,7 +830,8 @@ impl ChainSwapHandler {
};
let claim_tx = self
.swapper
.create_claim_tx(Swap::Chain(swap.clone()), claim_address.clone())?;
.create_claim_tx(Swap::Chain(swap.clone()), claim_address.clone())
.await?;
// Set the swap claim_tx_id before broadcasting.
// If another claim_tx_id has been set in the meantime, don't broadcast the claim tx
@@ -841,26 +844,26 @@ impl ChainSwapHandler {
let broadcast_res = match claim_tx {
// We attempt broadcasting via chain service, then fallback to Boltz
SdkTransaction::Liquid(tx) => {
self.liquid_chain_service
.broadcast(&tx)
.await
.map(|tx_id| tx_id.to_hex())
.or_else(|err| {
match self.liquid_chain_service.broadcast(&tx).await {
Ok(tx_id) => Ok(tx_id.to_hex()),
Err(err) => {
debug!(
"Could not broadcast claim tx via chain service for Chain swap {swap_id}: {err:?}"
);
"Could not broadcast claim tx via chain service for Chain swap {swap_id}: {err:?}"
);
let claim_tx_hex = tx.serialize().to_lower_hex_string();
self.swapper.broadcast_tx(self.config.network.into(), &claim_tx_hex)
})
}
SdkTransaction::Bitcoin(tx) => {
self.bitcoin_chain_service
.broadcast(&tx)
.map(|tx_id| tx_id.to_hex())
.map_err(|err| PaymentError::Generic {
err: err.to_string(),
})
self.swapper
.broadcast_tx(self.config.network.into(), &claim_tx_hex)
.await
}
}
}
SdkTransaction::Bitcoin(tx) => self
.bitcoin_chain_service
.broadcast(&tx)
.map(|tx_id| tx_id.to_hex())
.map_err(|err| PaymentError::Generic {
err: err.to_string(),
}),
};
match broadcast_res {
@@ -937,11 +940,14 @@ impl ChainSwapHandler {
);
}
let (refund_tx_size, refund_tx_fees_sat) = self.swapper.estimate_refund_broadcast(
Swap::Chain(swap),
refund_address,
Some(fee_rate_sat_per_vb as f64),
)?;
let (refund_tx_size, refund_tx_fees_sat) = self
.swapper
.estimate_refund_broadcast(
Swap::Chain(swap),
refund_address,
Some(fee_rate_sat_per_vb as f64),
)
.await?;
Ok((refund_tx_size, refund_tx_fees_sat, refund_tx_id))
}
@@ -985,13 +991,16 @@ impl ChainSwapHandler {
.get_script_utxos(&script_pk)
.await?;
let SdkTransaction::Bitcoin(refund_tx) = self.swapper.create_refund_tx(
Swap::Chain(swap.clone()),
refund_address,
utxos,
Some(broadcast_fee_rate_sat_per_vb as f64),
is_cooperative,
)?
let SdkTransaction::Bitcoin(refund_tx) = self
.swapper
.create_refund_tx(
Swap::Chain(swap.clone()),
refund_address,
utxos,
Some(broadcast_fee_rate_sat_per_vb as f64),
is_cooperative,
)
.await?
else {
return Err(PaymentError::Generic {
err: format!("Unexpected refund tx type returned for incoming Chain swap {id}",),
@@ -1054,13 +1063,16 @@ impl ChainSwapHandler {
.await?;
let refund_address = self.onchain_wallet.next_unused_address().await?.to_string();
let SdkTransaction::Liquid(refund_tx) = self.swapper.create_refund_tx(
Swap::Chain(swap.clone()),
&refund_address,
utxos,
None,
is_cooperative,
)?
let SdkTransaction::Liquid(refund_tx) = self
.swapper
.create_refund_tx(
Swap::Chain(swap.clone()),
&refund_address,
utxos,
None,
is_cooperative,
)
.await?
else {
return Err(PaymentError::Generic {
err: format!(

View File

@@ -5,7 +5,7 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use boltz_client::{
bitcoin::ScriptBuf,
boltz::ChainPair,
boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2},
network::Chain,
swaps::boltz::{
CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
@@ -164,6 +164,13 @@ impl Config {
external_input_parsers
}
pub(crate) fn default_boltz_url(&self) -> &str {
match self.network {
LiquidNetwork::Mainnet => BOLTZ_MAINNET_URL_V2,
LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
}
}
}
/// Network chosen for this Liquid SDK instance. Note that it represents both the Liquid and the

View File

@@ -1019,12 +1019,11 @@ mod tests {
false,
)?;
assert!(storage
assert!(!storage
.get_payments(&ListPaymentsRequest {
..Default::default()
})?
.first()
.is_some());
.is_empty());
assert!(storage.get_payment(&payment_tx_data.tx_id)?.is_some());
Ok(())

View File

@@ -341,7 +341,8 @@ impl ReceiveSwapHandler {
let claim_address = self.onchain_wallet.next_unused_address().await?.to_string();
let crate::prelude::Transaction::Liquid(claim_tx) = self
.swapper
.create_claim_tx(Swap::Receive(swap.clone()), Some(claim_address))?
.create_claim_tx(Swap::Receive(swap.clone()), Some(claim_address))
.await?
else {
return Err(PaymentError::Generic {
err: format!("Constructed invalid transaction for Receive swap {swap_id}"),
@@ -354,18 +355,18 @@ impl ReceiveSwapHandler {
match self.persister.set_receive_swap_claim_tx_id(swap_id, &tx_id) {
Ok(_) => {
// We attempt broadcasting via chain service, then fallback to Boltz
let broadcast_res = self.liquid_chain_service
.broadcast(&claim_tx)
.await
.map(|tx_id| tx_id.to_hex())
.or_else(|err| {
let broadcast_res = match self.liquid_chain_service.broadcast(&claim_tx).await {
Ok(tx_id) => Ok(tx_id.to_hex()),
Err(err) => {
debug!(
"Could not broadcast claim tx via chain service for Receive swap {swap_id}: {err:?}"
);
let claim_tx_hex = claim_tx.serialize().to_lower_hex_string();
self.swapper.broadcast_tx(self.config.network.into(), &claim_tx_hex)
});
self.swapper
.broadcast_tx(self.config.network.into(), &claim_tx_hex)
.await
}
};
match broadcast_res {
Ok(claim_tx_id) => {
// We insert a pseudo-claim-tx in case LWK fails to pick up the new mempool tx for a while

View File

@@ -49,7 +49,7 @@ impl Recoverer {
})
}
fn recover_cooperative_preimages(
async fn recover_cooperative_preimages(
&self,
recovered_send_data: &mut HashMap<String, &mut RecoveredOnchainDataSend>,
) -> HashMap<String, Txid> {
@@ -59,7 +59,7 @@ impl Recoverer {
continue;
};
match self.swapper.get_submarine_preimage(swap_id) {
match self.swapper.get_submarine_preimage(swap_id).await {
Ok(preimage) => recovered_data.preimage = Some(preimage),
Err(err) => {
warn!("Could not recover Send swap {swap_id} preimage cooperatively: {err:?}");
@@ -118,7 +118,9 @@ impl Recoverer {
mut recovered_send_data: HashMap<String, &mut RecoveredOnchainDataSend>,
) -> Result<()> {
// Recover the preimages by querying the swapper, only if there is a claim_tx_id
let failed_cooperative = self.recover_cooperative_preimages(&mut recovered_send_data);
let failed_cooperative = self
.recover_cooperative_preimages(&mut recovered_send_data)
.await;
// For those which failed, recover the preimages by querying onchain (non-cooperative case)
self.recover_non_cooperative_preimages(&mut recovered_send_data, failed_cooperative)

View File

@@ -26,6 +26,7 @@ use sdk_common::input_parser::InputType;
use sdk_common::liquid::LiquidAddressData;
use sdk_common::prelude::{FiatAPI, FiatCurrency, LnUrlPayError, LnUrlWithdrawError, Rate};
use signer::SdkSigner;
use swapper::boltz::proxy::BoltzProxyFetcher;
use tokio::sync::{watch, RwLock};
use tokio::time::MissedTickBehavior;
use tokio_stream::wrappers::BroadcastStream;
@@ -123,16 +124,7 @@ impl LiquidSdk {
req: ConnectWithSignerRequest,
signer: Box<dyn Signer>,
) -> Result<Arc<LiquidSdk>> {
let maybe_swapper_proxy_url =
match BreezServer::new("https://bs1.breez.technology:443".into(), None) {
Ok(breez_server) => breez_server
.fetch_boltz_swapper_urls()
.await
.ok()
.and_then(|swapper_urls| swapper_urls.first().cloned()),
Err(_) => None,
};
let sdk = LiquidSdk::new(req.config, maybe_swapper_proxy_url, Arc::new(signer))?;
let sdk = LiquidSdk::new(req.config, Arc::new(signer))?;
sdk.start()
.inspect_err(|e| error!("Failed to start an SDK instance: {:?}", e))
.await?;
@@ -164,11 +156,7 @@ impl LiquidSdk {
Ok(())
}
fn new(
config: Config,
swapper_proxy_url: Option<String>,
signer: Arc<Box<dyn Signer>>,
) -> Result<Arc<Self>> {
fn new(config: Config, signer: Arc<Box<dyn Signer>>) -> Result<Arc<Self>> {
if let Some(breez_api_key) = &config.breez_api_key {
Self::validate_breez_api_key(breez_api_key)?
}
@@ -199,11 +187,8 @@ impl LiquidSdk {
let event_manager = Arc::new(EventManager::new());
let (shutdown_sender, shutdown_receiver) = watch::channel::<()>(());
if let Some(swapper_proxy_url) = swapper_proxy_url {
persister.set_swapper_proxy_url(swapper_proxy_url)?;
}
let cached_swapper_proxy_url = persister.get_swapper_proxy_url()?;
let swapper = Arc::new(BoltzSwapper::new(config.clone(), cached_swapper_proxy_url));
let proxy_url_fetcher = Arc::new(BoltzProxyFetcher::new(persister.clone()));
let swapper = Arc::new(BoltzSwapper::new(config.clone(), proxy_url_fetcher));
let status_stream = Arc::<dyn SwapperStatusStream>::from(swapper.create_status_stream());
let recoverer = Arc::new(Recoverer::new(
@@ -316,13 +301,12 @@ impl LiquidSdk {
));
self.status_stream
.clone()
.start(reconnect_handler, self.shutdown_receiver.clone())
.await;
.start(reconnect_handler, self.shutdown_receiver.clone());
if let Some(sync_service) = self.sync_service.clone() {
sync_service.start(self.shutdown_receiver.clone()).await?;
sync_service.start(self.shutdown_receiver.clone());
}
self.track_new_blocks().await;
self.track_swap_updates().await;
self.track_new_blocks();
self.track_swap_updates();
Ok(())
}
@@ -345,7 +329,7 @@ impl LiquidSdk {
Ok(())
}
async fn track_new_blocks(self: &Arc<LiquidSdk>) {
fn track_new_blocks(self: &Arc<LiquidSdk>) {
let cloned = self.clone();
tokio::spawn(async move {
let mut current_liquid_block: u32 = 0;
@@ -431,7 +415,7 @@ impl LiquidSdk {
});
}
async fn track_swap_updates(self: &Arc<LiquidSdk>) {
fn track_swap_updates(self: &Arc<LiquidSdk>) {
let cloned = self.clone();
tokio::spawn(async move {
let mut shutdown_receiver = cloned.shutdown_receiver.clone();
@@ -756,13 +740,14 @@ impl LiquidSdk {
/// For submarine swaps (Liquid -> LN), the output amount (invoice amount) is checked if it fits
/// the pair limits. This is unlike all the other swap types, where the input amount is checked.
fn validate_submarine_pairs(
async fn validate_submarine_pairs(
&self,
receiver_amount_sat: u64,
) -> Result<SubmarinePair, PaymentError> {
let lbtc_pair = self
.swapper
.get_submarine_pairs()?
.get_submarine_pairs()
.await?
.ok_or(PaymentError::PairsNotFound)?;
lbtc_pair.limits.within(receiver_amount_sat)?;
@@ -777,9 +762,10 @@ impl LiquidSdk {
Ok(lbtc_pair)
}
fn get_chain_pair(&self, direction: Direction) -> Result<ChainPair, PaymentError> {
async fn get_chain_pair(&self, direction: Direction) -> Result<ChainPair, PaymentError> {
self.swapper
.get_chain_pair(direction)?
.get_chain_pair(direction)
.await?
.ok_or(PaymentError::PairsNotFound)
}
@@ -800,12 +786,12 @@ impl LiquidSdk {
Ok(())
}
fn get_and_validate_chain_pair(
async fn get_and_validate_chain_pair(
&self,
direction: Direction,
user_lockup_amount_sat: Option<u64>,
) -> Result<ChainPair, PaymentError> {
let pair = self.get_chain_pair(direction)?;
let pair = self.get_chain_pair(direction).await?;
if let Some(user_lockup_amount_sat) = user_lockup_amount_sat {
self.validate_user_lockup_amount_for_chain_pair(&pair, user_lockup_amount_sat)?;
}
@@ -1074,10 +1060,11 @@ impl LiquidSdk {
);
}
let lbtc_pair = self.validate_submarine_pairs(invoice_amount_sat)?;
let lbtc_pair = self.validate_submarine_pairs(invoice_amount_sat).await?;
let mrh_address = self
.swapper
.check_for_mrh(&invoice.bolt11)?
.check_for_mrh(&invoice.bolt11)
.await?
.map(|(address, _)| address);
asset_id = self.config.lbtc_asset_id();
(receiver_amount_sat, fees_sat, payment_destination) =
@@ -1153,7 +1140,7 @@ impl LiquidSdk {
);
}
let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?;
let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat).await?;
let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat);
let lockup_fees_sat = self
@@ -1265,7 +1252,8 @@ impl LiquidSdk {
} => {
let bolt12_invoice = self
.swapper
.get_bolt12_invoice(&offer.offer, *receiver_amount_sat)?;
.get_bolt12_invoice(&offer.offer, *receiver_amount_sat)
.await?;
self.pay_bolt12_invoice(offer, *receiver_amount_sat, &bolt12_invoice, *fees_sat)
.await
}
@@ -1292,7 +1280,7 @@ impl LiquidSdk {
Bolt11InvoiceDescription::Hash(_) => None,
};
match self.swapper.check_for_mrh(invoice)? {
match self.swapper.check_for_mrh(invoice).await? {
// If we find a valid MRH, extract the BIP21 address and pay to it via onchain tx
Some((address, _)) => {
info!("Found MRH for L-BTC address {address}, invoice amount_sat {amount_sat}");
@@ -1458,7 +1446,7 @@ impl LiquidSdk {
receiver_amount_sat: u64,
fees_sat: u64,
) -> Result<SendPaymentResponse, PaymentError> {
let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?;
let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat).await?;
let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat);
let user_lockup_amount_sat = receiver_amount_sat + boltz_fees_total;
let lockup_tx_fees_sat = self
@@ -1512,15 +1500,18 @@ impl LiquidSdk {
SubSwapStates::TransactionLockupFailed,
]),
});
let create_response = self.swapper.create_send_swap(CreateSubmarineRequest {
from: "L-BTC".to_string(),
to: "BTC".to_string(),
invoice: invoice.to_string(),
refund_public_key,
pair_hash: Some(lbtc_pair.hash.clone()),
referral_id: None,
webhook,
})?;
let create_response = self
.swapper
.create_send_swap(CreateSubmarineRequest {
from: "L-BTC".to_string(),
to: "BTC".to_string(),
invoice: invoice.to_string(),
refund_public_key,
pair_hash: Some(lbtc_pair.hash.clone()),
referral_id: None,
webhook,
})
.await?;
let swap_id = &create_response.id;
let create_response_json =
@@ -1530,7 +1521,7 @@ impl LiquidSdk {
let payer_amount_sat = fees_sat + receiver_amount_sat;
let swap = SendSwap {
id: swap_id.clone(),
id: swap_id.to_string(),
invoice: invoice.to_string(),
bolt12_offer,
payment_hash: Some(payment_hash.to_string()),
@@ -1575,13 +1566,15 @@ impl LiquidSdk {
let submarine_pair = self
.swapper
.get_submarine_pairs()?
.get_submarine_pairs()
.await?
.ok_or(PaymentError::PairsNotFound)?;
let send_limits = submarine_pair.limits;
let reverse_pair = self
.swapper
.get_reverse_swap_pairs()?
.get_reverse_swap_pairs()
.await?
.ok_or(PaymentError::PairsNotFound)?;
let receive_limits = reverse_pair.limits;
@@ -1603,7 +1596,7 @@ impl LiquidSdk {
pub async fn fetch_onchain_limits(&self) -> Result<OnchainPaymentLimitsResponse, PaymentError> {
self.ensure_is_started().await?;
let (pair_outgoing, pair_incoming) = self.swapper.get_chain_pairs()?;
let (pair_outgoing, pair_incoming) = self.swapper.get_chain_pairs().await?;
let send_limits = pair_outgoing
.ok_or(PaymentError::PairsNotFound)
.map(|pair| pair.limits)?;
@@ -1640,7 +1633,7 @@ impl LiquidSdk {
self.ensure_is_started().await?;
let get_info_res = self.get_info().await?;
let pair = self.get_chain_pair(Direction::Outgoing)?;
let pair = self.get_chain_pair(Direction::Outgoing).await?;
let claim_fees_sat = match req.fee_rate_sat_per_vbyte {
Some(sat_per_vbyte) => ESTIMATED_BTC_CLAIM_TX_VSIZE * sat_per_vbyte as u64,
None => pair.clone().fees.claim_estimate(),
@@ -1744,7 +1737,7 @@ impl LiquidSdk {
let claim_address = self.validate_bitcoin_address(&req.address).await?;
let balance_sat = self.get_info().await?.wallet_info.balance_sat;
let receiver_amount_sat = req.prepare_response.receiver_amount_sat;
let pair = self.get_chain_pair(Direction::Outgoing)?;
let pair = self.get_chain_pair(Direction::Outgoing).await?;
let claim_fees_sat = req.prepare_response.claim_fees_sat;
let server_fees_sat = pair.fees.server();
let server_lockup_amount_sat = receiver_amount_sat + claim_fees_sat;
@@ -1800,18 +1793,21 @@ impl LiquidSdk {
ChainSwapStates::TransactionServerConfirmed,
]),
});
let create_response = self.swapper.create_chain_swap(CreateChainRequest {
from: "L-BTC".to_string(),
to: "BTC".to_string(),
preimage_hash: preimage.sha256,
claim_public_key: Some(claim_public_key),
refund_public_key: Some(refund_public_key),
user_lock_amount: None,
server_lock_amount: Some(server_lockup_amount_sat),
pair_hash: Some(pair.hash.clone()),
referral_id: None,
webhook,
})?;
let create_response = self
.swapper
.create_chain_swap(CreateChainRequest {
from: "L-BTC".to_string(),
to: "BTC".to_string(),
preimage_hash: preimage.sha256,
claim_public_key: Some(claim_public_key),
refund_public_key: Some(refund_public_key),
user_lock_amount: None,
server_lock_amount: Some(server_lockup_amount_sat),
pair_hash: Some(pair.hash.clone()),
referral_id: None,
webhook,
})
.await?;
let create_response_json =
ChainSwap::from_boltz_struct_to_json(&create_response, &create_response.id)?;
@@ -1953,7 +1949,8 @@ impl LiquidSdk {
};
let reverse_pair = self
.swapper
.get_reverse_swap_pairs()?
.get_reverse_swap_pairs()
.await?
.ok_or(PaymentError::PairsNotFound)?;
fees_sat = reverse_pair.fees.total(payer_amount_sat);
@@ -1983,8 +1980,9 @@ impl LiquidSdk {
Some(ReceiveAmount::Bitcoin { payer_amount_sat }) => Some(payer_amount_sat),
None => None,
};
let pair =
self.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)?;
let pair = self
.get_and_validate_chain_pair(Direction::Incoming, payer_amount_sat)
.await?;
let claim_fees_sat = pair.fees.claim_estimate();
let server_fees_sat = pair.fees.server();
let service_fees_sat = payer_amount_sat
@@ -2145,7 +2143,8 @@ impl LiquidSdk {
) -> Result<ReceivePaymentResponse, PaymentError> {
let reverse_pair = self
.swapper
.get_reverse_swap_pairs()?
.get_reverse_swap_pairs()
.await?
.ok_or(PaymentError::PairsNotFound)?;
let new_fees_sat = reverse_pair.fees.total(payer_amount_sat);
ensure_sdk!(fees_sat == new_fees_sat, PaymentError::InvalidOrExpiredFees);
@@ -2192,7 +2191,7 @@ impl LiquidSdk {
referral_id: None,
webhook,
};
let create_response = self.swapper.create_receive_swap(v2_req)?;
let create_response = self.swapper.create_receive_swap(v2_req).await?;
// Reserve this address until the timeout block height
self.persister.insert_or_update_reserved_address(
@@ -2203,7 +2202,8 @@ impl LiquidSdk {
// Check if correct MRH was added to the invoice by Boltz
let (bip21_lbtc_address, _bip21_amount_btc) = self
.swapper
.check_for_mrh(&create_response.invoice)?
.check_for_mrh(&create_response.invoice)
.await?
.ok_or(PaymentError::receive_error("Invoice has no MRH"))?;
ensure_sdk!(
bip21_lbtc_address == mrh_addr_str,
@@ -2277,7 +2277,9 @@ impl LiquidSdk {
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 pair = self
.get_and_validate_chain_pair(Direction::Incoming, user_lockup_amount_sat)
.await?;
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
@@ -2312,18 +2314,21 @@ impl LiquidSdk {
ChainSwapStates::TransactionServerConfirmed,
]),
});
let create_response = self.swapper.create_chain_swap(CreateChainRequest {
from: "BTC".to_string(),
to: "L-BTC".to_string(),
preimage_hash: preimage.sha256,
claim_public_key: Some(claim_public_key),
refund_public_key: Some(refund_public_key),
user_lock_amount: user_lockup_amount_sat,
server_lock_amount: None,
pair_hash: Some(pair.hash.clone()),
referral_id: None,
webhook,
})?;
let create_response = self
.swapper
.create_chain_swap(CreateChainRequest {
from: "BTC".to_string(),
to: "L-BTC".to_string(),
preimage_hash: preimage.sha256,
claim_public_key: Some(claim_public_key),
refund_public_key: Some(refund_public_key),
user_lock_amount: user_lockup_amount_sat,
server_lock_amount: None,
pair_hash: Some(pair.hash.clone()),
referral_id: None,
webhook,
})
.await?;
let swap_id = create_response.id.clone();
let create_response_json =
@@ -2909,7 +2914,8 @@ impl LiquidSdk {
let server_lockup_quote = self
.swapper
.get_zero_amount_chain_swap_quote(&req.swap_id)?;
.get_zero_amount_chain_swap_quote(&req.swap_id)
.await?;
let actual_payer_amount_sat =
chain_swap
@@ -2957,7 +2963,10 @@ impl LiquidSdk {
}
);
let server_lockup_quote = self.swapper.get_zero_amount_chain_swap_quote(&swap_id)?;
let server_lockup_quote = self
.swapper
.get_zero_amount_chain_swap_quote(&swap_id)
.await?;
ensure_sdk!(
fees_sat == payer_amount_sat - server_lockup_quote.to_sat() + chain_swap.claim_fees_sat,
@@ -2973,7 +2982,7 @@ impl LiquidSdk {
let _ = self
.persister
.update_accepted_receiver_amount(&swap_id, None);
})?;
}).await?;
self.chain_swap_handler.update_swap_info(&ChainSwapUpdate {
swap_id,
to_state: Pending,
@@ -3096,7 +3105,8 @@ impl LiquidSdk {
);
let lbtc_pair = self
.swapper
.get_submarine_pairs()?
.get_submarine_pairs()
.await?
.ok_or(PaymentError::PairsNotFound)?;
let drain_fees_sat = self.estimate_drain_tx_fee(None, None).await?;
let drain_amount_sat = get_info_res.wallet_info.balance_sat - drain_fees_sat;
@@ -3634,7 +3644,7 @@ mod tests {
None,
)?);
LiquidSdk::track_swap_updates(&sdk).await;
LiquidSdk::track_swap_updates(&sdk);
// We spawn a new thread since updates can only be sent when called via async runtimes
tokio::spawn(async move {
@@ -3745,7 +3755,7 @@ mod tests {
status_stream.clone(),
)?);
LiquidSdk::track_swap_updates(&sdk).await;
LiquidSdk::track_swap_updates(&sdk);
// We spawn a new thread since updates can only be sent when called via async runtimes
tokio::spawn(async move {
@@ -3806,7 +3816,7 @@ mod tests {
None,
)?);
LiquidSdk::track_swap_updates(&sdk).await;
LiquidSdk::track_swap_updates(&sdk);
// We spawn a new thread since updates can only be sent when called via async runtimes
tokio::spawn(async move {
@@ -4039,7 +4049,7 @@ mod tests {
None,
)?);
LiquidSdk::track_swap_updates(&sdk).await;
LiquidSdk::track_swap_updates(&sdk);
// We spawn a new thread since updates can only be sent when called via async runtimes
tokio::spawn(async move {
@@ -4100,7 +4110,7 @@ mod tests {
Some(onchain_fee_rate_leeway_sat_per_vbyte),
)?);
LiquidSdk::track_swap_updates(&sdk).await;
LiquidSdk::track_swap_updates(&sdk);
let max_fee_increase_for_auto_accept_sat =
onchain_fee_rate_leeway_sat_per_vbyte as u64 * ESTIMATED_BTC_LOCKUP_TX_VSIZE;

View File

@@ -355,7 +355,7 @@ impl SendSwapHandler {
&send_swap.id
);
let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let claim_tx_details = self.swapper.get_send_claim_tx_details(send_swap)?;
let claim_tx_details = self.swapper.get_send_claim_tx_details(send_swap).await?;
self.update_swap_info(
&send_swap.id,
Complete,
@@ -364,7 +364,8 @@ impl SendSwapHandler {
None,
)?;
self.swapper
.claim_send_swap_cooperative(send_swap, claim_tx_details, &output_address)?;
.claim_send_swap_cooperative(send_swap, claim_tx_details, &output_address)
.await?;
Ok(())
}
@@ -447,13 +448,16 @@ impl SendSwapHandler {
.to_unconfidential()
.script_pubkey();
let utxos = self.chain_service.get_script_utxos(&script_pk).await?;
let SdkTransaction::Liquid(refund_tx) = self.swapper.create_refund_tx(
Swap::Send(swap.clone()),
&refund_address,
utxos,
None,
is_cooperative,
)?
let SdkTransaction::Liquid(refund_tx) = self
.swapper
.create_refund_tx(
Swap::Send(swap.clone()),
&refund_address,
utxos,
None,
is_cooperative,
)
.await?
else {
return Err(PaymentError::Generic {
err: format!(

View File

@@ -14,10 +14,10 @@ use crate::{
prelude::{ChainSwap, Direction, Swap, Utxo},
};
use super::BoltzSwapper;
use super::{BoltzSwapper, ProxyUrlFetcher};
impl BoltzSwapper {
pub(crate) fn new_btc_refund_wrapper(
impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
pub(crate) async fn new_btc_refund_wrapper(
&self,
swap: &Swap,
refund_address: &str,
@@ -30,7 +30,7 @@ impl BoltzSwapper {
swap_script.as_bitcoin_script()?,
refund_address,
&self.bitcoin_electrum_config,
self.boltz_url.clone(),
self.get_url().await?,
swap.id.clone(),
)
}
@@ -51,7 +51,7 @@ impl BoltzSwapper {
Ok(refund_wrapper)
}
pub(crate) fn new_btc_refund_tx(
pub(crate) async fn new_btc_refund_tx(
&self,
swap: &ChainSwap,
refund_address: &str,
@@ -91,7 +91,10 @@ impl BoltzSwapper {
let broadcast_fees_sat = (refund_tx_size as f64 * broadcast_fee_rate_sat_per_vb) as u64;
let cooperative = match is_cooperative {
true => self.get_cooperative_details(swap.id.clone(), None, None),
true => {
self.get_cooperative_details(swap.id.clone(), None, None)
.await?
}
false => None,
};
@@ -103,7 +106,7 @@ impl BoltzSwapper {
Ok(signed_tx)
}
pub(crate) fn new_outgoing_chain_claim_tx(
pub(crate) async fn new_outgoing_chain_claim_tx(
&self,
swap: &ChainSwap,
claim_address: String,
@@ -114,17 +117,18 @@ impl BoltzSwapper {
claim_swap_script,
claim_address,
&self.bitcoin_electrum_config,
self.boltz_url.clone(),
self.get_url().await?,
swap.id.clone(),
)?;
let (partial_sig, pub_nonce) = self.get_claim_partial_sig(swap)?;
let (partial_sig, pub_nonce) = self.get_claim_partial_sig(swap).await?;
let signed_tx = claim_tx_wrapper.sign_claim(
&claim_keypair,
&Preimage::from_str(&swap.preimage)?,
Fee::Absolute(swap.claim_fees_sat),
self.get_cooperative_details(swap.id.clone(), Some(pub_nonce), Some(partial_sig)),
self.get_cooperative_details(swap.id.clone(), Some(pub_nonce), Some(partial_sig))
.await?,
)?;
Ok(signed_tx)

View File

@@ -16,9 +16,9 @@ use crate::{
utils,
};
use super::BoltzSwapper;
use super::{BoltzSwapper, ProxyUrlFetcher};
impl BoltzSwapper {
impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
pub(crate) fn validate_send_swap_preimage(
&self,
swap_id: &str,
@@ -30,7 +30,7 @@ impl BoltzSwapper {
Ok(())
}
pub(crate) fn new_receive_claim_tx(
pub(crate) async fn new_receive_claim_tx(
&self,
swap: &ReceiveSwap,
claim_address: String,
@@ -41,7 +41,7 @@ impl BoltzSwapper {
swap_script,
claim_address,
&self.liquid_electrum_config,
self.boltz_url.clone(),
self.get_url().await?,
swap.id.clone(),
)?;
@@ -49,14 +49,15 @@ impl BoltzSwapper {
&swap.get_claim_keypair()?,
&Preimage::from_str(&swap.preimage)?,
Fee::Absolute(swap.claim_fees_sat),
self.get_cooperative_details(swap.id.clone(), None, None),
self.get_cooperative_details(swap.id.clone(), None, None)
.await?,
true,
)?;
Ok(signed_tx)
}
pub(crate) fn new_incoming_chain_claim_tx(
pub(crate) async fn new_incoming_chain_claim_tx(
&self,
swap: &ChainSwap,
claim_address: String,
@@ -67,17 +68,18 @@ impl BoltzSwapper {
swap_script,
claim_address,
&self.liquid_electrum_config,
self.boltz_url.clone(),
self.get_url().await?,
swap.id.clone(),
)?;
let (partial_sig, pub_nonce) = self.get_claim_partial_sig(swap)?;
let (partial_sig, pub_nonce) = self.get_claim_partial_sig(swap).await?;
let signed_tx = claim_tx_wrapper.sign_claim(
&claim_keypair,
&Preimage::from_str(&swap.preimage)?,
Fee::Absolute(swap.claim_fees_sat),
self.get_cooperative_details(swap.id.clone(), Some(pub_nonce), Some(partial_sig)),
self.get_cooperative_details(swap.id.clone(), Some(pub_nonce), Some(partial_sig))
.await?,
true,
)?;
@@ -88,7 +90,7 @@ impl BoltzSwapper {
(refund_tx_size as f64 * LIQUID_FEE_RATE_SAT_PER_VBYTE).ceil() as u64
}
pub(crate) fn new_lbtc_refund_wrapper(
pub(crate) async fn new_lbtc_refund_wrapper(
&self,
swap: &Swap,
refund_address: &str,
@@ -107,7 +109,7 @@ impl BoltzSwapper {
swap_script.as_liquid_script()?,
refund_address,
&self.liquid_electrum_config,
self.boltz_url.clone(),
self.get_url().await?,
swap.id.clone(),
)
}
@@ -118,7 +120,7 @@ impl BoltzSwapper {
swap_script,
refund_address,
&self.liquid_electrum_config,
self.boltz_url.clone(),
self.get_url().await?,
swap.id.clone(),
)
}
@@ -132,7 +134,7 @@ impl BoltzSwapper {
Ok(refund_wrapper)
}
pub(crate) fn new_lbtc_refund_tx(
pub(crate) async fn new_lbtc_refund_tx(
&self,
swap: &Swap,
refund_address: &str,
@@ -189,7 +191,10 @@ impl BoltzSwapper {
let broadcast_fees_sat = self.calculate_refund_fees(refund_tx_size);
let cooperative = match is_cooperative {
true => self.get_cooperative_details(swap_id.clone(), None, None),
true => {
self.get_cooperative_details(swap_id.clone(), None, None)
.await?
}
false => None,
};

View File

@@ -1,11 +1,20 @@
use std::str::FromStr;
use std::{
str::FromStr,
sync::{Arc, OnceLock},
};
use crate::{
error::{PaymentError, SdkError},
model::LIQUID_FEE_RATE_SAT_PER_VBYTE,
prelude::{ChainSwap, Config, Direction, LiquidNetwork, SendSwap, Swap, Transaction, Utxo},
};
use anyhow::Result;
use async_trait::async_trait;
use boltz_client::{
boltz::{
BoltzApiClientV2, ChainPair, Cooperative, CreateChainRequest, CreateChainResponse,
CreateReverseRequest, CreateReverseResponse, CreateSubmarineRequest,
CreateSubmarineResponse, ReversePair, SubmarineClaimTxResponse, SubmarinePair,
BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2,
},
elements::secp256k1_zkp::{MusigPartialSignature, MusigPubNonce},
network::{electrum::ElectrumConfig, Chain},
@@ -13,63 +22,35 @@ use boltz_client::{
Amount,
};
use log::info;
use url::Url;
use crate::{
error::{PaymentError, SdkError},
model::LIQUID_FEE_RATE_SAT_PER_VBYTE,
prelude::{ChainSwap, Config, Direction, LiquidNetwork, SendSwap, Swap, Transaction, Utxo},
};
use proxy::split_proxy_url;
use self::status_stream::BoltzStatusStream;
use super::{Swapper, SwapperStatusStream};
use super::{ProxyUrlFetcher, Swapper, SwapperStatusStream};
pub(crate) mod bitcoin;
pub(crate) mod liquid;
pub(crate) mod proxy;
pub mod status_stream;
pub struct BoltzSwapper {
client: BoltzApiClientV2,
boltz_url: String,
pub(crate) struct BoltzClient {
url: String,
referral_id: Option<String>,
config: Config,
liquid_electrum_config: ElectrumConfig,
bitcoin_electrum_config: ElectrumConfig,
inner: BoltzApiClientV2,
}
impl BoltzSwapper {
pub fn new(config: Config, swapper_proxy_url: Option<String>) -> Self {
let (boltz_api_base_url, referral_id) = match &config.network {
LiquidNetwork::Testnet => (None, None),
LiquidNetwork::Mainnet => match &swapper_proxy_url {
Some(swapper_proxy_url) => Url::parse(swapper_proxy_url)
.map(|url| match url.query() {
None => (None, None),
Some(query) => {
let api_base_url =
url.domain().map(|domain| format!("https://{domain}/v2"));
let parts: Vec<String> = query.split('=').map(Into::into).collect();
let referral_id = parts.get(1).cloned();
(api_base_url, referral_id)
}
})
.unwrap_or_default(),
None => (None, None),
},
};
let boltz_url = boltz_api_base_url.unwrap_or(
match config.network {
LiquidNetwork::Mainnet => BOLTZ_MAINNET_URL_V2,
LiquidNetwork::Testnet => BOLTZ_TESTNET_URL_V2,
}
.to_string(),
);
pub struct BoltzSwapper<P: ProxyUrlFetcher> {
config: Config,
client: OnceLock<BoltzClient>,
liquid_electrum_config: ElectrumConfig,
bitcoin_electrum_config: ElectrumConfig,
proxy_url: Arc<P>,
}
impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
pub fn new(config: Config, proxy_url: Arc<P>) -> Self {
Self {
client: BoltzApiClientV2::new(&boltz_url),
boltz_url,
referral_id,
proxy_url,
client: OnceLock::new(),
config: config.clone(),
liquid_electrum_config: ElectrumConfig::new(
config.network.into(),
@@ -88,7 +69,34 @@ impl BoltzSwapper {
}
}
fn get_claim_partial_sig(
async fn get_client(&self) -> Result<&BoltzClient> {
if let Some(client) = self.client.get() {
return Ok(client);
}
let (boltz_api_base_url, referral_id) = match &self.config.network {
LiquidNetwork::Testnet => (None, None),
LiquidNetwork::Mainnet => match self.proxy_url.fetch().await {
Ok(Some(swapper_proxy_url)) => split_proxy_url(swapper_proxy_url),
_ => (None, None),
},
};
let boltz_url = boltz_api_base_url.unwrap_or(self.config.default_boltz_url().to_string());
let client = self.client.get_or_init(|| BoltzClient {
inner: BoltzApiClientV2::new(&boltz_url),
url: boltz_url,
referral_id,
});
Ok(client)
}
async fn get_url(&self) -> Result<String> {
Ok(self.get_client().await?.url.clone())
}
async fn get_claim_partial_sig(
&self,
swap: &ChainSwap,
) -> Result<(MusigPartialSignature, MusigPubNonce), PaymentError> {
@@ -98,11 +106,16 @@ impl BoltzSwapper {
// We need it to calculate the musig partial sig for the claim tx from the other chain
let lockup_address = &swap.lockup_address;
let claim_tx_details = self.client.get_chain_claim_tx_details(&swap.id)?;
let claim_tx_details = self
.get_client()
.await?
.inner
.get_chain_claim_tx_details(&swap.id)?;
match swap.direction {
Direction::Incoming => {
let refund_tx_wrapper =
self.new_btc_refund_wrapper(&Swap::Chain(swap.clone()), lockup_address)?;
let refund_tx_wrapper = self
.new_btc_refund_wrapper(&Swap::Chain(swap.clone()), lockup_address)
.await?;
refund_tx_wrapper.partial_sign(
&refund_keypair,
@@ -111,8 +124,9 @@ impl BoltzSwapper {
)
}
Direction::Outgoing => {
let refund_tx_wrapper =
self.new_lbtc_refund_wrapper(&Swap::Chain(swap.clone()), lockup_address)?;
let refund_tx_wrapper = self
.new_lbtc_refund_wrapper(&Swap::Chain(swap.clone()), lockup_address)
.await?;
refund_tx_wrapper.partial_sign(
&refund_keypair,
@@ -124,48 +138,54 @@ impl BoltzSwapper {
.map_err(Into::into)
}
fn get_cooperative_details(
async fn get_cooperative_details(
&self,
swap_id: String,
pub_nonce: Option<MusigPubNonce>,
partial_sig: Option<MusigPartialSignature>,
) -> Option<Cooperative> {
Some(Cooperative {
boltz_api: &self.client,
) -> Result<Option<Cooperative>> {
Ok(Some(Cooperative {
boltz_api: &self.get_client().await?.inner,
swap_id,
pub_nonce,
partial_sig,
})
}))
}
}
impl Swapper for BoltzSwapper {
#[async_trait]
impl<P: ProxyUrlFetcher> Swapper for BoltzSwapper<P> {
/// Create a new chain swap
fn create_chain_swap(
async fn create_chain_swap(
&self,
req: CreateChainRequest,
) -> Result<CreateChainResponse, PaymentError> {
let client = self.get_client().await?;
let modified_req = CreateChainRequest {
referral_id: self.referral_id.clone(),
referral_id: client.referral_id.clone(),
..req.clone()
};
Ok(self.client.post_chain_req(modified_req)?)
Ok(client.inner.post_chain_req(modified_req)?)
}
/// Create a new send swap
fn create_send_swap(
async fn create_send_swap(
&self,
req: CreateSubmarineRequest,
) -> Result<CreateSubmarineResponse, PaymentError> {
let client = self.get_client().await?;
let modified_req = CreateSubmarineRequest {
referral_id: self.referral_id.clone(),
referral_id: client.referral_id.clone(),
..req.clone()
};
Ok(self.client.post_swap_req(&modified_req)?)
Ok(client.inner.post_swap_req(&modified_req)?)
}
fn get_chain_pair(&self, direction: Direction) -> Result<Option<ChainPair>, PaymentError> {
let pairs = self.client.get_chain_pairs()?;
async fn get_chain_pair(
&self,
direction: Direction,
) -> Result<Option<ChainPair>, PaymentError> {
let pairs = self.get_client().await?.inner.get_chain_pairs()?;
let pair = match direction {
Direction::Incoming => pairs.get_btc_to_lbtc_pair(),
Direction::Outgoing => pairs.get_lbtc_to_btc_pair(),
@@ -173,48 +193,68 @@ impl Swapper for BoltzSwapper {
Ok(pair)
}
fn get_chain_pairs(&self) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError> {
let pairs = self.client.get_chain_pairs()?;
async fn get_chain_pairs(
&self,
) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError> {
let pairs = self.get_client().await?.inner.get_chain_pairs()?;
let pair_outgoing = pairs.get_lbtc_to_btc_pair();
let pair_incoming = pairs.get_btc_to_lbtc_pair();
Ok((pair_outgoing, pair_incoming))
}
fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, SdkError> {
self.client
async fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, SdkError> {
self.get_client()
.await?
.inner
.get_quote(swap_id)
.map(|r| Amount::from_sat(r.amount))
.map_err(Into::into)
}
fn accept_zero_amount_chain_swap_quote(
async fn accept_zero_amount_chain_swap_quote(
&self,
swap_id: &str,
server_lockup_sat: u64,
) -> Result<(), PaymentError> {
self.client
self.get_client()
.await?
.inner
.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())
async fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> {
Ok(self
.get_client()
.await?
.inner
.get_submarine_pairs()?
.get_lbtc_to_btc_pair())
}
/// Get a submarine swap's preimage
fn get_submarine_preimage(&self, swap_id: &str) -> Result<String, PaymentError> {
Ok(self.client.get_submarine_preimage(swap_id)?.preimage)
async fn get_submarine_preimage(&self, swap_id: &str) -> Result<String, PaymentError> {
Ok(self
.get_client()
.await?
.inner
.get_submarine_preimage(swap_id)?
.preimage)
}
/// Get claim tx details which includes the preimage as a proof of payment.
/// It is used to validate the preimage before claiming which is the reason why we need to separate
/// the claim into two steps.
fn get_send_claim_tx_details(
async fn get_send_claim_tx_details(
&self,
swap: &SendSwap,
) -> Result<SubmarineClaimTxResponse, PaymentError> {
let claim_tx_response = self.client.get_submarine_claim_tx_details(&swap.id)?;
let claim_tx_response = self
.get_client()
.await?
.inner
.get_submarine_claim_tx_details(&swap.id)?;
info!("Received claim tx details: {:?}", &claim_tx_response);
self.validate_send_swap_preimage(&swap.id, &swap.invoice, &claim_tx_response.preimage)?;
@@ -223,7 +263,7 @@ impl Swapper for BoltzSwapper {
/// Claim send swap cooperatively. Here the remote swapper is the one that claims.
/// We are helping to use key spend path for cheaper fees.
fn claim_send_swap_cooperative(
async fn claim_send_swap_cooperative(
&self,
swap: &SendSwap,
claim_tx_response: SubmarineClaimTxResponse,
@@ -231,8 +271,9 @@ impl Swapper for BoltzSwapper {
) -> Result<(), PaymentError> {
let swap_id = &swap.id;
let keypair = swap.get_refund_keypair()?;
let refund_tx_wrapper =
self.new_lbtc_refund_wrapper(&Swap::Send(swap.clone()), refund_address)?;
let refund_tx_wrapper = self
.new_lbtc_refund_wrapper(&Swap::Send(swap.clone()), refund_address)
.await?;
self.validate_send_swap_preimage(swap_id, &swap.invoice, &claim_tx_response.preimage)?;
@@ -242,34 +283,39 @@ impl Swapper for BoltzSwapper {
&claim_tx_response.transaction_hash,
)?;
self.client.post_submarine_claim_tx_details(
&swap_id.to_string(),
pub_nonce,
partial_sig,
)?;
self.get_client()
.await?
.inner
.post_submarine_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?;
info!("Successfully sent claim details for swap-in {swap_id}");
Ok(())
}
// Create a new receive swap
fn create_receive_swap(
async fn create_receive_swap(
&self,
req: CreateReverseRequest,
) -> Result<CreateReverseResponse, PaymentError> {
let client = self.get_client().await?;
let modified_req = CreateReverseRequest {
referral_id: self.referral_id.clone(),
referral_id: client.referral_id.clone(),
..req.clone()
};
Ok(self.client.post_reverse_req(modified_req)?)
Ok(client.inner.post_reverse_req(modified_req)?)
}
// Get a reverse pair information
fn get_reverse_swap_pairs(&self) -> Result<Option<ReversePair>, PaymentError> {
Ok(self.client.get_reverse_pairs()?.get_btc_to_lbtc_pair())
async fn get_reverse_swap_pairs(&self) -> Result<Option<ReversePair>, PaymentError> {
Ok(self
.get_client()
.await?
.inner
.get_reverse_pairs()?
.get_btc_to_lbtc_pair())
}
/// Create a claim transaction for a receive or chain swap
fn create_claim_tx(
async fn create_claim_tx(
&self,
swap: Swap,
claim_address: Option<String>,
@@ -285,12 +331,14 @@ impl Swapper for BoltzSwapper {
});
};
match swap.direction {
Direction::Incoming => {
Transaction::Liquid(self.new_incoming_chain_claim_tx(swap, claim_address)?)
}
Direction::Outgoing => {
Transaction::Bitcoin(self.new_outgoing_chain_claim_tx(swap, claim_address)?)
}
Direction::Incoming => Transaction::Liquid(
self.new_incoming_chain_claim_tx(swap, claim_address)
.await?,
),
Direction::Outgoing => Transaction::Bitcoin(
self.new_outgoing_chain_claim_tx(swap, claim_address)
.await?,
),
}
}
Swap::Receive(swap) => {
@@ -302,7 +350,7 @@ impl Swapper for BoltzSwapper {
),
});
};
Transaction::Liquid(self.new_receive_claim_tx(swap, claim_address)?)
Transaction::Liquid(self.new_receive_claim_tx(swap, claim_address).await?)
}
Swap::Send(swap) => {
return Err(PaymentError::Generic {
@@ -318,7 +366,7 @@ impl Swapper for BoltzSwapper {
}
/// Estimate the refund broadcast transaction size and fees in sats for a send or chain swap
fn estimate_refund_broadcast(
async fn estimate_refund_broadcast(
&self,
swap: Swap,
refund_address: &str,
@@ -339,10 +387,10 @@ impl Swapper for BoltzSwapper {
}
};
let refund_tx_size = match self.new_lbtc_refund_wrapper(&swap, refund_address) {
let refund_tx_size = match self.new_lbtc_refund_wrapper(&swap, refund_address).await {
Ok(refund_tx_wrapper) => refund_tx_wrapper.size(&refund_keypair, &preimage, true)?,
Err(_) => {
let refund_tx_wrapper = self.new_btc_refund_wrapper(&swap, refund_address)?;
let refund_tx_wrapper = self.new_btc_refund_wrapper(&swap, refund_address).await?;
refund_tx_wrapper.size(&refund_keypair, &preimage)?
}
} as u32;
@@ -354,7 +402,7 @@ impl Swapper for BoltzSwapper {
}
/// Create a refund transaction for a send or chain swap
fn create_refund_tx(
async fn create_refund_tx(
&self,
swap: Swap,
refund_address: &str,
@@ -374,27 +422,26 @@ impl Swapper for BoltzSwapper {
});
};
Transaction::Bitcoin(self.new_btc_refund_tx(
chain_swap,
refund_address,
utxos,
broadcast_fee_rate_sat_per_vb,
is_cooperative,
)?)
Transaction::Bitcoin(
self.new_btc_refund_tx(
chain_swap,
refund_address,
utxos,
broadcast_fee_rate_sat_per_vb,
is_cooperative,
)
.await?,
)
}
Direction::Outgoing => Transaction::Liquid(self.new_lbtc_refund_tx(
&swap,
refund_address,
utxos,
is_cooperative,
)?),
Direction::Outgoing => Transaction::Liquid(
self.new_lbtc_refund_tx(&swap, refund_address, utxos, is_cooperative)
.await?,
),
},
Swap::Send(_) => Transaction::Liquid(self.new_lbtc_refund_tx(
&swap,
refund_address,
utxos,
is_cooperative,
)?),
Swap::Send(_) => Transaction::Liquid(
self.new_lbtc_refund_tx(&swap, refund_address, utxos, is_cooperative)
.await?,
),
Swap::Receive(_) => {
return Err(PaymentError::Generic {
err: format!(
@@ -407,8 +454,12 @@ impl Swapper for BoltzSwapper {
Ok(tx)
}
fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result<String, PaymentError> {
let response = self.client.broadcast_tx(chain, &tx_hex.into())?;
async fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result<String, PaymentError> {
let response = self
.get_client()
.await?
.inner
.broadcast_tx(chain, &tx_hex.into())?;
let err = format!("Unexpected response from Boltz server: {response}");
let tx_id = response
.as_object()
@@ -422,23 +473,34 @@ impl Swapper for BoltzSwapper {
}
fn create_status_stream(&self) -> Box<dyn SwapperStatusStream> {
Box::new(BoltzStatusStream::new(&self.boltz_url))
Box::new(BoltzStatusStream::new(
self.config.clone(),
self.proxy_url.clone(),
))
}
fn check_for_mrh(
async fn check_for_mrh(
&self,
invoice: &str,
) -> Result<Option<(String, boltz_client::bitcoin::Amount)>, PaymentError> {
boltz_client::swaps::magic_routing::check_for_mrh(
&self.client,
&self.get_client().await?.inner,
invoice,
self.config.network.into(),
)
.map_err(Into::into)
}
fn get_bolt12_invoice(&self, offer: &str, amount_sat: u64) -> Result<String, PaymentError> {
let invoice_res = self.client.get_bolt12_invoice(offer, amount_sat)?;
async fn get_bolt12_invoice(
&self,
offer: &str,
amount_sat: u64,
) -> Result<String, PaymentError> {
let invoice_res = self
.get_client()
.await?
.inner
.get_bolt12_invoice(offer, amount_sat)?;
info!("Received BOLT12 invoice response: {invoice_res:?}");
Ok(invoice_res.invoice)
}

View File

@@ -0,0 +1,66 @@
use std::sync::{Arc, OnceLock};
use anyhow::Result;
use async_trait::async_trait;
use sdk_common::prelude::BreezServer;
use url::Url;
use crate::{persist::Persister, swapper::ProxyUrlFetcher};
pub(crate) struct BoltzProxyFetcher {
url: OnceLock<Option<String>>,
persister: Arc<Persister>,
}
pub(crate) fn split_proxy_url(url: &str) -> (Option<String>, Option<String>) {
Url::parse(url)
.map(|url| {
let api_base_url = url.domain().map(|domain| format!("https://{domain}/v2"));
let referral_id = url.query().and_then(|q| {
q.split('=')
.map(Into::into)
.collect::<Vec<String>>()
.get(1)
.cloned()
});
(api_base_url, referral_id)
})
.unwrap_or_default()
}
impl BoltzProxyFetcher {
pub(crate) fn new(persister: Arc<Persister>) -> Self {
Self {
url: OnceLock::new(),
persister,
}
}
}
#[async_trait]
impl ProxyUrlFetcher for BoltzProxyFetcher {
async fn fetch(&self) -> Result<&Option<String>> {
if let Some(swapper_proxy_url) = self.url.get() {
return Ok(swapper_proxy_url);
}
let maybe_swapper_proxy_url =
match BreezServer::new("https://bs1.breez.technology:443".into(), None) {
Ok(breez_server) => {
let maybe_swapper_proxy_url = breez_server
.fetch_boltz_swapper_urls()
.await
.map(|swapper_urls| swapper_urls.first().cloned())?;
if let Some(swapper_proxy_url) = maybe_swapper_proxy_url.clone() {
self.persister.set_swapper_proxy_url(swapper_proxy_url)?;
}
maybe_swapper_proxy_url
}
Err(_) => self.persister.get_swapper_proxy_url().unwrap_or(None),
};
let swapper_proxy_url = self.url.get_or_init(|| maybe_swapper_proxy_url);
Ok(swapper_proxy_url)
}
}

View File

@@ -2,7 +2,6 @@ use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use boltz_client::swaps::boltz::{self, Subscription, SwapUpdate};
use futures_util::{SinkExt, StreamExt};
use log::{debug, error, info, warn};
@@ -13,28 +12,39 @@ use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};
use url::Url;
use crate::model::Config;
use crate::swapper::{ReconnectHandler, SwapperStatusStream};
use super::{split_proxy_url, ProxyUrlFetcher};
pub(crate) struct BoltzStatusStream {
url: String,
config: Config,
proxy_url: Arc<dyn ProxyUrlFetcher>,
subscription_notifier: broadcast::Sender<String>,
update_notifier: broadcast::Sender<boltz::Update>,
}
impl BoltzStatusStream {
pub(crate) fn new(url: &str) -> Self {
pub(crate) fn new(config: Config, proxy_url: Arc<dyn ProxyUrlFetcher>) -> Self {
let (subscription_notifier, _) = broadcast::channel::<String>(30);
let (update_notifier, _) = broadcast::channel::<boltz::Update>(30);
Self {
url: url.replace("http", "ws") + "/ws",
config,
proxy_url,
subscription_notifier,
update_notifier,
}
}
async fn connect(&self) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
let (socket, _) = connect_async(Url::parse(&self.url)?)
let default_url = self.config.default_boltz_url().to_string();
let url = match self.proxy_url.fetch().await {
Ok(Some(url)) => split_proxy_url(url).0.unwrap_or(default_url),
_ => default_url,
};
let url = url.replace("http", "ws") + "/ws";
let (socket, _) = connect_async(Url::parse(&url)?)
.await
.map_err(|e| anyhow!("Failed to connect to websocket: {e:?}"))?;
Ok(socket)
@@ -58,7 +68,6 @@ impl BoltzStatusStream {
}
}
#[async_trait]
impl SwapperStatusStream for BoltzStatusStream {
fn track_swap_id(&self, swap_id: &str) -> Result<()> {
let _ = self.subscription_notifier.send(swap_id.to_string());
@@ -69,7 +78,7 @@ impl SwapperStatusStream for BoltzStatusStream {
self.update_notifier.subscribe()
}
async fn start(
fn start(
self: Arc<Self>,
callback: Box<dyn ReconnectHandler>,
mut shutdown: watch::Receiver<()>,

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use anyhow::Result;
use async_trait::async_trait;
use boltz_client::{
boltz::{
@@ -22,55 +23,58 @@ pub(crate) use reconnect_handler::*;
pub(crate) mod boltz;
pub(crate) mod reconnect_handler;
#[async_trait]
pub trait Swapper: Send + Sync {
/// Create a new chain swap
fn create_chain_swap(
async fn create_chain_swap(
&self,
req: CreateChainRequest,
) -> Result<CreateChainResponse, PaymentError>;
/// Create a new send swap
fn create_send_swap(
async fn create_send_swap(
&self,
req: CreateSubmarineRequest,
) -> Result<CreateSubmarineResponse, PaymentError>;
/// Get the current rate, limits and fees for a given swap direction
fn get_chain_pair(&self, direction: Direction) -> Result<Option<ChainPair>, PaymentError>;
async fn get_chain_pair(&self, direction: Direction)
-> Result<Option<ChainPair>, PaymentError>;
/// Get the current rate, limits and fees for both swap directions
fn get_chain_pairs(&self) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError>;
async 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, SdkError>;
async fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result<Amount, SdkError>;
/// Accept a specific quote for a Zero-Amount Receive Chain Swap
fn accept_zero_amount_chain_swap_quote(
async 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>;
async fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError>;
/// Get a submarine swap's preimage
fn get_submarine_preimage(&self, swap_id: &str) -> Result<String, PaymentError>;
async fn get_submarine_preimage(&self, swap_id: &str) -> Result<String, PaymentError>;
/// Get send swap claim tx details which includes the preimage as a proof of payment.
/// It is used to validate the preimage before claiming which is the reason why we need to separate
/// the claim into two steps.
fn get_send_claim_tx_details(
async fn get_send_claim_tx_details(
&self,
swap: &SendSwap,
) -> Result<SubmarineClaimTxResponse, PaymentError>;
/// Claim send swap cooperatively. Here the remote swapper is the one that claims.
/// We are helping to use key spend path for cheaper fees.
fn claim_send_swap_cooperative(
async fn claim_send_swap_cooperative(
&self,
swap: &SendSwap,
claim_tx_response: SubmarineClaimTxResponse,
@@ -78,23 +82,23 @@ pub trait Swapper: Send + Sync {
) -> Result<(), PaymentError>;
/// Create a new receive swap
fn create_receive_swap(
async fn create_receive_swap(
&self,
req: CreateReverseRequest,
) -> Result<CreateReverseResponse, PaymentError>;
/// Get a reverse pair information
fn get_reverse_swap_pairs(&self) -> Result<Option<ReversePair>, PaymentError>;
async fn get_reverse_swap_pairs(&self) -> Result<Option<ReversePair>, PaymentError>;
/// Create a claim transaction for a receive or chain swap
fn create_claim_tx(
async fn create_claim_tx(
&self,
swap: Swap,
claim_address: Option<String>,
) -> Result<crate::prelude::Transaction, PaymentError>;
/// Estimate the refund broadcast transaction size and fees in sats for a send or chain swap
fn estimate_refund_broadcast(
async fn estimate_refund_broadcast(
&self,
swap: Swap,
refund_address: &str,
@@ -102,7 +106,7 @@ pub trait Swapper: Send + Sync {
) -> Result<(u32, u64), SdkError>;
/// Create a refund transaction for a send or chain swap
fn create_refund_tx(
async fn create_refund_tx(
&self,
swap: Swap,
refund_address: &str,
@@ -112,26 +116,30 @@ pub trait Swapper: Send + Sync {
) -> Result<crate::prelude::Transaction, PaymentError>;
/// Broadcasts a transaction and returns its id
fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result<String, PaymentError>;
async fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result<String, PaymentError>;
fn create_status_stream(&self) -> Box<dyn SwapperStatusStream>;
/// Look for a valid Magic Routing Hint. If found, validate it and extract the BIP21 info (amount, address).
fn check_for_mrh(
async fn check_for_mrh(
&self,
invoice: &str,
) -> Result<Option<(String, boltz_client::bitcoin::Amount)>, PaymentError>;
fn get_bolt12_invoice(&self, offer: &str, amount_sat: u64) -> Result<String, PaymentError>;
async fn get_bolt12_invoice(
&self,
offer: &str,
amount_sat: u64,
) -> Result<String, PaymentError>;
}
#[async_trait]
pub trait SwapperStatusStream: Send + Sync {
async fn start(
self: Arc<Self>,
callback: Box<dyn ReconnectHandler>,
shutdown: watch::Receiver<()>,
);
fn start(self: Arc<Self>, callback: Box<dyn ReconnectHandler>, shutdown: watch::Receiver<()>);
fn track_swap_id(&self, swap_id: &str) -> anyhow::Result<()>;
fn subscribe_swap_updates(&self) -> broadcast::Receiver<boltz_client::boltz::Update>;
}
#[async_trait]
pub(crate) trait ProxyUrlFetcher: Send + Sync + 'static {
async fn fetch(&self) -> Result<&Option<String>>;
}

View File

@@ -80,7 +80,7 @@ impl SyncService {
}
}
pub(crate) async fn start(self: Arc<Self>, mut shutdown: watch::Receiver<()>) -> Result<()> {
pub(crate) fn start(self: Arc<Self>, mut shutdown: watch::Receiver<()>) {
tokio::spawn(async move {
if let Err(err) = self.client.connect(self.remote_url.clone()).await {
log::warn!("Could not connect to sync service: {err:?}");
@@ -111,8 +111,6 @@ impl SyncService {
}
}
});
Ok(())
}
fn commit_record(&self, decryption_info: &DecryptionInfo, swap: Option<Swap>) -> Result<()> {

View File

@@ -247,7 +247,7 @@ impl BitcoinChainService for MockBitcoinChainService {
_retries: u64,
) -> Result<electrum_client::GetBalanceRes> {
Ok(GetBalanceRes {
confirmed: self.script_balance_sat.lock().unwrap().clone(),
confirmed: *self.script_balance_sat.lock().unwrap(),
unconfirmed: 0,
})
}

View File

@@ -17,6 +17,7 @@ use crate::{
use super::{
chain::{MockBitcoinChainService, MockLiquidChainService},
generate_random_string,
swapper::MockProxyUrlFetcher,
wallet::{MockSigner, MockWallet},
};
@@ -31,7 +32,10 @@ pub(crate) fn new_chain_swap_handler(persister: Arc<Persister>) -> Result<ChainS
let config = Config::testnet(None);
let signer: Arc<Box<dyn Signer>> = Arc::new(Box::new(MockSigner::new()?));
let onchain_wallet = Arc::new(MockWallet::new(signer)?);
let swapper = Arc::new(BoltzSwapper::new(config.clone(), None));
let swapper = Arc::new(BoltzSwapper::new(
config.clone(),
Arc::new(MockProxyUrlFetcher::new()),
));
let liquid_chain_service = Arc::new(MockLiquidChainService::new());
let bitcoin_chain_service = Arc::new(MockBitcoinChainService::new());

View File

@@ -1,7 +1,6 @@
#![cfg(test)]
use anyhow::Result;
use async_trait::async_trait;
use boltz_client::boltz;
use std::sync::Arc;
@@ -29,9 +28,8 @@ impl MockStatusStream {
}
}
#[async_trait]
impl SwapperStatusStream for MockStatusStream {
async fn start(
fn start(
self: Arc<Self>,
_callback: Box<dyn ReconnectHandler>,
_shutdown: watch::Receiver<()>,

View File

@@ -1,5 +1,7 @@
#![cfg(test)]
use anyhow::Result;
use async_trait::async_trait;
use boltz_client::{
boltz::{
ChainFees, ChainMinerFees, ChainPair, ChainSwapDetails, CreateChainResponse,
@@ -17,7 +19,7 @@ use crate::{
ensure_sdk,
error::{PaymentError, SdkError},
model::{Direction, SendSwap, Swap, Transaction as SdkTransaction, Utxo},
swapper::Swapper,
swapper::{ProxyUrlFetcher, Swapper},
test_utils::generate_random_string,
utils,
};
@@ -75,25 +77,45 @@ impl MockSwapper {
*self.zero_amount_swap_mock_config.lock().unwrap() = config;
}
fn get_zero_amount_swap_server_lockup_sat(&self) -> u64 {
fn new_chain_pair() -> ChainPair {
ChainPair {
hash: generate_random_string(10),
rate: 0.0,
limits: PairLimits {
maximal: u64::MAX,
minimal: 0,
maximal_zero_conf: 100_000,
},
fees: ChainFees {
percentage: 0.1,
miner_fees: ChainMinerFees {
server: 100,
user: PairMinerFees {
lockup: 100,
claim: 100,
},
},
},
}
}
async fn get_zero_amount_swap_server_lockup_sat(&self) -> u64 {
let zero_amount_swap_mock_config = self.zero_amount_swap_mock_config.lock().unwrap();
let pair = self
.get_chain_pair(Direction::Incoming)
.expect("mock get_chain_pair failed")
.expect("no chainpair in mock");
let pair = Self::new_chain_pair();
let fees = pair
.fees
.boltz(zero_amount_swap_mock_config.user_lockup_sat)
+ pair.fees.server()
+ zero_amount_swap_mock_config.onchain_fee_increase_sat;
zero_amount_swap_mock_config.user_lockup_sat - fees
}
}
#[async_trait]
impl Swapper for MockSwapper {
fn create_chain_swap(
async fn create_chain_swap(
&self,
_req: boltz_client::swaps::boltz::CreateChainRequest,
) -> Result<CreateChainResponse, PaymentError> {
@@ -104,7 +126,7 @@ impl Swapper for MockSwapper {
})
}
fn create_send_swap(
async fn create_send_swap(
&self,
req: boltz_client::swaps::boltz::CreateSubmarineRequest,
) -> Result<CreateSubmarineResponse, PaymentError> {
@@ -130,32 +152,16 @@ impl Swapper for MockSwapper {
})
}
fn get_chain_pair(
async fn get_chain_pair(
&self,
_direction: Direction,
) -> anyhow::Result<Option<ChainPair>, PaymentError> {
Ok(Some(ChainPair {
hash: generate_random_string(10),
rate: 0.0,
limits: PairLimits {
maximal: u64::MAX,
minimal: 0,
maximal_zero_conf: 100_000,
},
fees: ChainFees {
percentage: 0.1,
miner_fees: ChainMinerFees {
server: 100,
user: PairMinerFees {
lockup: 100,
claim: 100,
},
},
},
}))
Ok(Some(Self::new_chain_pair()))
}
fn get_chain_pairs(&self) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError> {
async fn get_chain_pairs(
&self,
) -> Result<(Option<ChainPair>, Option<ChainPair>), PaymentError> {
let test_pair = Some(ChainPair {
hash: generate_random_string(10),
rate: 0.0,
@@ -178,11 +184,11 @@ impl Swapper for MockSwapper {
Ok((test_pair.clone(), test_pair))
}
fn get_submarine_preimage(&self, _swap_id: &str) -> Result<String, PaymentError> {
async fn get_submarine_preimage(&self, _swap_id: &str) -> Result<String, PaymentError> {
Ok(Preimage::new().to_string().unwrap())
}
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> {
async fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> {
Ok(Some(SubmarinePair {
hash: generate_random_string(10),
rate: 0.0,
@@ -198,7 +204,7 @@ impl Swapper for MockSwapper {
}))
}
fn get_send_claim_tx_details(
async fn get_send_claim_tx_details(
&self,
_swap: &SendSwap,
) -> Result<SubmarineClaimTxResponse, PaymentError> {
@@ -212,7 +218,7 @@ impl Swapper for MockSwapper {
})
}
fn create_claim_tx(
async fn create_claim_tx(
&self,
swap: Swap,
_claim_address: Option<String>,
@@ -240,7 +246,7 @@ impl Swapper for MockSwapper {
})
}
fn estimate_refund_broadcast(
async fn estimate_refund_broadcast(
&self,
_swap: Swap,
_refund_address: &str,
@@ -249,7 +255,7 @@ impl Swapper for MockSwapper {
Ok((0, 0))
}
fn create_refund_tx(
async fn create_refund_tx(
&self,
swap: Swap,
_refund_address: &str,
@@ -280,7 +286,7 @@ impl Swapper for MockSwapper {
})
}
fn claim_send_swap_cooperative(
async fn claim_send_swap_cooperative(
&self,
_swap: &SendSwap,
_claim_tx_response: boltz_client::swaps::boltz::SubmarineClaimTxResponse,
@@ -289,7 +295,7 @@ impl Swapper for MockSwapper {
Ok(())
}
fn create_receive_swap(
async fn create_receive_swap(
&self,
_req: boltz_client::swaps::boltz::CreateReverseRequest,
) -> Result<CreateReverseResponse, PaymentError> {
@@ -305,7 +311,7 @@ impl Swapper for MockSwapper {
})
}
fn get_reverse_swap_pairs(&self) -> Result<Option<ReversePair>, PaymentError> {
async fn get_reverse_swap_pairs(&self) -> Result<Option<ReversePair>, PaymentError> {
Ok(Some(ReversePair {
hash: "".to_string(),
rate: 0.0,
@@ -323,7 +329,7 @@ impl Swapper for MockSwapper {
}))
}
fn broadcast_tx(
async fn broadcast_tx(
&self,
_chain: boltz_client::network::Chain,
tx_hex: &str,
@@ -336,7 +342,7 @@ impl Swapper for MockSwapper {
Box::new(MockStatusStream::new())
}
fn check_for_mrh(
async fn check_for_mrh(
&self,
_invoice: &str,
) -> Result<Option<(String, boltz_client::bitcoin::Amount)>, PaymentError> {
@@ -344,24 +350,43 @@ impl Swapper for MockSwapper {
unimplemented!()
}
fn get_bolt12_invoice(&self, _offer: &str, _amount_sat: u64) -> Result<String, PaymentError> {
async 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, SdkError> {
let server_lockup_amount_sat = self.get_zero_amount_swap_server_lockup_sat();
async fn get_zero_amount_chain_swap_quote(&self, _swap_id: &str) -> Result<Amount, SdkError> {
let server_lockup_amount_sat = self.get_zero_amount_swap_server_lockup_sat().await;
Ok(Amount::from_sat(server_lockup_amount_sat))
}
fn accept_zero_amount_chain_swap_quote(
async fn accept_zero_amount_chain_swap_quote(
&self,
_swap_id: &str,
server_lockup_sat: u64,
) -> Result<(), PaymentError> {
ensure_sdk!(
server_lockup_sat == self.get_zero_amount_swap_server_lockup_sat(),
server_lockup_sat == self.get_zero_amount_swap_server_lockup_sat().await,
PaymentError::InvalidOrExpiredFees
);
Ok(())
}
}
pub(crate) struct MockProxyUrlFetcher {}
impl MockProxyUrlFetcher {
pub(crate) fn new() -> Self {
Self {}
}
}
#[async_trait]
impl ProxyUrlFetcher for MockProxyUrlFetcher {
async fn fetch(&self) -> Result<&Option<String>> {
Ok(&None)
}
}