@@ -43,6 +43,12 @@ enum WalletSyncStatus {
43
43
InProgress { subscribers : tokio:: sync:: broadcast:: Sender < Result < ( ) , Error > > } ,
44
44
}
45
45
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
+
46
52
pub ( crate ) struct Wallet < D , B : Deref , E : Deref , L : Deref >
47
53
where
48
54
D : BatchDatabase ,
@@ -231,12 +237,8 @@ where
231
237
self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
232
238
}
233
239
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.
238
240
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 ,
240
242
) -> Result < Txid , Error > {
241
243
let confirmation_target = ConfirmationTarget :: OnchainPayment ;
242
244
let fee_rate = self . fee_estimator . estimate_fee_rate ( confirmation_target) ;
@@ -245,30 +247,108 @@ where
245
247
let locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
246
248
let mut tx_builder = locked_wallet. build_tx ( ) ;
247
249
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
+ } ,
259
307
}
260
308
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 ) ) => {
263
311
log_trace ! ( self . logger, "Created PSBT: {:?}" , psbt) ;
264
- psbt
312
+ ( psbt, tx_details )
265
313
} ,
266
314
Err ( err) => {
267
315
log_error ! ( self . logger, "Failed to create transaction: {}" , err) ;
268
316
return Err ( err. into ( ) ) ;
269
317
} ,
270
318
} ;
271
319
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
+
272
352
match locked_wallet. sign ( & mut psbt, SignOptions :: default ( ) ) {
273
353
Ok ( finalized) => {
274
354
if !finalized {
@@ -287,21 +367,33 @@ where
287
367
288
368
let txid = tx. txid ( ) ;
289
369
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
+ } ,
305
397
}
306
398
307
399
Ok ( txid)
0 commit comments