cardano_serialization_lib/builders/
tx_builder.rs

1#![allow(deprecated)]
2
3use crate::*;
4
5use super::*;
6use crate::builders::fakes::{fake_bootstrap_witness, fake_raw_key_public, fake_raw_key_sig};
7use crate::fees;
8use crate::utils;
9use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
10
11fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize {
12    let mut input_hashes: Ed25519KeyHashes = Ed25519KeyHashes::from(&tx_builder.inputs);
13    input_hashes.extend_move(Ed25519KeyHashes::from(&tx_builder.collateral));
14    input_hashes.extend_move(tx_builder.required_signers.clone());
15    if let Some(mint_builder) = &tx_builder.mint {
16        input_hashes.extend_move(Ed25519KeyHashes::from(&mint_builder.get_native_scripts()));
17    }
18    if let Some(withdrawals_builder) = &tx_builder.withdrawals {
19        input_hashes.extend_move(withdrawals_builder.get_required_signers());
20    }
21    if let Some(certs_builder) = &tx_builder.certs {
22        input_hashes.extend_move(certs_builder.get_required_signers());
23    }
24    if let Some(voting_builder) = &tx_builder.voting_procedures {
25        input_hashes.extend_move(voting_builder.get_required_signers());
26    }
27    input_hashes.len()
28}
29
30// tx_body must be the result of building from tx_builder
31// constructs the rest of the Transaction using fake witness data of the correct length
32// for use in calculating the size of the final Transaction
33pub(crate) fn fake_full_tx(
34    tx_builder: &TransactionBuilder,
35    body: TransactionBody,
36) -> Result<Transaction, JsError> {
37    let fake_sig = fake_raw_key_sig();
38
39    // recall: this includes keys for input, certs and withdrawals
40    let vkeys = match count_needed_vkeys(tx_builder) {
41        0 => None,
42        x => {
43            let mut result = Vkeywitnesses::new();
44            for i in 0..x {
45                let raw_key_public = fake_raw_key_public(i as u64);
46                let fake_vkey_witness = Vkeywitness::new(&Vkey::new(&raw_key_public), &fake_sig);
47                result.add(&fake_vkey_witness.clone());
48            }
49            Some(result)
50        }
51    };
52    let bootstraps = get_bootstraps(&tx_builder.inputs);
53    let bootstrap_keys = match bootstraps.len() {
54        0 => None,
55        _x => {
56            let mut result = BootstrapWitnesses::new();
57            let mut number = 1;
58            for addr in bootstraps {
59                number += 1;
60                // picking icarus over daedalus for fake witness generation shouldn't matter
61                result.add(&fake_bootstrap_witness(number, &ByronAddress::from_bytes(addr)?));
62            }
63            Some(result)
64        }
65    };
66    let (plutus_scripts, mut plutus_data, redeemers) = {
67        if let Some(s) = tx_builder.get_combined_plutus_scripts() {
68            let (s, d, r) = s.collect();
69            (Some(s), d, Some(r))
70        } else {
71            (None, None, None)
72        }
73    };
74
75    if let Some(extra_datums) = &tx_builder.extra_datums {
76        if let Some(d) = &mut plutus_data {
77            d.extend(extra_datums);
78        } else {
79            plutus_data = Some(extra_datums.clone());
80        }
81    }
82
83    let witness_set = TransactionWitnessSet::new_with_partial_dedup(
84        vkeys,
85        tx_builder.get_combined_native_scripts(),
86        bootstrap_keys,
87        plutus_scripts,
88        plutus_data,
89        redeemers,
90    );
91    Ok(Transaction {
92        body,
93        witness_set,
94        is_valid: true,
95        auxiliary_data: tx_builder.auxiliary_data.clone(),
96    })
97}
98
99fn assert_required_mint_scripts(
100    mint: &Mint,
101    maybe_mint_scripts: Option<&NativeScripts>,
102) -> Result<(), JsError> {
103    if maybe_mint_scripts.is_none_or_empty() {
104        return Err(JsError::from_str(
105            "Mint is present in the builder, but witness scripts are not provided!",
106        ));
107    }
108    let mint_scripts = maybe_mint_scripts.unwrap();
109    let witness_hashes: HashSet<ScriptHash> =
110        mint_scripts.iter().map(|script| script.hash()).collect();
111    for mint_hash in mint.keys().0.iter() {
112        if !witness_hashes.contains(mint_hash) {
113            return Err(JsError::from_str(&format!(
114                "No witness script is found for mint policy '{:?}'! Script is required!",
115                hex::encode(mint_hash.to_bytes()),
116            )));
117        }
118    }
119    Ok(())
120}
121
122fn min_fee(tx_builder: &TransactionBuilder) -> Result<Coin, JsError> {
123    // Commented out for performance, `min_fee` is a critical function
124    // This was mostly added here as a paranoid step anyways
125    // If someone is using `set_mint` and `add_mint*` API function, everything is expected to be intact
126    // TODO: figure out if assert is needed here and a better way to do it maybe only once if mint doesn't change
127    // if let Some(mint) = tx_builder.mint.as_ref() {
128    //     assert_required_mint_scripts(mint, tx_builder.mint_scripts.as_ref())?;
129    // }
130    let full_tx = fake_full_tx(tx_builder, tx_builder.build()?)?;
131    let mut fee: Coin = fees::min_fee(&full_tx, &tx_builder.config.fee_algo)?;
132
133    if let Some(ex_unit_prices) = &tx_builder.config.ex_unit_prices {
134        let script_fee: Coin = fees::min_script_fee(&full_tx, &ex_unit_prices)?;
135        fee = fee.checked_add(&script_fee)?;
136    } else {
137        if tx_builder.has_plutus_inputs() {
138            return Err(JsError::from_str(
139                "Plutus inputs are present but ex_unit_prices are missing in the config!",
140            ));
141        }
142    }
143
144    let total_ref_script_size = tx_builder.get_total_ref_scripts_size()?;
145    if let Some(ref_script_coins_per_byte) = &tx_builder.config.ref_script_coins_per_byte {
146        let script_ref_fee = min_ref_script_fee(total_ref_script_size, ref_script_coins_per_byte)?;
147        fee = fee.checked_add(&script_ref_fee)?;
148    } else {
149        if total_ref_script_size > 0 {
150            return Err(JsError::from_str(
151                "Referenced scripts are present but ref_script_coins_per_byte is missing in the config!",
152            ));
153        }
154    }
155
156    Ok(fee)
157}
158
159#[wasm_bindgen]
160pub enum CoinSelectionStrategyCIP2 {
161    /// Performs CIP2's Largest First ada-only selection. Will error if outputs contain non-ADA assets.
162    LargestFirst,
163    /// Performs CIP2's Random Improve ada-only selection. Will error if outputs contain non-ADA assets.
164    RandomImprove,
165    /// Same as LargestFirst, but before adding ADA, will insert by largest-first for each asset type.
166    LargestFirstMultiAsset,
167    /// Same as RandomImprove, but before adding ADA, will insert by random-improve for each asset type.
168    RandomImproveMultiAsset,
169}
170
171#[wasm_bindgen]
172#[derive(Clone, Debug)]
173pub struct TransactionBuilderConfig {
174    pub(crate) fee_algo: fees::LinearFee,
175    pub(crate) pool_deposit: Coin,  // protocol parameter
176    pub(crate) key_deposit: Coin,   // protocol parameter
177    pub(crate) max_value_size: u32, // protocol parameter
178    pub(crate) max_tx_size: u32,    // protocol parameter
179    pub(crate) data_cost: DataCost, // protocol parameter
180    pub(crate) ex_unit_prices: Option<ExUnitPrices>, // protocol parameter
181    pub(crate) ref_script_coins_per_byte: Option<UnitInterval>, // protocol parameter
182    pub(crate) prefer_pure_change: bool,
183    pub(crate) deduplicate_explicit_ref_inputs_with_regular_inputs: bool,
184    pub(crate) do_not_burn_extra_change: bool,
185}
186
187impl TransactionBuilderConfig {
188    pub(crate) fn utxo_cost(&self) -> DataCost {
189        self.data_cost.clone()
190    }
191}
192
193#[wasm_bindgen]
194#[derive(Clone, Debug)]
195pub struct TransactionBuilderConfigBuilder {
196    fee_algo: Option<fees::LinearFee>,
197    pool_deposit: Option<Coin>,                      // protocol parameter
198    key_deposit: Option<Coin>,                       // protocol parameter
199    max_value_size: Option<u32>,                     // protocol parameter
200    max_tx_size: Option<u32>,                        // protocol parameter
201    data_cost: Option<DataCost>,                     // protocol parameter
202    ex_unit_prices: Option<ExUnitPrices>,            // protocol parameter
203    ref_script_coins_per_byte: Option<UnitInterval>, // protocol parameter
204    prefer_pure_change: bool,
205    deduplicate_explicit_ref_inputs_with_regular_inputs: bool,
206    do_not_burn_extra_change: bool,
207}
208
209#[wasm_bindgen]
210impl TransactionBuilderConfigBuilder {
211    pub fn new() -> Self {
212        Self {
213            fee_algo: None,
214            pool_deposit: None,
215            key_deposit: None,
216            max_value_size: None,
217            max_tx_size: None,
218            data_cost: None,
219            ex_unit_prices: None,
220            ref_script_coins_per_byte: None,
221            prefer_pure_change: false,
222            deduplicate_explicit_ref_inputs_with_regular_inputs: false,
223            do_not_burn_extra_change: false,
224        }
225    }
226
227    pub fn fee_algo(&self, fee_algo: &fees::LinearFee) -> Self {
228        let mut cfg = self.clone();
229        cfg.fee_algo = Some(fee_algo.clone());
230        cfg
231    }
232
233    pub fn coins_per_utxo_byte(&self, coins_per_utxo_byte: &Coin) -> Self {
234        let mut cfg = self.clone();
235        cfg.data_cost = Some(DataCost::new_coins_per_byte(coins_per_utxo_byte));
236        cfg
237    }
238
239    pub fn ex_unit_prices(&self, ex_unit_prices: &ExUnitPrices) -> Self {
240        let mut cfg = self.clone();
241        cfg.ex_unit_prices = Some(ex_unit_prices.clone());
242        cfg
243    }
244
245    pub fn pool_deposit(&self, pool_deposit: &BigNum) -> Self {
246        let mut cfg = self.clone();
247        cfg.pool_deposit = Some(pool_deposit.clone());
248        cfg
249    }
250
251    pub fn key_deposit(&self, key_deposit: &BigNum) -> Self {
252        let mut cfg = self.clone();
253        cfg.key_deposit = Some(key_deposit.clone());
254        cfg
255    }
256
257    pub fn max_value_size(&self, max_value_size: u32) -> Self {
258        let mut cfg = self.clone();
259        cfg.max_value_size = Some(max_value_size);
260        cfg
261    }
262
263    pub fn max_tx_size(&self, max_tx_size: u32) -> Self {
264        let mut cfg = self.clone();
265        cfg.max_tx_size = Some(max_tx_size);
266        cfg
267    }
268
269    pub fn ref_script_coins_per_byte(&self, ref_script_coins_per_byte: &UnitInterval) -> Self {
270        let mut cfg = self.clone();
271        cfg.ref_script_coins_per_byte = Some(ref_script_coins_per_byte.clone());
272        cfg
273    }
274
275    pub fn prefer_pure_change(&self, prefer_pure_change: bool) -> Self {
276        let mut cfg = self.clone();
277        cfg.prefer_pure_change = prefer_pure_change;
278        cfg
279    }
280
281    ///Removes a ref input (that was set via set_reference_inputs) if the ref inputs was presented in regular tx inputs
282    pub fn deduplicate_explicit_ref_inputs_with_regular_inputs(&self, deduplicate_explicit_ref_inputs_with_regular_inputs: bool) -> Self {
283        let mut cfg = self.clone();
284        cfg.deduplicate_explicit_ref_inputs_with_regular_inputs = deduplicate_explicit_ref_inputs_with_regular_inputs;
285        cfg
286    }
287
288    ///If set to true, the transaction builder will not burn extra change if it's impossible to crate change output
289    ///due to the value being too small to cover min ada for change output. Instead, tx builder will throw an error.
290    pub fn do_not_burn_extra_change(&self, do_not_burn_extra_change: bool) -> Self {
291        let mut cfg = self.clone();
292        cfg.do_not_burn_extra_change = do_not_burn_extra_change;
293        cfg
294    }
295
296    pub fn build(&self) -> Result<TransactionBuilderConfig, JsError> {
297        let cfg: Self = self.clone();
298        Ok(TransactionBuilderConfig {
299            fee_algo: cfg
300                .fee_algo
301                .ok_or(JsError::from_str("uninitialized field: fee_algo"))?,
302            pool_deposit: cfg
303                .pool_deposit
304                .ok_or(JsError::from_str("uninitialized field: pool_deposit"))?,
305            key_deposit: cfg
306                .key_deposit
307                .ok_or(JsError::from_str("uninitialized field: key_deposit"))?,
308            max_value_size: cfg
309                .max_value_size
310                .ok_or(JsError::from_str("uninitialized field: max_value_size"))?,
311            max_tx_size: cfg
312                .max_tx_size
313                .ok_or(JsError::from_str("uninitialized field: max_tx_size"))?,
314            data_cost: cfg.data_cost.ok_or(JsError::from_str(
315                "uninitialized field: coins_per_utxo_byte or coins_per_utxo_word",
316            ))?,
317            ex_unit_prices: cfg.ex_unit_prices,
318            ref_script_coins_per_byte: cfg.ref_script_coins_per_byte,
319            prefer_pure_change: cfg.prefer_pure_change,
320            deduplicate_explicit_ref_inputs_with_regular_inputs: cfg.deduplicate_explicit_ref_inputs_with_regular_inputs,
321            do_not_burn_extra_change: cfg.do_not_burn_extra_change,
322        })
323    }
324}
325
326#[wasm_bindgen]
327#[derive(Clone, Debug)]
328pub struct ChangeConfig {
329    address: Address,
330    plutus_data: Option<OutputDatum>,
331    script_ref: Option<ScriptRef>,
332}
333
334#[wasm_bindgen]
335impl ChangeConfig {
336    pub fn new(address: &Address) -> Self {
337        Self {
338            address: address.clone(),
339            plutus_data: None,
340            script_ref: None,
341        }
342    }
343
344    pub fn change_address(&self, address: &Address) -> Self {
345        let mut c_cfg = self.clone();
346        c_cfg.address = address.clone();
347        c_cfg
348    }
349
350    pub fn change_plutus_data(&self, plutus_data: &OutputDatum) -> Self {
351        let mut c_cfg = self.clone();
352        c_cfg.plutus_data = Some(plutus_data.clone());
353        c_cfg
354    }
355
356    pub fn change_script_ref(&self, script_ref: &ScriptRef) -> Self {
357        let mut c_cfg = self.clone();
358        c_cfg.script_ref = Some(script_ref.clone());
359        c_cfg
360    }
361}
362
363#[derive(Clone, Debug)]
364pub(crate) enum TxBuilderFee {
365    Unspecified,
366    NotLess(Coin),
367    Exactly(Coin),
368}
369
370impl TxBuilderFee {
371    fn get_new_fee(&self, new_fee: Coin) -> Coin {
372        match self {
373            TxBuilderFee::Unspecified => new_fee,
374            TxBuilderFee::NotLess(old_fee) => {
375                if &new_fee < old_fee {
376                    old_fee.clone()
377                } else {
378                    new_fee
379                }
380            }
381            TxBuilderFee::Exactly(old_fee) => {
382                old_fee.clone()
383            }
384        }
385    }
386}
387
388#[wasm_bindgen]
389#[derive(Clone, Debug)]
390pub struct TransactionBuilder {
391    pub(crate) config: TransactionBuilderConfig,
392    pub(crate) inputs: TxInputsBuilder,
393    pub(crate) collateral: TxInputsBuilder,
394    pub(crate) outputs: TransactionOutputs,
395    pub(crate) fee_request: TxBuilderFee,
396    pub(crate) fee: Option<BigNum>,
397    pub(crate) ttl: Option<SlotBigNum>, // absolute slot number
398    pub(crate) certs: Option<CertificatesBuilder>,
399    pub(crate) withdrawals: Option<WithdrawalsBuilder>,
400    pub(crate) auxiliary_data: Option<AuxiliaryData>,
401    pub(crate) validity_start_interval: Option<SlotBigNum>,
402    pub(crate) mint: Option<MintBuilder>,
403    pub(crate) script_data_hash: Option<ScriptDataHash>,
404    pub(crate) required_signers: Ed25519KeyHashes,
405    pub(crate) collateral_return: Option<TransactionOutput>,
406    pub(crate) total_collateral: Option<Coin>,
407    pub(crate) reference_inputs: HashMap<TransactionInput, usize>,
408    pub(crate) extra_datums: Option<PlutusList>,
409    pub(crate) voting_procedures: Option<VotingBuilder>,
410    pub(crate) voting_proposals: Option<VotingProposalBuilder>,
411    pub(crate) current_treasury_value: Option<Coin>,
412    pub(crate) donation: Option<Coin>,
413}
414
415#[wasm_bindgen]
416impl TransactionBuilder {
417    /// This automatically selects and adds inputs from {inputs} consisting of just enough to cover
418    /// the outputs that have already been added.
419    /// This should be called after adding all certs/outputs/etc and will be an error otherwise.
420    /// Uses CIP2: https://github.com/cardano-foundation/CIPs/blob/master/CIP-0002/CIP-0002.md
421    /// Adding a change output must be called after via TransactionBuilder::add_change_if_needed()
422    /// This function, diverging from CIP2, takes into account fees and will attempt to add additional
423    /// inputs to cover the minimum fees. This does not, however, set the txbuilder's fee.
424    pub fn add_inputs_from(
425        &mut self,
426        inputs: &TransactionUnspentOutputs,
427        strategy: CoinSelectionStrategyCIP2,
428    ) -> Result<(), JsError> {
429        let mut available_inputs: Vec<&TransactionUnspentOutput> = inputs.0.iter().collect();
430        let have_no_inputs_in_tx = !self.inputs.has_inputs();
431        let mut input_total = self.get_total_input()?;
432        let mut output_total = self
433            .get_total_output()?
434            .checked_add(&Value::new(&self.min_fee()?))?;
435
436        if (input_total.coin >= output_total.coin) && have_no_inputs_in_tx {
437            if available_inputs.is_empty() {
438                return Err(JsError::from_str("No inputs to add. Transaction should have at least one input"));
439            }
440
441            //just add first input, to cover needs of one input
442            let input = available_inputs.pop().unwrap();
443            self.inputs.add_regular_utxo(&input)?;
444            input_total = input_total.checked_add(&input.output.amount)?;
445        }
446
447        match strategy {
448            CoinSelectionStrategyCIP2::LargestFirst => {
449                if self
450                    .outputs
451                    .0
452                    .iter()
453                    .any(|output| output.amount.multiasset.is_some())
454                {
455                    return Err(JsError::from_str("Multiasset values not supported by LargestFirst. Please use LargestFirstMultiAsset"));
456                }
457                self.cip2_largest_first_by(
458                    &available_inputs,
459                    &mut (0..available_inputs.len()).collect(),
460                    &mut input_total,
461                    &mut output_total,
462                    |value| Some(value.coin),
463                )?;
464            }
465            CoinSelectionStrategyCIP2::RandomImprove => {
466                if self
467                    .outputs
468                    .0
469                    .iter()
470                    .any(|output| output.amount.multiasset.is_some())
471                {
472                    return Err(JsError::from_str("Multiasset values not supported by RandomImprove. Please use RandomImproveMultiAsset"));
473                }
474                use rand::Rng;
475                let mut rng = rand::thread_rng();
476                let mut available_indices =
477                    (0..available_inputs.len()).collect::<BTreeSet<usize>>();
478                self.cip2_random_improve_by(
479                    &available_inputs,
480                    &mut available_indices,
481                    &mut input_total,
482                    &mut output_total,
483                    |value| Some(value.coin),
484                    &mut rng,
485                    true,
486                )?;
487                // Phase 3: add extra inputs needed for fees (not covered by CIP-2)
488                // We do this at the end because this new inputs won't be associated with
489                // a specific output, so the improvement algorithm we do above does not apply here.
490                while input_total.coin < output_total.coin {
491                    if available_indices.is_empty() {
492                        return Err(JsError::from_str("UTxO Balance Insufficient[x]"));
493                    }
494                    let i = *available_indices
495                        .iter()
496                        .nth(rng.gen_range(0..available_indices.len()))
497                        .unwrap();
498                    available_indices.remove(&i);
499                    let input = &available_inputs[i];
500                    let input_fee = self.fee_for_input(
501                        &input.output.address,
502                        &input.input,
503                        &input.output.amount,
504                    )?;
505                    self.inputs.add_regular_utxo(&input)?;
506                    input_total = input_total.checked_add(&input.output.amount)?;
507                    output_total = output_total.checked_add(&Value::new(&input_fee))?;
508                }
509            }
510            CoinSelectionStrategyCIP2::LargestFirstMultiAsset => {
511                // indices into {available_inputs} for inputs that contain {policy_id}:{asset_name}
512                let mut available_indices = (0..available_inputs.len()).collect::<Vec<usize>>();
513                // run largest-fist by each asset type
514                if let Some(ma) = output_total.multiasset.clone() {
515                    for (policy_id, assets) in ma.0.iter() {
516                        for (asset_name, _) in assets.0.iter() {
517                            self.cip2_largest_first_by(
518                                &available_inputs,
519                                &mut available_indices,
520                                &mut input_total,
521                                &mut output_total,
522                                |value| value.multiasset.as_ref()?.get(policy_id)?.get(asset_name),
523                            )?;
524                        }
525                    }
526                }
527                // add in remaining ADA
528                self.cip2_largest_first_by(
529                    &available_inputs,
530                    &mut available_indices,
531                    &mut input_total,
532                    &mut output_total,
533                    |value| Some(value.coin),
534                )?;
535            }
536            CoinSelectionStrategyCIP2::RandomImproveMultiAsset => {
537                use rand::Rng;
538                let mut rng = rand::thread_rng();
539                let mut available_indices =
540                    (0..available_inputs.len()).collect::<BTreeSet<usize>>();
541                // run random-improve by each asset type
542                if let Some(ma) = output_total.multiasset.clone() {
543                    for (policy_id, assets) in ma.0.iter() {
544                        for (asset_name, _) in assets.0.iter() {
545                            self.cip2_random_improve_by(
546                                &available_inputs,
547                                &mut available_indices,
548                                &mut input_total,
549                                &mut output_total,
550                                |value| value.multiasset.as_ref()?.get(policy_id)?.get(asset_name),
551                                &mut rng,
552                                false,
553                            )?;
554                        }
555                    }
556                }
557                // add in remaining ADA
558                self.cip2_random_improve_by(
559                    &available_inputs,
560                    &mut available_indices,
561                    &mut input_total,
562                    &mut output_total,
563                    |value| Some(value.coin),
564                    &mut rng,
565                    false,
566                )?;
567                // Phase 3: add extra inputs needed for fees (not covered by CIP-2)
568                // We do this at the end because this new inputs won't be associated with
569                // a specific output, so the improvement algorithm we do above does not apply here.
570                while input_total.coin < output_total.coin {
571                    if available_indices.is_empty() {
572                        return Err(JsError::from_str("UTxO Balance Insufficient[x]"));
573                    }
574                    let i = *available_indices
575                        .iter()
576                        .nth(rng.gen_range(0..available_indices.len()))
577                        .unwrap();
578                    available_indices.remove(&i);
579                    let input = &available_inputs[i];
580                    let input_fee = self.fee_for_input(
581                        &input.output.address,
582                        &input.input,
583                        &input.output.amount,
584                    )?;
585                    self.inputs.add_regular_utxo(&input)?;
586                    input_total = input_total.checked_add(&input.output.amount)?;
587                    output_total = output_total.checked_add(&Value::new(&input_fee))?;
588                }
589            }
590        }
591
592        Ok(())
593    }
594
595    fn cip2_largest_first_by<F>(
596        &mut self,
597        available_inputs: &Vec<&TransactionUnspentOutput>,
598        available_indices: &mut Vec<usize>,
599        input_total: &mut Value,
600        output_total: &mut Value,
601        by: F,
602    ) -> Result<(), JsError>
603    where
604        F: Fn(&Value) -> Option<BigNum>,
605    {
606        let mut relevant_indices = available_indices.clone();
607        relevant_indices.retain(|i| by(&available_inputs[*i].output.amount).is_some());
608        // ordered in ascending order by predicate {by}
609        relevant_indices
610            .sort_by_key(|i| by(&available_inputs[*i].output.amount).expect("filtered above"));
611
612        // iterate in decreasing order for predicate {by}
613        for i in relevant_indices.iter().rev() {
614            if by(input_total).unwrap_or(BigNum::zero())
615                >= by(output_total).expect("do not call on asset types that aren't in the output")
616            {
617                break;
618            }
619            let input = &available_inputs[*i];
620            // differing from CIP2, we include the needed fees in the targets instead of just output values
621            let input_fee =
622                self.fee_for_input(&input.output.address, &input.input, &input.output.amount)?;
623            self.inputs.add_regular_utxo(&input)?;
624            *input_total = input_total.checked_add(&input.output.amount)?;
625            *output_total = output_total.checked_add(&Value::new(&input_fee))?;
626            available_indices.swap_remove(available_indices.iter().position(|j| i == j).unwrap());
627        }
628
629        if by(input_total).unwrap_or(BigNum::zero())
630            < by(output_total).expect("do not call on asset types that aren't in the output")
631        {
632            return Err(JsError::from_str("UTxO Balance Insufficient"));
633        }
634
635        Ok(())
636    }
637
638    fn cip2_random_improve_by<F>(
639        &mut self,
640        available_inputs: &Vec<&TransactionUnspentOutput>,
641        available_indices: &mut BTreeSet<usize>,
642        input_total: &mut Value,
643        output_total: &mut Value,
644        by: F,
645        rng: &mut rand::rngs::ThreadRng,
646        pure_ada: bool,
647    ) -> Result<(), JsError>
648    where
649        F: Fn(&Value) -> Option<BigNum>,
650    {
651        use rand::Rng;
652        // Phase 1: Random Selection
653        let mut relevant_indices = available_indices
654            .iter()
655            .filter(|i| by(&available_inputs[**i].output.amount).is_some())
656            .cloned()
657            .collect::<Vec<usize>>();
658        let mut associated_indices: BTreeMap<TransactionOutput, Vec<usize>> = BTreeMap::new();
659        let mut outputs = self
660            .outputs
661            .0
662            .iter()
663            .filter(|output| by(&output.amount).is_some())
664            .cloned()
665            .collect::<Vec<TransactionOutput>>();
666        outputs.sort_by_key(|output| by(&output.amount).expect("filtered above"));
667        let mut available_coins = by(input_total).unwrap_or(BigNum::zero());
668        for output in outputs.iter().rev() {
669            // TODO: how should we adapt this to inputs being associated when running for other assets?
670            // if we do these two phases for each asset and don't take into account the other runs for other assets
671            // then we over-add (and potentially fail if we don't have plenty of inputs)
672            // On the other hand, the improvement phase it difficult to determine if a change is an improvement
673            // if we're trying to improve for multiple assets at a time without knowing how important each input is
674            // e.g. maybe we have lots of asset A but not much of B
675            // For now I will just have this be entirely separarte per-asset but we might want to in a later commit
676            // consider the improvements separately and have it take some kind of dot product / distance for assets
677            // during the improvement phase and have the improvement phase target multiple asset types at once.
678            // One issue with that is how to scale in between differnet assets. We could maybe normalize them by
679            // dividing each asset type by the sum of the required asset type in all outputs.
680            // Another possibility for adapting this to multiasstes is when associating an input x for asset type a
681            // we try and subtract all other assets b != a from the outputs we're trying to cover.
682            // It might make sense to diverge further and not consider it per-output and to instead just match against
683            // the sum of all outputs as one single value.
684            let mut added = available_coins.clone();
685            let needed = by(&output.amount).unwrap();
686            while added < needed {
687                if relevant_indices.is_empty() {
688                    return Err(JsError::from_str("UTxO Balance Insufficient"));
689                }
690                let random_index = rng.gen_range(0..relevant_indices.len());
691                let i = relevant_indices.swap_remove(random_index);
692                available_indices.remove(&i);
693                let input = &available_inputs[i];
694                added = added.checked_add(
695                    &by(&input.output.amount)
696                        .expect("do not call on asset types that aren't in the output"),
697                )?;
698                associated_indices
699                    .entry(output.clone())
700                    .or_default()
701                    .push(i);
702            }
703            available_coins = added.checked_sub(&needed)?;
704        }
705        if !relevant_indices.is_empty() && pure_ada {
706            // Phase 2: Improvement
707            for output in outputs.iter_mut() {
708                let associated = associated_indices.get_mut(output);
709                if let Some(associated) = associated {
710                    for i in associated.iter_mut() {
711                        let random_index = rng.gen_range(0..relevant_indices.len());
712                        let j: &mut usize = relevant_indices.get_mut(random_index).unwrap();
713                        let input = &available_inputs[*i];
714                        let new_input = &available_inputs[*j];
715                        let cur: u64 = (&by(&input.output.amount).unwrap_or(BigNum::zero())).into();
716                        let new: u64 = (&by(&new_input.output.amount).unwrap_or(BigNum::zero())).into();
717                        let min: u64 = (&by(&output.amount).unwrap_or(BigNum::zero())).into();
718                        let ideal = 2 * min;
719                        let max = 3 * min;
720                        let move_closer =
721                            (ideal as i128 - new as i128).abs() < (ideal as i128 - cur as i128).abs();
722                        let not_exceed_max = new < max;
723                        if move_closer && not_exceed_max {
724                            std::mem::swap(i, j);
725                            available_indices.insert(*i);
726                            available_indices.remove(j);
727                        }
728                    }
729                }
730            }
731        }
732
733        // after finalizing the improvement we need to actually add these results to the builder
734        for output in outputs.iter() {
735            if let Some(associated) = associated_indices.get(output) {
736                for i in associated.iter() {
737                    let input = &available_inputs[*i];
738                    let input_fee = self.fee_for_input(
739                        &input.output.address,
740                        &input.input,
741                        &input.output.amount,
742                    )?;
743                    self.inputs.add_regular_utxo(&input)?;
744                    *input_total = input_total.checked_add(&input.output.amount)?;
745                    *output_total = output_total.checked_add(&Value::new(&input_fee))?;
746                }
747            }
748        }
749
750        Ok(())
751    }
752
753    pub fn set_inputs(&mut self, inputs: &TxInputsBuilder) {
754        self.inputs = inputs.clone();
755    }
756
757    pub fn set_collateral(&mut self, collateral: &TxInputsBuilder) {
758        self.collateral = collateral.clone();
759    }
760
761    pub fn set_collateral_return(&mut self, collateral_return: &TransactionOutput) {
762        self.collateral_return = Some(collateral_return.clone());
763    }
764
765    pub fn remove_collateral_return(&mut self) {
766        self.collateral_return = None;
767    }
768
769    /// This function will set the collateral-return value and then auto-calculate and assign
770    /// the total collateral coin value. Will raise an error in case no collateral inputs are set
771    /// or in case the total collateral value will have any assets in it except coin.
772    pub fn set_collateral_return_and_total(
773        &mut self,
774        collateral_return: &TransactionOutput,
775    ) -> Result<(), JsError> {
776        let collateral = &self.collateral;
777        if collateral.len() == 0 {
778            return Err(JsError::from_str(
779                "Cannot calculate total collateral value when collateral inputs are missing",
780            ));
781        }
782        let col_input_value: Value = collateral.total_value()?;
783        let total_col: Value = col_input_value.checked_sub(&collateral_return.amount())?;
784        if total_col.multiasset.is_some() {
785            return Err(JsError::from_str(
786                "Total collateral value cannot contain assets!",
787            ));
788        }
789
790        let min_ada = min_ada_for_output(&collateral_return, &self.config.utxo_cost())?;
791        if min_ada > collateral_return.amount.coin {
792            return Err(JsError::from_str(&format!(
793                "Not enough coin to make return on the collateral value!\
794                 Increase amount of return coins. \
795                 Min ada for return {}, but was {}",
796                min_ada, collateral_return.amount.coin
797            )));
798        }
799
800        self.set_collateral_return(collateral_return);
801        self.total_collateral = Some(total_col.coin);
802        Ok(())
803    }
804
805    pub fn set_total_collateral(&mut self, total_collateral: &Coin) {
806        self.total_collateral = Some(total_collateral.clone());
807    }
808
809    pub fn remove_total_collateral(&mut self) {
810        self.total_collateral = None;
811    }
812
813    /// This function will set the total-collateral coin and then auto-calculate and assign
814    /// the collateral return value. Will raise an error in case no collateral inputs are set.
815    /// The specified address will be the received of the collateral return
816    pub fn set_total_collateral_and_return(
817        &mut self,
818        total_collateral: &Coin,
819        return_address: &Address,
820    ) -> Result<(), JsError> {
821        let collateral = &self.collateral;
822        if collateral.len() == 0 {
823            return Err(JsError::from_str(
824                "Cannot calculate collateral return when collateral inputs are missing",
825            ));
826        }
827        let col_input_value: Value = collateral.total_value()?;
828        let col_input_coin = col_input_value.coin;
829        if col_input_coin < *total_collateral {
830            return Err(JsError::from_str(
831                "Total collateral value cannot exceed the sum of collateral inputs",
832            ));
833        }
834
835        let col_return: Value = col_input_value.checked_sub(&Value::new(&total_collateral))?;
836        if col_return.multiasset.is_some() || col_return.coin > BigNum::zero() {
837            let return_output = TransactionOutput::new(return_address, &col_return);
838            let min_ada = min_ada_for_output(&return_output, &self.config.utxo_cost())?;
839            if min_ada > col_return.coin {
840                return Err(JsError::from_str(&format!(
841                    "Not enough coin to make return on the collateral value!\
842                 Decrease the total collateral value or add more collateral inputs. \
843                 Min ada for return {}, but was {}",
844                    min_ada, col_return.coin
845                )));
846            }
847            self.collateral_return = Some(return_output);
848        }
849        self.set_total_collateral(total_collateral);
850
851        Ok(())
852    }
853
854    pub fn add_reference_input(&mut self, reference_input: &TransactionInput) {
855        self.reference_inputs.insert(reference_input.clone(), 0);
856    }
857
858    pub fn add_script_reference_input(
859        &mut self,
860        reference_input: &TransactionInput,
861        script_size: usize,
862    ) {
863        self.reference_inputs
864            .insert(reference_input.clone(), script_size);
865    }
866
867    /// We have to know what kind of inputs these are to know what kind of mock witnesses to create since
868    /// 1) mock witnesses have different lengths depending on the type which changes the expecting fee
869    /// 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee
870    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
871    pub fn add_key_input(
872        &mut self,
873        hash: &Ed25519KeyHash,
874        input: &TransactionInput,
875        amount: &Value,
876    ) {
877        self.inputs.add_key_input(hash, input, amount);
878    }
879
880    /// This method will add the input to the builder and also register the required native script witness
881    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
882    pub fn add_native_script_input(
883        &mut self,
884        script: &NativeScript,
885        input: &TransactionInput,
886        amount: &Value,
887    ) {
888        self.inputs.add_native_script_input(
889            &NativeScriptSource::new(script),
890            input,
891            amount);
892    }
893
894    /// This method will add the input to the builder and also register the required plutus witness
895    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
896    pub fn add_plutus_script_input(
897        &mut self,
898        witness: &PlutusWitness,
899        input: &TransactionInput,
900        amount: &Value,
901    ) {
902        self.inputs.add_plutus_script_input(witness, input, amount);
903    }
904
905    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
906    pub fn add_bootstrap_input(
907        &mut self,
908        hash: &ByronAddress,
909        input: &TransactionInput,
910        amount: &Value,
911    ) {
912        self.inputs.add_bootstrap_input(hash, input, amount);
913    }
914
915    /// This function is replace for previous one add_input.
916    /// The functions adds a non script input, if it is a script input it returns an error.
917    /// To add script input you need to use add_native_script_input or add_plutus_script_input.
918    /// Also we recommend to use TxInputsBuilder and .set_inputs, because all add_*_input functions might be removed from transaction builder.
919    #[deprecated(since = "12.0.0", note = "Use `.set_inputs`")]
920    pub fn add_regular_input(
921        &mut self,
922        address: &Address,
923        input: &TransactionInput,
924        amount: &Value,
925    ) -> Result<(), JsError> {
926        self.inputs.add_regular_input(address, input, amount)
927    }
928
929    // This method should be used after outputs of the transaction is defined.
930    // It will attempt utxo selection initially then add change, if adding change fails
931    // then it will attempt to use up the rest of the available inputs, attempting to add change
932    // after every extra input.
933    pub fn add_inputs_from_and_change(
934        &mut self,
935        inputs: &TransactionUnspentOutputs,
936        strategy: CoinSelectionStrategyCIP2,
937        change_config: &ChangeConfig,
938    ) -> Result<bool, JsError> {
939        self.add_inputs_from(inputs, strategy)?;
940        if self.fee.is_some() {
941            return Err(JsError::from_str(
942                "Cannot calculate change if it was calculated before",
943            ))
944        }
945        let mut add_change_result = self
946            .add_change_if_needed_with_optional_script_and_datum(
947                &change_config.address,
948                change_config
949                    .plutus_data
950                    .clone()
951                    .map_or(None, |od| Some(od.0)),
952                change_config.script_ref.clone(),
953            );
954        match add_change_result {
955            Ok(v) => Ok(v),
956            Err(e) => {
957                let mut unused_inputs = TransactionUnspentOutputs::new();
958                for input in inputs.into_iter() {
959                    if self
960                        .inputs
961                        .inputs()
962                        .into_iter()
963                        .all(|used_input| input.input() != *used_input)
964                    {
965                        unused_inputs.add(input)
966                    }
967                }
968                unused_inputs.0.sort_by_key(|input| {
969                    input
970                        .clone()
971                        .output
972                        .amount
973                        .multiasset
974                        .map_or(0, |ma| ma.len())
975                });
976                unused_inputs.0.reverse();
977                while unused_inputs.0.len() > 0 {
978                    let last_input = unused_inputs.0.pop();
979                    match last_input {
980                        Some(input) => {
981                            self.inputs.add_regular_utxo(&input)?;
982                            add_change_result = self
983                                .add_change_if_needed_with_optional_script_and_datum(
984                                    &change_config.address,
985                                    change_config
986                                        .plutus_data
987                                        .clone()
988                                        .map_or(None, |od| Some(od.0)),
989                                    change_config.script_ref.clone(),
990                                );
991                            if let Ok(value) = add_change_result {
992                                return Ok(value);
993                            }
994                        }
995                        None => {
996                            return Err(JsError::from_str(
997                                "Unable to balance tx with available inputs",
998                            ))
999                        }
1000                    }
1001                }
1002                Err(e)
1003            }
1004        }
1005    }
1006
1007    // This method should be used after outputs of the transaction is defined.
1008    // It will attempt to fill the required values using the inputs given.
1009    // After which, it will attempt to set a collateral return output.
1010    pub fn add_inputs_from_and_change_with_collateral_return(
1011        &mut self,
1012        inputs: &TransactionUnspentOutputs,
1013        strategy: CoinSelectionStrategyCIP2,
1014        change_config: &ChangeConfig,
1015        collateral_percentage: &BigNum,
1016    ) -> Result<(), JsError> {
1017        let mut total_collateral = Value::zero();
1018        for collateral_input in self.collateral.iter() {
1019            total_collateral = total_collateral.checked_add(&collateral_input.amount)?;
1020        }
1021
1022        //set fake max total collateral and return
1023        self.set_total_collateral(&total_collateral.coin());
1024        self.set_collateral_return(&TransactionOutput::new(
1025            &change_config.address,
1026            &total_collateral,
1027        ));
1028
1029        let add_change_result = self.add_inputs_from_and_change(inputs, strategy, change_config);
1030
1031        self.remove_collateral_return();
1032        self.remove_total_collateral();
1033
1034        //check if adding inputs and change was successful
1035        add_change_result?;
1036
1037        let fee = self.get_fee_if_set().ok_or(JsError::from_str(
1038            "Cannot calculate collateral return if fee was not set",
1039        ))?;
1040
1041        let collateral_required = fee
1042            .checked_mul(&collateral_percentage)?
1043            .div_floor(&BigNum(100))
1044            .checked_add(&BigNum::one())?;
1045        let set_collateral_result =
1046            self.set_total_collateral_and_return(&collateral_required, &change_config.address);
1047
1048        if let Err(e) = set_collateral_result {
1049            self.remove_collateral_return();
1050            self.remove_total_collateral();
1051            return Err(e);
1052        }
1053
1054        Ok(())
1055    }
1056
1057    /// Returns a copy of the current script input witness scripts in the builder
1058    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
1059    pub fn get_native_input_scripts(&self) -> Option<NativeScripts> {
1060        self.inputs.get_native_input_scripts()
1061    }
1062
1063    /// Returns a copy of the current plutus input witness scripts in the builder.
1064    /// NOTE: each plutus witness will be cloned with a specific corresponding input index
1065    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
1066    pub fn get_plutus_input_scripts(&self) -> Option<PlutusWitnesses> {
1067        self.inputs.get_plutus_input_scripts()
1068    }
1069
1070    /// calculates how much the fee would increase if you added a given output
1071    pub fn fee_for_input(
1072        &self,
1073        address: &Address,
1074        input: &TransactionInput,
1075        amount: &Value,
1076    ) -> Result<Coin, JsError> {
1077        let mut self_copy = self.clone();
1078
1079        // we need some value for these for it to be a a valid transaction
1080        // but since we're only calculating the difference between the fee of two transactions
1081        // it doesn't matter what these are set as, since it cancels out
1082        self_copy.set_final_fee(BigNum::zero());
1083
1084        let fee_before = min_fee(&self_copy)?;
1085        let aligned_fee_before = self.fee_request.get_new_fee(fee_before);
1086
1087        self_copy.add_regular_input(&address, &input, &amount)?;
1088        let fee_after = min_fee(&self_copy)?;
1089        let aligned_fee_after = self.fee_request.get_new_fee(fee_after);
1090
1091        aligned_fee_after.checked_sub(&aligned_fee_before)
1092    }
1093
1094    /// Add explicit output via a TransactionOutput object
1095    pub fn add_output(&mut self, output: &TransactionOutput) -> Result<(), JsError> {
1096        let value_size = output.amount.to_bytes().len();
1097        if value_size > self.config.max_value_size as usize {
1098            return Err(JsError::from_str(&format!(
1099                "Maximum value size of {} exceeded. Found: {}",
1100                self.config.max_value_size, value_size
1101            )));
1102        }
1103        let min_ada = min_ada_for_output(&output, &self.config.utxo_cost())?;
1104        if output.amount().coin() < min_ada {
1105            Err(JsError::from_str(&format!(
1106                "Value {} less than the minimum UTXO value {}",
1107                output.amount().coin(),
1108                min_ada
1109            )))
1110        } else {
1111            self.outputs.add(output);
1112            Ok(())
1113        }
1114    }
1115
1116    /// calculates how much the fee would increase if you added a given output
1117    pub fn fee_for_output(&self, output: &TransactionOutput) -> Result<Coin, JsError> {
1118        let mut self_copy = self.clone();
1119
1120        // we need some value for these for it to be a a valid transaction
1121        // but since we're only calculating the different between the fee of two transactions
1122        // it doesn't matter what these are set as, since it cancels out
1123        self_copy.set_final_fee(BigNum::zero());
1124
1125        let fee_before = min_fee(&self_copy)?;
1126        let aligned_fee_before = self.fee_request.get_new_fee(fee_before);
1127
1128        self_copy.add_output(&output)?;
1129        let fee_after = min_fee(&self_copy)?;
1130        let aligned_fee_after = self.fee_request.get_new_fee(fee_after);
1131
1132        aligned_fee_after.checked_sub(&aligned_fee_before)
1133    }
1134
1135    ///Set exact fee for the transaction. If the real fee will be bigger then the set value, the transaction will not be created on .build_tx()
1136    pub fn set_fee(&mut self, fee: &Coin) {
1137        self.fee_request = TxBuilderFee::Exactly(fee.clone());
1138    }
1139
1140    ///Set minimal fee for the transaction. If the real fee will be bigger then the set value, the transaction will be created with the real fee.
1141    pub fn set_min_fee(&mut self, fee: &Coin) {
1142        self.fee_request = TxBuilderFee::NotLess(fee.clone());
1143    }
1144
1145    fn set_final_fee(&mut self, fee: Coin) {
1146        self.fee = match &self.fee_request {
1147            TxBuilderFee::Exactly(exact_fee) => Some(exact_fee.clone()),
1148            TxBuilderFee::NotLess(not_less) => {
1149                if &fee >= not_less
1150                { Some(fee) } else { Some(not_less.clone()) }
1151            },
1152            TxBuilderFee::Unspecified => Some(fee),
1153        }
1154    }
1155
1156    /// !!! DEPRECATED !!!
1157    /// Set ttl value.
1158    #[deprecated(
1159        since = "10.1.0",
1160        note = "Underlying value capacity of ttl (BigNum u64) bigger then Slot32. Use set_ttl_bignum instead."
1161    )]
1162    pub fn set_ttl(&mut self, ttl: Slot32) {
1163        self.ttl = Some(ttl.into())
1164    }
1165
1166    pub fn set_ttl_bignum(&mut self, ttl: &SlotBigNum) {
1167        self.ttl = Some(ttl.clone())
1168    }
1169
1170    pub fn remove_ttl(&mut self) {
1171        self.ttl = None;
1172    }
1173
1174    /// !!! DEPRECATED !!!
1175    /// Uses outdated slot number format.
1176    #[deprecated(
1177        since = "10.1.0",
1178        note = "Underlying value capacity of validity_start_interval (BigNum u64) bigger then Slot32. Use set_validity_start_interval_bignum instead."
1179    )]
1180    pub fn set_validity_start_interval(&mut self, validity_start_interval: Slot32) {
1181        self.validity_start_interval = Some(validity_start_interval.into())
1182    }
1183
1184    pub fn set_validity_start_interval_bignum(&mut self, validity_start_interval: SlotBigNum) {
1185        self.validity_start_interval = Some(validity_start_interval.clone())
1186    }
1187
1188    pub fn remove_validity_start_interval(&mut self) {
1189        self.validity_start_interval = None;
1190    }
1191
1192    /// !!! DEPRECATED !!!
1193    /// Can emit error if add a cert with script credential.
1194    /// Use set_certs_builder instead.
1195    #[deprecated(
1196        since = "11.4.1",
1197        note = "Can emit an error if you add a cert with script credential. Use set_certs_builder instead."
1198    )]
1199    pub fn set_certs(&mut self, certs: &Certificates) -> Result<(), JsError> {
1200        let mut builder = CertificatesBuilder::new();
1201        for cert in &certs.certs {
1202            builder.add(cert)?;
1203        }
1204
1205        self.certs = Some(builder);
1206
1207        Ok(())
1208    }
1209
1210    pub fn remove_certs(&mut self) {
1211        self.certs = None;
1212    }
1213
1214    pub fn set_certs_builder(&mut self, certs: &CertificatesBuilder) {
1215        self.certs = Some(certs.clone());
1216    }
1217
1218    /// !!! DEPRECATED !!!
1219    /// Can emit error if add a withdrawal with script credential.
1220    /// Use set_withdrawals_builder instead.
1221    #[deprecated(
1222        since = "11.4.1",
1223        note = "Can emit an error if you add a withdrawal with script credential. Use set_withdrawals_builder instead."
1224    )]
1225    pub fn set_withdrawals(&mut self, withdrawals: &Withdrawals) -> Result<(), JsError> {
1226        let mut withdrawals_builder = WithdrawalsBuilder::new();
1227        for (withdrawal, coin) in &withdrawals.0 {
1228            withdrawals_builder.add(&withdrawal, &coin)?;
1229        }
1230
1231        self.withdrawals = Some(withdrawals_builder);
1232
1233        Ok(())
1234    }
1235
1236    pub fn set_withdrawals_builder(&mut self, withdrawals: &WithdrawalsBuilder) {
1237        self.withdrawals = Some(withdrawals.clone());
1238    }
1239
1240    pub fn set_voting_builder(&mut self, voting_builder: &VotingBuilder) {
1241        self.voting_procedures = Some(voting_builder.clone());
1242    }
1243
1244    pub fn set_voting_proposal_builder(&mut self, voting_proposal_builder: &VotingProposalBuilder) {
1245        self.voting_proposals = Some(voting_proposal_builder.clone());
1246    }
1247
1248    pub fn remove_withdrawals(&mut self) {
1249        self.withdrawals = None;
1250    }
1251
1252    pub fn get_auxiliary_data(&self) -> Option<AuxiliaryData> {
1253        self.auxiliary_data.clone()
1254    }
1255
1256    /// Set explicit auxiliary data via an AuxiliaryData object
1257    /// It might contain some metadata plus native or Plutus scripts
1258    pub fn set_auxiliary_data(&mut self, auxiliary_data: &AuxiliaryData) {
1259        self.auxiliary_data = Some(auxiliary_data.clone())
1260    }
1261
1262    pub fn remove_auxiliary_data(&mut self) {
1263        self.auxiliary_data = None;
1264    }
1265
1266    /// Set metadata using a GeneralTransactionMetadata object
1267    /// It will be set to the existing or new auxiliary data in this builder
1268    pub fn set_metadata(&mut self, metadata: &GeneralTransactionMetadata) {
1269        let mut aux = self
1270            .auxiliary_data
1271            .as_ref()
1272            .cloned()
1273            .unwrap_or(AuxiliaryData::new());
1274        aux.set_metadata(metadata);
1275        self.set_auxiliary_data(&aux);
1276    }
1277
1278    /// Add a single metadatum using TransactionMetadatumLabel and TransactionMetadatum objects
1279    /// It will be securely added to existing or new metadata in this builder
1280    pub fn add_metadatum(&mut self, key: &TransactionMetadatumLabel, val: &TransactionMetadatum) {
1281        let mut metadata = self
1282            .auxiliary_data
1283            .as_ref()
1284            .map(|aux| aux.metadata().as_ref().cloned())
1285            .unwrap_or(None)
1286            .unwrap_or(GeneralTransactionMetadata::new());
1287        metadata.insert(key, val);
1288        self.set_metadata(&metadata);
1289    }
1290
1291    /// Add a single JSON metadatum using a TransactionMetadatumLabel and a String
1292    /// It will be securely added to existing or new metadata in this builder
1293    pub fn add_json_metadatum(
1294        &mut self,
1295        key: &TransactionMetadatumLabel,
1296        val: String,
1297    ) -> Result<(), JsError> {
1298        self.add_json_metadatum_with_schema(key, val, MetadataJsonSchema::NoConversions)
1299    }
1300
1301    /// Add a single JSON metadatum using a TransactionMetadatumLabel, a String, and a MetadataJsonSchema object
1302    /// It will be securely added to existing or new metadata in this builder
1303    pub fn add_json_metadatum_with_schema(
1304        &mut self,
1305        key: &TransactionMetadatumLabel,
1306        val: String,
1307        schema: MetadataJsonSchema,
1308    ) -> Result<(), JsError> {
1309        let metadatum = encode_json_str_to_metadatum(val, schema)?;
1310        self.add_metadatum(key, &metadatum);
1311        Ok(())
1312    }
1313
1314    pub fn set_mint_builder(&mut self, mint_builder: &MintBuilder) {
1315        self.mint = Some(mint_builder.clone());
1316    }
1317
1318    pub fn remove_mint_builder(&mut self) {
1319        self.mint = None;
1320    }
1321
1322    pub fn get_mint_builder(&self) -> Option<MintBuilder> {
1323        self.mint.clone()
1324    }
1325
1326    /// !!! DEPRECATED !!!
1327    /// Mints are defining by MintBuilder now.
1328    /// Use `.set_mint_builder()` and `MintBuilder` instead.
1329    #[deprecated(
1330        since = "11.2.0",
1331        note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
1332    )]
1333    /// Set explicit Mint object and the required witnesses to this builder
1334    /// it will replace any previously existing mint and mint scripts
1335    /// NOTE! Error will be returned in case a mint policy does not have a matching script
1336    pub fn set_mint(&mut self, mint: &Mint, mint_scripts: &NativeScripts) -> Result<(), JsError> {
1337        assert_required_mint_scripts(mint, Some(mint_scripts))?;
1338        let mut scripts_policies = HashMap::new();
1339        for scipt in mint_scripts {
1340            scripts_policies.insert(scipt.hash(), scipt.clone());
1341        }
1342
1343        let mut mint_builder = MintBuilder::new();
1344
1345        for (policy_id, asset_map) in &mint.0 {
1346            for (asset_name, amount) in &asset_map.0 {
1347                if let Some(script) = scripts_policies.get(policy_id) {
1348                    let native_script_source = NativeScriptSource::new(script);
1349                    let mint_witness = MintWitness::new_native_script(&native_script_source);
1350                    mint_builder.set_asset(&mint_witness, asset_name, amount)?;
1351                } else {
1352                    return Err(JsError::from_str(
1353                        "Mint policy does not have a matching script",
1354                    ));
1355                }
1356            }
1357        }
1358        self.mint = Some(mint_builder);
1359        Ok(())
1360    }
1361
1362    /// !!! DEPRECATED !!!
1363    /// Mints are defining by MintBuilder now.
1364    /// Use `.get_mint_builder()` and `.build()` instead.
1365    #[deprecated(
1366        since = "11.2.0",
1367        note = "Mints are defining by MintBuilder now. Use `.get_mint_builder()` and `.build()` instead."
1368    )]
1369    /// Returns a copy of the current mint state in the builder
1370    pub fn get_mint(&self) -> Option<Mint> {
1371        match &self.mint {
1372            Some(mint) => Some(mint.build().expect("MintBuilder is invalid")),
1373            None => None,
1374        }
1375    }
1376
1377    /// Returns a copy of the current mint witness scripts in the builder
1378    pub fn get_mint_scripts(&self) -> Option<NativeScripts> {
1379        match &self.mint {
1380            Some(mint) => Some(mint.get_native_scripts()),
1381            None => None,
1382        }
1383    }
1384
1385    /// !!! DEPRECATED !!!
1386    /// Mints are defining by MintBuilder now.
1387    /// Use `.set_mint_builder()` and `MintBuilder` instead.
1388    #[deprecated(
1389        since = "11.2.0",
1390        note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
1391    )]
1392    /// Add a mint entry to this builder using a PolicyID and MintAssets object
1393    /// It will be securely added to existing or new Mint in this builder
1394    /// It will replace any existing mint assets with the same PolicyID
1395    pub fn set_mint_asset(&mut self, policy_script: &NativeScript, mint_assets: &MintAssets) -> Result<(), JsError> {
1396        let native_script_source = NativeScriptSource::new(policy_script);
1397        let mint_witness = MintWitness::new_native_script(&native_script_source);
1398        if let Some(mint) = &mut self.mint {
1399            for (asset, amount) in mint_assets.0.iter() {
1400                mint.set_asset(&mint_witness, asset, amount)?;
1401            }
1402        } else {
1403            let mut mint = MintBuilder::new();
1404            for (asset, amount) in mint_assets.0.iter() {
1405                mint.set_asset(&mint_witness, asset, amount)?;
1406            }
1407            self.mint = Some(mint);
1408        }
1409        Ok(())
1410    }
1411
1412    /// !!! DEPRECATED !!!
1413    /// Mints are defining by MintBuilder now.
1414    /// Use `.set_mint_builder()` and `MintBuilder` instead.
1415    #[deprecated(
1416        since = "11.2.0",
1417        note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
1418    )]
1419    /// Add a mint entry to this builder using a PolicyID, AssetName, and Int object for amount
1420    /// It will be securely added to existing or new Mint in this builder
1421    /// It will replace any previous existing amount same PolicyID and AssetName
1422    pub fn add_mint_asset(
1423        &mut self,
1424        policy_script: &NativeScript,
1425        asset_name: &AssetName,
1426        amount: &Int,
1427    ) -> Result<(), JsError> {
1428        let native_script_source = NativeScriptSource::new(policy_script);
1429        let mint_witness = MintWitness::new_native_script(&native_script_source);
1430        if let Some(mint) = &mut self.mint {
1431            mint.add_asset(&mint_witness, asset_name, &amount)?;
1432        } else {
1433            let mut mint = MintBuilder::new();
1434            mint.add_asset(&mint_witness, asset_name, &amount)?;
1435            self.mint = Some(mint);
1436        }
1437        Ok(())
1438    }
1439
1440    /// Add a mint entry together with an output to this builder
1441    /// Using a PolicyID, AssetName, Int for amount, Address, and Coin (BigNum) objects
1442    /// The asset will be securely added to existing or new Mint in this builder
1443    /// A new output will be added with the specified Address, the Coin value, and the minted asset
1444    pub fn add_mint_asset_and_output(
1445        &mut self,
1446        policy_script: &NativeScript,
1447        asset_name: &AssetName,
1448        amount: &Int,
1449        output_builder: &TransactionOutputAmountBuilder,
1450        output_coin: &Coin,
1451    ) -> Result<(), JsError> {
1452        if !amount.is_positive() {
1453            return Err(JsError::from_str("Output value must be positive!"));
1454        }
1455        let policy_id: PolicyID = policy_script.hash();
1456        self.add_mint_asset(policy_script, asset_name, amount)?;
1457        let multiasset =
1458            Mint::new_from_entry(&policy_id, &MintAssets::new_from_entry(asset_name, amount)?)
1459                .as_positive_multiasset();
1460
1461        self.add_output(
1462            &output_builder
1463                .with_coin_and_asset(&output_coin, &multiasset)
1464                .build()?,
1465        )
1466    }
1467
1468    /// Add a mint entry together with an output to this builder
1469    /// Using a PolicyID, AssetName, Int for amount, and Address objects
1470    /// The asset will be securely added to existing or new Mint in this builder
1471    /// A new output will be added with the specified Address and the minted asset
1472    /// The output will be set to contain the minimum required amount of Coin
1473    pub fn add_mint_asset_and_output_min_required_coin(
1474        &mut self,
1475        policy_script: &NativeScript,
1476        asset_name: &AssetName,
1477        amount: &Int,
1478        output_builder: &TransactionOutputAmountBuilder,
1479    ) -> Result<(), JsError> {
1480        if !amount.is_positive() {
1481            return Err(JsError::from_str("Output value must be positive!"));
1482        }
1483        let policy_id: PolicyID = policy_script.hash();
1484        self.add_mint_asset(policy_script, asset_name, amount)?;
1485        let multiasset =
1486            Mint::new_from_entry(&policy_id, &MintAssets::new_from_entry(asset_name, amount)?)
1487                .as_positive_multiasset();
1488
1489        self.add_output(
1490            &output_builder
1491                .with_asset_and_min_required_coin_by_utxo_cost(
1492                    &multiasset,
1493                    &self.config.utxo_cost(),
1494                )?
1495                .build()?,
1496        )
1497    }
1498
1499    pub fn add_extra_witness_datum(&mut self, datum: &PlutusData) {
1500        if let Some(extra_datums) = &mut self.extra_datums {
1501            extra_datums.add(datum);
1502        } else {
1503            let mut extra_datums = PlutusList::new();
1504            extra_datums.add(datum);
1505            self.extra_datums = Some(extra_datums);
1506        }
1507    }
1508
1509    pub fn get_extra_witness_datums(&self) -> Option<PlutusList> {
1510        self.extra_datums.clone()
1511    }
1512
1513    pub fn set_donation(&mut self, donation: &Coin) {
1514        self.donation = Some(donation.clone());
1515    }
1516
1517    pub fn get_donation(&self) -> Option<Coin> {
1518        self.donation.clone()
1519    }
1520
1521    pub fn set_current_treasury_value(
1522        &mut self,
1523        current_treasury_value: &Coin,
1524    ) -> Result<(), JsError> {
1525        if current_treasury_value == &Coin::zero() {
1526            return Err(JsError::from_str("Current treasury value cannot be zero!"));
1527        }
1528        self.current_treasury_value = Some(current_treasury_value.clone());
1529        Ok(())
1530    }
1531
1532    pub fn get_current_treasury_value(&self) -> Option<Coin> {
1533        self.current_treasury_value.clone()
1534    }
1535
1536    pub fn new(cfg: &TransactionBuilderConfig) -> Self {
1537        Self {
1538            config: cfg.clone(),
1539            inputs: TxInputsBuilder::new(),
1540            collateral: TxInputsBuilder::new(),
1541            outputs: TransactionOutputs::new(),
1542            fee_request: TxBuilderFee::Unspecified,
1543            fee: None,
1544            ttl: None,
1545            certs: None,
1546            withdrawals: None,
1547            auxiliary_data: None,
1548            validity_start_interval: None,
1549            mint: None,
1550            script_data_hash: None,
1551            required_signers: Ed25519KeyHashes::new(),
1552            collateral_return: None,
1553            total_collateral: None,
1554            reference_inputs: HashMap::new(),
1555            extra_datums: None,
1556            voting_procedures: None,
1557            voting_proposals: None,
1558            donation: None,
1559            current_treasury_value: None,
1560        }
1561    }
1562
1563    pub fn get_reference_inputs(&self) -> TransactionInputs {
1564        let mut inputs: HashSet<TransactionInput> = HashSet::new();
1565
1566        let mut add_ref_inputs_set = |ref_inputs: TransactionInputs| {
1567            for input in &ref_inputs {
1568                if !self.inputs.has_input(&input) {
1569                    inputs.insert(input.clone());
1570                }
1571            }
1572        };
1573
1574        add_ref_inputs_set(self.inputs.get_ref_inputs());
1575
1576        if let Some(mint) = &self.mint {
1577            add_ref_inputs_set(mint.get_ref_inputs());
1578        }
1579
1580        if let Some(withdrawals) = &self.withdrawals {
1581            add_ref_inputs_set(withdrawals.get_ref_inputs());
1582        }
1583
1584        if let Some(certs) = &self.certs {
1585            add_ref_inputs_set(certs.get_ref_inputs());
1586        }
1587
1588        if let Some(voting_procedures) = &self.voting_procedures {
1589            add_ref_inputs_set(voting_procedures.get_ref_inputs());
1590        }
1591
1592        if let Some(voting_proposals) = &self.voting_proposals {
1593            add_ref_inputs_set(voting_proposals.get_ref_inputs());
1594        }
1595
1596        if self.config.deduplicate_explicit_ref_inputs_with_regular_inputs {
1597            add_ref_inputs_set(TransactionInputs::from_vec(
1598                self.reference_inputs.keys().cloned().collect())
1599            )
1600        } else {
1601            for input in self.reference_inputs.keys().cloned() {
1602                inputs.insert(input);
1603            }
1604        }
1605
1606        let vec_inputs = inputs.into_iter().collect();
1607        TransactionInputs::from_vec(vec_inputs)
1608    }
1609
1610    fn validate_inputs_intersection(&self) -> Result<(), JsError> {
1611        let ref_inputs = self.get_reference_inputs();
1612        for input in &ref_inputs {
1613            if self.inputs.has_input(input) {
1614                return Err(JsError::from_str(&format!(
1615                    "The reference input {:?} is also present in the regular transaction inputs set. \
1616    It's not allowed to have the same inputs in both the transaction's inputs set and the reference inputs set. \
1617    You can use the `deduplicate_explicit_ref_inputs_with_regular_inputs` parameter in the `TransactionConfigBuilder` \
1618    to enforce the removal of duplicate reference inputs."
1619                    , input)));
1620            }
1621        }
1622        Ok(())
1623    }
1624
1625    fn validate_fee(&self) -> Result<(), JsError> {
1626        if let Some(fee) = &self.get_fee_if_set() {
1627            let min_fee = min_fee(&self)?;
1628            if fee < &min_fee {
1629                Err(JsError::from_str(&format!(
1630                    "Fee is less than the minimum fee. Min fee: {}, Fee: {}",
1631                    min_fee, fee
1632                )))
1633            } else {
1634                Ok(())
1635            }
1636        } else {
1637            Err(JsError::from_str("Fee is not set"))
1638        }
1639    }
1640
1641    fn validate_balance(&self) -> Result<(), JsError> {
1642        let total_input = self.get_total_input()?;
1643        let mut total_output = self.get_total_output()?;
1644        let fee = self.get_fee_if_set();
1645        if let Some(fee) = fee {
1646            let out_coin = total_output.coin().checked_add(&fee)?;
1647            total_output.set_coin(&out_coin);
1648        }
1649        if total_input != total_output {
1650            Err(JsError::from_str(&format!(
1651                "Total input and total output are not equal. Total input: {}, Total output: {}",
1652                total_input.to_json()?, total_output.to_json()?
1653            )))
1654        } else {
1655            Ok(())
1656        }
1657    }
1658
1659    pub(crate) fn get_total_ref_scripts_size(&self) -> Result<usize, JsError> {
1660        let mut sizes_map = HashMap::new();
1661        fn add_to_map<'a>(
1662            item: (&'a TransactionInput, usize),
1663            sizes_map: &mut HashMap<&'a TransactionInput, usize>,
1664        ) -> Result<(), JsError> {
1665            if sizes_map.entry(item.0).or_insert(item.1) != &item.1 {
1666                Err(JsError::from_str(&format!(
1667                    "Different script sizes for the same ref input {}",
1668                    item.0
1669                )))
1670            } else {
1671                Ok(())
1672            }
1673        }
1674
1675        //inputs with an inlined scripts
1676        for item in self.inputs.get_inputs_with_ref_script_size() {
1677            add_to_map(item, &mut sizes_map)?
1678        }
1679
1680        //inputs with a ref script witness
1681        for item in self.inputs.get_script_ref_inputs_with_size() {
1682            add_to_map(item, &mut sizes_map)?
1683        }
1684
1685        for (tx_in, size) in &self.reference_inputs {
1686            add_to_map((tx_in, *size), &mut sizes_map)?
1687        }
1688
1689        if let Some(mint) = &self.mint {
1690            for item in mint.get_script_ref_inputs_with_size() {
1691                add_to_map(item, &mut sizes_map)?
1692            }
1693        }
1694
1695        if let Some(withdrawals) = &self.withdrawals {
1696            for item in withdrawals.get_script_ref_inputs_with_size() {
1697                add_to_map(item, &mut sizes_map)?
1698            }
1699        }
1700
1701        if let Some(certs) = &self.certs {
1702            for item in certs.get_script_ref_inputs_with_size() {
1703                add_to_map(item, &mut sizes_map)?
1704            }
1705        }
1706
1707        if let Some(voting_procedures) = &self.voting_procedures {
1708            for item in voting_procedures.get_script_ref_inputs_with_size() {
1709                add_to_map(item, &mut sizes_map)?
1710            }
1711        }
1712
1713        if let Some(voting_proposals) = &self.voting_proposals {
1714            for item in voting_proposals.get_script_ref_inputs_with_size() {
1715                add_to_map(item, &mut sizes_map)?
1716            }
1717        }
1718
1719        Ok(sizes_map.values().sum())
1720    }
1721
1722    /// does not include refunds or withdrawals
1723    pub fn get_explicit_input(&self) -> Result<Value, JsError> {
1724        self.inputs
1725            .iter()
1726            .try_fold(Value::zero(), |acc, ref tx_builder_input| {
1727                acc.checked_add(&tx_builder_input.amount)
1728            })
1729    }
1730
1731    /// withdrawals and refunds
1732    pub fn get_implicit_input(&self) -> Result<Value, JsError> {
1733        let mut implicit_input = Value::zero();
1734        if let Some(withdrawals) = &self.withdrawals {
1735            implicit_input = implicit_input.checked_add(&withdrawals.get_total_withdrawals()?)?;
1736        }
1737        if let Some(refunds) = &self.certs {
1738            implicit_input = implicit_input.checked_add(
1739                &refunds
1740                    .get_certificates_refund(&self.config.pool_deposit, &self.config.key_deposit)?,
1741            )?;
1742        }
1743
1744        Ok(implicit_input)
1745    }
1746
1747    /// Returns mint as tuple of (mint_value, burn_value) or two zero values
1748    fn get_mint_as_values(&self) -> (Value, Value) {
1749        self.mint
1750            .as_ref()
1751            .map(|m| {
1752                let mint = m.build_unchecked();
1753                (
1754                    Value::new_from_assets(&mint.as_positive_multiasset()),
1755                    Value::new_from_assets(&mint.as_negative_multiasset()),
1756                )
1757            })
1758            .unwrap_or((Value::zero(), Value::zero()))
1759    }
1760
1761    /// Return explicit input plus implicit input plus mint
1762    pub fn get_total_input(&self) -> Result<Value, JsError> {
1763        let (mint_value, _) = self.get_mint_as_values();
1764        self.get_explicit_input()?
1765            .checked_add(&self.get_implicit_input()?)?
1766            .checked_add(&mint_value)
1767    }
1768
1769    /// Return explicit output plus deposit plus burn
1770    pub fn get_total_output(&self) -> Result<Value, JsError> {
1771        let (_, burn_value) = self.get_mint_as_values();
1772        let mut total = self
1773            .get_explicit_output()?
1774            .checked_add(&Value::new(&self.get_deposit()?))?
1775            .checked_add(&burn_value)?;
1776        if let Some(donation) = &self.donation {
1777            total = total.checked_add(&Value::new(donation))?;
1778        }
1779        Ok(total)
1780    }
1781
1782    /// does not include fee
1783    pub fn get_explicit_output(&self) -> Result<Value, JsError> {
1784        self.outputs
1785            .0
1786            .iter()
1787            .try_fold(Value::new(&BigNum::zero()), |acc, ref output| {
1788                acc.checked_add(&output.amount())
1789            })
1790    }
1791
1792    pub fn get_deposit(&self) -> Result<Coin, JsError> {
1793        let mut total_deposit = Coin::zero();
1794        if let Some(certs) = &self.certs {
1795            total_deposit =
1796                total_deposit.checked_add(&certs.get_certificates_deposit(
1797                    &self.config.pool_deposit,
1798                    &self.config.key_deposit,
1799                )?)?;
1800        }
1801
1802        if let Some(voting_proposal_builder) = &self.voting_proposals {
1803            total_deposit =
1804                total_deposit.checked_add(&voting_proposal_builder.get_total_deposit()?)?;
1805        }
1806
1807        Ok(total_deposit)
1808    }
1809
1810    pub fn get_fee_if_set(&self) -> Option<Coin> {
1811        if let Some(fee) = &self.fee {
1812           return Some(fee.clone())
1813        };
1814
1815        match self.fee_request {
1816            TxBuilderFee::Exactly(fee) => Some(fee),
1817            TxBuilderFee::NotLess(fee) => Some(fee),
1818            TxBuilderFee::Unspecified => None
1819        }
1820    }
1821
1822    /// Warning: this function will mutate the /fee/ field
1823    /// Make sure to call this function last after setting all other tx-body properties
1824    /// Editing inputs, outputs, mint, etc. after change been calculated
1825    /// might cause a mismatch in calculated fee versus the required fee
1826    pub fn add_change_if_needed(&mut self, address: &Address) -> Result<bool, JsError> {
1827        self.add_change_if_needed_with_optional_script_and_datum(address, None, None)
1828    }
1829
1830    pub fn add_change_if_needed_with_datum(
1831        &mut self,
1832        address: &Address,
1833        plutus_data: &OutputDatum,
1834    ) -> Result<bool, JsError> {
1835        self.add_change_if_needed_with_optional_script_and_datum(
1836            address,
1837            Some(plutus_data.0.clone()),
1838            None,
1839        )
1840    }
1841
1842    fn add_change_if_needed_with_optional_script_and_datum(
1843        &mut self,
1844        address: &Address,
1845        plutus_data: Option<DataOption>,
1846        script_ref: Option<ScriptRef>,
1847    ) -> Result<bool, JsError> {
1848        let fee = match &self.fee {
1849            None => self.min_fee(),
1850            // generating the change output involves changing the fee
1851            Some(_x) => {
1852                return Err(JsError::from_str(
1853                    "Cannot calculate change if fee was explicitly specified",
1854                ))
1855            }
1856        }?;
1857
1858        let input_total = self.get_total_input()?;
1859        let output_total = self.get_total_output()?;
1860
1861        let shortage = get_input_shortage(&input_total, &output_total, &fee)?;
1862        if let Some(shortage) = shortage {
1863            return Err(JsError::from_str(&format!(
1864                "Insufficient input in transaction. {}",
1865                shortage
1866            )));
1867        }
1868
1869        use std::cmp::Ordering;
1870        match &input_total.partial_cmp(&output_total.checked_add(&Value::new(&fee))?) {
1871            Some(Ordering::Equal) => {
1872                // recall: min_fee assumed the fee was the maximum possible so we definitely have enough input to cover whatever fee it ends up being
1873                self.set_final_fee(input_total.checked_sub(&output_total)?.coin());
1874                Ok(false)
1875            }
1876            Some(Ordering::Less) => Err(JsError::from_str("Insufficient input in transaction")),
1877            Some(Ordering::Greater) => {
1878                fn has_assets(ma: Option<MultiAsset>) -> bool {
1879                    ma.map(|assets| assets.len() > 0).unwrap_or(false)
1880                }
1881                let change_estimator = input_total.checked_sub(&output_total)?;
1882                if has_assets(change_estimator.multiasset()) {
1883                    fn will_adding_asset_make_output_overflow(
1884                        output: &TransactionOutput,
1885                        current_assets: &Assets,
1886                        asset_to_add: (PolicyID, AssetName, BigNum),
1887                        max_value_size: u32,
1888                        data_cost: &DataCost,
1889                    ) -> Result<bool, JsError> {
1890                        let (policy, asset_name, value) = asset_to_add;
1891                        let mut current_assets_clone = current_assets.clone();
1892                        current_assets_clone.insert(&asset_name, &value);
1893                        let mut amount_clone = output.amount.clone();
1894                        let mut val = Value::new(&Coin::zero());
1895                        let mut ma = MultiAsset::new();
1896
1897                        ma.insert(&policy, &current_assets_clone);
1898                        val.set_multiasset(&ma);
1899                        amount_clone = amount_clone.checked_add(&val)?;
1900
1901                        // calculate minADA for more precise max value size
1902                        let mut calc = MinOutputAdaCalculator::new_empty(data_cost)?;
1903                        calc.set_amount(&val);
1904                        let min_ada = calc.calculate_ada()?;
1905                        amount_clone.set_coin(&min_ada);
1906
1907                        Ok(amount_clone.to_bytes().len() > max_value_size as usize)
1908                    }
1909                    fn pack_nfts_for_change(
1910                        max_value_size: u32,
1911                        data_cost: &DataCost,
1912                        change_address: &Address,
1913                        change_estimator: &Value,
1914                        plutus_data: &Option<DataOption>,
1915                        script_ref: &Option<ScriptRef>,
1916                    ) -> Result<Vec<MultiAsset>, JsError> {
1917                        // we insert the entire available ADA temporarily here since that could potentially impact the size
1918                        // as it could be 1, 2 3 or 4 bytes for Coin.
1919                        let mut change_assets: Vec<MultiAsset> = Vec::new();
1920
1921                        let mut base_coin = Value::new(&change_estimator.coin());
1922                        base_coin.set_multiasset(&MultiAsset::new());
1923                        let mut output = TransactionOutput {
1924                            address: change_address.clone(),
1925                            amount: base_coin.clone(),
1926                            plutus_data: plutus_data.clone(),
1927                            script_ref: script_ref.clone(),
1928                            serialization_format: None,
1929                        };
1930                        // If this becomes slow on large TXs we can optimize it like the following
1931                        // to avoid cloning + reserializing the entire output.
1932                        // This would probably be more relevant if we use a smarter packing algorithm
1933                        // which might need to compare more size differences than greedy
1934                        //let mut bytes_used = output.to_bytes().len();
1935
1936                        // a greedy packing is done here to avoid an exponential bin-packing
1937                        // which in most cases likely shouldn't be the difference between
1938                        // having an extra change output or not unless there are gigantic
1939                        // differences in NFT policy sizes
1940                        for (policy, assets) in change_estimator.multiasset().unwrap().0.iter() {
1941                            // for simplicity we also don't split assets within a single policy since
1942                            // you would need to have a very high amoun of assets (which add 1-36 bytes each)
1943                            // in a single policy to make a difference. In the future if this becomes an issue
1944                            // we can change that here.
1945
1946                            // this is the other part of the optimization but we need to take into account
1947                            // the difference between CBOR encoding which can change which happens in two places:
1948                            // a) length within assets of one policy id
1949                            // b) length of the entire multiasset
1950                            // so for simplicity we will just do it the safe, naive way unless
1951                            // performance becomes an issue.
1952                            //let extra_bytes = policy.to_bytes().len() + assets.to_bytes().len() + 2 + cbor_len_diff;
1953                            //if bytes_used + extra_bytes <= max_value_size as usize {
1954                            let mut old_amount = output.amount.clone();
1955                            let mut val = Value::new(&Coin::zero());
1956                            let mut next_nft = MultiAsset::new();
1957
1958                            let asset_names = assets.keys();
1959                            let mut rebuilt_assets = Assets::new();
1960                            for n in 0..asset_names.len() {
1961                                let asset_name = asset_names.get(n);
1962                                let value = assets.get(&asset_name).unwrap();
1963
1964                                if will_adding_asset_make_output_overflow(
1965                                    &output,
1966                                    &rebuilt_assets,
1967                                    (policy.clone(), asset_name.clone(), value),
1968                                    max_value_size,
1969                                    data_cost,
1970                                )? {
1971                                    // if we got here, this means we will run into a overflow error,
1972                                    // so we want to split into multiple outputs, for that we...
1973
1974                                    // 1. insert the current assets as they are, as this won't overflow
1975                                    next_nft.insert(policy, &rebuilt_assets);
1976                                    val.set_multiasset(&next_nft);
1977                                    output.amount = output.amount.checked_add(&val)?;
1978                                    change_assets.push(output.amount.multiasset().unwrap());
1979
1980                                    // 2. create a new output with the base coin value as zero
1981                                    base_coin = Value::new(&Coin::zero());
1982                                    base_coin.set_multiasset(&MultiAsset::new());
1983                                    output = TransactionOutput {
1984                                        address: change_address.clone(),
1985                                        amount: base_coin.clone(),
1986                                        plutus_data: plutus_data.clone(),
1987                                        script_ref: script_ref.clone(),
1988                                        serialization_format: None,
1989                                    };
1990
1991                                    // 3. continue building the new output from the asset we stopped
1992                                    old_amount = output.amount.clone();
1993                                    val = Value::new(&Coin::zero());
1994                                    next_nft = MultiAsset::new();
1995
1996                                    rebuilt_assets = Assets::new();
1997                                }
1998
1999                                rebuilt_assets.insert(&asset_name, &value);
2000                            }
2001
2002                            next_nft.insert(policy, &rebuilt_assets);
2003                            val.set_multiasset(&next_nft);
2004                            output.amount = output.amount.checked_add(&val)?;
2005
2006                            // calculate minADA for more precise max value size
2007                            let mut amount_clone = output.amount.clone();
2008                            let mut calc = MinOutputAdaCalculator::new_empty(data_cost)?;
2009                            calc.set_amount(&val);
2010                            let min_ada = calc.calculate_ada()?;
2011                            amount_clone.set_coin(&min_ada);
2012
2013                            if amount_clone.to_bytes().len() > max_value_size as usize {
2014                                output.amount = old_amount;
2015                                break;
2016                            }
2017                        }
2018                        change_assets.push(output.amount.multiasset().unwrap());
2019                        Ok(change_assets)
2020                    }
2021                    let mut change_left = input_total.checked_sub(&output_total)?;
2022                    let mut new_fee = fee.clone();
2023                    // we might need multiple change outputs for cases where the change has many asset types
2024                    // which surpass the max UTXO size limit
2025                    let utxo_cost = self.config.utxo_cost();
2026                    let mut calc = MinOutputAdaCalculator::new_empty(&utxo_cost)?;
2027                    if let Some(data) = &plutus_data {
2028                        match data {
2029                            DataOption::DataHash(data_hash) => calc.set_data_hash(data_hash),
2030                            DataOption::Data(datum) => calc.set_plutus_data(datum),
2031                        };
2032                    }
2033                    if let Some(script_ref) = &script_ref {
2034                        calc.set_script_ref(script_ref);
2035                    }
2036                    let minimum_utxo_val = calc.calculate_ada()?;
2037                    while let Some(Ordering::Greater) = change_left
2038                        .multiasset
2039                        .as_ref()
2040                        .map_or_else(|| None, |ma| ma.partial_cmp(&MultiAsset::new()))
2041                    {
2042                        let nft_changes = pack_nfts_for_change(
2043                            self.config.max_value_size,
2044                            &utxo_cost,
2045                            address,
2046                            &change_left,
2047                            &plutus_data.clone(),
2048                            &script_ref.clone(),
2049                        )?;
2050                        if nft_changes.len() == 0 {
2051                            // this likely should never happen
2052                            return Err(JsError::from_str("NFTs too large for change output"));
2053                        }
2054                        for nft_change in nft_changes.iter() {
2055                            // we only add the minimum needed (for now) to cover this output
2056                            let mut change_value = Value::new(&Coin::zero());
2057                            change_value.set_multiasset(&nft_change);
2058                            let mut calc = MinOutputAdaCalculator::new_empty(&utxo_cost)?;
2059                            //TODO add precise calculation
2060                            let mut fake_change = change_value.clone();
2061                            fake_change.set_coin(&change_left.coin);
2062                            calc.set_amount(&fake_change);
2063                            if let Some(data) = &plutus_data {
2064                                match data {
2065                                    DataOption::DataHash(data_hash) => {
2066                                        calc.set_data_hash(data_hash)
2067                                    }
2068                                    DataOption::Data(datum) => calc.set_plutus_data(datum),
2069                                };
2070                            }
2071                            if let Some(script_ref) = &script_ref {
2072                                calc.set_script_ref(script_ref);
2073                            }
2074                            let min_ada = calc.calculate_ada()?;
2075                            change_value.set_coin(&min_ada);
2076                            let change_output = TransactionOutput {
2077                                address: address.clone(),
2078                                amount: change_value.clone(),
2079                                plutus_data: plutus_data.clone(),
2080                                script_ref: script_ref.clone(),
2081                                serialization_format: None,
2082                            };
2083
2084                            // increase fee
2085                            let fee_for_change = self.fee_for_output(&change_output)?;
2086                            new_fee = new_fee.checked_add(&fee_for_change)?;
2087                            if change_left.coin() < min_ada.checked_add(&new_fee)? {
2088                                return Err(JsError::from_str("Not enough ADA leftover to include non-ADA assets in a change address"));
2089                            }
2090                            change_left = change_left.checked_sub(&change_value)?;
2091                            self.add_output(&change_output)?;
2092                        }
2093                    }
2094                    change_left = change_left.checked_sub(&Value::new(&new_fee))?;
2095                    // add potentially a separate pure ADA change output
2096                    let left_above_minimum = change_left.coin.compare(&minimum_utxo_val) > 0;
2097                    if self.config.prefer_pure_change && left_above_minimum {
2098                        let pure_output = TransactionOutput {
2099                            address: address.clone(),
2100                            amount: change_left.clone(),
2101                            plutus_data: plutus_data.clone(),
2102                            script_ref: script_ref.clone(),
2103                            serialization_format: None,
2104                        };
2105                        let additional_fee = self.fee_for_output(&pure_output)?;
2106                        let potential_pure_value =
2107                            change_left.checked_sub(&Value::new(&additional_fee))?;
2108                        let potential_pure_above_minimum =
2109                            potential_pure_value.coin.compare(&minimum_utxo_val) > 0;
2110                        if potential_pure_above_minimum {
2111                            new_fee = new_fee.checked_add(&additional_fee)?;
2112                            change_left = Value::zero();
2113                            self.add_output(&TransactionOutput {
2114                                address: address.clone(),
2115                                amount: potential_pure_value.clone(),
2116                                plutus_data: plutus_data.clone(),
2117                                script_ref: script_ref.clone(),
2118                                serialization_format: None,
2119                            })?;
2120                        }
2121                    }
2122                    self.set_final_fee(new_fee);
2123                    // add in the rest of the ADA
2124                    if !change_left.is_zero() {
2125                        self.outputs.0.last_mut().unwrap().amount = self
2126                            .outputs
2127                            .0
2128                            .last()
2129                            .unwrap()
2130                            .amount
2131                            .checked_add(&change_left)?;
2132                    }
2133                    Ok(true)
2134                } else {
2135                    let mut calc = MinOutputAdaCalculator::new_empty(&self.config.utxo_cost())?;
2136                    calc.set_amount(&change_estimator);
2137                    if let Some(data) = &plutus_data {
2138                        match data {
2139                            DataOption::DataHash(data_hash) => calc.set_data_hash(data_hash),
2140                            DataOption::Data(datum) => calc.set_plutus_data(datum),
2141                        };
2142                    }
2143                    if let Some(script_ref) = &script_ref {
2144                        calc.set_script_ref(script_ref);
2145                    }
2146                    let min_ada = calc.calculate_ada()?;
2147
2148                    // no-asset case so we have no problem burning the rest if there is no other option
2149                    fn burn_extra(
2150                        builder: &mut TransactionBuilder,
2151                        burn_amount: &BigNum,
2152                    ) -> Result<bool, JsError> {
2153                        if builder.config.do_not_burn_extra_change {
2154                            return Err(JsError::from_str("Not enough ADA leftover to include a new change output"));
2155                        }
2156                        let fee_request = &builder.fee_request;
2157                        // recall: min_fee assumed the fee was the maximum possible so we definitely have enough input to cover whatever fee it ends up being
2158                        match fee_request {
2159                            TxBuilderFee::Exactly(fee) => {
2160                                if burn_amount > fee {
2161                                    return Err(JsError::from_str("Not enough ADA leftover to include a new change output. And leftovers is bigger than fee upper bound"));
2162                                }
2163                            }
2164                            _ => {}
2165                        }
2166                        builder.set_final_fee(burn_amount.clone());
2167                        Ok(false) // not enough input to covert the extra fee from adding an output so we just burn whatever is left
2168                    }
2169                    match change_estimator.coin() >= min_ada {
2170                        false => burn_extra(self, &change_estimator.coin()),
2171                        true => {
2172                            // check how much the fee would increase if we added a change output
2173                            let fee_for_change = self.fee_for_output(&TransactionOutput {
2174                                address: address.clone(),
2175                                amount: change_estimator.clone(),
2176                                plutus_data: plutus_data.clone(),
2177                                script_ref: script_ref.clone(),
2178                                serialization_format: None,
2179                            })?;
2180
2181                            let new_fee = fee.checked_add(&fee_for_change)?;
2182                            match change_estimator.coin()
2183                                >= min_ada.checked_add(&Value::new(&new_fee).coin())?
2184                            {
2185                                false => burn_extra(self, &change_estimator.coin()),
2186                                true => {
2187                                    // recall: min_fee assumed the fee was the maximum possible so we definitely have enough input to cover whatever fee it ends up being
2188                                    self.set_final_fee(new_fee);
2189
2190                                    self.add_output(&TransactionOutput {
2191                                        address: address.clone(),
2192                                        amount: change_estimator
2193                                            .checked_sub(&Value::new(&new_fee.clone()))?,
2194                                        plutus_data: plutus_data.clone(),
2195                                        script_ref: script_ref.clone(),
2196                                        serialization_format: None,
2197                                    })?;
2198
2199                                    Ok(true)
2200                                }
2201                            }
2202                        }
2203                    }
2204                }
2205            }
2206            None => Err(JsError::from_str(
2207                "missing input or output for some native asset",
2208            )),
2209        }
2210    }
2211
2212    /// This method will calculate the script hash data
2213    /// using the plutus datums and redeemers already present in the builder
2214    /// along with the provided cost model, and will register the calculated value
2215    /// in the builder to be used when building the tx body.
2216    /// In case there are no plutus input witnesses present - nothing will change
2217    /// You can set specific hash value using `.set_script_data_hash`
2218    /// NOTE: this function will check which language versions are used in the present scripts
2219    /// and will assert and require for a corresponding cost-model to be present in the passed map.
2220    /// Only the cost-models for the present language versions will be used in the hash calculation.
2221    pub fn calc_script_data_hash(&mut self, cost_models: &Costmdls) -> Result<(), JsError> {
2222        let mut used_langs = BTreeSet::new();
2223        let mut retained_cost_models = Costmdls::new();
2224        let mut plutus_witnesses = PlutusWitnesses::new();
2225        if let Some(mut inputs_plutus) = self.inputs.get_plutus_input_scripts() {
2226            used_langs.append(&mut self.inputs.get_used_plutus_lang_versions());
2227            plutus_witnesses.0.append(&mut inputs_plutus.0)
2228        }
2229        if let Some(mut collateral_plutus) = self.collateral.get_plutus_input_scripts() {
2230            used_langs.append(&mut self.collateral.get_used_plutus_lang_versions());
2231            plutus_witnesses.0.append(&mut collateral_plutus.0)
2232        }
2233        if let Some(mint_builder) = &self.mint {
2234            used_langs.append(&mut mint_builder.get_used_plutus_lang_versions());
2235            plutus_witnesses
2236                .0
2237                .append(&mut mint_builder.get_plutus_witnesses().0)
2238        }
2239        if let Some(certs_builder) = &self.certs {
2240            used_langs.append(&mut certs_builder.get_used_plutus_lang_versions());
2241            plutus_witnesses
2242                .0
2243                .append(&mut certs_builder.get_plutus_witnesses().0)
2244        }
2245        if let Some(withdrawals_builder) = &self.withdrawals {
2246            used_langs.append(&mut withdrawals_builder.get_used_plutus_lang_versions());
2247            plutus_witnesses
2248                .0
2249                .append(&mut withdrawals_builder.get_plutus_witnesses().0)
2250        }
2251        if let Some(voting_builder) = &self.voting_procedures {
2252            used_langs.append(&mut voting_builder.get_used_plutus_lang_versions());
2253            plutus_witnesses
2254                .0
2255                .append(&mut voting_builder.get_plutus_witnesses().0)
2256        }
2257
2258        if let Some(voting_proposal_builder) = &self.voting_proposals {
2259            used_langs.append(&mut voting_proposal_builder.get_used_plutus_lang_versions());
2260            plutus_witnesses
2261                .0
2262                .append(&mut voting_proposal_builder.get_plutus_witnesses().0)
2263        }
2264
2265        let (_scripts, mut datums, redeemers) = plutus_witnesses.collect();
2266        for lang in used_langs {
2267            match cost_models.get(&lang) {
2268                Some(cost) => {
2269                    retained_cost_models.insert(&lang, &cost);
2270                }
2271                _ => {
2272                    return Err(JsError::from_str(&format!(
2273                        "Missing cost model for language version: {:?}",
2274                        lang
2275                    )))
2276                }
2277            }
2278        }
2279
2280        if let Some(extra_datum) = &self.extra_datums {
2281            if datums.is_none() {
2282                datums = Some(PlutusList::new());
2283            }
2284
2285            for datum in extra_datum {
2286                if let Some(datums) = &mut datums {
2287                    datums.add(datum);
2288                }
2289            }
2290        }
2291
2292        if datums.is_some() || redeemers.len() > 0 || retained_cost_models.len() > 0 {
2293            self.script_data_hash =
2294                Some(hash_script_data(&redeemers, &retained_cost_models, datums));
2295        }
2296
2297        Ok(())
2298    }
2299
2300    /// Sets the specified hash value.
2301    /// Alternatively you can use `.calc_script_data_hash` to calculate the hash automatically.
2302    /// Or use `.remove_script_data_hash` to delete the previously set value
2303    pub fn set_script_data_hash(&mut self, hash: &ScriptDataHash) {
2304        self.script_data_hash = Some(hash.clone());
2305    }
2306
2307    /// Deletes any previously set plutus data hash value.
2308    /// Use `.set_script_data_hash` or `.calc_script_data_hash` to set it.
2309    pub fn remove_script_data_hash(&mut self) {
2310        self.script_data_hash = None;
2311    }
2312
2313    pub fn add_required_signer(&mut self, key: &Ed25519KeyHash) {
2314        self.required_signers.add(key);
2315    }
2316
2317    fn build_and_size(&self) -> Result<(TransactionBody, usize), JsError> {
2318        let fee = self
2319            .get_fee_if_set()
2320            .ok_or_else(|| JsError::from_str("Fee not specified"))?;
2321
2322        let built = TransactionBody {
2323            inputs: self.inputs.inputs(),
2324            outputs: self.outputs.clone(),
2325            fee,
2326            ttl: self.ttl,
2327            certs: self.certs.as_ref().map(|x| x.build()),
2328            withdrawals: self.withdrawals.as_ref().map(|x| x.build()),
2329            update: None,
2330            auxiliary_data_hash: self
2331                .auxiliary_data
2332                .as_ref()
2333                .map(|x| utils::hash_auxiliary_data(x)),
2334            validity_start_interval: self.validity_start_interval,
2335            mint: self.mint.as_ref()
2336                .map(|x| x.build())
2337                .transpose()?,
2338            script_data_hash: self.script_data_hash.clone(),
2339            collateral: self.collateral.inputs_option(),
2340            required_signers: self.required_signers.to_option(),
2341            network_id: None,
2342            collateral_return: self.collateral_return.clone(),
2343            total_collateral: self.total_collateral.clone(),
2344            reference_inputs: self.get_reference_inputs().to_option(),
2345            voting_procedures: self.voting_procedures.as_ref().map(|x| x.build()),
2346            voting_proposals: self.voting_proposals.as_ref().map(|x| x.build()),
2347            donation: self.donation.clone(),
2348            current_treasury_value: self.current_treasury_value.clone(),
2349        };
2350        // we must build a tx with fake data (of correct size) to check the final Transaction size
2351        let full_tx = fake_full_tx(self, built)?;
2352        let full_tx_size = full_tx.to_bytes().len();
2353        return Ok((full_tx.body, full_tx_size));
2354    }
2355
2356    pub fn full_size(&self) -> Result<usize, JsError> {
2357        return self.build_and_size().map(|r| r.1);
2358    }
2359
2360    pub fn output_sizes(&self) -> Vec<usize> {
2361        return self.outputs.0.iter().map(|o| o.to_bytes().len()).collect();
2362    }
2363
2364    /// Returns object the body of the new transaction
2365    /// Auxiliary data itself is not included
2366    /// You can use `get_auxiliary_data` or `build_tx`
2367    pub fn build(&self) -> Result<TransactionBody, JsError> {
2368        let (body, full_tx_size) = self.build_and_size()?;
2369        if full_tx_size > self.config.max_tx_size as usize {
2370            Err(JsError::from_str(&format!(
2371                "Maximum transaction size of {} exceeded. Found: {}",
2372                self.config.max_tx_size, full_tx_size
2373            )))
2374        } else {
2375            Ok(body)
2376        }
2377    }
2378
2379    fn get_combined_native_scripts(&self) -> Option<NativeScripts> {
2380        let mut ns = NativeScripts::new();
2381        if let Some(input_scripts) = self.inputs.get_native_input_scripts() {
2382            input_scripts.iter().for_each(|s| {
2383                ns.add(s);
2384            });
2385        }
2386        if let Some(input_scripts) = self.collateral.get_native_input_scripts() {
2387            input_scripts.iter().for_each(|s| {
2388                ns.add(s);
2389            });
2390        }
2391        if let Some(mint_builder) = &self.mint {
2392            mint_builder.get_native_scripts().iter().for_each(|s| {
2393                ns.add(s);
2394            });
2395        }
2396        if let Some(certificates_builder) = &self.certs {
2397            certificates_builder
2398                .get_native_scripts()
2399                .iter()
2400                .for_each(|s| {
2401                    ns.add(s);
2402                });
2403        }
2404        if let Some(withdrawals_builder) = &self.withdrawals {
2405            withdrawals_builder
2406                .get_native_scripts()
2407                .iter()
2408                .for_each(|s| {
2409                    ns.add(s);
2410                });
2411        }
2412        if let Some(voting_builder) = &self.voting_procedures {
2413            voting_builder.get_native_scripts().iter().for_each(|s| {
2414                ns.add(s);
2415            });
2416        }
2417
2418        if ns.len() > 0 {
2419            Some(ns)
2420        } else {
2421            None
2422        }
2423    }
2424
2425    fn get_combined_plutus_scripts(&self) -> Option<PlutusWitnesses> {
2426        let mut res = PlutusWitnesses::new();
2427        if let Some(scripts) = self.inputs.get_plutus_input_scripts() {
2428            scripts.0.iter().for_each(|s| {
2429                res.add(s);
2430            })
2431        }
2432        if let Some(scripts) = self.collateral.get_plutus_input_scripts() {
2433            scripts.0.iter().for_each(|s| {
2434                res.add(s);
2435            })
2436        }
2437        if let Some(mint_builder) = &self.mint {
2438            mint_builder.get_plutus_witnesses().0.iter().for_each(|s| {
2439                res.add(s);
2440            })
2441        }
2442        if let Some(certificates_builder) = &self.certs {
2443            certificates_builder
2444                .get_plutus_witnesses()
2445                .0
2446                .iter()
2447                .for_each(|s| {
2448                    res.add(s);
2449                })
2450        }
2451        if let Some(withdrawals_builder) = &self.withdrawals {
2452            withdrawals_builder
2453                .get_plutus_witnesses()
2454                .0
2455                .iter()
2456                .for_each(|s| {
2457                    res.add(s);
2458                })
2459        }
2460        if let Some(voting_builder) = &self.voting_procedures {
2461            voting_builder
2462                .get_plutus_witnesses()
2463                .0
2464                .iter()
2465                .for_each(|s| {
2466                    res.add(s);
2467                })
2468        }
2469        if let Some(voting_proposal_builder) = &self.voting_proposals {
2470            voting_proposal_builder
2471                .get_plutus_witnesses()
2472                .0
2473                .iter()
2474                .for_each(|s| {
2475                    res.add(s);
2476                })
2477        }
2478        if res.len() > 0 {
2479            Some(res)
2480        } else {
2481            None
2482        }
2483    }
2484
2485    // This function should be producing the total witness-set
2486    // that is created by the tx-builder itself,
2487    // before the transaction is getting signed by the actual wallet.
2488    // E.g. scripts or something else that has been used during the tx preparation
2489    pub(crate) fn get_witness_set(&self) -> TransactionWitnessSet {
2490        let mut wit = TransactionWitnessSet::new();
2491        if let Some(scripts) = self.get_combined_native_scripts() {
2492            wit.set_native_scripts(&scripts);
2493        }
2494        let mut all_datums = None;
2495        if let Some(pw) = self.get_combined_plutus_scripts() {
2496            let (scripts, datums, redeemers) = pw.collect();
2497            wit.set_plutus_scripts(&scripts);
2498            all_datums = datums;
2499            wit.set_redeemers(&redeemers);
2500        }
2501
2502        if let Some(extra_datum) = &self.extra_datums {
2503            if all_datums.is_none() {
2504                all_datums = Some(PlutusList::new());
2505            }
2506
2507            for datum in extra_datum {
2508                if let Some(datums) = &mut all_datums {
2509                    datums.add(datum);
2510                }
2511            }
2512        }
2513
2514        if let Some(datums) = &all_datums {
2515            wit.set_plutus_data(datums);
2516        }
2517
2518        wit
2519    }
2520
2521    fn has_plutus_inputs(&self) -> bool {
2522        if self.inputs.has_plutus_scripts() {
2523            return true;
2524        }
2525        if self.mint.as_ref().map_or(false, |m| m.has_plutus_scripts()) {
2526            return true;
2527        }
2528        if self
2529            .certs
2530            .as_ref()
2531            .map_or(false, |c| c.has_plutus_scripts())
2532        {
2533            return true;
2534        }
2535        if self
2536            .withdrawals
2537            .as_ref()
2538            .map_or(false, |w| w.has_plutus_scripts())
2539        {
2540            return true;
2541        }
2542        if self
2543            .voting_procedures
2544            .as_ref()
2545            .map_or(false, |w| w.has_plutus_scripts())
2546        {
2547            return true;
2548        }
2549        if self
2550            .voting_proposals
2551            .as_ref()
2552            .map_or(false, |w| w.has_plutus_scripts())
2553        {
2554            return true;
2555        }
2556
2557        return false;
2558    }
2559
2560    /// Returns full Transaction object with the body and the auxiliary data
2561    /// NOTE: witness_set will contain all mint_scripts if any been added or set
2562    /// NOTE: is_valid set to true
2563    /// NOTE: Will fail in case there are any script inputs added with no corresponding witness
2564    pub fn build_tx(&self) -> Result<Transaction, JsError> {
2565        if self.has_plutus_inputs() {
2566            if self.script_data_hash.is_none() {
2567                return Err(JsError::from_str(
2568                    "Plutus inputs are present, but script data hash is not specified",
2569                ));
2570            }
2571            if self.collateral.len() == 0 {
2572                return Err(JsError::from_str(
2573                    "Plutus inputs are present, but no collateral inputs are added",
2574                ));
2575            }
2576        }
2577        self.validate_inputs_intersection()?;
2578        self.validate_fee()?;
2579        self.validate_balance()?;
2580        self.build_tx_unsafe()
2581    }
2582
2583    /// Similar to `.build_tx()` but will NOT fail in case there are missing script witnesses
2584    pub fn build_tx_unsafe(&self) -> Result<Transaction, JsError> {
2585        Ok(Transaction {
2586            body: self.build()?,
2587            witness_set: self.get_witness_set(),
2588            is_valid: true,
2589            auxiliary_data: self.auxiliary_data.clone(),
2590        })
2591    }
2592
2593    /// warning: sum of all parts of a transaction must equal 0. You cannot just set the fee to the min value and forget about it
2594    /// warning: min_fee may be slightly larger than the actual minimum fee (ex: a few lovelaces)
2595    /// this is done to simplify the library code, but can be fixed later
2596    pub fn min_fee(&self) -> Result<Coin, JsError> {
2597        let mut self_copy = self.clone();
2598        self_copy.set_final_fee((0x1_00_00_00_00u64).into());
2599        Ok(self.fee_request.get_new_fee(min_fee(&self_copy)?))
2600    }
2601}