breez_sdk_liquid/swapper/boltz/
liquid.rs1use std::str::FromStr;
2
3use boltz_client::{
4 boltz::SwapTxKind, elements::Transaction, fees::Fee, network::LiquidClient,
5 util::secrets::Preimage, ElementsAddress as Address, LBtcSwapTx,
6};
7use log::info;
8
9use crate::{
10 ensure_sdk,
11 error::{PaymentError, SdkError},
12 prelude::{ChainSwap, Direction, ReceiveSwap, Swap, Utxo, LIQUID_FEE_RATE_SAT_PER_VBYTE},
13 utils,
14};
15
16use super::{BoltzSwapper, ProxyUrlFetcher};
17
18impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
19 pub(crate) fn validate_send_swap_preimage(
20 &self,
21 swap_id: &str,
22 invoice: &str,
23 preimage: &str,
24 ) -> Result<(), PaymentError> {
25 utils::verify_payment_hash(preimage, invoice)?;
26 info!("Preimage is valid for Send Swap {swap_id}");
27 Ok(())
28 }
29
30 pub(crate) async fn new_receive_claim_tx(
31 &self,
32 swap: &ReceiveSwap,
33 claim_address: String,
34 ) -> Result<Transaction, PaymentError> {
35 let liquid_client = self.get_liquid_client()?;
36 let swap_script = swap.get_swap_script()?;
37
38 let claim_tx_wrapper = LBtcSwapTx::new_claim(
39 swap_script,
40 claim_address,
41 liquid_client,
42 &self.get_boltz_client().await?.inner,
43 swap.id.clone(),
44 )
45 .await?;
46
47 let signed_tx = claim_tx_wrapper
48 .sign_claim(
49 &swap.get_claim_keypair()?,
50 &Preimage::from_str(&swap.preimage)?,
51 Fee::Absolute(swap.claim_fees_sat),
52 self.get_cooperative_details(swap.id.clone(), None, None)
53 .await?,
54 true,
55 )
56 .await?;
57
58 Ok(signed_tx)
59 }
60
61 pub(crate) async fn new_incoming_chain_claim_tx(
62 &self,
63 swap: &ChainSwap,
64 claim_address: String,
65 ) -> Result<Transaction, PaymentError> {
66 let liquid_client = self.get_liquid_client()?;
67 let claim_keypair = swap.get_claim_keypair()?;
68 let swap_script = swap.get_claim_swap_script()?.as_liquid_script()?;
69 let claim_tx_wrapper = LBtcSwapTx::new_claim(
70 swap_script,
71 claim_address,
72 liquid_client,
73 &self.get_boltz_client().await?.inner,
74 swap.id.clone(),
75 )
76 .await?;
77
78 let (partial_sig, pub_nonce) = self.get_claim_partial_sig(swap).await?;
79
80 let signed_tx = claim_tx_wrapper
81 .sign_claim(
82 &claim_keypair,
83 &Preimage::from_str(&swap.preimage)?,
84 Fee::Absolute(swap.claim_fees_sat),
85 self.get_cooperative_details(swap.id.clone(), Some(pub_nonce), Some(partial_sig))
86 .await?,
87 true,
88 )
89 .await?;
90
91 Ok(signed_tx)
92 }
93
94 fn calculate_refund_fees(&self, refund_tx_size: usize) -> u64 {
95 (refund_tx_size as f64 * LIQUID_FEE_RATE_SAT_PER_VBYTE).ceil() as u64
96 }
97
98 pub(crate) async fn new_lbtc_refund_wrapper(
99 &self,
100 swap: &Swap,
101 refund_address: &str,
102 ) -> Result<LBtcSwapTx, SdkError> {
103 let liquid_client = self.get_liquid_client()?;
104 let refund_wrapper = match swap {
105 Swap::Chain(swap) => match swap.direction {
106 Direction::Incoming => {
107 return Err(SdkError::generic(format!(
108 "Cannot create Liquid refund wrapper for incoming Chain swap {}",
109 swap.id
110 )));
111 }
112 Direction::Outgoing => {
113 let swap_script = swap.get_lockup_swap_script()?;
114 LBtcSwapTx::new_refund(
115 swap_script.as_liquid_script()?,
116 refund_address,
117 liquid_client,
118 &self.get_boltz_client().await?.inner,
119 swap.id.clone(),
120 )
121 .await
122 }
123 },
124 Swap::Send(swap) => {
125 let swap_script = swap.get_swap_script()?;
126 LBtcSwapTx::new_refund(
127 swap_script,
128 refund_address,
129 liquid_client,
130 &self.get_boltz_client().await?.inner,
131 swap.id.clone(),
132 )
133 .await
134 }
135 Swap::Receive(swap) => {
136 return Err(SdkError::generic(format!(
137 "Cannot create Liquid refund wrapper for Receive swap {}",
138 swap.id
139 )));
140 }
141 }?;
142 Ok(refund_wrapper)
143 }
144
145 pub(crate) async fn new_lbtc_refund_tx(
146 &self,
147 swap: &Swap,
148 refund_address: &str,
149 utxos: Vec<Utxo>,
150 is_cooperative: bool,
151 ) -> Result<Transaction, SdkError> {
152 let liquid_client = self.get_liquid_client()?;
153
154 let (swap_script, refund_keypair) = match swap {
155 Swap::Chain(swap) => {
156 ensure_sdk!(
157 swap.direction == Direction::Outgoing,
158 SdkError::generic("Cannot create LBTC refund tx for incoming Chain swaps")
159 );
160
161 (
162 swap.get_lockup_swap_script()?.as_liquid_script()?,
163 swap.get_refund_keypair()?,
164 )
165 }
166 Swap::Send(swap) => (swap.get_swap_script()?, swap.get_refund_keypair()?),
167 Swap::Receive(_) => {
168 return Err(SdkError::generic(
169 "Cannot create LBTC refund tx for Receive swaps.",
170 ));
171 }
172 };
173 let swap_id = swap.id();
174
175 let address = Address::from_str(refund_address)
176 .map_err(|err| SdkError::generic(format!("Could not parse address: {err:?}")))?;
177
178 let genesis_hash = liquid_client.get_genesis_hash().await?;
179
180 let (funding_outpoint, funding_tx_out) =
181 *utxos
182 .first()
183 .and_then(|utxo| utxo.as_liquid())
184 .ok_or(SdkError::generic("No refundable UTXOs found"))?;
185
186 let refund_tx = LBtcSwapTx {
187 kind: SwapTxKind::Refund,
188 swap_script,
189 output_address: address,
190 funding_outpoint,
191 funding_utxo: funding_tx_out,
192 genesis_hash,
193 };
194
195 let refund_tx_size = refund_tx.size(&refund_keypair, is_cooperative, true)?;
196 let broadcast_fees_sat = self.calculate_refund_fees(refund_tx_size);
197
198 let cooperative = match is_cooperative {
199 true => {
200 self.get_cooperative_details(swap_id.clone(), None, None)
201 .await?
202 }
203 false => None,
204 };
205
206 let signed_tx = refund_tx
207 .sign_refund(
208 &refund_keypair,
209 Fee::Absolute(broadcast_fees_sat),
210 cooperative,
211 true,
212 )
213 .await?;
214 Ok(signed_tx)
215 }
216}