@@ -274,25 +274,29 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
274274 /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
275275 /// the "utxos" and the "unspendable" list, it will be spent.
276276 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+ } ) ;
296300 }
297301
298302 Ok ( self )
@@ -306,6 +310,106 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
306310 self . add_utxos ( & [ outpoint] )
307311 }
308312
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+
309413 /// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
310414 ///
311415 /// At a minimum to add a foreign UTXO we need:
@@ -697,6 +801,35 @@ impl fmt::Display for AddUtxoError {
697801#[ cfg( feature = "std" ) ]
698802impl std:: error:: Error for AddUtxoError { }
699803
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+
700833#[ derive( Debug ) ]
701834/// Error returned from [`TxBuilder::add_foreign_utxo`].
702835pub enum AddForeignUtxoError {
0 commit comments