@@ -28,7 +28,7 @@ use lightning_invoice::RawBolt11Invoice;
28
28
29
29
use bdk_chain:: spk_client:: { FullScanRequest , SyncRequest } ;
30
30
use bdk_chain:: ChainPosition ;
31
- use bdk_wallet:: { KeychainKind , PersistedWallet , SignOptions , Update } ;
31
+ use bdk_wallet:: { Balance , KeychainKind , PersistedWallet , SignOptions , Update } ;
32
32
33
33
use bitcoin:: blockdata:: constants:: WITNESS_SCALE_FACTOR ;
34
34
use bitcoin:: blockdata:: locktime:: absolute:: LockTime ;
@@ -45,6 +45,12 @@ use bitcoin::{
45
45
use std:: ops:: Deref ;
46
46
use std:: sync:: { Arc , Mutex } ;
47
47
48
+ pub ( crate ) enum OnchainSendAmount {
49
+ ExactRetainingReserve { amount_sats : u64 , cur_anchor_reserve_sats : u64 } ,
50
+ AllRetainingReserve { cur_anchor_reserve_sats : u64 } ,
51
+ AllDrainingReserve ,
52
+ }
53
+
48
54
pub ( crate ) mod persist;
49
55
pub ( crate ) mod ser;
50
56
@@ -215,6 +221,12 @@ where
215
221
) ;
216
222
}
217
223
224
+ self . get_balances_inner ( balance, total_anchor_channels_reserve_sats)
225
+ }
226
+
227
+ fn get_balances_inner (
228
+ & self , balance : Balance , total_anchor_channels_reserve_sats : u64 ,
229
+ ) -> Result < ( u64 , u64 ) , Error > {
218
230
let ( total, spendable) = (
219
231
balance. total ( ) . to_sat ( ) ,
220
232
balance. trusted_spendable ( ) . to_sat ( ) . saturating_sub ( total_anchor_channels_reserve_sats) ,
@@ -229,32 +241,95 @@ where
229
241
self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
230
242
}
231
243
232
- /// Send funds to the given address.
233
- ///
234
- /// If `amount_msat_or_drain` is `None` the wallet will be drained, i.e., all available funds will be
235
- /// spent.
236
244
pub ( crate ) fn send_to_address (
237
- & self , address : & bitcoin:: Address , amount_or_drain : Option < Amount > ,
245
+ & self , address : & bitcoin:: Address , send_amount : OnchainSendAmount ,
238
246
) -> Result < Txid , Error > {
239
247
let confirmation_target = ConfirmationTarget :: OnchainPayment ;
240
248
let fee_rate = self . fee_estimator . estimate_fee_rate ( confirmation_target) ;
241
249
242
250
let tx = {
243
251
let mut locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
244
- let mut tx_builder = locked_wallet. build_tx ( ) ;
245
-
246
- if let Some ( amount) = amount_or_drain {
247
- tx_builder
248
- . add_recipient ( address. script_pubkey ( ) , amount)
249
- . fee_rate ( fee_rate)
250
- . enable_rbf ( ) ;
251
- } else {
252
- tx_builder
253
- . drain_wallet ( )
254
- . drain_to ( address. script_pubkey ( ) )
255
- . fee_rate ( fee_rate)
256
- . enable_rbf ( ) ;
257
- }
252
+
253
+ // Prepare the tx_builder. We properly check the reserve requirements (again) further down.
254
+ let tx_builder = match send_amount {
255
+ OnchainSendAmount :: ExactRetainingReserve { amount_sats, .. } => {
256
+ let mut tx_builder = locked_wallet. build_tx ( ) ;
257
+ let amount = Amount :: from_sat ( amount_sats) ;
258
+ tx_builder
259
+ . add_recipient ( address. script_pubkey ( ) , amount)
260
+ . fee_rate ( fee_rate)
261
+ . enable_rbf ( ) ;
262
+ tx_builder
263
+ } ,
264
+ OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats } => {
265
+ let change_address_info = locked_wallet. peek_address ( KeychainKind :: Internal , 0 ) ;
266
+ let balance = locked_wallet. balance ( ) ;
267
+ let spendable_amount_sats = self
268
+ . get_balances_inner ( balance, cur_anchor_reserve_sats)
269
+ . map ( |( _, s) | s)
270
+ . unwrap_or ( 0 ) ;
271
+ let tmp_tx = {
272
+ let mut tmp_tx_builder = locked_wallet. build_tx ( ) ;
273
+ tmp_tx_builder
274
+ . drain_wallet ( )
275
+ . drain_to ( address. script_pubkey ( ) )
276
+ . add_recipient (
277
+ change_address_info. address . script_pubkey ( ) ,
278
+ Amount :: from_sat ( cur_anchor_reserve_sats) ,
279
+ )
280
+ . fee_rate ( fee_rate)
281
+ . enable_rbf ( ) ;
282
+ match tmp_tx_builder. finish ( ) {
283
+ Ok ( psbt) => psbt. unsigned_tx ,
284
+ Err ( err) => {
285
+ log_error ! (
286
+ self . logger,
287
+ "Failed to create temporary transaction: {}" ,
288
+ err
289
+ ) ;
290
+ return Err ( err. into ( ) ) ;
291
+ } ,
292
+ }
293
+ } ;
294
+
295
+ let estimated_tx_fee = locked_wallet. calculate_fee ( & tmp_tx) . map_err ( |e| {
296
+ log_error ! (
297
+ self . logger,
298
+ "Failed to calculate fee of temporary transaction: {}" ,
299
+ e
300
+ ) ;
301
+ e
302
+ } ) ?;
303
+ let estimated_spendable_amount = Amount :: from_sat (
304
+ spendable_amount_sats. saturating_sub ( estimated_tx_fee. to_sat ( ) ) ,
305
+ ) ;
306
+
307
+ if estimated_spendable_amount == Amount :: ZERO {
308
+ log_error ! ( self . logger,
309
+ "Unable to send payment without infringing on Anchor reserves. Available: {}sats, estimated fee required: {}sats." ,
310
+ spendable_amount_sats,
311
+ estimated_tx_fee,
312
+ ) ;
313
+ return Err ( Error :: InsufficientFunds ) ;
314
+ }
315
+
316
+ let mut tx_builder = locked_wallet. build_tx ( ) ;
317
+ tx_builder
318
+ . add_recipient ( address. script_pubkey ( ) , estimated_spendable_amount)
319
+ . fee_absolute ( estimated_tx_fee)
320
+ . enable_rbf ( ) ;
321
+ tx_builder
322
+ } ,
323
+ OnchainSendAmount :: AllDrainingReserve => {
324
+ let mut tx_builder = locked_wallet. build_tx ( ) ;
325
+ tx_builder
326
+ . drain_wallet ( )
327
+ . drain_to ( address. script_pubkey ( ) )
328
+ . fee_rate ( fee_rate)
329
+ . enable_rbf ( ) ;
330
+ tx_builder
331
+ } ,
332
+ } ;
258
333
259
334
let mut psbt = match tx_builder. finish ( ) {
260
335
Ok ( psbt) => {
@@ -267,6 +342,58 @@ where
267
342
} ,
268
343
} ;
269
344
345
+ // Check the reserve requirements (again) and return an error if they aren't met.
346
+ match send_amount {
347
+ OnchainSendAmount :: ExactRetainingReserve {
348
+ amount_sats,
349
+ cur_anchor_reserve_sats,
350
+ } => {
351
+ let balance = locked_wallet. balance ( ) ;
352
+ let spendable_amount_sats = self
353
+ . get_balances_inner ( balance, cur_anchor_reserve_sats)
354
+ . map ( |( _, s) | s)
355
+ . unwrap_or ( 0 ) ;
356
+ let tx_fee_sats = locked_wallet
357
+ . calculate_fee ( & psbt. unsigned_tx )
358
+ . map_err ( |e| {
359
+ log_error ! (
360
+ self . logger,
361
+ "Failed to calculate fee of candidate transaction: {}" ,
362
+ e
363
+ ) ;
364
+ e
365
+ } ) ?
366
+ . to_sat ( ) ;
367
+ if spendable_amount_sats < amount_sats. saturating_add ( tx_fee_sats) {
368
+ log_error ! ( self . logger,
369
+ "Unable to send payment due to insufficient funds. Available: {}sats, Required: {}sats + {}sats fee" ,
370
+ spendable_amount_sats,
371
+ amount_sats,
372
+ tx_fee_sats,
373
+ ) ;
374
+ return Err ( Error :: InsufficientFunds ) ;
375
+ }
376
+ } ,
377
+ OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats } => {
378
+ let balance = locked_wallet. balance ( ) ;
379
+ let spendable_amount_sats = self
380
+ . get_balances_inner ( balance, cur_anchor_reserve_sats)
381
+ . map ( |( _, s) | s)
382
+ . unwrap_or ( 0 ) ;
383
+ let ( sent, received) = locked_wallet. sent_and_received ( & psbt. unsigned_tx ) ;
384
+ let drain_amount = sent - received;
385
+ if spendable_amount_sats < drain_amount. to_sat ( ) {
386
+ log_error ! ( self . logger,
387
+ "Unable to send payment due to insufficient funds. Available: {}sats, Required: {}" ,
388
+ spendable_amount_sats,
389
+ drain_amount,
390
+ ) ;
391
+ return Err ( Error :: InsufficientFunds ) ;
392
+ }
393
+ } ,
394
+ _ => { } ,
395
+ }
396
+
270
397
match locked_wallet. sign ( & mut psbt, SignOptions :: default ( ) ) {
271
398
Ok ( finalized) => {
272
399
if !finalized {
@@ -295,21 +422,33 @@ where
295
422
296
423
let txid = tx. compute_txid ( ) ;
297
424
298
- if let Some ( amount) = amount_or_drain {
299
- log_info ! (
300
- self . logger,
301
- "Created new transaction {} sending {}sats on-chain to address {}" ,
302
- txid,
303
- amount. to_sat( ) ,
304
- address
305
- ) ;
306
- } else {
307
- log_info ! (
308
- self . logger,
309
- "Created new transaction {} sending all available on-chain funds to address {}" ,
310
- txid,
311
- address
312
- ) ;
425
+ match send_amount {
426
+ OnchainSendAmount :: ExactRetainingReserve { amount_sats, .. } => {
427
+ log_info ! (
428
+ self . logger,
429
+ "Created new transaction {} sending {}sats on-chain to address {}" ,
430
+ txid,
431
+ amount_sats,
432
+ address
433
+ ) ;
434
+ } ,
435
+ OnchainSendAmount :: AllRetainingReserve { cur_anchor_reserve_sats } => {
436
+ log_info ! (
437
+ self . logger,
438
+ "Created new transaction {} sending available on-chain funds retaining a reserve of {}sats to address {}" ,
439
+ txid,
440
+ cur_anchor_reserve_sats,
441
+ address,
442
+ ) ;
443
+ } ,
444
+ OnchainSendAmount :: AllDrainingReserve => {
445
+ log_info ! (
446
+ self . logger,
447
+ "Created new transaction {} sending all available on-chain funds to address {}" ,
448
+ txid,
449
+ address
450
+ ) ;
451
+ } ,
313
452
}
314
453
315
454
Ok ( txid)
0 commit comments