@@ -65,6 +65,8 @@ pub(crate) enum OnchainSendAmount {
6565pub ( crate ) mod persist;
6666pub ( crate ) mod ser;
6767
68+ const DUST_LIMIT_SATS : u64 = 546 ;
69+
6870pub ( crate ) struct Wallet {
6971 // A BDK on-chain wallet.
7072 inner : Mutex < PersistedWallet < KVStoreWalletPersister > > ,
@@ -480,6 +482,105 @@ impl Wallet {
480482 self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
481483 }
482484
485+ fn build_drain_psbt (
486+ & self , locked_wallet : & mut PersistedWallet < KVStoreWalletPersister > ,
487+ drain_script : ScriptBuf , cur_anchor_reserve_sats : u64 , fee_rate : FeeRate ,
488+ shared_input : Option < & Input > ,
489+ ) -> Result < Psbt , Error > {
490+ let anchor_address = if cur_anchor_reserve_sats > DUST_LIMIT_SATS {
491+ Some ( locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) )
492+ } else {
493+ None
494+ } ;
495+
496+ let mut tx_builder = locked_wallet. build_tx ( ) ;
497+ tx_builder. drain_wallet ( ) . drain_to ( drain_script) . fee_rate ( fee_rate) ;
498+
499+ if let Some ( address_info) = anchor_address {
500+ tx_builder. add_recipient (
501+ address_info. address . script_pubkey ( ) ,
502+ Amount :: from_sat ( cur_anchor_reserve_sats) ,
503+ ) ;
504+ }
505+
506+ if let Some ( input) = shared_input {
507+ let psbt_input = psbt:: Input {
508+ witness_utxo : Some ( input. previous_utxo . clone ( ) ) ,
509+ ..Default :: default ( )
510+ } ;
511+ let weight = Weight :: from_wu ( input. satisfaction_weight ) ;
512+ tx_builder. only_witness_utxo ( ) . exclude_unconfirmed ( ) ;
513+ tx_builder. add_foreign_utxo ( input. outpoint , psbt_input, weight) . map_err ( |e| {
514+ log_error ! ( self . logger, "Failed to add shared input for fee estimation: {e}" ) ;
515+ Error :: ChannelSplicingFailed
516+ } ) ?;
517+ }
518+
519+ let psbt = tx_builder. finish ( ) . map_err ( |err| {
520+ log_error ! ( self . logger, "Failed to create temporary drain transaction: {err}" ) ;
521+ err
522+ } ) ?;
523+
524+ Ok ( psbt)
525+ }
526+
527+ /// Builds a temporary drain transaction and returns the maximum amount that would be sent to
528+ /// the drain output, along with the PSBT for further inspection.
529+ ///
530+ /// The caller is responsible for cancelling the PSBT via `locked_wallet.cancel_tx()`.
531+ fn get_max_drain_amount (
532+ & self , locked_wallet : & mut PersistedWallet < KVStoreWalletPersister > ,
533+ drain_script : ScriptBuf , cur_anchor_reserve_sats : u64 , fee_rate : FeeRate ,
534+ shared_input : Option < & Input > ,
535+ ) -> Result < ( u64 , Psbt ) , Error > {
536+ let balance = locked_wallet. balance ( ) ;
537+ let spendable_amount_sats =
538+ self . get_balances_inner ( balance, cur_anchor_reserve_sats) . map ( |( _, s) | s) . unwrap_or ( 0 ) ;
539+
540+ if spendable_amount_sats == 0 {
541+ log_error ! (
542+ self . logger,
543+ "Unable to determine max amount: no spendable funds available."
544+ ) ;
545+ return Err ( Error :: InsufficientFunds ) ;
546+ }
547+
548+ let tmp_psbt = self . build_drain_psbt (
549+ locked_wallet,
550+ drain_script. clone ( ) ,
551+ cur_anchor_reserve_sats,
552+ fee_rate,
553+ shared_input,
554+ ) ?;
555+
556+ let drain_output_value = tmp_psbt
557+ . unsigned_tx
558+ . output
559+ . iter ( )
560+ . find ( |o| o. script_pubkey == drain_script)
561+ . map ( |o| o. value )
562+ . ok_or_else ( || {
563+ log_error ! ( self . logger, "Failed to find drain output in temporary transaction" ) ;
564+ Error :: InsufficientFunds
565+ } ) ?;
566+
567+ let shared_input_value = shared_input. map ( |i| i. previous_utxo . value . to_sat ( ) ) . unwrap_or ( 0 ) ;
568+
569+ let max_amount = drain_output_value. to_sat ( ) . saturating_sub ( shared_input_value) ;
570+
571+ if max_amount < DUST_LIMIT_SATS {
572+ log_error ! (
573+ self . logger,
574+ "Unable to proceed: available funds would be consumed entirely by fees. \
575+ Available: {spendable_amount_sats}sats, drain output: {}sats.",
576+ drain_output_value. to_sat( ) ,
577+ ) ;
578+ return Err ( Error :: InsufficientFunds ) ;
579+ }
580+
581+ Ok ( ( max_amount, tmp_psbt) )
582+ }
583+
483584 pub ( crate ) fn parse_and_validate_address ( & self , address : & Address ) -> Result < Address , Error > {
484585 Address :: < NetworkUnchecked > :: from_str ( address. to_string ( ) . as_str ( ) )
485586 . map_err ( |_| Error :: InvalidAddress ) ?
@@ -503,7 +604,6 @@ impl Wallet {
503604 let mut locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
504605
505606 // Prepare the tx_builder. We properly check the reserve requirements (again) further down.
506- const DUST_LIMIT_SATS : u64 = 546 ;
507607 let tx_builder = match send_amount {
508608 OnchainSendAmount :: ExactRetainingReserve { amount_sats, .. } => {
509609 let mut tx_builder = locked_wallet. build_tx ( ) ;
@@ -514,63 +614,29 @@ impl Wallet {
514614 OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats }
515615 if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
516616 {
517- let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
518- let balance = locked_wallet. balance ( ) ;
519- let spendable_amount_sats = self
520- . get_balances_inner ( balance, cur_anchor_reserve_sats)
521- . map ( |( _, s) | s)
522- . unwrap_or ( 0 ) ;
523- let tmp_tx = {
524- let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
525- tmp_tx_builder
526- . drain_wallet ( )
527- . drain_to ( address. script_pubkey ( ) )
528- . add_recipient (
529- change_address_info. address . script_pubkey ( ) ,
530- Amount :: from_sat ( cur_anchor_reserve_sats) ,
531- )
532- . fee_rate ( fee_rate) ;
533- match tmp_tx_builder. finish ( ) {
534- Ok ( psbt) => psbt. unsigned_tx ,
535- Err ( err) => {
536- log_error ! (
537- self . logger,
538- "Failed to create temporary transaction: {}" ,
539- err
540- ) ;
541- return Err ( err. into ( ) ) ;
542- } ,
543- }
544- } ;
617+ let ( max_amount, tmp_psbt) = self . get_max_drain_amount (
618+ & mut locked_wallet,
619+ address. script_pubkey ( ) ,
620+ cur_anchor_reserve_sats,
621+ fee_rate,
622+ None ,
623+ ) ?;
545624
546- let estimated_tx_fee = locked_wallet. calculate_fee ( & tmp_tx) . map_err ( |e| {
547- log_error ! (
548- self . logger,
549- "Failed to calculate fee of temporary transaction: {}" ,
625+ let estimated_tx_fee =
626+ locked_wallet. calculate_fee ( & tmp_psbt. unsigned_tx ) . map_err ( |e| {
627+ log_error ! (
628+ self . logger,
629+ "Failed to calculate fee of temporary transaction: {}" ,
630+ e
631+ ) ;
550632 e
551- ) ;
552- e
553- } ) ?;
554-
555- // 'cancel' the transaction to free up any used change addresses
556- locked_wallet. cancel_tx ( & tmp_tx) ;
557-
558- let estimated_spendable_amount = Amount :: from_sat (
559- spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
560- ) ;
633+ } ) ?;
561634
562- if estimated_spendable_amount == Amount :: ZERO {
563- log_error ! ( self . logger,
564- "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
565- spendable_amount_sats,
566- estimated_tx_fee,
567- ) ;
568- return Err ( Error :: InsufficientFunds ) ;
569- }
635+ locked_wallet. cancel_tx ( & tmp_psbt. unsigned_tx ) ;
570636
571637 let mut tx_builder = locked_wallet. build_tx ( ) ;
572638 tx_builder
573- . add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount )
639+ . add_recipient ( address. script_pubkey ( ) , Amount :: from_sat ( max_amount ) )
574640 . fee_absolute ( estimated_tx_fee) ;
575641 tx_builder
576642 } ,
0 commit comments