ark_client/fee_estimation.rs
1use crate::batch;
2use crate::batch::BatchOutputType;
3use crate::wallet::OnchainWallet;
4use crate::Client;
5use crate::Error;
6use crate::SwapStorage;
7use ark_core::ArkAddress;
8use bitcoin::Address;
9use bitcoin::Amount;
10use bitcoin::OutPoint;
11use bitcoin::SignedAmount;
12use rand::CryptoRng;
13use rand::Rng;
14
15impl<B, W, S> Client<B, W, S>
16where
17 B: crate::Blockchain,
18 W: OnchainWallet,
19 S: SwapStorage + 'static,
20{
21 /// Estimates the fee to collaboratively redeem VTXOs to an on-chain Bitcoin address.
22 ///
23 /// This function calculates the expected fee for moving funds from the Ark protocol
24 /// back to a standard on-chain Bitcoin address through a collaborative redemption process.
25 /// The fee is estimated by creating a simulated intent and querying the Ark server.
26 ///
27 /// # Arguments
28 ///
29 /// * `rng` - A random number generator for creating the intent
30 /// * `to_address` - The on-chain Bitcoin address to send funds to
31 /// * `to_amount` - The amount to send to the destination address
32 ///
33 /// # Returns
34 ///
35 /// Returns the estimated fee as a [`SignedAmount`]. The fee will be deducted from
36 /// the total available balance when performing the actual redemption.
37 ///
38 /// # Errors
39 ///
40 /// Returns an error if:
41 /// - The available balance is insufficient for the requested amount
42 /// - Failed to fetch VTXOs or boarding inputs
43 /// - Failed to communicate with the Ark server
44 pub async fn estimate_onchain_fees<R>(
45 &self,
46 rng: &mut R,
47 to_address: Address,
48 to_amount: Amount,
49 ) -> Result<SignedAmount, Error>
50 where
51 R: Rng + CryptoRng + Clone,
52 {
53 let server_info = self.server_info().await?;
54
55 let (change_address, _) = self.get_offchain_address_with_server_info(&server_info)?;
56
57 let (boarding_inputs, vtxo_inputs, total_amount) = self
58 .fetch_commitment_transaction_inputs(&server_info, crate::utils::unix_now()?)
59 .await?;
60
61 let change_amount = total_amount.checked_sub(to_amount).ok_or_else(|| {
62 Error::coin_select(format!(
63 "cannot afford to send {to_amount}, only have {total_amount}"
64 ))
65 })?;
66
67 tracing::info!(
68 %to_address,
69 gross_amount = %to_amount,
70 change_address = %change_address.encode(),
71 %change_amount,
72 ?boarding_inputs,
73 "Estimating fee to collaboratively redeem outputs"
74 );
75
76 let intent = self.prepare_intent(
77 &mut rng.clone(),
78 boarding_inputs,
79 vtxo_inputs,
80 BatchOutputType::OffBoard {
81 to_address,
82 to_amount,
83 change_address,
84 change_amount,
85 },
86 batch::PrepareIntentKind::EstimateFee,
87 server_info.dust,
88 )?;
89
90 let amount = self.network_client().estimate_fees(intent.intent).await?;
91
92 Ok(amount)
93 }
94
95 /// Estimates the fee to join the next batch and settle funds to an Ark address.
96 ///
97 /// This function calculates the expected fee for consolidating all available VTXOs
98 /// and boarding outputs into fresh VTXOs through the Ark batch process. The full
99 /// available balance will be used, with fees deducted from the resulting VTXO.
100 ///
101 /// Use this to estimate fees before calling [`settle`](crate::Client::settle) or
102 /// similar batch operations.
103 ///
104 /// # Arguments
105 ///
106 /// * `rng` - A random number generator for creating the intent
107 /// * `to_address` - The Ark address to receive the settled funds
108 ///
109 /// # Returns
110 ///
111 /// Returns the estimated fee as a [`SignedAmount`]. This fee will be deducted from
112 /// the total available balance when joining the actual batch.
113 ///
114 /// # Errors
115 ///
116 /// Returns an error if:
117 /// - Failed to fetch VTXOs or boarding inputs
118 /// - Failed to communicate with the Ark server
119 pub async fn estimate_batch_fees<R>(
120 &self,
121 rng: &mut R,
122 to_address: ArkAddress,
123 ) -> Result<SignedAmount, Error>
124 where
125 R: Rng + CryptoRng + Clone,
126 {
127 let server_info = self.server_info().await?;
128
129 let (boarding_inputs, vtxo_inputs, total_amount) = self
130 .fetch_commitment_transaction_inputs(&server_info, crate::utils::unix_now()?)
131 .await?;
132
133 tracing::info!(
134 %to_address,
135 gross_amount = %total_amount,
136 ?boarding_inputs,
137 "Estimating fee to board outputs"
138 );
139
140 let intent = self.prepare_intent(
141 &mut rng.clone(),
142 boarding_inputs,
143 vtxo_inputs,
144 BatchOutputType::Board {
145 to_address,
146 to_amount: total_amount,
147 },
148 batch::PrepareIntentKind::EstimateFee,
149 server_info.dust,
150 )?;
151
152 let amount = self.network_client().estimate_fees(intent.intent).await?;
153
154 Ok(amount)
155 }
156
157 /// Estimates the fee to collaboratively redeem specific VTXOs to an on-chain Bitcoin address.
158 ///
159 /// This function is similar to [`estimate_onchain_fees`](Self::estimate_onchain_fees), but
160 /// allows you to specify exactly which VTXOs to use as inputs instead of using automatic
161 /// coin selection. This is useful when you want to estimate fees for redeeming specific
162 /// UTXOs.
163 ///
164 /// # Arguments
165 ///
166 /// * `rng` - A random number generator for creating the intent
167 /// * `input_vtxos` - An iterator of [`OutPoint`]s specifying which VTXOs to use as inputs
168 /// * `to_address` - The on-chain Bitcoin address to send funds to
169 /// * `to_amount` - The amount to send to the destination address
170 ///
171 /// # Returns
172 ///
173 /// Returns the estimated fee as a [`SignedAmount`]. The fee will be deducted from
174 /// the total input amount, with any remainder going to change.
175 ///
176 /// # Errors
177 ///
178 /// Returns an error if:
179 /// - No matching VTXO outpoints are found
180 /// - The total input amount is insufficient for the requested amount plus fees
181 /// - Failed to fetch VTXOs
182 /// - Failed to communicate with the Ark server
183 pub async fn estimate_onchain_fees_vtxo_selection<R>(
184 &self,
185 rng: &mut R,
186 input_vtxos: impl Iterator<Item = OutPoint> + Clone,
187 to_address: Address,
188 to_amount: Amount,
189 ) -> Result<SignedAmount, Error>
190 where
191 R: Rng + CryptoRng + Clone,
192 {
193 let server_info = self.server_info().await?;
194
195 let (change_address, _) = self.get_offchain_address_with_server_info(&server_info)?;
196
197 let vtxo_inputs = self
198 .selected_batch_settleable_vtxo_inputs(&server_info, input_vtxos)
199 .await?;
200
201 if vtxo_inputs.is_empty() {
202 return Err(Error::ad_hoc("no matching VTXO outpoints found"));
203 }
204
205 let total_input_amount = vtxo_inputs
206 .iter()
207 .fold(Amount::ZERO, |acc, vtxo| acc + vtxo.amount());
208
209 let change_amount = total_input_amount.checked_sub(to_amount).ok_or_else(|| {
210 Error::coin_select(format!(
211 "cannot afford to send {to_amount}, only have {total_input_amount}"
212 ))
213 })?;
214
215 tracing::info!(
216 %to_address,
217 %to_amount,
218 %total_input_amount,
219 change_address = %change_address.encode(),
220 %change_amount,
221 num_vtxos = vtxo_inputs.len(),
222 "Estimating fee to collaboratively redeem selected VTXOs"
223 );
224
225 let intent = self.prepare_intent(
226 &mut rng.clone(),
227 vec![], // No boarding inputs when using specific VTXOs
228 vtxo_inputs,
229 BatchOutputType::OffBoard {
230 to_address,
231 to_amount,
232 change_address,
233 change_amount,
234 },
235 batch::PrepareIntentKind::EstimateFee,
236 server_info.dust,
237 )?;
238
239 let amount = self.network_client().estimate_fees(intent.intent).await?;
240
241 Ok(amount)
242 }
243
244 /// Estimates the fee to join the next batch with specific VTXOs and settle to an Ark address.
245 ///
246 /// This function is similar to [`estimate_batch_fees`](Self::estimate_batch_fees), but allows
247 /// you to specify exactly which VTXOs to use as inputs instead of using all available VTXOs.
248 /// This is useful when you want to estimate fees for settling specific UTXOs into fresh VTXOs.
249 ///
250 /// # Arguments
251 ///
252 /// * `rng` - A random number generator for creating the intent
253 /// * `input_vtxos` - An iterator of [`OutPoint`]s specifying which VTXOs to use as inputs
254 /// * `to_address` - The Ark address to receive the settled funds
255 ///
256 /// # Returns
257 ///
258 /// Returns the estimated fee as a [`SignedAmount`]. The fee will be deducted from
259 /// the total input amount when joining the actual batch.
260 ///
261 /// # Errors
262 ///
263 /// Returns an error if:
264 /// - No matching VTXO outpoints are found
265 /// - Failed to fetch VTXOs
266 /// - Failed to communicate with the Ark server
267 pub async fn estimate_batch_fees_vtxo_selection<R>(
268 &self,
269 rng: &mut R,
270 input_vtxos: impl Iterator<Item = OutPoint> + Clone,
271 to_address: ArkAddress,
272 ) -> Result<SignedAmount, Error>
273 where
274 R: Rng + CryptoRng + Clone,
275 {
276 let server_info = self.server_info().await?;
277
278 let vtxo_inputs = self
279 .selected_batch_settleable_vtxo_inputs(&server_info, input_vtxos)
280 .await?;
281
282 if vtxo_inputs.is_empty() {
283 return Err(Error::ad_hoc("no matching VTXO outpoints found"));
284 }
285
286 let total_input_amount = vtxo_inputs
287 .iter()
288 .fold(Amount::ZERO, |acc, vtxo| acc + vtxo.amount());
289
290 tracing::info!(
291 %to_address,
292 %total_input_amount,
293 num_vtxos = vtxo_inputs.len(),
294 "Estimating fee to settle selected VTXOs"
295 );
296
297 let intent = self.prepare_intent(
298 &mut rng.clone(),
299 vec![], // No boarding inputs when using specific VTXOs
300 vtxo_inputs,
301 BatchOutputType::Board {
302 to_address,
303 to_amount: total_input_amount,
304 },
305 batch::PrepareIntentKind::EstimateFee,
306 server_info.dust,
307 )?;
308
309 let amount = self.network_client().estimate_fees(intent.intent).await?;
310
311 Ok(amount)
312 }
313}