@@ -52,8 +52,9 @@ use bitcoin::{
52
52
53
53
use std:: ops:: Deref ;
54
54
use std:: str:: FromStr ;
55
- use std:: sync:: { Arc , Mutex } ;
55
+ use std:: sync:: { Arc , Mutex , MutexGuard } ;
56
56
57
+ #[ derive( Debug , Copy , Clone ) ]
57
58
pub ( crate ) enum OnchainSendAmount {
58
59
ExactRetainingReserve { amount_sats : u64 , cur_anchor_reserve_sats : u64 } ,
59
60
AllRetainingReserve { cur_anchor_reserve_sats : u64 } ,
@@ -352,6 +353,114 @@ where
352
353
. map_err ( |_| Error :: InvalidAddress )
353
354
}
354
355
356
+ pub ( crate ) fn estimate_fee (
357
+ & self , address : & Address , send_amount : OnchainSendAmount , fee_rate : Option < FeeRate > ,
358
+ ) -> Result < Amount , Error > {
359
+ let mut locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
360
+
361
+ // Use the set fee_rate or default to fee estimation.
362
+ let confirmation_target = ConfirmationTarget :: OnchainPayment ;
363
+ let fee_rate =
364
+ fee_rate. unwrap_or_else ( || self . fee_estimator . estimate_fee_rate ( confirmation_target) ) ;
365
+
366
+ self . estimate_fee_internal ( & mut locked_wallet, address, send_amount, fee_rate) . map_err (
367
+ |e| {
368
+ log_error ! ( self . logger, "Failed to estimate fee: {e}" ) ;
369
+ e
370
+ } ,
371
+ )
372
+ }
373
+
374
+ pub ( crate ) fn estimate_fee_internal (
375
+ & self , locked_wallet : & mut MutexGuard < PersistedWallet < KVStoreWalletPersister > > ,
376
+ address : & Address , send_amount : OnchainSendAmount , fee_rate : FeeRate ,
377
+ ) -> Result < Amount , Error > {
378
+ const DUST_LIMIT_SATS : u64 = 546 ;
379
+ match send_amount {
380
+ OnchainSendAmount :: ExactRetainingReserve { amount_sats, .. } => {
381
+ let mut tx_builder = locked_wallet. build_tx ( ) ;
382
+ let amount = Amount :: from_sat ( amount_sats) ;
383
+ tx_builder. add_recipient ( address. script_pubkey ( ) , amount) . fee_rate ( fee_rate) ;
384
+
385
+ let psbt = match tx_builder. finish ( ) {
386
+ Ok ( psbt) => psbt,
387
+ Err ( err) => {
388
+ log_error ! ( self . logger, "Failed to create temporary transaction: {}" , err) ;
389
+ return Err ( err. into ( ) ) ;
390
+ } ,
391
+ } ;
392
+
393
+ // 'cancel' the transaction to free up any used change addresses
394
+ locked_wallet. cancel_tx ( & psbt. unsigned_tx ) ;
395
+ psbt. fee ( ) . map_err ( |_| Error :: WalletOperationFailed )
396
+ } ,
397
+ OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats }
398
+ if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
399
+ {
400
+ let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
401
+ let balance = locked_wallet. balance ( ) ;
402
+ let spendable_amount_sats = self
403
+ . get_balances_inner ( balance, cur_anchor_reserve_sats)
404
+ . map ( |( _, s) | s)
405
+ . unwrap_or ( 0 ) ;
406
+ let mut tx_builder = locked_wallet. build_tx ( ) ;
407
+ tx_builder
408
+ . drain_wallet ( )
409
+ . drain_to ( address. script_pubkey ( ) )
410
+ . add_recipient (
411
+ change_address_info. address . script_pubkey ( ) ,
412
+ Amount :: from_sat ( cur_anchor_reserve_sats) ,
413
+ )
414
+ . fee_rate ( fee_rate) ;
415
+
416
+ let psbt = match tx_builder. finish ( ) {
417
+ Ok ( psbt) => psbt,
418
+ Err ( err) => {
419
+ log_error ! ( self . logger, "Failed to create temporary transaction: {}" , err) ;
420
+ return Err ( err. into ( ) ) ;
421
+ } ,
422
+ } ;
423
+
424
+ // 'cancel' the transaction to free up any used change addresses
425
+ locked_wallet. cancel_tx ( & psbt. unsigned_tx ) ;
426
+
427
+ let estimated_tx_fee = psbt. fee ( ) . map_err ( |_| Error :: WalletOperationFailed ) ?;
428
+
429
+ // enforce the reserve requirements to make sure we can actually afford the tx + fee
430
+ let estimated_spendable_amount = Amount :: from_sat (
431
+ spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
432
+ ) ;
433
+
434
+ if estimated_spendable_amount == Amount :: ZERO {
435
+ log_error ! ( self . logger,
436
+ "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
437
+ spendable_amount_sats,
438
+ estimated_tx_fee,
439
+ ) ;
440
+ return Err ( Error :: InsufficientFunds ) ;
441
+ }
442
+
443
+ Ok ( estimated_tx_fee)
444
+ } ,
445
+ OnchainSendAmount :: AllDrainingReserve
446
+ | OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats : _ } => {
447
+ let mut tx_builder = locked_wallet. build_tx ( ) ;
448
+ tx_builder. drain_wallet ( ) . drain_to ( address. script_pubkey ( ) ) . fee_rate ( fee_rate) ;
449
+ let psbt = match tx_builder. finish ( ) {
450
+ Ok ( psbt) => psbt,
451
+ Err ( err) => {
452
+ log_error ! ( self . logger, "Failed to create temporary transaction: {}" , err) ;
453
+ return Err ( err. into ( ) ) ;
454
+ } ,
455
+ } ;
456
+
457
+ // 'cancel' the transaction to free up any used change addresses
458
+ locked_wallet. cancel_tx ( & psbt. unsigned_tx ) ;
459
+ psbt. fee ( ) . map_err ( |_| Error :: WalletOperationFailed )
460
+ } ,
461
+ }
462
+ }
463
+
355
464
pub ( crate ) fn send_to_address (
356
465
& self , address : & bitcoin:: Address , send_amount : OnchainSendAmount ,
357
466
fee_rate : Option < FeeRate > ,
@@ -378,60 +487,24 @@ where
378
487
OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats }
379
488
if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
380
489
{
381
- let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
382
490
let balance = locked_wallet. balance ( ) ;
383
491
let spendable_amount_sats = self
384
492
. get_balances_inner ( balance, cur_anchor_reserve_sats)
385
493
. map ( |( _, s) | s)
386
494
. unwrap_or ( 0 ) ;
387
- let tmp_tx = {
388
- let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
389
- tmp_tx_builder
390
- . drain_wallet ( )
391
- . drain_to ( address. script_pubkey ( ) )
392
- . add_recipient (
393
- change_address_info. address . script_pubkey ( ) ,
394
- Amount :: from_sat ( cur_anchor_reserve_sats) ,
395
- )
396
- . fee_rate ( fee_rate) ;
397
- match tmp_tx_builder. finish ( ) {
398
- Ok ( psbt) => psbt. unsigned_tx ,
399
- Err ( err) => {
400
- log_error ! (
401
- self . logger,
402
- "Failed to create temporary transaction: {}" ,
403
- err
404
- ) ;
405
- return Err ( err. into ( ) ) ;
406
- } ,
407
- }
408
- } ;
409
495
410
- let estimated_tx_fee = locked_wallet. calculate_fee ( & tmp_tx) . map_err ( |e| {
411
- log_error ! (
412
- self . logger,
413
- "Failed to calculate fee of temporary transaction: {}" ,
496
+ // estimate_fee_internal will enforce that we are retaining the reserve limits
497
+ let estimated_tx_fee = self
498
+ . estimate_fee_internal ( & mut locked_wallet, address, send_amount, fee_rate)
499
+ . map_err ( |e| {
500
+ log_error ! ( self . logger, "Failed to estimate fee: {e}" ) ;
414
501
e
415
- ) ;
416
- e
417
- } ) ?;
418
-
419
- // 'cancel' the transaction to free up any used change addresses
420
- locked_wallet. cancel_tx ( & tmp_tx) ;
502
+ } ) ?;
421
503
422
504
let estimated_spendable_amount = Amount :: from_sat (
423
505
spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
424
506
) ;
425
507
426
- if estimated_spendable_amount == Amount :: ZERO {
427
- log_error ! ( self . logger,
428
- "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
429
- spendable_amount_sats,
430
- estimated_tx_fee,
431
- ) ;
432
- return Err ( Error :: InsufficientFunds ) ;
433
- }
434
-
435
508
let mut tx_builder = locked_wallet. build_tx ( ) ;
436
509
tx_builder
437
510
. add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount)
0 commit comments