Skip to main content

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}