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> {
74 let (_, ark_info) = self.require_server().await?;
75
76 let (inputs, fee) = match self.select_vtxos_to_cover_with_fee(
77 amount, |a, v| ark_info.fees.lightning_send.calculate(a, v).context("fee overflowed"),
78 ).await {
79 Ok((inputs, fee)) => (inputs, fee),
80 Err(_) => {
81 let info = [VtxoFeeInfo { amount, expiry_blocks: u32::MAX }];
84 let fee = ark_info.fees.lightning_send.calculate(amount, info)
85 .context("fee overflowed")?;
86 (Vec::new(), fee)
87 },
88 };
89 let total_cost = amount.checked_add(fee).unwrap_or(Amount::MAX);
90 let vtxo_ids = inputs.into_iter().map(|v| v.id()).collect();
91
92 Ok(FeeEstimate::new(total_cost, fee, amount, vtxo_ids))
93 }
94
95 pub async fn estimate_offboard<G>(
98 &self,
99 address: &bitcoin::Address,
100 vtxos: impl IntoIterator<Item = impl AsRef<Vtxo<G>>>,
101 ) -> Result<FeeEstimate> {
102 let (_, ark_info) = self.require_server().await?;
103 let script_buf = address.script_pubkey();
104 let current_height = self.chain.tip().await?;
105
106 let vtxos = vtxos.into_iter();
107 let capacity = vtxos.size_hint().1.unwrap_or(vtxos.size_hint().0);
108 let mut vtxo_ids = Vec::with_capacity(capacity);
109 let mut fee_info = Vec::with_capacity(capacity);
110 let mut amount = Amount::ZERO;
111 for vtxo in vtxos {
112 let vtxo = vtxo.as_ref();
113 vtxo_ids.push(vtxo.id());
114 fee_info.push(VtxoFeeInfo::from_vtxo_and_tip(vtxo, current_height));
115 amount = amount + vtxo.amount();
116 }
117
118 let fee = ark_info.fees.offboard.calculate(
119 &script_buf,
120 amount,
121 ark_info.offboard_feerate,
122 fee_info,
123 ).context("Error whilst calculating offboard fee")?;
124
125 let net_amount = amount.checked_sub(fee).unwrap_or(Amount::ZERO);
126 Ok(FeeEstimate::new(amount, fee, net_amount, vtxo_ids))
127 }
128
129 pub async fn estimate_refresh_fee<G>(
132 &self,
133 vtxos: impl IntoIterator<Item = impl AsRef<Vtxo<G>>>,
134 ) -> Result<FeeEstimate> {
135 let (_, ark_info) = self.require_server().await?;
136 let current_height = self.chain.tip().await?;
137
138 let vtxos = vtxos.into_iter();
139 let capacity = vtxos.size_hint().1.unwrap_or(vtxos.size_hint().0);
140 let mut vtxo_ids = Vec::with_capacity(capacity);
141 let mut vtxo_fee_infos = Vec::with_capacity(capacity);
142 let mut total_amount = Amount::ZERO;
143 for vtxo in vtxos.into_iter() {
144 let vtxo = vtxo.as_ref();
145 vtxo_ids.push(vtxo.id());
146 vtxo_fee_infos.push(VtxoFeeInfo::from_vtxo_and_tip(vtxo, current_height));
147 total_amount = total_amount + vtxo.amount();
148 }
149
150 let fee = ark_info.fees.refresh.calculate(vtxo_fee_infos).context("fee overflowed")?;
152 let output_amount = total_amount.checked_sub(fee).unwrap_or(Amount::ZERO);
153 Ok(FeeEstimate::new(total_amount, fee, output_amount, vtxo_ids))
154 }
155
156 pub async fn estimate_send_onchain(
165 &self,
166 address: &bitcoin::Address,
167 amount: Amount,
168 ) -> Result<FeeEstimate> {
169 let (_, ark_info) = self.require_server().await?;
170 let script_buf = address.script_pubkey();
171
172 let (inputs, fee) = match self.select_vtxos_to_cover_with_fee(
173 amount, |a, v|
174 ark_info.fees.offboard.calculate(&script_buf, a, ark_info.offboard_feerate, v)
175 .ok_or_else(|| anyhow!("Error whilst calculating fee"))
176 ).await {
177 Ok((inputs, fee)) => (inputs, fee),
178 Err(_) => {
179 let info = [VtxoFeeInfo { amount, expiry_blocks: u32::MAX }];
182 let fee = ark_info.fees.offboard.calculate(
183 &script_buf, amount, ark_info.offboard_feerate, info,
184 ).context("fee overflowed")?;
185 (Vec::new(), fee)
186 }
187 };
188
189 let total_cost = amount.checked_add(fee).unwrap_or(Amount::MAX);
190 let vtxo_ids = inputs.into_iter().map(|v| v.id()).collect();
191
192 Ok(FeeEstimate::new(total_cost, fee, amount, vtxo_ids))
193 }
194}