@@ -2642,6 +2642,8 @@ use bdk_tx::{
26422642use miniscript:: plan:: { Assets , Plan } ;
26432643use psbt_params:: SelectionStrategy ;
26442644
2645+ use crate :: wallet:: psbt_params:: AssetsExt ;
2646+
26452647/// Maps a chain position to tx confirmation status, if `pos` is the confirmed
26462648/// variant.
26472649///
@@ -2671,20 +2673,59 @@ fn status_from_position(pos: ChainPosition<ConfirmationBlockTime>) -> Option<TxS
26712673}
26722674
26732675impl Wallet {
2676+ /// Return the "keys" assets, i.e. the ones we can trivially infer by scanning
2677+ /// the pubkeys of the wallet's descriptors.
2678+ fn assets ( & self ) -> Assets {
2679+ let mut pks = vec ! [ ] ;
2680+ for ( _, desc) in self . keychains ( ) {
2681+ desc. for_each_key ( |k| {
2682+ pks. extend ( k. clone ( ) . into_single_keys ( ) ) ;
2683+ true
2684+ } ) ;
2685+ }
2686+
2687+ Assets :: new ( ) . add ( pks)
2688+ }
2689+
26742690 /// Create PSBT with the given `params` and `rng`.
26752691 pub fn create_psbt (
26762692 & self ,
26772693 params : psbt_params:: Params ,
26782694 rng : & mut impl RngCore ,
26792695 ) -> Result < ( Psbt , Finalizer ) , CreatePsbtError > {
2696+ // Get spend assets
2697+ let assets = match params. assets {
2698+ Some ( ref params_assets) => {
2699+ let mut assets = Assets :: new ( ) ;
2700+ assets. extend ( params_assets) ;
2701+ // Fill in the "keys" assets if none are provided.
2702+ if assets. keys . is_empty ( ) {
2703+ assets. extend ( & self . assets ( ) ) ;
2704+ }
2705+ assets
2706+ }
2707+ None => self . assets ( ) ,
2708+ } ;
2709+
26802710 // Get input candidates
2681- let assets = self . assets ( ) ;
2682- // TODO: We need to handle the case where we are unable to plan
2683- // a must-spend input.
2711+ let manually_selected: HashSet < OutPoint > = params. utxos . clone ( ) . into_iter ( ) . collect ( ) ;
26842712 let ( must_spend, mut may_spend) : ( Vec < Input > , Vec < Input > ) = self
26852713 . list_unspent ( )
26862714 . flat_map ( |output| self . plan_input ( & output, & assets) )
2687- . partition ( |input| params. utxos . contains ( & input. prev_outpoint ( ) ) ) ;
2715+ . partition ( |input| manually_selected. contains ( & input. prev_outpoint ( ) ) ) ;
2716+
2717+ if must_spend
2718+ . iter ( )
2719+ . map ( |input| input. prev_outpoint ( ) )
2720+ . any ( |op| !manually_selected. contains ( & op) )
2721+ {
2722+ // Try plans again, this time propagating the error.
2723+ for op in manually_selected {
2724+ if self . try_plan ( op, & assets) . is_none ( ) {
2725+ return Err ( CreatePsbtError :: Plan ( op) ) ;
2726+ }
2727+ }
2728+ }
26882729
26892730 if let SelectionStrategy :: SingleRandomDraw = params. coin_selection {
26902731 utils:: shuffle_slice ( & mut may_spend, rng) ;
@@ -2755,35 +2796,6 @@ impl Wallet {
27552796 Ok ( ( psbt, finalizer) )
27562797 }
27572798
2758- /// Return the "keys" assets, i.e. the ones we can trivially infer by scanning
2759- /// the pubkeys of the wallet's descriptors.
2760- fn assets ( & self ) -> Assets {
2761- let mut pks = vec ! [ ] ;
2762- for ( _, desc) in self . keychains ( ) {
2763- desc. for_each_key ( |k| {
2764- pks. push ( k. clone ( ) ) ;
2765- true
2766- } ) ;
2767- }
2768-
2769- Assets :: new ( ) . add ( pks)
2770- }
2771-
2772- /// Attempt to create a spending plan for the UTXO of the given `outpoint`
2773- /// with the provided `assets`.
2774- ///
2775- /// Return `None` if `outpoint` doesn't correspond to an indexed txout, or
2776- /// if the assets are not sufficient to create a plan.
2777- fn try_plan ( & self , outpoint : OutPoint , assets : & Assets ) -> Option < Plan > {
2778- let indexer = & self . indexed_graph . index ;
2779- let ( ( keychain, index) , _) = indexer. txout ( outpoint) ?;
2780- let desc = indexer
2781- . get_descriptor ( keychain) ?
2782- . at_derivation_index ( index)
2783- . expect ( "must be valid derivation index" ) ;
2784- desc. plan ( assets) . ok ( )
2785- }
2786-
27872799 /// Plan the output with the available assets and return a new [`Input`].
27882800 fn plan_input ( & self , output : & LocalOutput , assets : & Assets ) -> Option < Input > {
27892801 let op = output. outpoint ;
@@ -2803,6 +2815,24 @@ impl Wallet {
28032815
28042816 None
28052817 }
2818+
2819+ /// Attempt to create a spending plan for the UTXO of the given `outpoint`
2820+ /// with the provided `assets`.
2821+ ///
2822+ /// Return `None` if `outpoint` doesn't correspond to an indexed txout, or
2823+ /// if the assets are not sufficient to create a plan.
2824+ //
2825+ // TODO: This should internally set the after/older for `outpoint`
2826+ // based on the current height and age of the coin respectively.
2827+ fn try_plan ( & self , outpoint : OutPoint , assets : & Assets ) -> Option < Plan > {
2828+ let indexer = & self . indexed_graph . index ;
2829+ let ( ( keychain, index) , _) = indexer. txout ( outpoint) ?;
2830+ let desc = indexer
2831+ . get_descriptor ( keychain) ?
2832+ . at_derivation_index ( index)
2833+ . expect ( "must be valid derivation index" ) ;
2834+ desc. plan ( assets) . ok ( )
2835+ }
28062836}
28072837
28082838/// Error when creating a PSBT.
@@ -2812,6 +2842,8 @@ pub enum CreatePsbtError {
28122842 Bnb ( bdk_coin_select:: NoBnbSolution ) ,
28132843 /// Non-sufficient funds
28142844 NSF ( bdk_coin_select:: InsufficientFunds ) ,
2845+ /// Failed to create a spend [`Plan`] for a manually selected output
2846+ Plan ( OutPoint ) ,
28152847 /// Failed to create PSBT
28162848 Psbt ( bdk_tx:: CreatePsbtError ) ,
28172849 /// Selector error
@@ -2823,6 +2855,7 @@ impl fmt::Display for CreatePsbtError {
28232855 match self {
28242856 Self :: Bnb ( e) => write ! ( f, "{e}" ) ,
28252857 Self :: NSF ( e) => write ! ( f, "{e}" ) ,
2858+ Self :: Plan ( op) => write ! ( f, "failed to create a plan for txout with outpoint {op}" ) ,
28262859 Self :: Psbt ( e) => write ! ( f, "{e}" ) ,
28272860 Self :: Selector ( e) => write ! ( f, "{e}" ) ,
28282861 }
0 commit comments