Skip to main content

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}