@@ -71,6 +71,8 @@ pub(crate) enum OnchainSendAmount {
7171pub ( crate ) mod persist;
7272pub ( crate ) mod ser;
7373
74+ const DUST_LIMIT_SATS : u64 = 546 ;
75+
7476pub ( crate ) struct Wallet {
7577 // A BDK on-chain wallet.
7678 inner : Mutex < PersistedWallet < KVStoreWalletPersister > > ,
@@ -533,6 +535,105 @@ impl Wallet {
533535 self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
534536 }
535537
538+ fn build_drain_psbt (
539+ & self , locked_wallet : & mut PersistedWallet < KVStoreWalletPersister > ,
540+ drain_script : ScriptBuf , cur_anchor_reserve_sats : u64 , fee_rate : FeeRate ,
541+ shared_input : Option < & Input > ,
542+ ) -> Result < Psbt , Error > {
543+ let anchor_address = if cur_anchor_reserve_sats > DUST_LIMIT_SATS {
544+ Some ( locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) )
545+ } else {
546+ None
547+ } ;
548+
549+ let mut tx_builder = locked_wallet. build_tx ( ) ;
550+ tx_builder. drain_wallet ( ) . drain_to ( drain_script) . fee_rate ( fee_rate) ;
551+
552+ if let Some ( address_info) = anchor_address {
553+ tx_builder. add_recipient (
554+ address_info. address . script_pubkey ( ) ,
555+ Amount :: from_sat ( cur_anchor_reserve_sats) ,
556+ ) ;
557+ }
558+
559+ if let Some ( input) = shared_input {
560+ let psbt_input = psbt:: Input {
561+ witness_utxo : Some ( input. previous_utxo . clone ( ) ) ,
562+ ..Default :: default ( )
563+ } ;
564+ let weight = Weight :: from_wu ( input. satisfaction_weight ) ;
565+ tx_builder. only_witness_utxo ( ) . exclude_unconfirmed ( ) ;
566+ tx_builder. add_foreign_utxo ( input. outpoint , psbt_input, weight) . map_err ( |e| {
567+ log_error ! ( self . logger, "Failed to add shared input for fee estimation: {e}" ) ;
568+ Error :: ChannelSplicingFailed
569+ } ) ?;
570+ }
571+
572+ let psbt = tx_builder. finish ( ) . map_err ( |err| {
573+ log_error ! ( self . logger, "Failed to create temporary drain transaction: {err}" ) ;
574+ err
575+ } ) ?;
576+
577+ Ok ( psbt)
578+ }
579+
580+ /// Builds a temporary drain transaction and returns the maximum amount that would be sent to
581+ /// the drain output, along with the PSBT for further inspection.
582+ ///
583+ /// The caller is responsible for cancelling the PSBT via `locked_wallet.cancel_tx()`.
584+ fn get_max_drain_amount (
585+ & self , locked_wallet : & mut PersistedWallet < KVStoreWalletPersister > ,
586+ drain_script : ScriptBuf , cur_anchor_reserve_sats : u64 , fee_rate : FeeRate ,
587+ shared_input : Option < & Input > ,
588+ ) -> Result < ( u64 , Psbt ) , Error > {
589+ let balance = locked_wallet. balance ( ) ;
590+ let spendable_amount_sats =
591+ self . get_balances_inner ( balance, cur_anchor_reserve_sats) . map ( |( _, s) | s) . unwrap_or ( 0 ) ;
592+
593+ if spendable_amount_sats == 0 {
594+ log_error ! (
595+ self . logger,
596+ "Unable to determine max amount: no spendable funds available."
597+ ) ;
598+ return Err ( Error :: InsufficientFunds ) ;
599+ }
600+
601+ let tmp_psbt = self . build_drain_psbt (
602+ locked_wallet,
603+ drain_script. clone ( ) ,
604+ cur_anchor_reserve_sats,
605+ fee_rate,
606+ shared_input,
607+ ) ?;
608+
609+ let drain_output_value = tmp_psbt
610+ . unsigned_tx
611+ . output
612+ . iter ( )
613+ . find ( |o| o. script_pubkey == drain_script)
614+ . map ( |o| o. value )
615+ . ok_or_else ( || {
616+ log_error ! ( self . logger, "Failed to find drain output in temporary transaction" ) ;
617+ Error :: InsufficientFunds
618+ } ) ?;
619+
620+ let shared_input_value = shared_input. map ( |i| i. previous_utxo . value . to_sat ( ) ) . unwrap_or ( 0 ) ;
621+
622+ let max_amount = drain_output_value. to_sat ( ) . saturating_sub ( shared_input_value) ;
623+
624+ if max_amount < DUST_LIMIT_SATS {
625+ log_error ! (
626+ self . logger,
627+ "Unable to proceed: available funds would be consumed entirely by fees. \
628+ Available: {spendable_amount_sats}sats, drain output: {}sats.",
629+ drain_output_value. to_sat( ) ,
630+ ) ;
631+ return Err ( Error :: InsufficientFunds ) ;
632+ }
633+
634+ Ok ( ( max_amount, tmp_psbt) )
635+ }
636+
536637 pub ( crate ) fn parse_and_validate_address ( & self , address : & Address ) -> Result < Address , Error > {
537638 Address :: < NetworkUnchecked > :: from_str ( address. to_string ( ) . as_str ( ) )
538639 . map_err ( |_| Error :: InvalidAddress ) ?
@@ -556,7 +657,6 @@ impl Wallet {
556657 let mut locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
557658
558659 // Prepare the tx_builder. We properly check the reserve requirements (again) further down.
559- const DUST_LIMIT_SATS : u64 = 546 ;
560660 let tx_builder = match send_amount {
561661 OnchainSendAmount :: ExactRetainingReserve { amount_sats, .. } => {
562662 let mut tx_builder = locked_wallet. build_tx ( ) ;
@@ -567,63 +667,29 @@ impl Wallet {
567667 OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats }
568668 if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
569669 {
570- let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
571- let balance = locked_wallet. balance ( ) ;
572- let spendable_amount_sats = self
573- . get_balances_inner ( balance, cur_anchor_reserve_sats)
574- . map ( |( _, s) | s)
575- . unwrap_or ( 0 ) ;
576- let tmp_tx = {
577- let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
578- tmp_tx_builder
579- . drain_wallet ( )
580- . drain_to ( address. script_pubkey ( ) )
581- . add_recipient (
582- change_address_info. address . script_pubkey ( ) ,
583- Amount :: from_sat ( cur_anchor_reserve_sats) ,
584- )
585- . fee_rate ( fee_rate) ;
586- match tmp_tx_builder. finish ( ) {
587- Ok ( psbt) => psbt. unsigned_tx ,
588- Err ( err) => {
589- log_error ! (
590- self . logger,
591- "Failed to create temporary transaction: {}" ,
592- err
593- ) ;
594- return Err ( err. into ( ) ) ;
595- } ,
596- }
597- } ;
670+ let ( max_amount, tmp_psbt) = self . get_max_drain_amount (
671+ & mut locked_wallet,
672+ address. script_pubkey ( ) ,
673+ cur_anchor_reserve_sats,
674+ fee_rate,
675+ None ,
676+ ) ?;
598677
599- let estimated_tx_fee = locked_wallet. calculate_fee ( & tmp_tx) . map_err ( |e| {
600- log_error ! (
601- self . logger,
602- "Failed to calculate fee of temporary transaction: {}" ,
678+ let estimated_tx_fee =
679+ locked_wallet. calculate_fee ( & tmp_psbt. unsigned_tx ) . map_err ( |e| {
680+ log_error ! (
681+ self . logger,
682+ "Failed to calculate fee of temporary transaction: {}" ,
683+ e
684+ ) ;
603685 e
604- ) ;
605- e
606- } ) ?;
607-
608- // 'cancel' the transaction to free up any used change addresses
609- locked_wallet. cancel_tx ( & tmp_tx) ;
610-
611- let estimated_spendable_amount = Amount :: from_sat (
612- spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
613- ) ;
686+ } ) ?;
614687
615- if estimated_spendable_amount == Amount :: ZERO {
616- log_error ! ( self . logger,
617- "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
618- spendable_amount_sats,
619- estimated_tx_fee,
620- ) ;
621- return Err ( Error :: InsufficientFunds ) ;
622- }
688+ locked_wallet. cancel_tx ( & tmp_psbt. unsigned_tx ) ;
623689
624690 let mut tx_builder = locked_wallet. build_tx ( ) ;
625691 tx_builder
626- . add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount )
692+ . add_recipient ( address. script_pubkey ( ) , Amount :: from_sat ( max_amount ) )
627693 . fee_absolute ( estimated_tx_fee) ;
628694 tx_builder
629695 } ,
0 commit comments