@@ -43,6 +43,12 @@ enum WalletSyncStatus {
4343 InProgress { subscribers : tokio:: sync:: broadcast:: Sender < Result < ( ) , Error > > } ,
4444}
4545
46+ pub ( crate ) enum OnchainSendType {
47+ SendRetainingReserve { amount_sats : u64 , cur_anchor_reserve_sats : u64 } ,
48+ SendAllRetainingReserve { cur_anchor_reserve_sats : u64 } ,
49+ SendAllDrainingReserve ,
50+ }
51+
4652pub struct Wallet < D , B : Deref , E : Deref , L : Deref >
4753where
4854 D : BatchDatabase ,
@@ -233,12 +239,8 @@ where
233239 self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
234240 }
235241
236- /// Send funds to the given address.
237- ///
238- /// If `amount_msat_or_drain` is `None` the wallet will be drained, i.e., all available funds will be
239- /// spent.
240242 pub ( crate ) fn send_to_address (
241- & self , address : & bitcoin:: Address , amount_msat_or_drain : Option < u64 > ,
243+ & self , address : & bitcoin:: Address , send_amount : OnchainSendType ,
242244 ) -> Result < Txid , Error > {
243245 let confirmation_target = ConfirmationTarget :: OutputSpendingFee ;
244246 let fee_rate = FeeRate :: from_sat_per_kwu (
@@ -249,30 +251,108 @@ where
249251 let locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
250252 let mut tx_builder = locked_wallet. build_tx ( ) ;
251253
252- if let Some ( amount_sats) = amount_msat_or_drain {
253- tx_builder
254- . add_recipient ( address. script_pubkey ( ) , amount_sats)
255- . fee_rate ( fee_rate)
256- . enable_rbf ( ) ;
257- } else {
258- tx_builder
259- . drain_wallet ( )
260- . drain_to ( address. script_pubkey ( ) )
261- . fee_rate ( fee_rate)
262- . enable_rbf ( ) ;
254+ // Prepare the tx_builder. We properly check the reserve requirements (again) further down.
255+ match send_amount {
256+ OnchainSendType :: SendRetainingReserve { amount_sats, .. } => {
257+ tx_builder
258+ . add_recipient ( address. script_pubkey ( ) , amount_sats)
259+ . fee_rate ( fee_rate)
260+ . enable_rbf ( ) ;
261+ } ,
262+ OnchainSendType :: SendAllRetainingReserve { cur_anchor_reserve_sats } => {
263+ let spendable_amount_sats =
264+ self . get_spendable_amount_sats ( cur_anchor_reserve_sats) . unwrap_or ( 0 ) ;
265+ // TODO: can we make this closer resemble the actual transaction?
266+ // As draining the wallet always will only add one output, this method likely
267+ // under-estimates the fee rate a bit.
268+ let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
269+ tmp_tx_builder
270+ . drain_wallet ( )
271+ . drain_to ( address. script_pubkey ( ) )
272+ . fee_rate ( fee_rate)
273+ . enable_rbf ( ) ;
274+ let tmp_tx_details = match tmp_tx_builder. finish ( ) {
275+ Ok ( ( _, tmp_tx_details) ) => tmp_tx_details,
276+ Err ( err) => {
277+ log_error ! (
278+ self . logger,
279+ "Failed to create temporary transaction: {}" ,
280+ err
281+ ) ;
282+ return Err ( err. into ( ) ) ;
283+ } ,
284+ } ;
285+
286+ let estimated_tx_fee_sats = tmp_tx_details. fee . unwrap_or ( 0 ) ;
287+ let estimated_spendable_amount_sats =
288+ spendable_amount_sats. saturating_sub ( estimated_tx_fee_sats) ;
289+
290+ if estimated_spendable_amount_sats == 0 {
291+ log_error ! ( self . logger,
292+ "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
293+ spendable_amount_sats,
294+ estimated_tx_fee_sats,
295+ ) ;
296+ return Err ( Error :: InsufficientFunds ) ;
297+ }
298+
299+ tx_builder
300+ . add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount_sats)
301+ . fee_absolute ( estimated_tx_fee_sats)
302+ . enable_rbf ( ) ;
303+ } ,
304+ OnchainSendType :: SendAllDrainingReserve => {
305+ tx_builder
306+ . drain_wallet ( )
307+ . drain_to ( address. script_pubkey ( ) )
308+ . fee_rate ( fee_rate)
309+ . enable_rbf ( ) ;
310+ } ,
263311 }
264312
265- let mut psbt = match tx_builder. finish ( ) {
266- Ok ( ( psbt, _ ) ) => {
313+ let ( mut psbt, tx_details ) = match tx_builder. finish ( ) {
314+ Ok ( ( psbt, tx_details ) ) => {
267315 log_trace ! ( self . logger, "Created PSBT: {:?}" , psbt) ;
268- psbt
316+ ( psbt, tx_details )
269317 } ,
270318 Err ( err) => {
271319 log_error ! ( self . logger, "Failed to create transaction: {}" , err) ;
272320 return Err ( err. into ( ) ) ;
273321 } ,
274322 } ;
275323
324+ // Check the reserve requirements (again) and return an error if they aren't met.
325+ match send_amount {
326+ OnchainSendType :: SendRetainingReserve { amount_sats, cur_anchor_reserve_sats } => {
327+ let spendable_amount_sats =
328+ self . get_spendable_amount_sats ( cur_anchor_reserve_sats) . unwrap_or ( 0 ) ;
329+ let tx_fee_sats = tx_details. fee . unwrap_or ( 0 ) ;
330+ if spendable_amount_sats < amount_sats + tx_fee_sats {
331+ log_error ! ( self . logger,
332+ "Unable to send payment due to insufficient funds. Available: {}sats, Required: {}sats + {}sats fee" ,
333+ spendable_amount_sats,
334+ amount_sats,
335+ tx_fee_sats,
336+ ) ;
337+ return Err ( Error :: InsufficientFunds ) ;
338+ }
339+ } ,
340+ OnchainSendType :: SendAllRetainingReserve { cur_anchor_reserve_sats } => {
341+ let spendable_amount_sats =
342+ self . get_spendable_amount_sats ( cur_anchor_reserve_sats) . unwrap_or ( 0 ) ;
343+ let drain_amount_sats = tx_details. sent - tx_details. received ;
344+ if spendable_amount_sats < drain_amount_sats {
345+ log_error ! ( self . logger,
346+ "Unable to send payment due to insufficient funds. Available: {}sats, Required: {}sats" ,
347+ spendable_amount_sats,
348+ drain_amount_sats,
349+ ) ;
350+ return Err ( Error :: InsufficientFunds ) ;
351+ }
352+ } ,
353+ _ => { } ,
354+ }
355+
276356 match locked_wallet. sign ( & mut psbt, SignOptions :: default ( ) ) {
277357 Ok ( finalized) => {
278358 if !finalized {
@@ -291,21 +371,33 @@ where
291371
292372 let txid = tx. txid ( ) ;
293373
294- if let Some ( amount_sats) = amount_msat_or_drain {
295- log_info ! (
296- self . logger,
297- "Created new transaction {} sending {}sats on-chain to address {}" ,
298- txid,
299- amount_sats,
300- address
301- ) ;
302- } else {
303- log_info ! (
304- self . logger,
305- "Created new transaction {} sending all available on-chain funds to address {}" ,
306- txid,
307- address
308- ) ;
374+ match send_amount {
375+ OnchainSendType :: SendRetainingReserve { amount_sats, .. } => {
376+ log_info ! (
377+ self . logger,
378+ "Created new transaction {} sending {}sats on-chain to address {}" ,
379+ txid,
380+ amount_sats,
381+ address
382+ ) ;
383+ } ,
384+ OnchainSendType :: SendAllRetainingReserve { cur_anchor_reserve_sats } => {
385+ log_info ! (
386+ self . logger,
387+ "Created new transaction {} sending available on-chain funds retaining a reserve of {}sats to address {}" ,
388+ txid,
389+ address,
390+ cur_anchor_reserve_sats,
391+ ) ;
392+ } ,
393+ OnchainSendType :: SendAllDrainingReserve => {
394+ log_info ! (
395+ self . logger,
396+ "Created new transaction {} sending all available on-chain funds to address {}" ,
397+ txid,
398+ address
399+ ) ;
400+ } ,
309401 }
310402
311403 Ok ( txid)
0 commit comments