From e7a7adaf8a466181ce9a0c5d46909aa105eec6b8 Mon Sep 17 00:00:00 2001 From: yse <70684173+hydra-yse@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:24:55 +0100 Subject: [PATCH] feat: remove swapper initialization at startup (#712) --- lib/core/src/chain_swap.rs | 88 +++--- lib/core/src/model.rs | 9 +- lib/core/src/persist/mod.rs | 5 +- lib/core/src/receive_swap.rs | 19 +- lib/core/src/recover/recoverer.rs | 8 +- lib/core/src/sdk.rs | 198 ++++++------ lib/core/src/send_swap.rs | 22 +- lib/core/src/swapper/boltz/bitcoin.rs | 24 +- lib/core/src/swapper/boltz/liquid.rs | 33 +- lib/core/src/swapper/boltz/mod.rs | 322 ++++++++++++-------- lib/core/src/swapper/boltz/proxy.rs | 66 ++++ lib/core/src/swapper/boltz/status_stream.rs | 23 +- lib/core/src/swapper/mod.rs | 56 ++-- lib/core/src/sync/mod.rs | 4 +- lib/core/src/test_utils/chain.rs | 2 +- lib/core/src/test_utils/chain_swap.rs | 6 +- lib/core/src/test_utils/status_stream.rs | 4 +- lib/core/src/test_utils/swapper.rs | 117 ++++--- 18 files changed, 610 insertions(+), 396 deletions(-) create mode 100644 lib/core/src/swapper/boltz/proxy.rs diff --git a/lib/core/src/chain_swap.rs b/lib/core/src/chain_swap.rs index e759869..a95798e 100644 --- a/lib/core/src/chain_swap.rs +++ b/lib/core/src/chain_swap.rs @@ -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!( diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index c5b7fde..431948f 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -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 diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 5fe5158..2a4f917 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -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(()) diff --git a/lib/core/src/receive_swap.rs b/lib/core/src/receive_swap.rs index 4f41ace..29a5729 100644 --- a/lib/core/src/receive_swap.rs +++ b/lib/core/src/receive_swap.rs @@ -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 diff --git a/lib/core/src/recover/recoverer.rs b/lib/core/src/recover/recoverer.rs index 1c2f8cc..fc31fca 100644 --- a/lib/core/src/recover/recoverer.rs +++ b/lib/core/src/recover/recoverer.rs @@ -49,7 +49,7 @@ impl Recoverer { }) } - fn recover_cooperative_preimages( + async fn recover_cooperative_preimages( &self, recovered_send_data: &mut HashMap, ) -> HashMap { @@ -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, ) -> 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) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 70219ab..0405056 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -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, ) -> Result> { - 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, - signer: Arc>, - ) -> Result> { + fn new(config: Config, signer: Arc>) -> Result> { 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::::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) { + fn track_new_blocks(self: &Arc) { 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) { + fn track_swap_updates(self: &Arc) { 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 { 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 { + async fn get_chain_pair(&self, direction: Direction) -> Result { 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, ) -> Result { - 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 { - 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 { 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 { 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, fees_sat: u64, ) -> Result { - 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; diff --git a/lib/core/src/send_swap.rs b/lib/core/src/send_swap.rs index 16a623b..3d0ef8a 100644 --- a/lib/core/src/send_swap.rs +++ b/lib/core/src/send_swap.rs @@ -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!( diff --git a/lib/core/src/swapper/boltz/bitcoin.rs b/lib/core/src/swapper/boltz/bitcoin.rs index 782f018..9ff71fd 100644 --- a/lib/core/src/swapper/boltz/bitcoin.rs +++ b/lib/core/src/swapper/boltz/bitcoin.rs @@ -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 BoltzSwapper

{ + 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) diff --git a/lib/core/src/swapper/boltz/liquid.rs b/lib/core/src/swapper/boltz/liquid.rs index ed816ed..fa8c251 100644 --- a/lib/core/src/swapper/boltz/liquid.rs +++ b/lib/core/src/swapper/boltz/liquid.rs @@ -16,9 +16,9 @@ use crate::{ utils, }; -use super::BoltzSwapper; +use super::{BoltzSwapper, ProxyUrlFetcher}; -impl BoltzSwapper { +impl BoltzSwapper

{ 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, }; diff --git a/lib/core/src/swapper/boltz/mod.rs b/lib/core/src/swapper/boltz/mod.rs index 5a033df..ef3de44 100644 --- a/lib/core/src/swapper/boltz/mod.rs +++ b/lib/core/src/swapper/boltz/mod.rs @@ -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, - config: Config, - liquid_electrum_config: ElectrumConfig, - bitcoin_electrum_config: ElectrumConfig, + inner: BoltzApiClientV2, } -impl BoltzSwapper { - pub fn new(config: Config, swapper_proxy_url: Option) -> 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 = 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 { + config: Config, + client: OnceLock, + liquid_electrum_config: ElectrumConfig, + bitcoin_electrum_config: ElectrumConfig, + proxy_url: Arc

, +} +impl BoltzSwapper

{ + pub fn new(config: Config, proxy_url: Arc

) -> 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 { + 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, partial_sig: Option, - ) -> Option { - Some(Cooperative { - boltz_api: &self.client, + ) -> Result> { + Ok(Some(Cooperative { + boltz_api: &self.get_client().await?.inner, swap_id, pub_nonce, partial_sig, - }) + })) } } -impl Swapper for BoltzSwapper { +#[async_trait] +impl Swapper for BoltzSwapper

{ /// Create a new chain swap - fn create_chain_swap( + async fn create_chain_swap( &self, req: CreateChainRequest, ) -> Result { + 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 { + 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, PaymentError> { - let pairs = self.client.get_chain_pairs()?; + async fn get_chain_pair( + &self, + direction: Direction, + ) -> Result, 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, Option), PaymentError> { - let pairs = self.client.get_chain_pairs()?; + async fn get_chain_pairs( + &self, + ) -> Result<(Option, Option), 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 { - self.client + async fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result { + 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, PaymentError> { - Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair()) + async fn get_submarine_pairs(&self) -> Result, 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 { - Ok(self.client.get_submarine_preimage(swap_id)?.preimage) + async fn get_submarine_preimage(&self, swap_id: &str) -> Result { + 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 { - 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 { + 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, PaymentError> { - Ok(self.client.get_reverse_pairs()?.get_btc_to_lbtc_pair()) + async fn get_reverse_swap_pairs(&self) -> Result, 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, @@ -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 { - let response = self.client.broadcast_tx(chain, &tx_hex.into())?; + async fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result { + 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 { - 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, 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 { - let invoice_res = self.client.get_bolt12_invoice(offer, amount_sat)?; + async fn get_bolt12_invoice( + &self, + offer: &str, + amount_sat: u64, + ) -> Result { + 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) } diff --git a/lib/core/src/swapper/boltz/proxy.rs b/lib/core/src/swapper/boltz/proxy.rs new file mode 100644 index 0000000..3bc5090 --- /dev/null +++ b/lib/core/src/swapper/boltz/proxy.rs @@ -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>, + persister: Arc, +} + +pub(crate) fn split_proxy_url(url: &str) -> (Option, Option) { + 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::>() + .get(1) + .cloned() + }); + (api_base_url, referral_id) + }) + .unwrap_or_default() +} + +impl BoltzProxyFetcher { + pub(crate) fn new(persister: Arc) -> Self { + Self { + url: OnceLock::new(), + persister, + } + } +} + +#[async_trait] +impl ProxyUrlFetcher for BoltzProxyFetcher { + async fn fetch(&self) -> Result<&Option> { + 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) + } +} diff --git a/lib/core/src/swapper/boltz/status_stream.rs b/lib/core/src/swapper/boltz/status_stream.rs index 32dbe11..f077a2a 100644 --- a/lib/core/src/swapper/boltz/status_stream.rs +++ b/lib/core/src/swapper/boltz/status_stream.rs @@ -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, subscription_notifier: broadcast::Sender, update_notifier: broadcast::Sender, } impl BoltzStatusStream { - pub(crate) fn new(url: &str) -> Self { + pub(crate) fn new(config: Config, proxy_url: Arc) -> Self { let (subscription_notifier, _) = broadcast::channel::(30); let (update_notifier, _) = broadcast::channel::(30); Self { - url: url.replace("http", "ws") + "/ws", + config, + proxy_url, subscription_notifier, update_notifier, } } async fn connect(&self) -> Result>> { - 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, callback: Box, mut shutdown: watch::Receiver<()>, diff --git a/lib/core/src/swapper/mod.rs b/lib/core/src/swapper/mod.rs index a350423..f17e3c4 100644 --- a/lib/core/src/swapper/mod.rs +++ b/lib/core/src/swapper/mod.rs @@ -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; /// Create a new send swap - fn create_send_swap( + async fn create_send_swap( &self, req: CreateSubmarineRequest, ) -> Result; /// Get the current rate, limits and fees for a given swap direction - fn get_chain_pair(&self, direction: Direction) -> Result, PaymentError>; + async fn get_chain_pair(&self, direction: Direction) + -> Result, PaymentError>; /// Get the current rate, limits and fees for both swap directions - fn get_chain_pairs(&self) -> Result<(Option, Option), PaymentError>; + async fn get_chain_pairs(&self) + -> Result<(Option, Option), 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; + async fn get_zero_amount_chain_swap_quote(&self, swap_id: &str) -> Result; /// 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, PaymentError>; + async fn get_submarine_pairs(&self) -> Result, PaymentError>; /// Get a submarine swap's preimage - fn get_submarine_preimage(&self, swap_id: &str) -> Result; + async fn get_submarine_preimage(&self, swap_id: &str) -> Result; /// 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; /// 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; /// Get a reverse pair information - fn get_reverse_swap_pairs(&self) -> Result, PaymentError>; + async fn get_reverse_swap_pairs(&self) -> Result, 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, ) -> Result; /// 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; /// Broadcasts a transaction and returns its id - fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result; + async fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result; fn create_status_stream(&self) -> Box; /// 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, PaymentError>; - fn get_bolt12_invoice(&self, offer: &str, amount_sat: u64) -> Result; + async fn get_bolt12_invoice( + &self, + offer: &str, + amount_sat: u64, + ) -> Result; } -#[async_trait] pub trait SwapperStatusStream: Send + Sync { - async fn start( - self: Arc, - callback: Box, - shutdown: watch::Receiver<()>, - ); + fn start(self: Arc, callback: Box, shutdown: watch::Receiver<()>); fn track_swap_id(&self, swap_id: &str) -> anyhow::Result<()>; fn subscribe_swap_updates(&self) -> broadcast::Receiver; } + +#[async_trait] +pub(crate) trait ProxyUrlFetcher: Send + Sync + 'static { + async fn fetch(&self) -> Result<&Option>; +} diff --git a/lib/core/src/sync/mod.rs b/lib/core/src/sync/mod.rs index 9bb20a6..284d5c5 100644 --- a/lib/core/src/sync/mod.rs +++ b/lib/core/src/sync/mod.rs @@ -80,7 +80,7 @@ impl SyncService { } } - pub(crate) async fn start(self: Arc, mut shutdown: watch::Receiver<()>) -> Result<()> { + pub(crate) fn start(self: Arc, 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) -> Result<()> { diff --git a/lib/core/src/test_utils/chain.rs b/lib/core/src/test_utils/chain.rs index e892f30..c5494c3 100644 --- a/lib/core/src/test_utils/chain.rs +++ b/lib/core/src/test_utils/chain.rs @@ -247,7 +247,7 @@ impl BitcoinChainService for MockBitcoinChainService { _retries: u64, ) -> Result { Ok(GetBalanceRes { - confirmed: self.script_balance_sat.lock().unwrap().clone(), + confirmed: *self.script_balance_sat.lock().unwrap(), unconfirmed: 0, }) } diff --git a/lib/core/src/test_utils/chain_swap.rs b/lib/core/src/test_utils/chain_swap.rs index dc10d9a..a1e4a4b 100644 --- a/lib/core/src/test_utils/chain_swap.rs +++ b/lib/core/src/test_utils/chain_swap.rs @@ -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) -> Result> = 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()); diff --git a/lib/core/src/test_utils/status_stream.rs b/lib/core/src/test_utils/status_stream.rs index d8dedf3..cfea086 100644 --- a/lib/core/src/test_utils/status_stream.rs +++ b/lib/core/src/test_utils/status_stream.rs @@ -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, _callback: Box, _shutdown: watch::Receiver<()>, diff --git a/lib/core/src/test_utils/swapper.rs b/lib/core/src/test_utils/swapper.rs index 24e4418..1e11d4d 100644 --- a/lib/core/src/test_utils/swapper.rs +++ b/lib/core/src/test_utils/swapper.rs @@ -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 { @@ -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 { @@ -130,32 +152,16 @@ impl Swapper for MockSwapper { }) } - fn get_chain_pair( + async fn get_chain_pair( &self, _direction: Direction, ) -> anyhow::Result, 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, Option), PaymentError> { + async fn get_chain_pairs( + &self, + ) -> Result<(Option, Option), 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 { + async fn get_submarine_preimage(&self, _swap_id: &str) -> Result { Ok(Preimage::new().to_string().unwrap()) } - fn get_submarine_pairs(&self) -> Result, PaymentError> { + async fn get_submarine_pairs(&self) -> Result, 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 { @@ -212,7 +218,7 @@ impl Swapper for MockSwapper { }) } - fn create_claim_tx( + async fn create_claim_tx( &self, swap: Swap, _claim_address: Option, @@ -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 { @@ -305,7 +311,7 @@ impl Swapper for MockSwapper { }) } - fn get_reverse_swap_pairs(&self) -> Result, PaymentError> { + async fn get_reverse_swap_pairs(&self) -> Result, 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, PaymentError> { @@ -344,24 +350,43 @@ impl Swapper for MockSwapper { unimplemented!() } - fn get_bolt12_invoice(&self, _offer: &str, _amount_sat: u64) -> Result { + async fn get_bolt12_invoice( + &self, + _offer: &str, + _amount_sat: u64, + ) -> Result { unimplemented!() } - fn get_zero_amount_chain_swap_quote(&self, _swap_id: &str) -> Result { - 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 { + 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> { + Ok(&None) + } +}