1use anyhow::{Context, Result};
4use bitcoin::Amount;
5
6use ark::{Vtxo, VtxoId};
7use ark::fees::VtxoFeeInfo;
8
9use crate::Wallet;
10
11#[derive(Debug, Clone)]
15pub struct FeeEstimate {
16 pub gross_amount: Amount,
18 pub fee: Amount,
20 pub net_amount: Amount,
22 pub vtxos_spent: Vec<VtxoId>,
24}
25
26impl FeeEstimate {
27 pub fn new(
28 gross_amount: Amount,
29 fee: Amount,
30 net_amount: Amount,
31 vtxos_spent: Vec<VtxoId>,
32 ) -> Self {
33 Self {
34 gross_amount,
35 fee,
36 net_amount,
37 vtxos_spent,
38 }
39 }
40}
41
42impl Wallet {
43 pub async fn estimate_board_offchain_fee(&self, board_amount: Amount) -> Result<FeeEstimate> {
47 let (_, ark_info) = self.require_server().await?;
48 let fee = ark_info.fees.board.calculate(board_amount).context("fee overflowed")?;
49 let net_amount = board_amount.checked_sub(fee).unwrap_or(Amount::ZERO);
50
51 Ok(FeeEstimate::new(board_amount, fee, net_amount, vec![]))
52 }
53
54 pub async fn estimate_lightning_receive_fee(&self, amount: Amount) -> Result<FeeEstimate> {
57 let (_, ark_info) = self.require_server().await?;
58
59 let fee = ark_info.fees.lightning_receive.calculate(amount).context("fee overflowed")?;
60 let net_amount = amount.checked_sub(fee).unwrap_or(Amount::ZERO);
61
62 Ok(FeeEstimate::new(amount, fee, net_amount, vec![]))
63 }
64
65 pub async fn estimate_lightning_send_fee(&self, amount: Amount) -> Result<FeeEstimate> {
73 let (_, ark_info) = self.require_server().await?;
74
75 let (inputs, fee) = self.select_vtxos_to_cover_with_fee(
76 amount, |a, v| ark_info.fees.lightning_send.calculate(a, v).context("fee overflowed"),
77 ).await?;
78
79 let total_cost = amount.checked_add(fee).unwrap_or(Amount::MAX);
80 let vtxo_ids = inputs.into_iter().map(|v| v.id()).collect();
81
82 Ok(FeeEstimate::new(total_cost, fee, amount, vtxo_ids))
83 }
84
85 pub async fn estimate_offboard<G>(
88 &self,
89 address: &bitcoin::Address,
90 vtxos: impl IntoIterator<Item = impl AsRef<Vtxo<G>>>,
91 ) -> Result<FeeEstimate> {
92 let (_, ark_info) = self.require_server().await?;
93 let script_buf = address.script_pubkey();
94 let current_height = self.chain.tip().await?;
95
96 let vtxos = vtxos.into_iter();
97 let capacity = vtxos.size_hint().1.unwrap_or(vtxos.size_hint().0);
98 let mut vtxo_ids = Vec::with_capacity(capacity);
99 let mut fee_info = Vec::with_capacity(capacity);
100 let mut amount = Amount::ZERO;
101 for vtxo in vtxos {
102 let vtxo = vtxo.as_ref();
103 vtxo_ids.push(vtxo.id());
104 fee_info.push(VtxoFeeInfo::from_vtxo_and_tip(vtxo, current_height));
105 amount = amount + vtxo.amount();
106 }
107
108 let fee = ark_info.fees.offboard.calculate(
109 &script_buf,
110 amount,
111 ark_info.offboard_feerate,
112 fee_info,
113 ).context("Error whilst calculating offboard fee")?;
114
115 let net_amount = amount.checked_sub(fee).unwrap_or(Amount::ZERO);
116 Ok(FeeEstimate::new(amount, fee, net_amount, vtxo_ids))
117 }
118
119 pub async fn estimate_refresh_fee<G>(
122 &self,
123 vtxos: impl IntoIterator<Item = impl AsRef<Vtxo<G>>>,
124 ) -> Result<FeeEstimate> {
125 let (_, ark_info) = self.require_server().await?;
126 let current_height = self.chain.tip().await?;
127
128 let vtxos = vtxos.into_iter();
129 let capacity = vtxos.size_hint().1.unwrap_or(vtxos.size_hint().0);
130 let mut vtxo_ids = Vec::with_capacity(capacity);
131 let mut vtxo_fee_infos = Vec::with_capacity(capacity);
132 let mut total_amount = Amount::ZERO;
133 for vtxo in vtxos.into_iter() {
134 let vtxo = vtxo.as_ref();
135 vtxo_ids.push(vtxo.id());
136 vtxo_fee_infos.push(VtxoFeeInfo::from_vtxo_and_tip(vtxo, current_height));
137 total_amount = total_amount + vtxo.amount();
138 }
139
140 let fee = ark_info.fees.refresh.calculate(vtxo_fee_infos).context("fee overflowed")?;
142 let output_amount = total_amount.checked_sub(fee).unwrap_or(Amount::ZERO);
143 Ok(FeeEstimate::new(total_amount, fee, output_amount, vtxo_ids))
144 }
145
146 pub async fn estimate_send_onchain(
154 &self,
155 address: &bitcoin::Address,
156 amount: Amount,
157 ) -> Result<FeeEstimate> {
158 let (_, ark_info) = self.require_server().await?;
159 let script_buf = address.script_pubkey();
160
161 let (inputs, fee) = self.select_vtxos_to_cover_with_fee(
162 amount, |a, v|
163 ark_info.fees.offboard.calculate(&script_buf, a, ark_info.offboard_feerate, v)
164 .ok_or_else(|| anyhow!("Error whilst calculating fee"))
165 ).await?;
166
167 let total_cost = amount.checked_add(fee).unwrap_or(Amount::MAX);
168 let vtxo_ids = inputs.into_iter().map(|v| v.id()).collect();
169
170 Ok(FeeEstimate::new(total_cost, fee, amount, vtxo_ids))
171 }
172}