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