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).await?,
53 true,
54 )
55 .await?;
56
57 Ok(signed_tx)
58 }
59
60 pub(crate) async fn new_incoming_chain_claim_tx(
61 &self,
62 swap: &ChainSwap,
63 claim_address: String,
64 ) -> Result<Transaction, PaymentError> {
65 let liquid_client = self.get_liquid_client()?;
66 let claim_keypair = swap.get_claim_keypair()?;
67 let swap_script = swap.get_claim_swap_script()?.as_liquid_script()?;
68 let claim_tx_wrapper = LBtcSwapTx::new_claim(
69 swap_script,
70 claim_address,
71 liquid_client,
72 &self.get_boltz_client().await?.inner,
73 swap.id.clone(),
74 )
75 .await?;
76
77 let signature = self.get_claim_partial_sig(swap).await?;
78
79 let signed_tx = claim_tx_wrapper
80 .sign_claim(
81 &claim_keypair,
82 &Preimage::from_str(&swap.preimage)?,
83 Fee::Absolute(swap.claim_fees_sat),
84 self.get_cooperative_details(swap.id.clone(), signature)
85 .await?,
86 true,
87 )
88 .await?;
89
90 Ok(signed_tx)
91 }
92
93 fn calculate_refund_fees(&self, refund_tx_size: usize) -> u64 {
94 (refund_tx_size as f64 * LIQUID_FEE_RATE_SAT_PER_VBYTE).ceil() as u64
95 }
96
97 pub(crate) async fn new_lbtc_refund_wrapper(
98 &self,
99 swap: &Swap,
100 refund_address: &str,
101 ) -> Result<LBtcSwapTx, SdkError> {
102 let liquid_client = self.get_liquid_client()?;
103 let refund_wrapper = match swap {
104 Swap::Chain(swap) => match swap.direction {
105 Direction::Incoming => {
106 return Err(SdkError::generic(format!(
107 "Cannot create Liquid refund wrapper for incoming Chain swap {}",
108 swap.id
109 )));
110 }
111 Direction::Outgoing => {
112 let swap_script = swap.get_lockup_swap_script()?;
113 LBtcSwapTx::new_refund(
114 swap_script.as_liquid_script()?,
115 refund_address,
116 liquid_client,
117 &self.get_boltz_client().await?.inner,
118 swap.id.clone(),
119 )
120 .await
121 }
122 },
123 Swap::Send(swap) => {
124 let swap_script = swap.get_swap_script()?;
125 LBtcSwapTx::new_refund(
126 swap_script,
127 refund_address,
128 liquid_client,
129 &self.get_boltz_client().await?.inner,
130 swap.id.clone(),
131 )
132 .await
133 }
134 Swap::Receive(swap) => {
135 return Err(SdkError::generic(format!(
136 "Cannot create Liquid refund wrapper for Receive swap {}",
137 swap.id
138 )));
139 }
140 }?;
141 Ok(refund_wrapper)
142 }
143
144 pub(crate) async fn new_lbtc_refund_tx(
145 &self,
146 swap: &Swap,
147 refund_address: &str,
148 utxos: Vec<Utxo>,
149 is_cooperative: bool,
150 ) -> Result<Transaction, SdkError> {
151 let liquid_client = self.get_liquid_client()?;
152
153 let (swap_script, refund_keypair) = match swap {
154 Swap::Chain(swap) => {
155 ensure_sdk!(
156 swap.direction == Direction::Outgoing,
157 SdkError::generic("Cannot create LBTC refund tx for incoming Chain swaps")
158 );
159
160 (
161 swap.get_lockup_swap_script()?.as_liquid_script()?,
162 swap.get_refund_keypair()?,
163 )
164 }
165 Swap::Send(swap) => (swap.get_swap_script()?, swap.get_refund_keypair()?),
166 Swap::Receive(_) => {
167 return Err(SdkError::generic(
168 "Cannot create LBTC refund tx for Receive swaps.",
169 ));
170 }
171 };
172 let swap_id = swap.id();
173
174 let address = Address::from_str(refund_address)
175 .map_err(|err| SdkError::generic(format!("Could not parse address: {err:?}")))?;
176
177 let genesis_hash = liquid_client.get_genesis_hash().await?;
178
179 let (funding_outpoint, funding_tx_out) =
180 *utxos
181 .first()
182 .and_then(|utxo| utxo.as_liquid())
183 .ok_or(SdkError::generic("No refundable UTXOs found"))?;
184
185 let refund_tx = LBtcSwapTx {
186 kind: SwapTxKind::Refund,
187 swap_script,
188 output_address: address,
189 funding_outpoint,
190 funding_utxo: funding_tx_out,
191 genesis_hash,
192 };
193
194 let refund_tx_size = refund_tx.size(&refund_keypair, is_cooperative, true)?;
195 let broadcast_fees_sat = self.calculate_refund_fees(refund_tx_size);
196
197 let cooperative = match is_cooperative {
198 true => self.get_cooperative_details(swap_id.clone(), None).await?,
199 false => None,
200 };
201
202 let signed_tx = refund_tx
203 .sign_refund(
204 &refund_keypair,
205 Fee::Absolute(broadcast_fees_sat),
206 cooperative,
207 true,
208 )
209 .await?;
210 Ok(signed_tx)
211 }
212}