# TxBuilder redesign Goals of the redesign: - More intuitive design - Easier to customize (how group coins, coin selection, etc) - Less trait heavy (I'm looking at you, CoinSelectionAlgorithm) - Keep it simple: most users won't customize the cs or coin grouping, the API should still feel lightweight for them - Avoid unclear behaviors - Example: TxBuilder now allows setting both feerate and fee absolute - Example: TxBuilder now allows inserting an utxo both in the must be spent and in the unspendable list Issues with the current tx design: - Collection issue: [#988](https://github.com/bitcoindevkit/bdk/issues/988) - CS fallback algorithm can't be changed - (Somewhat related) Remove Database type paramter from CoinSelectionAlgorithm [#281](https://github.com/bitcoindevkit/bdk/issues/281) - UTXO locking [#849](https://github.com/bitcoindevkit/bdk/issues/849) - Maybe controversial, but we should RBF by default [#791](https://github.com/bitcoindevkit/bdk/issues/791) - Support merging multiple transactions while bumping the fees (https://github.com/bitcoindevkit/bdk/blob/master/crates/bdk/src/wallet/mod.rs#L1017) - `TxBuilder::add_psbt` [#153](https://github.com/bitcoindevkit/bdk/issues/153) - Selecting the change addresss type to be same as recipient's address - - TO CHECK: is it possible to put a relative timelock on the tx? I don't see any nsequence param in TxParams This document assumes a very basic understanding of rust-miniscript planning module. More info here: [rust-miniscript + bdk - planning module](/zNsz5LV9S06l1xTa8-6yqg) The main idea expressed in [#899](https://github.com/bitcoindevkit/bdk/issues/899) is to split the TxBuilder in stages. I'm copying the stages here for clarity, and adding an obvious first step which was missing in the issue ## 0. User options - User will use the `TxParams` struct to define how to build the transaction. My proposal for the `TxParams` struct: ```rust pub struct TxParams<K> { pub recipients: Vec<(Script, AmountOrDrain)>, pub change_keychain: K /* TODO: document that the change output might not be there */ /* same as the current txparams: */ pub fee_policy: FeePolicy, pub sighash: psbt::PsbtSighashType, pub locktime: LockTime, pub rbf: RbfValue, pub version: Version, pub allow_dust: bool, pub current_height: Option<u32>, } pub enum AmountOrDrain { Amount(Amount), Drain(/* ratio */), } ``` - New fields: - `recipients: Vec<(Script, AmountOrDrain)>` - For each script I can either provide an amount to send, or ask bdk to drain the wallet. - This is I think pretty powerful as, for example, on can express "Send 10BTC to Alice, and the remaining split between Bob and Charlie" as `{'Alice':'10', 'Bob': 'Drain(1)', 'Charlie': 'Drain(1)'}`. - This replaces the current [drain_wallet](https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html#method.drain_wallet)/[drain_to](https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html#method.drain_to). - `change_keychain: K` - where to send the change. - TODO: figure out how to provide a nice default. Maybe we want wallets to have one change keychain and N main keychains? Maybe we want keychains with different roles and spend priorities? ## 1. Coin control - The `CoinControl` structure is useful for assigning a plan to each utxo, and grouping them. - Each Utxo can either be a `Planned` one or a `PsbtInput` one: ```rust pub enum Utxo<P> { Planned { outpoint: OutPoint, txout: TxOut, confirmation_time: ConfirmationTime, plan_id: Option<P>, }, PsbtInput { outpoint: OutPoint, satisfaction_weight: u32, psbt_input: PsbtInput, }, } ``` - Utxos are grouped in groups, in a structure called `CoinGroup`. Each group has a set of "unspendable" utxos, which either don't have a plan, the user explicitly marked as "unspendable", or are locked. This means that some groups are partially spendable - the user might not want to spend a partial group due to privacy loss. - The `CoinControl` structure will contain all the utxo groups, and each utxo will have a reference to its respective plans. It will also hold a `grouping_fn: Fn(&Utxo) -> G` that is going to be used to assing a specific group to a coin. - The `grouping_fn` has to be in `CoinControl` and not passed externally every time utxos are added, to make sure that the user will use the same function for all the utxos. - The default `grouping_fn` will group coins by locking script: - For planned utxos, we know the locking script - For psbt inputs, we know the locking script if we have either the witness utxo or the non witness utxo. If we don't have either, we have to create a "fake script" that is unique to the coin, so that we can put the coin in a group by itself. - `CoinControl` is going to be populated through the `add_utxos` method, which accepts a vector of utxos all related to a certain descriptor, and the list of assets that can be used: ```rust fn add_utxos(&self, utxos: Vec<Utxo>, descriptor: Descriptor<??>, Assets: &[Asset], unspendable: Vec<OutPoint>) { } ``` - Users are required to pass all the possible utxos to add_utxos, even the ones that they don't want to spend, provided that they also add it to the `unspendable` list - TODO: figure out coins that can't be spent together. AFAIK we need some additional logic in the coin selection to support this ## 2. Coin selection (WIP) - Select coins for the actual transaction. - The coin selection step can be skipped if the user is manually selecting the coins. This means that we don't need any `manually_selected_only` option - Handling `must_be_used`: user can just ask input a smaller target for the CS and then manually adding the must_be_used coins. - TODO: coin selection needs to be skipped if `recipients` contains at least one `Drain` (as in that case we don't need to select coins, we just pick them all!). I would like to have a way of making this easier for users... - Need a way to translate the `CoinControl` struct of the last step into the appropriate parameter for the coin selection function ### PR [#924](https://github.com/bitcoindevkit/bdk/pull/924) [WIP] - Updates the coin selection - User now calls `CoinSelector::new(candidates, base_weight).bnb()` ## 3. Transaction preparation - Can be just a function that will need the selected coins, the tx params, the tx ordering (https://docs.rs/bdk/latest/bdk/wallet/tx_builder/enum.TxOrdering.html) and the drain strategy. The function can then return a `Result<Transaction, Whatever>` ```rust prepare_tx( inputs: Vec<Utxo>, tx_params: TxParams, drain: Drain, ordering: TxOrdering ) -> Result<Transaction, PrepareTxError> ``` ## 4. PSBT preparation (Transaction -> PSBT) - Given the selected coins and respective plans, the function will create the PSBT input - The function will also accepts some parameters to modify its behaviour: - `only_witness_utxo` - `add_global_xpub` - `include_output_redeem_witness_script` ## 5. Signing - The function will accept the PSBT and a bdk::Signer and will return the signed PSBT, and a `finalized` bool ## How the API will look like (WIP) ```rust // Step 0 let tx_params = TxParams { ... }; // Step 1 let plans = TxPlan::default().add_keychain(my_keychain, my_assets)?; let coins = wallet.list_unspent_by_keychain(my_keychain); let mut coin_control = CoinControl::default(); coin_control.add_foreign_utxo_with_plan(..); coin_control.add_local_utxos(coins); // Step 2: IDK let (selected_coins, drain) = idk; // ?? let selected_coins_with_plans = idk; // ?? // Step 3: let tx = prepare_tx(&selected_coins, tx_params, drain, tx_ordering); // Step 4 let psbt = prepare_psbt(tx, selected_coins_with_plans); // Step 5 let signer = wallet.get_signers(); let (psbt, finalized) = sign_tx(signers, psbt); ``` ## TODO - [ ] Figure out locking and unlocking - [ ] Add PSBT for native coinjoin - [ ] Coins that can't be spent together? :sweat_smile: - [ ] Double check Galoy feedback on the old txbuilder - [ ] Bump txs, CPFP txs, Cancel txs methods