@@ -274,25 +274,29 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
274
274
/// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
275
275
/// the "utxos" and the "unspendable" list, it will be spent.
276
276
pub fn add_utxos ( & mut self , outpoints : & [ OutPoint ] ) -> Result < & mut Self , AddUtxoError > {
277
- {
278
- let wallet = & mut self . wallet ;
279
- let utxos = outpoints
280
- . iter ( )
281
- . map ( |outpoint| {
282
- wallet
283
- . get_utxo ( * outpoint)
284
- . ok_or ( AddUtxoError :: UnknownUtxo ( * outpoint) )
285
- } )
286
- . collect :: < Result < Vec < _ > , _ > > ( ) ?;
287
-
288
- for utxo in utxos {
289
- let descriptor = wallet. public_descriptor ( utxo. keychain ) ;
290
- let satisfaction_weight = descriptor. max_weight_to_satisfy ( ) . unwrap ( ) ;
291
- self . params . utxos . push ( WeightedUtxo {
292
- satisfaction_weight,
293
- utxo : Utxo :: Local ( utxo) ,
294
- } ) ;
295
- }
277
+ let wallet = & mut self . wallet ;
278
+ let utxos = outpoints
279
+ . iter ( )
280
+ . map ( |outpoint| {
281
+ wallet
282
+ . get_utxo ( * outpoint)
283
+ . or_else ( || {
284
+ // allow selecting a spent output if we're bumping fee
285
+ self . params
286
+ . bumping_fee
287
+ . and_then ( |_| wallet. get_output ( * outpoint) )
288
+ } )
289
+ . ok_or ( AddUtxoError :: UnknownUtxo ( * outpoint) )
290
+ } )
291
+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
292
+
293
+ for utxo in utxos {
294
+ let descriptor = wallet. public_descriptor ( utxo. keychain ) ;
295
+ let satisfaction_weight = descriptor. max_weight_to_satisfy ( ) . unwrap ( ) ;
296
+ self . params . utxos . push ( WeightedUtxo {
297
+ satisfaction_weight,
298
+ utxo : Utxo :: Local ( utxo) ,
299
+ } ) ;
296
300
}
297
301
298
302
Ok ( self )
@@ -306,6 +310,106 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
306
310
self . add_utxos ( & [ outpoint] )
307
311
}
308
312
313
+ /// Replace an unconfirmed transaction.
314
+ ///
315
+ /// This method attempts to create a replacement for the transaction with `txid` by
316
+ /// looking for the largest input that is owned by this wallet and adding it to the
317
+ /// list of UTXOs to spend.
318
+ ///
319
+ /// # Note
320
+ ///
321
+ /// Aside from reusing one of the inputs, the method makes no assumptions about the
322
+ /// structure of the replacement, so if you need to reuse the original recipient(s)
323
+ /// and/or change address, you should add them manually before [`finish`] is called.
324
+ ///
325
+ /// # Example
326
+ ///
327
+ /// Create a replacement for an unconfirmed wallet transaction
328
+ ///
329
+ /// ```rust,no_run
330
+ /// # let mut wallet = bdk_wallet::doctest_wallet!();
331
+ /// let wallet_txs = wallet.transactions().collect::<Vec<_>>();
332
+ /// let tx = wallet_txs.first().expect("must have wallet tx");
333
+ ///
334
+ /// if !tx.chain_position.is_confirmed() {
335
+ /// let txid = tx.tx_node.txid;
336
+ /// let mut builder = wallet.build_tx();
337
+ /// builder.replace_tx(txid).expect("should replace");
338
+ ///
339
+ /// // Continue building tx...
340
+ ///
341
+ /// let psbt = builder.finish()?;
342
+ /// }
343
+ /// # Ok::<_, anyhow::Error>(())
344
+ /// ```
345
+ ///
346
+ /// # Errors
347
+ ///
348
+ /// - If the original transaction is not found in the tx graph
349
+ /// - If the orginal transaction is confirmed
350
+ /// - If none of the inputs are owned by this wallet
351
+ ///
352
+ /// [`finish`]: TxBuilder::finish
353
+ pub fn replace_tx ( & mut self , txid : Txid ) -> Result < & mut Self , ReplaceTxError > {
354
+ let tx = self
355
+ . wallet
356
+ . indexed_graph
357
+ . graph ( )
358
+ . get_tx ( txid)
359
+ . ok_or ( ReplaceTxError :: MissingTransaction ) ?;
360
+ if self
361
+ . wallet
362
+ . transactions ( )
363
+ . find ( |c| c. tx_node . txid == txid)
364
+ . map ( |c| c. chain_position . is_confirmed ( ) )
365
+ . unwrap_or ( false )
366
+ {
367
+ return Err ( ReplaceTxError :: TransactionConfirmed ) ;
368
+ }
369
+ let outpoint = tx
370
+ . input
371
+ . iter ( )
372
+ . filter_map ( |txin| {
373
+ let prev_tx = self
374
+ . wallet
375
+ . indexed_graph
376
+ . graph ( )
377
+ . get_tx ( txin. previous_output . txid ) ?;
378
+ let txout = & prev_tx. output [ txin. previous_output . vout as usize ] ;
379
+ if self . wallet . is_mine ( txout. script_pubkey . clone ( ) ) {
380
+ Some ( ( txin. previous_output , txout. value ) )
381
+ } else {
382
+ None
383
+ }
384
+ } )
385
+ . max_by_key ( |( _, value) | * value)
386
+ . map ( |( op, _) | op)
387
+ . ok_or ( ReplaceTxError :: NonReplaceable ) ?;
388
+
389
+ // add previous fee
390
+ if let Ok ( absolute) = self . wallet . calculate_fee ( & tx) {
391
+ let rate = absolute / tx. weight ( ) ;
392
+ let previous_fee = PreviousFee { absolute, rate } ;
393
+ self . params . bumping_fee = Some ( previous_fee) ;
394
+ }
395
+
396
+ self . add_utxo ( outpoint) . map_err ( |e| match e {
397
+ AddUtxoError :: UnknownUtxo ( op) => ReplaceTxError :: MissingOutput ( op) ,
398
+ } ) ?;
399
+
400
+ // do not try to spend the outputs of the tx being replaced
401
+ self . params
402
+ . unspendable
403
+ . extend ( ( 0 ..tx. output . len ( ) ) . map ( |vout| OutPoint :: new ( txid, vout as u32 ) ) ) ;
404
+
405
+ Ok ( self )
406
+ }
407
+
408
+ /// Get the previous feerate, i.e. the feerate of the tx being fee-bumped, if any.
409
+ pub fn previous_fee ( & self ) -> Option < FeeRate > {
410
+ self . params . bumping_fee . map ( |p| p. rate )
411
+ }
412
+
309
413
/// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
310
414
///
311
415
/// At a minimum to add a foreign UTXO we need:
@@ -697,6 +801,35 @@ impl fmt::Display for AddUtxoError {
697
801
#[ cfg( feature = "std" ) ]
698
802
impl std:: error:: Error for AddUtxoError { }
699
803
804
+ /// Error returned by [`TxBuilder::replace_tx`].
805
+ #[ derive( Debug ) ]
806
+ pub enum ReplaceTxError {
807
+ /// Unable to find a locally owned output
808
+ MissingOutput ( OutPoint ) ,
809
+ /// Transaction was not found in tx graph
810
+ MissingTransaction ,
811
+ /// Transaction can't be replaced by this wallet
812
+ NonReplaceable ,
813
+ /// Transaction is already confirmed
814
+ TransactionConfirmed ,
815
+ }
816
+
817
+ impl fmt:: Display for ReplaceTxError {
818
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
819
+ match self {
820
+ Self :: MissingOutput ( op) => {
821
+ write ! ( f, "could not find wallet output for outpoint {}" , op)
822
+ }
823
+ Self :: MissingTransaction => write ! ( f, "transaction not found in tx graph" ) ,
824
+ Self :: NonReplaceable => write ! ( f, "no replaceable input found" ) ,
825
+ Self :: TransactionConfirmed => write ! ( f, "cannot replace a confirmed tx" ) ,
826
+ }
827
+ }
828
+ }
829
+
830
+ #[ cfg( feature = "std" ) ]
831
+ impl std:: error:: Error for ReplaceTxError { }
832
+
700
833
#[ derive( Debug ) ]
701
834
/// Error returned from [`TxBuilder::add_foreign_utxo`].
702
835
pub enum AddForeignUtxoError {
0 commit comments