@@ -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 struct Wallet < D , B : Deref , E : Deref , L : Deref >
47
53
where
48
54
D : BatchDatabase ,
@@ -233,12 +239,8 @@ where
233
239
self . get_balances ( total_anchor_channels_reserve_sats) . map ( |( _, s) | s)
234
240
}
235
241
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.
240
242
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 ,
242
244
) -> Result < Txid , Error > {
243
245
let confirmation_target = ConfirmationTarget :: OutputSpendingFee ;
244
246
let fee_rate = FeeRate :: from_sat_per_kwu (
@@ -249,30 +251,108 @@ where
249
251
let locked_wallet = self . inner . lock ( ) . unwrap ( ) ;
250
252
let mut tx_builder = locked_wallet. build_tx ( ) ;
251
253
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
+ } ,
263
311
}
264
312
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 ) ) => {
267
315
log_trace ! ( self . logger, "Created PSBT: {:?}" , psbt) ;
268
- psbt
316
+ ( psbt, tx_details )
269
317
} ,
270
318
Err ( err) => {
271
319
log_error ! ( self . logger, "Failed to create transaction: {}" , err) ;
272
320
return Err ( err. into ( ) ) ;
273
321
} ,
274
322
} ;
275
323
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
+
276
356
match locked_wallet. sign ( & mut psbt, SignOptions :: default ( ) ) {
277
357
Ok ( finalized) => {
278
358
if !finalized {
@@ -291,21 +371,33 @@ where
291
371
292
372
let txid = tx. txid ( ) ;
293
373
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
+ } ,
309
401
}
310
402
311
403
Ok ( txid)
0 commit comments