@@ -52,8 +52,9 @@ use bitcoin::{
5252
5353use std:: ops:: Deref ;
5454use std:: str:: FromStr ;
55- use std:: sync:: { Arc , Mutex } ;
55+ use std:: sync:: { Arc , Mutex , MutexGuard } ;
5656
57+ #[ derive( Debug , Copy , Clone ) ]
5758pub ( crate ) enum OnchainSendAmount {
5859 ExactRetainingReserve { amount_sats : u64 , cur_anchor_reserve_sats : u64 } ,
5960 AllRetainingReserve { cur_anchor_reserve_sats : u64 } ,
@@ -352,6 +353,114 @@ where
352353 . map_err ( |_| Error :: InvalidAddress )
353354 }
354355
356+ pub ( crate ) fn estimate_fee (
357+ & self , address : & Address , send_amount : OnchainSendAmount , fee_rate : Option < FeeRate > ,
358+ ) -> Result < Amount , Error > {
359+ let mut locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
360+
361+ // Use the set fee_rate or default to fee estimation.
362+ let confirmation_target = ConfirmationTarget :: OnchainPayment ;
363+ let fee_rate =
364+ fee_rate. unwrap_or_else ( || self . fee_estimator . estimate_fee_rate ( confirmation_target) ) ;
365+
366+ self . estimate_fee_internal ( & mut locked_wallet, address, send_amount, fee_rate) . map_err (
367+ |e| {
368+ log_error ! ( self . logger, "Failed to estimate fee: {e}" ) ;
369+ e
370+ } ,
371+ )
372+ }
373+
374+ pub ( crate ) fn estimate_fee_internal (
375+ & self , locked_wallet : & mut MutexGuard < PersistedWallet < KVStoreWalletPersister > > ,
376+ address : & Address , send_amount : OnchainSendAmount , fee_rate : FeeRate ,
377+ ) -> Result < Amount , Error > {
378+ const DUST_LIMIT_SATS : u64 = 546 ;
379+ match send_amount {
380+ OnchainSendAmount :: ExactRetainingReserve { amount_sats, .. } => {
381+ let mut tx_builder = locked_wallet. build_tx ( ) ;
382+ let amount = Amount :: from_sat ( amount_sats) ;
383+ tx_builder. add_recipient ( address. script_pubkey ( ) , amount) . fee_rate ( fee_rate) ;
384+
385+ let psbt = match tx_builder. finish ( ) {
386+ Ok ( psbt) => psbt,
387+ Err ( err) => {
388+ log_error ! ( self . logger, "Failed to create temporary transaction: {}" , err) ;
389+ return Err ( err. into ( ) ) ;
390+ } ,
391+ } ;
392+
393+ // 'cancel' the transaction to free up any used change addresses
394+ locked_wallet. cancel_tx ( & psbt. unsigned_tx ) ;
395+ psbt. fee ( ) . map_err ( |_| Error :: WalletOperationFailed )
396+ } ,
397+ OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats }
398+ if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
399+ {
400+ let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
401+ let balance = locked_wallet. balance ( ) ;
402+ let spendable_amount_sats = self
403+ . get_balances_inner ( balance, cur_anchor_reserve_sats)
404+ . map ( |( _, s) | s)
405+ . unwrap_or ( 0 ) ;
406+ let mut tx_builder = locked_wallet. build_tx ( ) ;
407+ tx_builder
408+ . drain_wallet ( )
409+ . drain_to ( address. script_pubkey ( ) )
410+ . add_recipient (
411+ change_address_info. address . script_pubkey ( ) ,
412+ Amount :: from_sat ( cur_anchor_reserve_sats) ,
413+ )
414+ . fee_rate ( fee_rate) ;
415+
416+ let psbt = match tx_builder. finish ( ) {
417+ Ok ( psbt) => psbt,
418+ Err ( err) => {
419+ log_error ! ( self . logger, "Failed to create temporary transaction: {}" , err) ;
420+ return Err ( err. into ( ) ) ;
421+ } ,
422+ } ;
423+
424+ // 'cancel' the transaction to free up any used change addresses
425+ locked_wallet. cancel_tx ( & psbt. unsigned_tx ) ;
426+
427+ let estimated_tx_fee = psbt. fee ( ) . map_err ( |_| Error :: WalletOperationFailed ) ?;
428+
429+ // enforce the reserve requirements to make sure we can actually afford the tx + fee
430+ let estimated_spendable_amount = Amount :: from_sat (
431+ spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
432+ ) ;
433+
434+ if estimated_spendable_amount == Amount :: ZERO {
435+ log_error ! ( self . logger,
436+ "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
437+ spendable_amount_sats,
438+ estimated_tx_fee,
439+ ) ;
440+ return Err ( Error :: InsufficientFunds ) ;
441+ }
442+
443+ Ok ( estimated_tx_fee)
444+ } ,
445+ OnchainSendAmount :: AllDrainingReserve
446+ | OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats : _ } => {
447+ let mut tx_builder = locked_wallet. build_tx ( ) ;
448+ tx_builder. drain_wallet ( ) . drain_to ( address. script_pubkey ( ) ) . fee_rate ( fee_rate) ;
449+ let psbt = match tx_builder. finish ( ) {
450+ Ok ( psbt) => psbt,
451+ Err ( err) => {
452+ log_error ! ( self . logger, "Failed to create temporary transaction: {}" , err) ;
453+ return Err ( err. into ( ) ) ;
454+ } ,
455+ } ;
456+
457+ // 'cancel' the transaction to free up any used change addresses
458+ locked_wallet. cancel_tx ( & psbt. unsigned_tx ) ;
459+ psbt. fee ( ) . map_err ( |_| Error :: WalletOperationFailed )
460+ } ,
461+ }
462+ }
463+
355464 pub ( crate ) fn send_to_address (
356465 & self , address : & bitcoin:: Address , send_amount : OnchainSendAmount ,
357466 fee_rate : Option < FeeRate > ,
@@ -378,60 +487,24 @@ where
378487 OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats }
379488 if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
380489 {
381- let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
382490 let balance = locked_wallet. balance ( ) ;
383491 let spendable_amount_sats = self
384492 . get_balances_inner ( balance, cur_anchor_reserve_sats)
385493 . map ( |( _, s) | s)
386494 . unwrap_or ( 0 ) ;
387- let tmp_tx = {
388- let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
389- tmp_tx_builder
390- . drain_wallet ( )
391- . drain_to ( address. script_pubkey ( ) )
392- . add_recipient (
393- change_address_info. address . script_pubkey ( ) ,
394- Amount :: from_sat ( cur_anchor_reserve_sats) ,
395- )
396- . fee_rate ( fee_rate) ;
397- match tmp_tx_builder. finish ( ) {
398- Ok ( psbt) => psbt. unsigned_tx ,
399- Err ( err) => {
400- log_error ! (
401- self . logger,
402- "Failed to create temporary transaction: {}" ,
403- err
404- ) ;
405- return Err ( err. into ( ) ) ;
406- } ,
407- }
408- } ;
409495
410- let estimated_tx_fee = locked_wallet. calculate_fee ( & tmp_tx) . map_err ( |e| {
411- log_error ! (
412- self . logger,
413- "Failed to calculate fee of temporary transaction: {}" ,
496+ // estimate_fee_internal will enforce that we are retaining the reserve limits
497+ let estimated_tx_fee = self
498+ . estimate_fee_internal ( & mut locked_wallet, address, send_amount, fee_rate)
499+ . map_err ( |e| {
500+ log_error ! ( self . logger, "Failed to estimate fee: {e}" ) ;
414501 e
415- ) ;
416- e
417- } ) ?;
418-
419- // 'cancel' the transaction to free up any used change addresses
420- locked_wallet. cancel_tx ( & tmp_tx) ;
502+ } ) ?;
421503
422504 let estimated_spendable_amount = Amount :: from_sat (
423505 spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
424506 ) ;
425507
426- if estimated_spendable_amount == Amount :: ZERO {
427- log_error ! ( self . logger,
428- "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
429- spendable_amount_sats,
430- estimated_tx_fee,
431- ) ;
432- return Err ( Error :: InsufficientFunds ) ;
433- }
434-
435508 let mut tx_builder = locked_wallet. build_tx ( ) ;
436509 tx_builder
437510 . add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount)
0 commit comments