Skip to content

Commit 22a5d77

Browse files
committed
feat: add calculate_total_fee method for fee estimation
Add a new method to calculate transaction fees without broadcasting, allowing users to estimate costs before committing to a transaction. - Add `calculate_transaction_fee` to wallet module - Add `calculate_total_fee` to OnchainPayment public API - Expose via UniFFI bindings for Swift/Kotlin/Python - Consolidate common transaction building logic The implementation reuses the same transaction building logic as `send_to_address` to ensure accurate fee calculations.
1 parent 207c299 commit 22a5d77

File tree

3 files changed

+354
-243
lines changed

3 files changed

+354
-243
lines changed

bindings/ldk_node.udl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ interface OnchainPayment {
232232
Txid accelerate_by_cpfp([ByRef]Txid txid, FeeRate? fee_rate, Address? destination_address);
233233
[Throws=NodeError]
234234
FeeRate calculate_cpfp_fee_rate([ByRef]Txid parent_txid, boolean urgent);
235+
[Throws=NodeError]
236+
u64 calculate_total_fee([ByRef]Address address, u64 amount_sats, FeeRate? fee_rate, sequence<SpendableUtxo>? utxos_to_spend);
235237
};
236238

237239
enum CoinSelectionAlgorithm {

src/payment/onchain.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,59 @@ impl OnchainPayment {
148148
Ok(selected_utxos)
149149
}
150150

151+
/// Calculates the total fee for an on-chain payment without sending it.
152+
///
153+
/// This method simulates creating a transaction to the given address for the specified amount
154+
/// and returns the total fee that would be paid. This is useful for displaying fee estimates
155+
/// to users before they confirm a transaction.
156+
///
157+
/// The calculation respects any on-chain reserve requirements and validates that sufficient
158+
/// funds are available, just like [`send_to_address`].
159+
///
160+
/// # Arguments
161+
///
162+
/// * `address` - The Bitcoin address to send to
163+
/// * `amount_sats` - The amount to send in satoshis
164+
/// * `fee_rate` - Optional fee rate to use (if None, will estimate based on current network conditions)
165+
/// * `utxos_to_spend` - Optional list of specific UTXOs to use for the transaction
166+
///
167+
/// # Returns
168+
///
169+
/// The total fee in satoshis that would be paid for this transaction.
170+
///
171+
/// # Errors
172+
///
173+
/// * [`Error::NotRunning`] - If the node is not running
174+
/// * [`Error::InvalidAddress`] - If the address is invalid
175+
/// * [`Error::InsufficientFunds`] - If there are insufficient funds for the payment
176+
/// * [`Error::WalletOperationFailed`] - If fee calculation fails
177+
///
178+
/// [`send_to_address`]: Self::send_to_address
179+
pub fn calculate_total_fee(
180+
&self, address: &bitcoin::Address, amount_sats: u64, fee_rate: Option<FeeRate>,
181+
utxos_to_spend: Option<Vec<SpendableUtxo>>,
182+
) -> Result<u64, Error> {
183+
let rt_lock = self.runtime.read().unwrap();
184+
if rt_lock.is_none() {
185+
return Err(Error::NotRunning);
186+
}
187+
188+
let cur_anchor_reserve_sats =
189+
crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
190+
let send_amount =
191+
OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats };
192+
let outpoints = utxos_to_spend.map(|utxos| utxos.into_iter().map(|u| u.outpoint).collect());
193+
let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate);
194+
195+
self.wallet.calculate_transaction_fee(
196+
address,
197+
send_amount,
198+
fee_rate_opt,
199+
outpoints,
200+
&self.channel_manager,
201+
)
202+
}
203+
151204
/// Send an on-chain payment to the given address.
152205
///
153206
/// This will respect any on-chain reserve we need to keep, i.e., won't allow to cut into
@@ -325,7 +378,9 @@ impl OnchainPayment {
325378
/// * [`Error::TransactionNotFound`] - If the parent transaction can't be found
326379
/// * [`Error::TransactionAlreadyConfirmed`] - If the parent transaction is already confirmed
327380
/// * [`Error::WalletOperationFailed`] - If fee calculation fails
328-
pub fn calculate_cpfp_fee_rate(&self, parent_txid: &Txid, urgent: bool) -> Result<FeeRate, Error> {
381+
pub fn calculate_cpfp_fee_rate(
382+
&self, parent_txid: &Txid, urgent: bool,
383+
) -> Result<FeeRate, Error> {
329384
let rt_lock = self.runtime.read().unwrap();
330385
if rt_lock.is_none() {
331386
return Err(Error::NotRunning);

0 commit comments

Comments
 (0)