From 272c32fc64cf9984e929ff33b3f6dbd2f799152d Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Wed, 29 May 2024 17:52:54 +0300 Subject: [PATCH 01/11] Extract out swapper logic to its own module --- lib/core/src/lib.rs | 1 + lib/core/src/swapper.rs | 300 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 lib/core/src/swapper.rs diff --git a/lib/core/src/lib.rs b/lib/core/src/lib.rs index 645ed37..0a2a2fc 100644 --- a/lib/core/src/lib.rs +++ b/lib/core/src/lib.rs @@ -9,4 +9,5 @@ pub mod logger; pub mod model; pub mod persist; pub mod sdk; +pub(crate) mod swapper; pub(crate) mod utils; diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs new file mode 100644 index 0000000..0f4db78 --- /dev/null +++ b/lib/core/src/swapper.rs @@ -0,0 +1,300 @@ +use std::str::FromStr; + +use anyhow::Result; +use boltz_client::network::electrum::ElectrumConfig; +use boltz_client::network::Chain; +use boltz_client::swaps::boltzv2::{ + BoltzApiClientV2, CreateReverseRequest, CreateReverseResponse, CreateSubmarineRequest, + CreateSubmarineResponse, ReversePair, SubmarinePair, +}; + +use boltz_client::error::Error; + +use boltz_client::util::secrets::Preimage; +use boltz_client::{Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, LBtcSwapTxV2}; +use log::{debug, info}; +use serde_json::Value; + +use crate::error::PaymentError; +use crate::model::{Network, ReceiveSwap, SendSwap}; + +pub const BOLTZ_TESTNET_URL_V2: &str = "https://api.testnet.boltz.exchange/v2"; +pub const BOLTZ_MAINNET_URL_V2: &str = "https://api.boltz.exchange/v2"; + +pub trait Swapper: Send + Sync { + // Send swap + fn create_send_swap( + &self, + req: CreateSubmarineRequest, + ) -> Result; + fn get_submarine_pairs(&self) -> Result, PaymentError>; + fn refund_send_swap_cooperative( + &self, + swap: &SendSwap, + output_address: &String, + broadcast_fees_sat: Amount, + keypair: &Keypair, + ) -> Result; + fn refund_send_swap_non_cooperative( + &self, + swap: &SendSwap, + keypair: &Keypair, + broadcast_fees_sat: Amount, + output_address: &String, + ) -> Result; + fn claim_send_swap_cooperative( + &self, + swap: &SendSwap, + invoice: &str, + keypair: &Keypair, + ) -> Result<(), PaymentError>; + + // Receive swap + fn create_receive_swap( + &self, + req: CreateReverseRequest, + ) -> Result; + fn get_reverse_swap_pairs(&self) -> Result, PaymentError>; + fn claim_receive_swap( + &self, + swap: &ReceiveSwap, + keypair: &Keypair, + claim_address: String, + ) -> Result; + + // chain broadcast + fn broadcast_tx(&self, chain: Chain, tx_hex: &String) -> Result; +} + +pub struct BoltzSwapper { + client: BoltzApiClientV2, + electrum_config: ElectrumConfig, + network: Network, +} + +impl BoltzSwapper { + pub fn new(network: Network, electrum_config: ElectrumConfig) -> BoltzSwapper { + BoltzSwapper { + client: BoltzApiClientV2::new(BoltzSwapper::boltz_url_v2(&network)), + electrum_config, + network, + } + } + + fn boltz_url_v2(network: &Network) -> &'static str { + match network { + Network::LiquidTestnet => BOLTZ_TESTNET_URL_V2, + Network::Liquid => BOLTZ_MAINNET_URL_V2, + } + } + + fn new_refund_tx( + &self, + swap: &SendSwap, + keypair: &Keypair, + output_address: &String, + ) -> Result { + let create_response = swap + .get_boltz_create_response() + .map_err(|e| Error::Generic(e.to_string()))?; + + let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( + &create_response, + keypair.public_key().into(), + )?; + + Ok(LBtcSwapTxV2::new_refund( + swap_script.clone(), + output_address, + &self.electrum_config, + Self::boltz_url_v2(&self.network).to_string(), + swap.id.to_string(), + )?) + } + + fn validate_send_swap_preimage( + &self, + swap_id: &str, + invoice: &str, + preimage: &str, + ) -> Result<(), PaymentError> { + Self::verify_payment_hash(preimage, invoice)?; + info!("Preimage is valid for Send Swap {swap_id}"); + Ok(()) + } + + fn verify_payment_hash(preimage: &str, invoice: &str) -> Result<(), PaymentError> { + let preimage = Preimage::from_str(preimage)?; + let preimage_hash = preimage.sha256.to_string(); + let invoice = + Bolt11Invoice::from_str(invoice).map_err(|e| Error::Generic(e.to_string()))?; + let invoice_payment_hash = invoice.payment_hash(); + + (invoice_payment_hash.to_string() == preimage_hash) + .then_some(()) + .ok_or(PaymentError::InvalidPreimage) + } +} + +impl Swapper for BoltzSwapper { + fn create_send_swap( + &self, + req: CreateSubmarineRequest, + ) -> Result { + Ok(self.client.post_swap_req(&req)?) + } + + fn create_receive_swap( + &self, + req: CreateReverseRequest, + ) -> Result { + Ok(self.client.post_reverse_req(req)?) + } + + fn claim_send_swap_cooperative( + &self, + swap: &SendSwap, + invoice: &str, + keypair: &Keypair, + ) -> Result<(), PaymentError> { + let swap_id = &swap.id; + debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); + let refund_tx = self.new_refund_tx(swap, keypair, &"".into())?; + + let claim_tx_response = self.client.get_claim_tx_details(&swap_id.to_string())?; + debug!("Received claim tx details: {:?}", &claim_tx_response); + + self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage)?; + + let (partial_sig, pub_nonce) = + refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; + + self.client + .post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; + debug!("Successfully sent claim details for swap-in {swap_id}"); + Ok(()) + } + + fn get_submarine_pairs(&self) -> Result, PaymentError> { + Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair()) + } + + fn get_reverse_swap_pairs(&self) -> Result, PaymentError> { + Ok(self.client.get_reverse_pairs()?.get_btc_to_lbtc_pair()) + } + + fn broadcast_tx(&self, chain: Chain, tx_hex: &String) -> Result { + Ok(self.client.broadcast_tx(chain, tx_hex)?) + } + + fn refund_send_swap_cooperative( + &self, + swap: &SendSwap, + output_address: &String, + broadcast_fees_sat: Amount, + keypair: &Keypair, + ) -> Result { + info!("Initiating cooperative refund for Send Swap {}", &swap.id); + let create_response = swap + .get_boltz_create_response() + .map_err(|e| Error::Generic(e.to_string()))?; + + let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( + &create_response, + keypair.public_key().into(), + )?; + let refund_tx = self.new_refund_tx(swap, keypair, output_address)?; + + let cooperative = Some((&self.client, &swap.id)); + let tx = refund_tx.sign_refund( + &swap + .get_refund_keypair() + .map_err(|e| Error::Generic(e.to_string()))?, + broadcast_fees_sat, + cooperative, + )?; + let is_lowball = match self.network { + Network::Liquid => None, + Network::LiquidTestnet => { + Some((&self.client, boltz_client::network::Chain::LiquidTestnet)) + } + }; + let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; + info!( + "Successfully broadcast cooperative refund for Send Swap {}", + &swap.id + ); + Ok(refund_tx_id.clone()) + } + + fn refund_send_swap_non_cooperative( + &self, + swap: &SendSwap, + keypair: &Keypair, + broadcast_fees_sat: Amount, + output_address: &String, + ) -> Result { + let refund_tx = self.new_refund_tx(swap, keypair, output_address)?; + let tx = refund_tx.sign_refund( + &swap + .get_refund_keypair() + .map_err(|e| Error::Generic(e.to_string()))?, + broadcast_fees_sat, + None, + )?; + let is_lowball = match self.network { + Network::Liquid => None, + Network::LiquidTestnet => { + Some((&self.client, boltz_client::network::Chain::LiquidTestnet)) + } + }; + let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; + info!( + "Successfully broadcast non-cooperative refund for swap-in {}", + swap.id + ); + Ok(refund_tx_id) + } + + fn claim_receive_swap( + &self, + swap: &ReceiveSwap, + keypair: &Keypair, + claim_address: String, + ) -> Result { + let create_response = swap + .get_boltz_create_response() + .map_err(|e| Error::Generic(e.to_string()))?; + let swap_script = LBtcSwapScriptV2::reverse_from_swap_resp( + &create_response, + keypair.public_key().into(), + )?; + let swap_id = &swap.id; + let claim_tx_wrapper = LBtcSwapTxV2::new_claim( + swap_script, + claim_address, + &self.electrum_config, + BoltzSwapper::boltz_url_v2(&self.network).into(), + swap.id.clone(), + )?; + + let cooperative = Some((&self.client, swap.id.clone())); + let claim_tx = claim_tx_wrapper.sign_claim( + keypair, + &Preimage::from_str(&swap.preimage)?, + Amount::from_sat(swap.claim_fees_sat), + // Enable cooperative claim (Some) or not (None) + cooperative, + // None + )?; + + let claim_tx_id = claim_tx_wrapper.broadcast( + &claim_tx, + &self.electrum_config, + Some((&self.client, self.network.into())), + )?; + info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}"); + debug!("Claim Tx {:?}", claim_tx); + Ok(claim_tx_id) + } +} From 806806f166ab3cf7e9ee7dfbd190a2d72e3f3d36 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Wed, 29 May 2024 18:02:07 +0300 Subject: [PATCH 02/11] Add some comments --- lib/core/src/swapper.rs | 105 ++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index 0f4db78..278a995 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -22,12 +22,16 @@ pub const BOLTZ_TESTNET_URL_V2: &str = "https://api.testnet.boltz.exchange/v2"; pub const BOLTZ_MAINNET_URL_V2: &str = "https://api.boltz.exchange/v2"; pub trait Swapper: Send + Sync { - // Send swap + /// Create a new send swap fn create_send_swap( &self, req: CreateSubmarineRequest, ) -> Result; + + /// Get a submarine pair information fn get_submarine_pairs(&self) -> Result, PaymentError>; + + /// Refund a cooperatively send swap fn refund_send_swap_cooperative( &self, swap: &SendSwap, @@ -35,6 +39,8 @@ pub trait Swapper: Send + Sync { broadcast_fees_sat: Amount, keypair: &Keypair, ) -> Result; + + /// Refund non-cooperatively send swap fn refund_send_swap_non_cooperative( &self, swap: &SendSwap, @@ -42,6 +48,9 @@ pub trait Swapper: Send + Sync { broadcast_fees_sat: Amount, output_address: &String, ) -> 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( &self, swap: &SendSwap, @@ -49,12 +58,16 @@ pub trait Swapper: Send + Sync { keypair: &Keypair, ) -> Result<(), PaymentError>; - // Receive swap + // Create a new receive swap fn create_receive_swap( &self, req: CreateReverseRequest, ) -> Result; + + // Get a reverse pair information fn get_reverse_swap_pairs(&self) -> Result, PaymentError>; + + /// Claim receive swap. Here the local swapper is the one that claims. fn claim_receive_swap( &self, swap: &ReceiveSwap, @@ -137,6 +150,7 @@ impl BoltzSwapper { } impl Swapper for BoltzSwapper { + /// Create a new send swap fn create_send_swap( &self, req: CreateSubmarineRequest, @@ -144,49 +158,12 @@ impl Swapper for BoltzSwapper { Ok(self.client.post_swap_req(&req)?) } - fn create_receive_swap( - &self, - req: CreateReverseRequest, - ) -> Result { - Ok(self.client.post_reverse_req(req)?) - } - - fn claim_send_swap_cooperative( - &self, - swap: &SendSwap, - invoice: &str, - keypair: &Keypair, - ) -> Result<(), PaymentError> { - let swap_id = &swap.id; - debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); - let refund_tx = self.new_refund_tx(swap, keypair, &"".into())?; - - let claim_tx_response = self.client.get_claim_tx_details(&swap_id.to_string())?; - debug!("Received claim tx details: {:?}", &claim_tx_response); - - self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage)?; - - let (partial_sig, pub_nonce) = - refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; - - self.client - .post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; - debug!("Successfully sent claim details for swap-in {swap_id}"); - Ok(()) - } - + /// Get a submarine pair information fn get_submarine_pairs(&self) -> Result, PaymentError> { Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair()) } - fn get_reverse_swap_pairs(&self) -> Result, PaymentError> { - Ok(self.client.get_reverse_pairs()?.get_btc_to_lbtc_pair()) - } - - fn broadcast_tx(&self, chain: Chain, tx_hex: &String) -> Result { - Ok(self.client.broadcast_tx(chain, tx_hex)?) - } - + /// Refund a cooperatively send swap fn refund_send_swap_cooperative( &self, swap: &SendSwap, @@ -227,6 +204,7 @@ impl Swapper for BoltzSwapper { Ok(refund_tx_id.clone()) } + /// Refund non-cooperatively send swap fn refund_send_swap_non_cooperative( &self, swap: &SendSwap, @@ -256,6 +234,46 @@ impl Swapper for BoltzSwapper { Ok(refund_tx_id) } + /// 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( + &self, + swap: &SendSwap, + invoice: &str, + keypair: &Keypair, + ) -> Result<(), PaymentError> { + let swap_id = &swap.id; + debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); + let refund_tx = self.new_refund_tx(swap, keypair, &"".into())?; + + let claim_tx_response = self.client.get_claim_tx_details(&swap_id.to_string())?; + debug!("Received claim tx details: {:?}", &claim_tx_response); + + self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage)?; + + let (partial_sig, pub_nonce) = + refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; + + self.client + .post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; + debug!("Successfully sent claim details for swap-in {swap_id}"); + Ok(()) + } + + // Create a new receive swap + fn create_receive_swap( + &self, + req: CreateReverseRequest, + ) -> Result { + Ok(self.client.post_reverse_req(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()) + } + + /// Claim receive swap. Here the local swapper is the one that claims. fn claim_receive_swap( &self, swap: &ReceiveSwap, @@ -297,4 +315,9 @@ impl Swapper for BoltzSwapper { debug!("Claim Tx {:?}", claim_tx); Ok(claim_tx_id) } + + // chain broadcast + fn broadcast_tx(&self, chain: Chain, tx_hex: &String) -> Result { + Ok(self.client.broadcast_tx(chain, tx_hex)?) + } } From a9168f651d4a77fc9791e6d835a83b5ff667c626 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Fri, 31 May 2024 23:13:10 +0300 Subject: [PATCH 03/11] integrate swapper trait --- lib/core/src/sdk.rs | 241 ++++++++++++---------------------------- lib/core/src/swapper.rs | 87 ++++++++------- 2 files changed, 121 insertions(+), 207 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 3898047..0bed4fc 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -19,16 +19,15 @@ use boltz_client::{ liquidv2::LBtcSwapTxV2, }, util::secrets::Preimage, - Amount, Bolt11Invoice, ElementsAddress, Keypair, LBtcSwapScriptV2, + Amount, Bolt11Invoice, ElementsAddress, LBtcSwapScriptV2, }; use log::{debug, error, info, warn}; use lwk_common::{singlesig_desc, Signer, Singlesig}; use lwk_signer::{AnySigner, SwSigner}; use lwk_wollet::bitcoin::Witness; -use lwk_wollet::elements::LockTime; use lwk_wollet::hashes::{sha256, Hash}; use lwk_wollet::{ - elements::{Address, Transaction}, + elements::{Address, Transaction, LockTime}, BlockchainBackend, ElementsNetwork, FsPersister, Wollet as LwkWollet, WolletDescriptor, }; use tokio::sync::{watch, Mutex, RwLock}; @@ -36,6 +35,7 @@ use tokio::time::MissedTickBehavior; use crate::error::LiquidSdkError; use crate::model::PaymentState::*; +use crate::swapper::{BoltzSwapper, Swapper}; use crate::{ boltz_status_stream::BoltzStatusStream, ensure_sdk, @@ -62,6 +62,7 @@ pub struct LiquidSdk { persister: Arc, event_manager: Arc, status_stream: Arc, + swapper: Arc, is_started: RwLock, shutdown_sender: watch::Sender<()>, shutdown_receiver: watch::Receiver<()>, @@ -104,6 +105,10 @@ impl LiquidSdk { let status_stream = Arc::new(BoltzStatusStream::new(&config.boltz_url, persister.clone())); let (shutdown_sender, shutdown_receiver) = watch::channel::<()>(()); + let electrum_config = config.get_electrum_config(); + + let swapper = Arc::new(BoltzSwapper::new(config.network, electrum_config)); + let sdk = Arc::new(LiquidSdk { config, lwk_wollet: Arc::new(Mutex::new(lwk_wollet)), @@ -111,6 +116,7 @@ impl LiquidSdk { persister, event_manager, status_stream, + swapper, is_started: RwLock::new(false), shutdown_sender, shutdown_receiver, @@ -510,14 +516,20 @@ impl LiquidSdk { Some(claim_tx_id) => { warn!("Claim tx for Receive Swap {id} was already broadcast: txid {claim_tx_id}") } - None => match self.try_claim(&receive_swap).await { - Ok(()) => {} + None => { + self.try_handle_receive_swap_update(&receive_swap.id, Pending, None) + .await?; + + match self.try_claim(&receive_swap).await { + Ok(_) => {} Err(err) => match err { PaymentError::AlreadyClaimed => warn!("Funds already claimed for Receive Swap {id}"), _ => error!("Claim for Receive Swap {id} failed: {err}") } - }, + + } } + } Ok(()) } @@ -587,19 +599,9 @@ impl LiquidSdk { // Boltz has detected the lockup in the mempool, we can speed up // the claim by doing so cooperatively Ok(SubSwapStates::TransactionClaimPending) => { - let keypair = ongoing_send_swap.get_refund_keypair()?; - let swap_script = ongoing_send_swap.get_swap_script().map_err(|e| { - anyhow!("Could not rebuild refund details for Send Swap {id}: {e:?}") - })?; - - self.cooperate_send_swap_claim( - id, - &swap_script, - &ongoing_send_swap.invoice, - &keypair, - ) - .await - .map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?; + self.cooperate_send_swap_claim(&ongoing_send_swap) + .await + .map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?; Ok(()) } @@ -720,8 +722,21 @@ impl LiquidSdk { }) } - pub(crate) fn boltz_client_v2(&self) -> BoltzApiClientV2 { - BoltzApiClientV2::new(&self.config.boltz_url) + pub(crate) fn boltz_url_v2(network: Network) -> &'static str { + match network { + Network::Testnet => BOLTZ_TESTNET_URL_V2, + Network::Mainnet => BOLTZ_MAINNET_URL_V2, + } + } + + fn network_config(&self) -> ElectrumConfig { + ElectrumConfig::new( + self.network.into(), + &self.electrum_url.to_string(), + true, + true, + 100, + ) } async fn build_tx( @@ -767,12 +782,12 @@ impl LiquidSdk { } fn validate_submarine_pairs( - client: &BoltzApiClientV2, + &self, receiver_amount_sat: u64, ) -> Result { - let lbtc_pair = client + let lbtc_pair = self + .swapper .get_submarine_pairs()? - .get_lbtc_to_btc_pair() .ok_or(PaymentError::PairsNotFound)?; lbtc_pair.limits.within(receiver_amount_sat)?; @@ -816,8 +831,7 @@ impl LiquidSdk { .ok_or(PaymentError::AmountOutOfRange)? / 1000; - let client = self.boltz_client_v2(); - let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; + let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?; let broadcast_fees_sat = self .get_broadcast_fee_estimation(receiver_amount_sat) @@ -856,36 +870,10 @@ impl LiquidSdk { )?) } - async fn try_refund_cooperative( - &self, - swap: &SendSwap, - refund_tx: &LBtcSwapTxV2, - broadcast_fees_sat: Amount, - is_lowball: Option<(&BoltzApiClientV2, Chain)>, - ) -> Result { - info!("Initiating cooperative refund for Send Swap {}", &swap.id); - let tx = refund_tx.sign_refund( - &swap.get_refund_keypair()?, - broadcast_fees_sat, - Some((&self.boltz_client_v2(), &swap.id)), - )?; - - let refund_tx_id = - refund_tx.broadcast(&tx, &self.config.get_electrum_config(), is_lowball)?; - info!( - "Successfully broadcast cooperative refund for Send Swap {}", - &swap.id - ); - Ok(refund_tx_id.clone()) - } - async fn try_refund_non_cooperative( &self, swap: &SendSwap, - swap_script: &LBtcSwapScriptV2, - refund_tx: LBtcSwapTxV2, broadcast_fees_sat: Amount, - is_lowball: Option<(&BoltzApiClientV2, Chain)>, ) -> Result { info!( "Initiating non-cooperative refund for Send Swap {}", @@ -893,30 +881,19 @@ impl LiquidSdk { ); let current_height = self.lwk_wollet.lock().await.tip().height(); - let locktime_from_height = - LockTime::from_height(current_height).map_err(|e| PaymentError::Generic { - err: format!("Cannot convert current block height to lock time: {e:?}"), - })?; + let output_address = self.next_unused_address().await?.to_string(); + let refund_tx_id = self.swapper.refund_send_swap_non_cooperative( + swap, + broadcast_fees_sat, + &output_address, + current_height, + )?; - info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime); - match utils::is_locktime_expired(locktime_from_height, swap_script.locktime) { - true => { - let tx = refund_tx.sign_refund(&swap.get_refund_keypair()?, broadcast_fees_sat, None)?; - let refund_tx_id = - refund_tx.broadcast(&tx, &self.config.get_electrum_config(), is_lowball)?; - info!( - "Successfully broadcast non-cooperative refund for Send Swap {}", - swap.id - ); - Ok(refund_tx_id) - } - false => Err(PaymentError::Generic { - err: format!( - "Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}", - locktime_from_height, swap_script.locktime - ) - }) - } + info!( + "Successfully broadcast non-cooperative refund for Send Swap {}, tx: {}", + swap.id, refund_tx_id + ); + Ok(refund_tx_id) } async fn try_refund(&self, swap: &SendSwap) -> Result { @@ -925,27 +902,17 @@ impl LiquidSdk { let amount_sat = get_invoice_amount!(swap.invoice); let broadcast_fees_sat = Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat).await?); - let client = self.boltz_client_v2(); - let is_lowball = match self.config.network { - Network::Mainnet => None, - Network::Testnet => Some((&client, boltz_client::network::Chain::LiquidTestnet)), - }; - match self - .try_refund_cooperative(swap, &refund_tx, broadcast_fees_sat, is_lowball) - .await - { + let output_address = self.next_unused_address().await?.to_string(); + let refund_res = + self.swapper + .refund_send_swap_cooperative(swap, &output_address, broadcast_fees_sat); + match refund_res { Ok(res) => Ok(res), Err(e) => { warn!("Cooperative refund failed: {:?}", e); - self.try_refund_non_cooperative( - swap, - &swap_script, - refund_tx, - broadcast_fees_sat, - is_lowball, - ) - .await + self.try_refund_non_cooperative(swap, broadcast_fees_sat) + .await } } } @@ -964,28 +931,12 @@ impl LiquidSdk { } /// Interact with Boltz to assist in them doing a cooperative claim - async fn cooperate_send_swap_claim( - &self, - swap_id: &str, - swap_script: &LBtcSwapScriptV2, - invoice: &str, - keypair: &Keypair, - ) -> Result<(), PaymentError> { - debug!("Claim is pending for Send Swap {swap_id}. Initiating cooperative claim"); - let client = self.boltz_client_v2(); - let refund_tx = self.new_refund_tx(swap_id, swap_script).await?; - - let claim_tx_response = client.get_claim_tx_details(&swap_id.to_string())?; - debug!("Received claim tx details: {:?}", &claim_tx_response); - - self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage) - .await?; - - let (partial_sig, pub_nonce) = - refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; - - client.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; - debug!("Successfully sent claim details for Send Swap {swap_id}"); + async fn cooperate_send_swap_claim(&self, send_swap: &SendSwap) -> Result<(), PaymentError> { + debug!( + "Claim is pending for Send Swap {}. Initiating cooperative claim", + &send_swap.id + ); + self.swapper.claim_send_swap_cooperative(send_swap)?; Ok(()) } @@ -1032,8 +983,7 @@ impl LiquidSdk { self.validate_invoice(&req.invoice)?; let receiver_amount_sat = get_invoice_amount!(req.invoice); - let client = self.boltz_client_v2(); - let lbtc_pair = Self::validate_submarine_pairs(&client, receiver_amount_sat)?; + let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?; let broadcast_fees_sat = self .get_broadcast_fee_estimation(receiver_amount_sat) .await?; @@ -1050,8 +1000,7 @@ impl LiquidSdk { compressed: true, inner: keypair.public_key(), }; - - let create_response = client.post_swap_req(&CreateSubmarineRequest { + let create_response = self.swapper.create_send_swap(CreateSubmarineRequest { from: "L-BTC".to_string(), to: "BTC".to_string(), invoice: req.invoice.to_string(), @@ -1143,45 +1092,10 @@ impl LiquidSdk { ongoing_receive_swap.claim_tx_id.is_none(), PaymentError::AlreadyClaimed ); - let swap_id = &ongoing_receive_swap.id; - debug!("Trying to claim Receive Swap {swap_id}",); - - self.try_handle_receive_swap_update(swap_id, Pending, None) - .await?; - - let keypair = ongoing_receive_swap.get_claim_keypair()?; - let create_response = ongoing_receive_swap.get_boltz_create_response()?; - let swap_script = LBtcSwapScriptV2::reverse_from_swap_resp( - &create_response, - keypair.public_key().into(), - )?; - - let claim_address = self.next_unused_address().await?.to_string(); - let claim_tx_wrapper = LBtcSwapTxV2::new_claim( - swap_script, - claim_address, - &self.config.get_electrum_config(), - self.config.clone().boltz_url, - ongoing_receive_swap.id.clone(), - )?; - - let claim_tx = claim_tx_wrapper.sign_claim( - &keypair, - &Preimage::from_str(&ongoing_receive_swap.preimage)?, - Amount::from_sat(ongoing_receive_swap.claim_fees_sat), - // Enable cooperative claim (Some) or not (None) - Some((&self.boltz_client_v2(), swap_id.clone())), - // None - )?; - - let claim_tx_id = claim_tx_wrapper.broadcast( - &claim_tx, - &self.config.get_electrum_config(), - Some((&self.boltz_client_v2(), self.config.network.into())), - )?; - info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}"); - debug!("Claim Tx {:?}", claim_tx); + let claim_address = self.next_unused_address().await?.to_string(); + let claim_tx_id = self.swapper.claim_receive_swap(ongoing_receive_swap, claim_address)?; + info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {}", swap_id); // We insert a pseudo-claim-tx in case LWK fails to pick up the new mempool tx for a while // This makes the tx known to the SDK (get_info, list_payments) instantly @@ -1204,12 +1118,7 @@ impl LiquidSdk { req: &PrepareReceiveRequest, ) -> Result { self.ensure_is_started().await?; - - let reverse_pair = self - .boltz_client_v2() - .get_reverse_pairs()? - .get_btc_to_lbtc_pair() - .ok_or(PaymentError::PairsNotFound)?; + let reverse_pair = self.swapper.get_reverse_swap_pairs()?.ok_or(PaymentError::PairsNotFound)?; let payer_amount_sat = req.payer_amount_sat; let fees_sat = reverse_pair.fees.total(req.payer_amount_sat); @@ -1238,11 +1147,7 @@ impl LiquidSdk { let payer_amount_sat = req.payer_amount_sat; let fees_sat = req.fees_sat; - let reverse_pair = self - .boltz_client_v2() - .get_reverse_pairs()? - .get_btc_to_lbtc_pair() - .ok_or(PaymentError::PairsNotFound)?; + let reverse_pair = self.swapper.get_reverse_swap_pairs()?.ok_or(PaymentError::PairsNotFound)?; let new_fees_sat = reverse_pair.fees.total(req.payer_amount_sat); ensure_sdk!(fees_sat == new_fees_sat, PaymentError::InvalidOrExpiredFees); @@ -1264,7 +1169,7 @@ impl LiquidSdk { address_signature: None, referral_id: None, }; - let create_response = self.boltz_client_v2().post_reverse_req(v2_req)?; + let create_response = self.swapper.create_receive_swap(v2_req)?; let swap_id = create_response.id.clone(); let invoice = Bolt11Invoice::from_str(&create_response.invoice) diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index 278a995..7c82dce 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -13,6 +13,7 @@ use boltz_client::error::Error; use boltz_client::util::secrets::Preimage; use boltz_client::{Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, LBtcSwapTxV2}; use log::{debug, info}; +use lwk_wollet::elements::{LockTime, LockTime::*}; use serde_json::Value; use crate::error::PaymentError; @@ -37,26 +38,20 @@ pub trait Swapper: Send + Sync { swap: &SendSwap, output_address: &String, broadcast_fees_sat: Amount, - keypair: &Keypair, ) -> Result; /// Refund non-cooperatively send swap fn refund_send_swap_non_cooperative( &self, swap: &SendSwap, - keypair: &Keypair, broadcast_fees_sat: Amount, output_address: &String, + current_height: u32, ) -> 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( - &self, - swap: &SendSwap, - invoice: &str, - keypair: &Keypair, - ) -> Result<(), PaymentError>; + fn claim_send_swap_cooperative(&self, swap: &SendSwap) -> Result<(), PaymentError>; // Create a new receive swap fn create_receive_swap( @@ -71,7 +66,6 @@ pub trait Swapper: Send + Sync { fn claim_receive_swap( &self, swap: &ReceiveSwap, - keypair: &Keypair, claim_address: String, ) -> Result; @@ -96,8 +90,8 @@ impl BoltzSwapper { fn boltz_url_v2(network: &Network) -> &'static str { match network { - Network::LiquidTestnet => BOLTZ_TESTNET_URL_V2, - Network::Liquid => BOLTZ_MAINNET_URL_V2, + Network::Testnet => BOLTZ_TESTNET_URL_V2, + Network::Mainnet => BOLTZ_MAINNET_URL_V2, } } @@ -169,18 +163,14 @@ impl Swapper for BoltzSwapper { swap: &SendSwap, output_address: &String, broadcast_fees_sat: Amount, - keypair: &Keypair, ) -> Result { info!("Initiating cooperative refund for Send Swap {}", &swap.id); let create_response = swap .get_boltz_create_response() .map_err(|e| Error::Generic(e.to_string()))?; - let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( - &create_response, - keypair.public_key().into(), - )?; - let refund_tx = self.new_refund_tx(swap, keypair, output_address)?; + let refund_keypair = swap.get_refund_keypair()?; + let refund_tx = self.new_refund_tx(swap, &refund_keypair, output_address)?; let cooperative = Some((&self.client, &swap.id)); let tx = refund_tx.sign_refund( @@ -191,10 +181,8 @@ impl Swapper for BoltzSwapper { cooperative, )?; let is_lowball = match self.network { - Network::Liquid => None, - Network::LiquidTestnet => { - Some((&self.client, boltz_client::network::Chain::LiquidTestnet)) - } + Network::Mainnet => None, + Network::Testnet => Some((&self.client, boltz_client::network::Chain::LiquidTestnet)), }; let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; info!( @@ -208,11 +196,39 @@ impl Swapper for BoltzSwapper { fn refund_send_swap_non_cooperative( &self, swap: &SendSwap, - keypair: &Keypair, broadcast_fees_sat: Amount, output_address: &String, + current_height: u32, ) -> Result { - let refund_tx = self.new_refund_tx(swap, keypair, output_address)?; + let keypair = swap.get_refund_keypair()?; + let create_response = swap + .get_boltz_create_response() + .map_err(|e| Error::Generic(e.to_string()))?; + let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( + &create_response, + keypair.public_key().into(), + )?; + let locktime_from_height = + LockTime::from_height(current_height).map_err(|e| PaymentError::Generic { + err: format!("Cannot convert current block height to lock time: {e:?}"), + })?; + + info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime); + let is_locktime_satisfied = match (locktime_from_height, swap_script.locktime) { + (Blocks(n), Blocks(lock_time)) => n >= lock_time, + (Seconds(n), Seconds(lock_time)) => n >= lock_time, + _ => false, // Not using the same units + }; + if !is_locktime_satisfied { + return Err(PaymentError::Generic { + err: format!( + "Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}", + locktime_from_height, swap_script.locktime + ) + }); + } + + let refund_tx = self.new_refund_tx(swap, &keypair, output_address)?; let tx = refund_tx.sign_refund( &swap .get_refund_keypair() @@ -221,10 +237,8 @@ impl Swapper for BoltzSwapper { None, )?; let is_lowball = match self.network { - Network::Liquid => None, - Network::LiquidTestnet => { - Some((&self.client, boltz_client::network::Chain::LiquidTestnet)) - } + Network::Mainnet => None, + Network::Testnet => Some((&self.client, boltz_client::network::Chain::LiquidTestnet)), }; let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; info!( @@ -236,23 +250,18 @@ 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( - &self, - swap: &SendSwap, - invoice: &str, - keypair: &Keypair, - ) -> Result<(), PaymentError> { + fn claim_send_swap_cooperative(&self, swap: &SendSwap) -> Result<(), PaymentError> { let swap_id = &swap.id; - debug!("Claim is pending for swap-in {swap_id}. Initiating cooperative claim"); - let refund_tx = self.new_refund_tx(swap, keypair, &"".into())?; + let keypair = swap.get_refund_keypair()?; + let refund_tx = self.new_refund_tx(swap, &keypair, &"".into())?; let claim_tx_response = self.client.get_claim_tx_details(&swap_id.to_string())?; debug!("Received claim tx details: {:?}", &claim_tx_response); - self.validate_send_swap_preimage(swap_id, invoice, &claim_tx_response.preimage)?; + self.validate_send_swap_preimage(swap_id, &swap.invoice, &claim_tx_response.preimage)?; let (partial_sig, pub_nonce) = - refund_tx.submarine_partial_sig(keypair, &claim_tx_response)?; + refund_tx.submarine_partial_sig(&keypair, &claim_tx_response)?; self.client .post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; @@ -277,9 +286,9 @@ impl Swapper for BoltzSwapper { fn claim_receive_swap( &self, swap: &ReceiveSwap, - keypair: &Keypair, claim_address: String, ) -> Result { + let keypair = swap.get_claim_keypair()?; let create_response = swap .get_boltz_create_response() .map_err(|e| Error::Generic(e.to_string()))?; @@ -298,7 +307,7 @@ impl Swapper for BoltzSwapper { let cooperative = Some((&self.client, swap.id.clone())); let claim_tx = claim_tx_wrapper.sign_claim( - keypair, + &keypair, &Preimage::from_str(&swap.preimage)?, Amount::from_sat(swap.claim_fees_sat), // Enable cooperative claim (Some) or not (None) From 9db2e6d6da1f5a75885575db87ecee8eb060469d Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 01:47:05 +0300 Subject: [PATCH 04/11] Allow resending to timeouted swaps --- lib/core/src/sdk.rs | 88 ++++++++++++++++++----------------------- lib/core/src/swapper.rs | 42 +++++++++++--------- 2 files changed, 61 insertions(+), 69 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 0bed4fc..c69efbf 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -9,14 +9,12 @@ use std::{ use anyhow::{anyhow, Result}; use boltz_client::lightning_invoice::Bolt11InvoiceDescription; -use boltz_client::network::Chain; use boltz_client::swaps::boltzv2; use boltz_client::ToHex; use boltz_client::{ swaps::{ boltz::{RevSwapStates, SubSwapStates}, - boltzv2::*, - liquidv2::LBtcSwapTxV2, + boltzv2::*, }, util::secrets::Preimage, Amount, Bolt11Invoice, ElementsAddress, LBtcSwapScriptV2, @@ -204,19 +202,28 @@ impl LiquidSdk { tokio::select! { update = updates_stream.recv() => match update { Ok(boltzv2::Update { id, status }) => { - let _ = cloned.sync().await; - - if let Ok(_) = cloned.try_handle_send_swap_boltz_status(&status, &id).await - { - info!("Handled send swap update"); - } else if let Ok(_) = cloned - .try_handle_receive_swap_boltz_status(&status, &id) - .await - { - info!("Handled receive swap update"); - } else { - warn!("Unhandled swap {id}: {status}") - } + let _ = cloned.sync().await; + match cloned.persister.fetch_send_swap_by_id(&id) { + Ok(_) => { + match cloned.try_handle_send_swap_boltz_status(&status, &id).await { + Ok(_) => info!("Succesfully handled Send Swap {id} update"), + Err(e) => error!("Failed to handle Send Swap {id} update: {e}") + } + } + _ => { + match cloned.persister.fetch_receive_swap(&id) { + Ok(Some(_)) => { + match cloned.try_handle_receive_swap_boltz_status(&status, &id).await { + Ok(_) => info!("Succesfully handled Receive Swap {id} update"), + Err(e) => error!("Failed to handle Receive Swap {id} update: {e}") + } + } + _ => { + error!("Could not find Swap {id}"); + } + } + } + } } Err(e) => error!("Received stream error: {e:?}"), }, @@ -563,7 +570,7 @@ impl LiquidSdk { ongoing_send_swap.state, ongoing_send_swap.lockup_tx_id.clone(), ) { - (PaymentState::Created, None) => { + (PaymentState::Created, None) | (PaymentState::TimedOut, None) => { let create_response = ongoing_send_swap.get_boltz_create_response()?; let lockup_tx_id = self.lockup_funds(id, &create_response).await?; @@ -601,7 +608,10 @@ impl LiquidSdk { Ok(SubSwapStates::TransactionClaimPending) => { self.cooperate_send_swap_claim(&ongoing_send_swap) .await - .map_err(|e| anyhow!("Could not post claim details. Err: {e:?}"))?; + .map_err(|e| { + error!("Could not cooperate Send Swap {id} claim: {e}"); + anyhow!("Could not post claim details. Err: {e:?}") + })?; Ok(()) } @@ -612,6 +622,8 @@ impl LiquidSdk { self.get_preimage_from_script_path_claim_spend(&ongoing_send_swap)?; self.validate_send_swap_preimage(id, &ongoing_send_swap.invoice, &preimage) .await?; + self.try_handle_send_swap_update(id, Complete, Some(&preimage), None, None) + .await?; Ok(()) } @@ -729,16 +741,6 @@ impl LiquidSdk { } } - fn network_config(&self) -> ElectrumConfig { - ElectrumConfig::new( - self.network.into(), - &self.electrum_url.to_string(), - true, - true, - 100, - ) - } - async fn build_tx( &self, fee_rate: Option, @@ -854,22 +856,6 @@ impl LiquidSdk { .ok_or(PaymentError::InvalidPreimage) } - async fn new_refund_tx( - &self, - swap_id: &str, - swap_script: &LBtcSwapScriptV2, - ) -> Result { - let output_address = self.next_unused_address().await?.to_string(); - let network_config = self.config.get_electrum_config(); - Ok(LBtcSwapTxV2::new_refund( - swap_script.clone(), - &output_address, - &network_config, - self.config.clone().boltz_url, - swap_id.to_string(), - )?) - } - async fn try_refund_non_cooperative( &self, swap: &SendSwap, @@ -896,9 +882,7 @@ impl LiquidSdk { Ok(refund_tx_id) } - async fn try_refund(&self, swap: &SendSwap) -> Result { - let swap_script = swap.get_swap_script()?; - let refund_tx = self.new_refund_tx(&swap.id, &swap_script).await?; + async fn try_refund(&self, swap: &SendSwap) -> Result { let amount_sat = get_invoice_amount!(swap.invoice); let broadcast_fees_sat = Amount::from_sat(self.get_broadcast_fee_estimation(amount_sat).await?); @@ -926,8 +910,7 @@ impl LiquidSdk { ) -> Result<(), PaymentError> { Self::verify_payment_hash(preimage, invoice)?; info!("Preimage is valid for Send Swap {swap_id}"); - self.try_handle_send_swap_update(swap_id, Complete, Some(preimage), None, None) - .await + Ok(()) } /// Interact with Boltz to assist in them doing a cooperative claim @@ -936,7 +919,10 @@ impl LiquidSdk { "Claim is pending for Send Swap {}. Initiating cooperative claim", &send_swap.id ); - self.swapper.claim_send_swap_cooperative(send_swap)?; + let output_address = self.next_unused_address().await?.to_string(); + let preimage = self.swapper.claim_send_swap_cooperative(send_swap, &output_address)?; + self.try_handle_send_swap_update(&send_swap.id, Complete, Some(&preimage), None, None) + .await?; Ok(()) } @@ -1030,6 +1016,7 @@ impl LiquidSdk { swap } }; + self.status_stream.track_swap_id(&swap.id)?; let accept_zero_conf = swap.get_boltz_create_response()?.accept_zero_conf; self.wait_for_payment(swap.id, accept_zero_conf) @@ -1309,6 +1296,7 @@ impl LiquidSdk { .ok_or_else(|| anyhow!("Found no input for claim tx"))?; let script_witness_bytes = input.clone().witness.script_witness; + info!("Found Send Swap {id} claim tx witness: {script_witness_bytes:?}"); let script_witness = Witness::from(script_witness_bytes); let preimage_bytes = script_witness diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index 7c82dce..a597f28 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -36,7 +36,7 @@ pub trait Swapper: Send + Sync { fn refund_send_swap_cooperative( &self, swap: &SendSwap, - output_address: &String, + output_address: &str, broadcast_fees_sat: Amount, ) -> Result; @@ -45,13 +45,17 @@ pub trait Swapper: Send + Sync { &self, swap: &SendSwap, broadcast_fees_sat: Amount, - output_address: &String, + output_address: &str, current_height: u32, ) -> 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(&self, swap: &SendSwap) -> Result<(), PaymentError>; + fn claim_send_swap_cooperative( + &self, + swap: &SendSwap, + output_address: &str, + ) -> Result; // Create a new receive swap fn create_receive_swap( @@ -70,7 +74,7 @@ pub trait Swapper: Send + Sync { ) -> Result; // chain broadcast - fn broadcast_tx(&self, chain: Chain, tx_hex: &String) -> Result; + fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result; } pub struct BoltzSwapper { @@ -161,16 +165,12 @@ impl Swapper for BoltzSwapper { fn refund_send_swap_cooperative( &self, swap: &SendSwap, - output_address: &String, + output_address: &str, broadcast_fees_sat: Amount, ) -> Result { info!("Initiating cooperative refund for Send Swap {}", &swap.id); - let create_response = swap - .get_boltz_create_response() - .map_err(|e| Error::Generic(e.to_string()))?; - let refund_keypair = swap.get_refund_keypair()?; - let refund_tx = self.new_refund_tx(swap, &refund_keypair, output_address)?; + let refund_tx = self.new_refund_tx(swap, &refund_keypair, &output_address.into())?; let cooperative = Some((&self.client, &swap.id)); let tx = refund_tx.sign_refund( @@ -197,7 +197,7 @@ impl Swapper for BoltzSwapper { &self, swap: &SendSwap, broadcast_fees_sat: Amount, - output_address: &String, + output_address: &str, current_height: u32, ) -> Result { let keypair = swap.get_refund_keypair()?; @@ -228,7 +228,7 @@ impl Swapper for BoltzSwapper { }); } - let refund_tx = self.new_refund_tx(swap, &keypair, output_address)?; + let refund_tx = self.new_refund_tx(swap, &keypair, &output_address.into())?; let tx = refund_tx.sign_refund( &swap .get_refund_keypair() @@ -250,13 +250,17 @@ 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(&self, swap: &SendSwap) -> Result<(), PaymentError> { + fn claim_send_swap_cooperative( + &self, + swap: &SendSwap, + output_address: &str, + ) -> Result { let swap_id = &swap.id; let keypair = swap.get_refund_keypair()?; - let refund_tx = self.new_refund_tx(swap, &keypair, &"".into())?; + let refund_tx = self.new_refund_tx(swap, &keypair, &output_address.into())?; let claim_tx_response = self.client.get_claim_tx_details(&swap_id.to_string())?; - debug!("Received claim tx details: {:?}", &claim_tx_response); + info!("Received claim tx details: {:?}", &claim_tx_response); self.validate_send_swap_preimage(swap_id, &swap.invoice, &claim_tx_response.preimage)?; @@ -265,8 +269,8 @@ impl Swapper for BoltzSwapper { self.client .post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; - debug!("Successfully sent claim details for swap-in {swap_id}"); - Ok(()) + info!("Successfully sent claim details for swap-in {swap_id}"); + Ok(claim_tx_response.preimage) } // Create a new receive swap @@ -326,7 +330,7 @@ impl Swapper for BoltzSwapper { } // chain broadcast - fn broadcast_tx(&self, chain: Chain, tx_hex: &String) -> Result { - Ok(self.client.broadcast_tx(chain, tx_hex)?) + fn broadcast_tx(&self, chain: Chain, tx_hex: &str) -> Result { + Ok(self.client.broadcast_tx(chain, &tx_hex.into())?) } } From 9e12b58be1130123773c344aedd5ec0b424d43d7 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 08:53:52 +0300 Subject: [PATCH 05/11] fix handle send swap. --- lib/core/src/sdk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index c69efbf..e29c72a 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -204,7 +204,7 @@ impl LiquidSdk { Ok(boltzv2::Update { id, status }) => { let _ = cloned.sync().await; match cloned.persister.fetch_send_swap_by_id(&id) { - Ok(_) => { + Ok(Some(_)) => { match cloned.try_handle_send_swap_boltz_status(&status, &id).await { Ok(_) => info!("Succesfully handled Send Swap {id} update"), Err(e) => error!("Failed to handle Send Swap {id} update: {e}") From c763902a199bd17d9af7751c4b9801b4b5b5a0d8 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 09:08:06 +0300 Subject: [PATCH 06/11] remove unused import --- lib/core/src/sdk.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index e29c72a..2fadf74 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -17,7 +17,7 @@ use boltz_client::{ boltzv2::*, }, util::secrets::Preimage, - Amount, Bolt11Invoice, ElementsAddress, LBtcSwapScriptV2, + Amount, Bolt11Invoice, ElementsAddress, }; use log::{debug, error, info, warn}; use lwk_common::{singlesig_desc, Signer, Singlesig}; @@ -732,14 +732,7 @@ impl LiquidSdk { pending_receive_sat, pubkey: self.lwk_signer.xpub().public_key.to_string(), }) - } - - pub(crate) fn boltz_url_v2(network: Network) -> &'static str { - match network { - Network::Testnet => BOLTZ_TESTNET_URL_V2, - Network::Mainnet => BOLTZ_MAINNET_URL_V2, - } - } + } async fn build_tx( &self, From d5f291128ce702b61bcae38645b10dd4943e60f4 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 09:45:39 +0300 Subject: [PATCH 07/11] use get_swap_script --- lib/core/src/model.rs | 19 ++++++++++++++++ lib/core/src/swapper.rs | 49 +++++++++-------------------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 1540abe..6bcc256 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -342,6 +342,25 @@ impl ReceiveSwap { Ok(res) } + pub(crate) fn get_swap_script(&self) -> Result { + let keypair = self.get_claim_keypair()?; + let create_response = + self.get_boltz_create_response() + .map_err(|e| PaymentError::Generic { + err: format!( + "Failed to create swap script for Send Swap {}: {e:?}", + self.id + ), + })?; + LBtcSwapScriptV2::reverse_from_swap_resp(&create_response, keypair.public_key().into()) + .map_err(|e| PaymentError::Generic { + err: format!( + "Failed to create swap script for Send Swap {}: {e:?}", + self.id + ), + }) + } + pub(crate) fn from_boltz_struct_to_json( create_response: &CreateReverseResponse, expected_swap_id: &str, diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index a597f28..a38148f 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -11,13 +11,14 @@ use boltz_client::swaps::boltzv2::{ use boltz_client::error::Error; use boltz_client::util::secrets::Preimage; -use boltz_client::{Amount, Bolt11Invoice, Keypair, LBtcSwapScriptV2, LBtcSwapTxV2}; +use boltz_client::{Amount, Bolt11Invoice, LBtcSwapTxV2}; use log::{debug, info}; -use lwk_wollet::elements::{LockTime, LockTime::*}; +use lwk_wollet::elements::LockTime; use serde_json::Value; use crate::error::PaymentError; use crate::model::{Network, ReceiveSwap, SendSwap}; +use crate::utils; pub const BOLTZ_TESTNET_URL_V2: &str = "https://api.testnet.boltz.exchange/v2"; pub const BOLTZ_MAINNET_URL_V2: &str = "https://api.boltz.exchange/v2"; @@ -102,17 +103,9 @@ impl BoltzSwapper { fn new_refund_tx( &self, swap: &SendSwap, - keypair: &Keypair, output_address: &String, ) -> Result { - let create_response = swap - .get_boltz_create_response() - .map_err(|e| Error::Generic(e.to_string()))?; - - let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( - &create_response, - keypair.public_key().into(), - )?; + let swap_script = swap.get_swap_script()?; Ok(LBtcSwapTxV2::new_refund( swap_script.clone(), @@ -169,8 +162,7 @@ impl Swapper for BoltzSwapper { broadcast_fees_sat: Amount, ) -> Result { info!("Initiating cooperative refund for Send Swap {}", &swap.id); - let refund_keypair = swap.get_refund_keypair()?; - let refund_tx = self.new_refund_tx(swap, &refund_keypair, &output_address.into())?; + let refund_tx = self.new_refund_tx(swap, &output_address.into())?; let cooperative = Some((&self.client, &swap.id)); let tx = refund_tx.sign_refund( @@ -200,26 +192,14 @@ impl Swapper for BoltzSwapper { output_address: &str, current_height: u32, ) -> Result { - let keypair = swap.get_refund_keypair()?; - let create_response = swap - .get_boltz_create_response() - .map_err(|e| Error::Generic(e.to_string()))?; - let swap_script = LBtcSwapScriptV2::submarine_from_swap_resp( - &create_response, - keypair.public_key().into(), - )?; + let swap_script = swap.get_swap_script()?; let locktime_from_height = LockTime::from_height(current_height).map_err(|e| PaymentError::Generic { err: format!("Cannot convert current block height to lock time: {e:?}"), })?; info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime); - let is_locktime_satisfied = match (locktime_from_height, swap_script.locktime) { - (Blocks(n), Blocks(lock_time)) => n >= lock_time, - (Seconds(n), Seconds(lock_time)) => n >= lock_time, - _ => false, // Not using the same units - }; - if !is_locktime_satisfied { + if utils::is_locktime_expired(locktime_from_height, swap_script.locktime) { return Err(PaymentError::Generic { err: format!( "Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}", @@ -228,7 +208,7 @@ impl Swapper for BoltzSwapper { }); } - let refund_tx = self.new_refund_tx(swap, &keypair, &output_address.into())?; + let refund_tx = self.new_refund_tx(swap, &output_address.into())?; let tx = refund_tx.sign_refund( &swap .get_refund_keypair() @@ -257,7 +237,7 @@ impl Swapper for BoltzSwapper { ) -> Result { let swap_id = &swap.id; let keypair = swap.get_refund_keypair()?; - let refund_tx = self.new_refund_tx(swap, &keypair, &output_address.into())?; + let refund_tx = self.new_refund_tx(swap, &output_address.into())?; let claim_tx_response = self.client.get_claim_tx_details(&swap_id.to_string())?; info!("Received claim tx details: {:?}", &claim_tx_response); @@ -292,14 +272,7 @@ impl Swapper for BoltzSwapper { swap: &ReceiveSwap, claim_address: String, ) -> Result { - let keypair = swap.get_claim_keypair()?; - let create_response = swap - .get_boltz_create_response() - .map_err(|e| Error::Generic(e.to_string()))?; - let swap_script = LBtcSwapScriptV2::reverse_from_swap_resp( - &create_response, - keypair.public_key().into(), - )?; + let swap_script = swap.get_swap_script()?; let swap_id = &swap.id; let claim_tx_wrapper = LBtcSwapTxV2::new_claim( swap_script, @@ -311,7 +284,7 @@ impl Swapper for BoltzSwapper { let cooperative = Some((&self.client, swap.id.clone())); let claim_tx = claim_tx_wrapper.sign_claim( - &keypair, + &swap.get_claim_keypair()?, &Preimage::from_str(&swap.preimage)?, Amount::from_sat(swap.claim_fees_sat), // Enable cooperative claim (Some) or not (None) From 26524f8697b2433123690dc0b560494473980690 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 10:08:09 +0300 Subject: [PATCH 08/11] use config in swapper --- lib/core/src/sdk.rs | 4 +--- lib/core/src/swapper.rs | 35 +++++++++++++++++------------------ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 2fadf74..3563e84 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -103,9 +103,7 @@ impl LiquidSdk { let status_stream = Arc::new(BoltzStatusStream::new(&config.boltz_url, persister.clone())); let (shutdown_sender, shutdown_receiver) = watch::channel::<()>(()); - let electrum_config = config.get_electrum_config(); - - let swapper = Arc::new(BoltzSwapper::new(config.network, electrum_config)); + let swapper = Arc::new(BoltzSwapper::new(config.clone())); let sdk = Arc::new(LiquidSdk { config, diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index a38148f..635842e 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use anyhow::Result; -use boltz_client::network::electrum::ElectrumConfig; use boltz_client::network::Chain; use boltz_client::swaps::boltzv2::{ BoltzApiClientV2, CreateReverseRequest, CreateReverseResponse, CreateSubmarineRequest, @@ -17,7 +16,7 @@ use lwk_wollet::elements::LockTime; use serde_json::Value; use crate::error::PaymentError; -use crate::model::{Network, ReceiveSwap, SendSwap}; +use crate::model::{Config, Network, ReceiveSwap, SendSwap}; use crate::utils; pub const BOLTZ_TESTNET_URL_V2: &str = "https://api.testnet.boltz.exchange/v2"; @@ -80,16 +79,14 @@ pub trait Swapper: Send + Sync { pub struct BoltzSwapper { client: BoltzApiClientV2, - electrum_config: ElectrumConfig, - network: Network, + config: Config, } impl BoltzSwapper { - pub fn new(network: Network, electrum_config: ElectrumConfig) -> BoltzSwapper { + pub fn new(config: Config) -> BoltzSwapper { BoltzSwapper { - client: BoltzApiClientV2::new(BoltzSwapper::boltz_url_v2(&network)), - electrum_config, - network, + client: BoltzApiClientV2::new(BoltzSwapper::boltz_url_v2(&config.network)), + config, } } @@ -110,8 +107,8 @@ impl BoltzSwapper { Ok(LBtcSwapTxV2::new_refund( swap_script.clone(), output_address, - &self.electrum_config, - Self::boltz_url_v2(&self.network).to_string(), + &self.config.get_electrum_config(), + Self::boltz_url_v2(&self.config.network).to_string(), swap.id.to_string(), )?) } @@ -172,11 +169,12 @@ impl Swapper for BoltzSwapper { broadcast_fees_sat, cooperative, )?; - let is_lowball = match self.network { + let is_lowball = match self.config.network { Network::Mainnet => None, Network::Testnet => Some((&self.client, boltz_client::network::Chain::LiquidTestnet)), }; - let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; + let refund_tx_id = + refund_tx.broadcast(&tx, &self.config.get_electrum_config(), is_lowball)?; info!( "Successfully broadcast cooperative refund for Send Swap {}", &swap.id @@ -216,11 +214,12 @@ impl Swapper for BoltzSwapper { broadcast_fees_sat, None, )?; - let is_lowball = match self.network { + let is_lowball = match self.config.network { Network::Mainnet => None, Network::Testnet => Some((&self.client, boltz_client::network::Chain::LiquidTestnet)), }; - let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; + let refund_tx_id = + refund_tx.broadcast(&tx, &self.config.get_electrum_config(), is_lowball)?; info!( "Successfully broadcast non-cooperative refund for swap-in {}", swap.id @@ -277,8 +276,8 @@ impl Swapper for BoltzSwapper { let claim_tx_wrapper = LBtcSwapTxV2::new_claim( swap_script, claim_address, - &self.electrum_config, - BoltzSwapper::boltz_url_v2(&self.network).into(), + &self.config.get_electrum_config(), + BoltzSwapper::boltz_url_v2(&self.config.network).into(), swap.id.clone(), )?; @@ -294,8 +293,8 @@ impl Swapper for BoltzSwapper { let claim_tx_id = claim_tx_wrapper.broadcast( &claim_tx, - &self.electrum_config, - Some((&self.client, self.network.into())), + &self.config.get_electrum_config(), + Some((&self.client, self.config.network.into())), )?; info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}"); debug!("Claim Tx {:?}", claim_tx); From fd749e7e35a2e4ebbe5a8716d2db21d83a3606e6 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 10:09:29 +0300 Subject: [PATCH 09/11] fix expired condition --- lib/core/src/swapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index 635842e..120263c 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -197,7 +197,7 @@ impl Swapper for BoltzSwapper { })?; info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime); - if utils::is_locktime_expired(locktime_from_height, swap_script.locktime) { + if !utils::is_locktime_expired(locktime_from_height, swap_script.locktime) { return Err(PaymentError::Generic { err: format!( "Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}", From b7e703d2971a217c72168734102579cc7bca1872 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 10:35:04 +0300 Subject: [PATCH 10/11] simplified getting boltz url from config --- lib/core/src/model.rs | 4 ++-- lib/core/src/swapper.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 6bcc256..b1a9cd1 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -348,14 +348,14 @@ impl ReceiveSwap { self.get_boltz_create_response() .map_err(|e| PaymentError::Generic { err: format!( - "Failed to create swap script for Send Swap {}: {e:?}", + "Failed to create swap script for Receive Swap {}: {e:?}", self.id ), })?; LBtcSwapScriptV2::reverse_from_swap_resp(&create_response, keypair.public_key().into()) .map_err(|e| PaymentError::Generic { err: format!( - "Failed to create swap script for Send Swap {}: {e:?}", + "Failed to create swap script for Receive Swap {}: {e:?}", self.id ), }) diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index 120263c..e59a54c 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -85,7 +85,7 @@ pub struct BoltzSwapper { impl BoltzSwapper { pub fn new(config: Config) -> BoltzSwapper { BoltzSwapper { - client: BoltzApiClientV2::new(BoltzSwapper::boltz_url_v2(&config.network)), + client: BoltzApiClientV2::new(&config.boltz_url), config, } } From eafe7feb39cac2daf4f985e8e8449ebfac282d7d Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Sat, 1 Jun 2024 17:40:33 +0300 Subject: [PATCH 11/11] use url from config --- lib/core/src/swapper.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/core/src/swapper.rs b/lib/core/src/swapper.rs index e59a54c..405d37c 100644 --- a/lib/core/src/swapper.rs +++ b/lib/core/src/swapper.rs @@ -19,9 +19,6 @@ use crate::error::PaymentError; use crate::model::{Config, Network, ReceiveSwap, SendSwap}; use crate::utils; -pub const BOLTZ_TESTNET_URL_V2: &str = "https://api.testnet.boltz.exchange/v2"; -pub const BOLTZ_MAINNET_URL_V2: &str = "https://api.boltz.exchange/v2"; - pub trait Swapper: Send + Sync { /// Create a new send swap fn create_send_swap( @@ -90,13 +87,6 @@ impl BoltzSwapper { } } - fn boltz_url_v2(network: &Network) -> &'static str { - match network { - Network::Testnet => BOLTZ_TESTNET_URL_V2, - Network::Mainnet => BOLTZ_MAINNET_URL_V2, - } - } - fn new_refund_tx( &self, swap: &SendSwap, @@ -108,7 +98,7 @@ impl BoltzSwapper { swap_script.clone(), output_address, &self.config.get_electrum_config(), - Self::boltz_url_v2(&self.config.network).to_string(), + self.config.boltz_url.clone(), swap.id.to_string(), )?) } @@ -277,7 +267,7 @@ impl Swapper for BoltzSwapper { swap_script, claim_address, &self.config.get_electrum_config(), - BoltzSwapper::boltz_url_v2(&self.config.network).into(), + self.config.boltz_url.clone(), swap.id.clone(), )?;