@@ -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 ( crate ) struct Wallet < D , B : Deref , E : Deref , L : Deref >
4753where
4854 D : BatchDatabase ,
@@ -231,12 +237,8 @@ where
231237 self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
232238 }
233239
234- /// Send funds to the given address.
235- ///
236- /// If `amount_msat_or_drain` is `None` the wallet will be drained, i.e., all available funds will be
237- /// spent.
238240 pub ( crate ) fn send_to_address (
239- & self , address : & bitcoin:: Address , amount_msat_or_drain : Option < u64 > ,
241+ & self , address : & bitcoin:: Address , send_amount : OnchainSendType ,
240242 ) -> Result < Txid , Error > {
241243 let confirmation_target = ConfirmationTarget :: OnchainPayment ;
242244 let fee_rate = self . fee_estimator . estimate_fee_rate ( confirmation_target) ;
@@ -245,30 +247,108 @@ where
245247 let locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
246248 let mut tx_builder = locked_wallet. build_tx ( ) ;
247249
248- if let Some ( amount_sats) = amount_msat_or_drain {
249- tx_builder
250- . add_recipient ( address. script_pubkey ( ) , amount_sats)
251- . fee_rate ( fee_rate)
252- . enable_rbf ( ) ;
253- } else {
254- tx_builder
255- . drain_wallet ( )
256- . drain_to ( address. script_pubkey ( ) )
257- . fee_rate ( fee_rate)
258- . enable_rbf ( ) ;
250+ // Prepare the tx_builder. We properly check the reserve requirements (again) further down.
251+ match send_amount {
252+ OnchainSendType :: SendRetainingReserve { amount_sats, .. } => {
253+ tx_builder
254+ . add_recipient ( address. script_pubkey ( ) , amount_sats)
255+ . fee_rate ( fee_rate)
256+ . enable_rbf ( ) ;
257+ } ,
258+ OnchainSendType :: SendAllRetainingReserve { cur_anchor_reserve_sats } => {
259+ let spendable_amount_sats =
260+ self . get_spendable_amount_sats ( cur_anchor_reserve_sats) . unwrap_or ( 0 ) ;
261+ // TODO: can we make this closer resemble the actual transaction?
262+ // As draining the wallet always will only add one output, this method likely
263+ // under-estimates the fee rate a bit.
264+ let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
265+ tmp_tx_builder
266+ . drain_wallet ( )
267+ . drain_to ( address. script_pubkey ( ) )
268+ . fee_rate ( fee_rate)
269+ . enable_rbf ( ) ;
270+ let tmp_tx_details = match tmp_tx_builder. finish ( ) {
271+ Ok ( ( _, tmp_tx_details) ) => tmp_tx_details,
272+ Err ( err) => {
273+ log_error ! (
274+ self . logger,
275+ "Failed to create temporary transaction: {}" ,
276+ err
277+ ) ;
278+ return Err ( err. into ( ) ) ;
279+ } ,
280+ } ;
281+
282+ let estimated_tx_fee_sats = tmp_tx_details. fee . unwrap_or ( 0 ) ;
283+ let estimated_spendable_amount_sats =
284+ spendable_amount_sats. saturating_sub ( estimated_tx_fee_sats) ;
285+
286+ if estimated_spendable_amount_sats == 0 {
287+ log_error ! ( self . logger,
288+ "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
289+ spendable_amount_sats,
290+ estimated_tx_fee_sats,
291+ ) ;
292+ return Err ( Error :: InsufficientFunds ) ;
293+ }
294+
295+ tx_builder
296+ . add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount_sats)
297+ . fee_absolute ( estimated_tx_fee_sats)
298+ . enable_rbf ( ) ;
299+ } ,
300+ OnchainSendType :: SendAllDrainingReserve => {
301+ tx_builder
302+ . drain_wallet ( )
303+ . drain_to ( address. script_pubkey ( ) )
304+ . fee_rate ( fee_rate)
305+ . enable_rbf ( ) ;
306+ } ,
259307 }
260308
261- let mut psbt = match tx_builder. finish ( ) {
262- Ok ( ( psbt, _ ) ) => {
309+ let ( mut psbt, tx_details ) = match tx_builder. finish ( ) {
310+ Ok ( ( psbt, tx_details ) ) => {
263311 log_trace ! ( self . logger, "Created PSBT: {:?}" , psbt) ;
264- psbt
312+ ( psbt, tx_details )
265313 } ,
266314 Err ( err) => {
267315 log_error ! ( self . logger, "Failed to create transaction: {}" , err) ;
268316 return Err ( err. into ( ) ) ;
269317 } ,
270318 } ;
271319
320+ // Check the reserve requirements (again) and return an error if they aren't met.
321+ match send_amount {
322+ OnchainSendType :: SendRetainingReserve { amount_sats, cur_anchor_reserve_sats } => {
323+ let spendable_amount_sats =
324+ self . get_spendable_amount_sats ( cur_anchor_reserve_sats) . unwrap_or ( 0 ) ;
325+ let tx_fee_sats = tx_details. fee . unwrap_or ( 0 ) ;
326+ if spendable_amount_sats < amount_sats + tx_fee_sats {
327+ log_error ! ( self . logger,
328+ "Unable to send payment due to insufficient funds. Available: {}sats, Required: {}sats + {}sats fee" ,
329+ spendable_amount_sats,
330+ amount_sats,
331+ tx_fee_sats,
332+ ) ;
333+ return Err ( Error :: InsufficientFunds ) ;
334+ }
335+ } ,
336+ OnchainSendType :: SendAllRetainingReserve { cur_anchor_reserve_sats } => {
337+ let spendable_amount_sats =
338+ self . get_spendable_amount_sats ( cur_anchor_reserve_sats) . unwrap_or ( 0 ) ;
339+ let drain_amount_sats = tx_details. sent - tx_details. received ;
340+ if spendable_amount_sats < drain_amount_sats {
341+ log_error ! ( self . logger,
342+ "Unable to send payment due to insufficient funds. Available: {}sats, Required: {}sats" ,
343+ spendable_amount_sats,
344+ drain_amount_sats,
345+ ) ;
346+ return Err ( Error :: InsufficientFunds ) ;
347+ }
348+ } ,
349+ _ => { } ,
350+ }
351+
272352 match locked_wallet. sign ( & mut psbt, SignOptions :: default ( ) ) {
273353 Ok ( finalized) => {
274354 if !finalized {
@@ -287,21 +367,33 @@ where
287367
288368 let txid = tx. txid ( ) ;
289369
290- if let Some ( amount_sats) = amount_msat_or_drain {
291- log_info ! (
292- self . logger,
293- "Created new transaction {} sending {}sats on-chain to address {}" ,
294- txid,
295- amount_sats,
296- address
297- ) ;
298- } else {
299- log_info ! (
300- self . logger,
301- "Created new transaction {} sending all available on-chain funds to address {}" ,
302- txid,
303- address
304- ) ;
370+ match send_amount {
371+ OnchainSendType :: SendRetainingReserve { amount_sats, .. } => {
372+ log_info ! (
373+ self . logger,
374+ "Created new transaction {} sending {}sats on-chain to address {}" ,
375+ txid,
376+ amount_sats,
377+ address
378+ ) ;
379+ } ,
380+ OnchainSendType :: SendAllRetainingReserve { cur_anchor_reserve_sats } => {
381+ log_info ! (
382+ self . logger,
383+ "Created new transaction {} sending available on-chain funds retaining a reserve of {}sats to address {}" ,
384+ txid,
385+ address,
386+ cur_anchor_reserve_sats,
387+ ) ;
388+ } ,
389+ OnchainSendType :: SendAllDrainingReserve => {
390+ log_info ! (
391+ self . logger,
392+ "Created new transaction {} sending all available on-chain funds to address {}" ,
393+ txid,
394+ address
395+ ) ;
396+ } ,
305397 }
306398
307399 Ok ( txid)
0 commit comments