#![allow(deprecated)]
use crate::*;
use super::*;
use crate::fees;
use crate::utils;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
pub(crate) fn fake_private_key() -> Bip32PrivateKey {
    Bip32PrivateKey::from_bytes(&[
        0xb8, 0xf2, 0xbe, 0xce, 0x9b, 0xdf, 0xe2, 0xb0, 0x28, 0x2f, 0x5b, 0xad, 0x70, 0x55, 0x62,
        0xac, 0x99, 0x6e, 0xfb, 0x6a, 0xf9, 0x6b, 0x64, 0x8f, 0x44, 0x45, 0xec, 0x44, 0xf4, 0x7a,
        0xd9, 0x5c, 0x10, 0xe3, 0xd7, 0x2f, 0x26, 0xed, 0x07, 0x54, 0x22, 0xa3, 0x6e, 0xd8, 0x58,
        0x5c, 0x74, 0x5a, 0x0e, 0x11, 0x50, 0xbc, 0xce, 0xba, 0x23, 0x57, 0xd0, 0x58, 0x63, 0x69,
        0x91, 0xf3, 0x8a, 0x37, 0x91, 0xe2, 0x48, 0xde, 0x50, 0x9c, 0x07, 0x0d, 0x81, 0x2a, 0xb2,
        0xfd, 0xa5, 0x78, 0x60, 0xac, 0x87, 0x6b, 0xc4, 0x89, 0x19, 0x2c, 0x1e, 0xf4, 0xce, 0x25,
        0x3c, 0x19, 0x7e, 0xe2, 0x19, 0xa4,
    ])
    .unwrap()
}
pub(crate) fn fake_raw_key_sig() -> Ed25519Signature {
    Ed25519Signature::from_bytes(vec![
        36, 248, 153, 211, 155, 23, 253, 93, 102, 193, 146, 196, 181, 13, 52, 62, 66, 247, 35, 91,
        48, 80, 76, 138, 231, 97, 159, 147, 200, 40, 220, 109, 206, 69, 104, 221, 105, 23, 124, 85,
        24, 40, 73, 45, 119, 122, 103, 39, 253, 102, 194, 251, 204, 189, 168, 194, 174, 237, 146,
        3, 44, 153, 121, 10,
    ])
    .unwrap()
}
pub(crate) fn fake_raw_key_public() -> PublicKey {
    PublicKey::from_bytes(&[
        207, 118, 57, 154, 33, 13, 232, 114, 14, 159, 168, 148, 228, 94, 65, 226, 154, 181, 37,
        227, 11, 196, 2, 128, 28, 7, 98, 80, 209, 88, 91, 205,
    ])
    .unwrap()
}
fn count_needed_vkeys(tx_builder: &TransactionBuilder) -> usize {
    let mut input_hashes: RequiredSignersSet = RequiredSignersSet::from(&tx_builder.inputs);
    input_hashes.extend(RequiredSignersSet::from(&tx_builder.collateral));
    input_hashes.extend(RequiredSignersSet::from(&tx_builder.required_signers));
    if let Some(mint_builder) = &tx_builder.mint {
        input_hashes.extend(RequiredSignersSet::from(&mint_builder.get_native_scripts()));
    }
    if let Some(withdrawals_builder) = &tx_builder.withdrawals {
        input_hashes.extend(withdrawals_builder.get_required_signers());
    }
    if let Some(certs_builder) = &tx_builder.certs {
        input_hashes.extend(certs_builder.get_required_signers());
    }
    if let Some(voting_builder) = &tx_builder.voting_procedures {
        input_hashes.extend(voting_builder.get_required_signers());
    }
    input_hashes.len()
}
pub(crate) fn fake_full_tx(
    tx_builder: &TransactionBuilder,
    body: TransactionBody,
) -> Result<Transaction, JsError> {
    let fake_key_root = fake_private_key();
    let raw_key_public = fake_raw_key_public();
    let fake_sig = fake_raw_key_sig();
    let vkeys = match count_needed_vkeys(tx_builder) {
        0 => None,
        x => {
            let fake_vkey_witness = Vkeywitness::new(&Vkey::new(&raw_key_public), &fake_sig);
            let mut result = Vkeywitnesses::new();
            for _i in 0..x {
                result.add(&fake_vkey_witness.clone());
            }
            Some(result)
        }
    };
    let bootstraps = get_bootstraps(&tx_builder.inputs);
    let bootstrap_keys = match bootstraps.len() {
        0 => None,
        _x => {
            let mut result = BootstrapWitnesses::new();
            for addr in bootstraps {
                result.add(&make_icarus_bootstrap_witness(
                    &TransactionHash::from([0u8; TransactionHash::BYTE_COUNT]),
                    &ByronAddress::from_bytes(addr.clone()).unwrap(),
                    &fake_key_root,
                ));
            }
            Some(result)
        }
    };
    let (plutus_scripts, plutus_data, redeemers) = {
        if let Some(s) = tx_builder.get_combined_plutus_scripts() {
            let (s, d, r) = s.collect();
            (Some(s), d, Some(r))
        } else {
            (None, None, None)
        }
    };
    let witness_set = TransactionWitnessSet {
        vkeys,
        native_scripts: tx_builder.get_combined_native_scripts(),
        bootstraps: bootstrap_keys,
        plutus_scripts,
        plutus_data,
        redeemers,
    };
    Ok(Transaction {
        body,
        witness_set,
        is_valid: true,
        auxiliary_data: tx_builder.auxiliary_data.clone(),
    })
}
fn assert_required_mint_scripts(
    mint: &Mint,
    maybe_mint_scripts: Option<&NativeScripts>,
) -> Result<(), JsError> {
    if maybe_mint_scripts.is_none_or_empty() {
        return Err(JsError::from_str(
            "Mint is present in the builder, but witness scripts are not provided!",
        ));
    }
    let mint_scripts = maybe_mint_scripts.unwrap();
    let witness_hashes: HashSet<ScriptHash> =
        mint_scripts.0.iter().map(|script| script.hash()).collect();
    for mint_hash in mint.keys().0.iter() {
        if !witness_hashes.contains(mint_hash) {
            return Err(JsError::from_str(&format!(
                "No witness script is found for mint policy '{:?}'! Script is required!",
                hex::encode(mint_hash.to_bytes()),
            )));
        }
    }
    Ok(())
}
fn min_fee(tx_builder: &TransactionBuilder) -> Result<Coin, JsError> {
    let full_tx = fake_full_tx(tx_builder, tx_builder.build()?)?;
    let fee: Coin = fees::min_fee(&full_tx, &tx_builder.config.fee_algo)?;
    if let Some(ex_unit_prices) = &tx_builder.config.ex_unit_prices {
        let script_fee: Coin = fees::min_script_fee(&full_tx, &ex_unit_prices)?;
        return fee.checked_add(&script_fee);
    }
    if tx_builder.has_plutus_inputs() {
        return Err(JsError::from_str(
            "Plutus inputs are present but ex unit prices are missing in the config!",
        ));
    }
    Ok(fee)
}
#[wasm_bindgen]
pub enum CoinSelectionStrategyCIP2 {
    LargestFirst,
    RandomImprove,
    LargestFirstMultiAsset,
    RandomImproveMultiAsset,
}
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct TransactionBuilderConfig {
    pub(crate) fee_algo: fees::LinearFee,
    pub(crate) pool_deposit: Coin,            pub(crate) key_deposit: Coin,             pub(crate) max_value_size: u32,           pub(crate) max_tx_size: u32,              pub(crate) data_cost: DataCost,           pub(crate) ex_unit_prices: Option<ExUnitPrices>, pub(crate) prefer_pure_change: bool,
}
impl TransactionBuilderConfig {
    pub(crate) fn utxo_cost(&self) -> DataCost {
        self.data_cost.clone()
    }
}
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct TransactionBuilderConfigBuilder {
    fee_algo: Option<fees::LinearFee>,
    pool_deposit: Option<Coin>,            key_deposit: Option<Coin>,             max_value_size: Option<u32>,           max_tx_size: Option<u32>,              data_cost: Option<DataCost>,           ex_unit_prices: Option<ExUnitPrices>,  prefer_pure_change: bool,
}
#[wasm_bindgen]
impl TransactionBuilderConfigBuilder {
    pub fn new() -> Self {
        Self {
            fee_algo: None,
            pool_deposit: None,
            key_deposit: None,
            max_value_size: None,
            max_tx_size: None,
            data_cost: None,
            ex_unit_prices: None,
            prefer_pure_change: false,
        }
    }
    pub fn fee_algo(&self, fee_algo: &fees::LinearFee) -> Self {
        let mut cfg = self.clone();
        cfg.fee_algo = Some(fee_algo.clone());
        cfg
    }
    #[deprecated(
        since = "11.0.0",
        note = "Since babbage era cardano nodes use coins per byte. Use '.coins_per_utxo_byte' instead."
    )]
    pub fn coins_per_utxo_word(&self, coins_per_utxo_word: &Coin) -> Self {
        let mut cfg = self.clone();
        cfg.data_cost = Some(DataCost::new_coins_per_word(coins_per_utxo_word));
        cfg
    }
    pub fn coins_per_utxo_byte(&self, coins_per_utxo_byte: &Coin) -> Self {
        let mut cfg = self.clone();
        cfg.data_cost = Some(DataCost::new_coins_per_byte(coins_per_utxo_byte));
        cfg
    }
    pub fn ex_unit_prices(&self, ex_unit_prices: &ExUnitPrices) -> Self {
        let mut cfg = self.clone();
        cfg.ex_unit_prices = Some(ex_unit_prices.clone());
        cfg
    }
    pub fn pool_deposit(&self, pool_deposit: &BigNum) -> Self {
        let mut cfg = self.clone();
        cfg.pool_deposit = Some(pool_deposit.clone());
        cfg
    }
    pub fn key_deposit(&self, key_deposit: &BigNum) -> Self {
        let mut cfg = self.clone();
        cfg.key_deposit = Some(key_deposit.clone());
        cfg
    }
    pub fn max_value_size(&self, max_value_size: u32) -> Self {
        let mut cfg = self.clone();
        cfg.max_value_size = Some(max_value_size);
        cfg
    }
    pub fn max_tx_size(&self, max_tx_size: u32) -> Self {
        let mut cfg = self.clone();
        cfg.max_tx_size = Some(max_tx_size);
        cfg
    }
    pub fn prefer_pure_change(&self, prefer_pure_change: bool) -> Self {
        let mut cfg = self.clone();
        cfg.prefer_pure_change = prefer_pure_change;
        cfg
    }
    pub fn build(&self) -> Result<TransactionBuilderConfig, JsError> {
        let cfg: Self = self.clone();
        Ok(TransactionBuilderConfig {
            fee_algo: cfg
                .fee_algo
                .ok_or(JsError::from_str("uninitialized field: fee_algo"))?,
            pool_deposit: cfg
                .pool_deposit
                .ok_or(JsError::from_str("uninitialized field: pool_deposit"))?,
            key_deposit: cfg
                .key_deposit
                .ok_or(JsError::from_str("uninitialized field: key_deposit"))?,
            max_value_size: cfg
                .max_value_size
                .ok_or(JsError::from_str("uninitialized field: max_value_size"))?,
            max_tx_size: cfg
                .max_tx_size
                .ok_or(JsError::from_str("uninitialized field: max_tx_size"))?,
            data_cost: cfg.data_cost.ok_or(JsError::from_str(
                "uninitialized field: coins_per_utxo_byte or coins_per_utxo_word",
            ))?,
            ex_unit_prices: cfg.ex_unit_prices,
            prefer_pure_change: cfg.prefer_pure_change,
        })
    }
}
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct TransactionBuilder {
    pub(crate) config: TransactionBuilderConfig,
    pub(crate) inputs: TxInputsBuilder,
    pub(crate) collateral: TxInputsBuilder,
    pub(crate) outputs: TransactionOutputs,
    pub(crate) fee: Option<Coin>,
    pub(crate) ttl: Option<SlotBigNum>, pub(crate) certs: Option<CertificatesBuilder>,
    pub(crate) withdrawals: Option<WithdrawalsBuilder>,
    pub(crate) auxiliary_data: Option<AuxiliaryData>,
    pub(crate) validity_start_interval: Option<SlotBigNum>,
    pub(crate) mint: Option<MintBuilder>,
    pub(crate) script_data_hash: Option<ScriptDataHash>,
    pub(crate) required_signers: Ed25519KeyHashes,
    pub(crate) collateral_return: Option<TransactionOutput>,
    pub(crate) total_collateral: Option<Coin>,
    pub(crate) reference_inputs: HashSet<TransactionInput>,
    pub(crate) extra_datums: Option<PlutusList>,
    pub(crate) voting_procedures: Option<VotingBuilder>,
    pub(crate) voting_proposals: Option<VotingProposalBuilder>,
    pub(crate) current_treasury_value: Option<Coin>,
    pub(crate) donation: Option<Coin>,
}
#[wasm_bindgen]
impl TransactionBuilder {
    pub fn add_inputs_from(
        &mut self,
        inputs: &TransactionUnspentOutputs,
        strategy: CoinSelectionStrategyCIP2,
    ) -> Result<(), JsError> {
        let available_inputs = &inputs.0.clone();
        let mut input_total = self.get_total_input()?;
        let mut output_total = self
            .get_total_output()?
            .checked_add(&Value::new(&self.min_fee()?))?;
        match strategy {
            CoinSelectionStrategyCIP2::LargestFirst => {
                if self
                    .outputs
                    .0
                    .iter()
                    .any(|output| output.amount.multiasset.is_some())
                {
                    return Err(JsError::from_str("Multiasset values not supported by LargestFirst. Please use LargestFirstMultiAsset"));
                }
                self.cip2_largest_first_by(
                    available_inputs,
                    &mut (0..available_inputs.len()).collect(),
                    &mut input_total,
                    &mut output_total,
                    |value| Some(value.coin),
                )?;
            }
            CoinSelectionStrategyCIP2::RandomImprove => {
                if self
                    .outputs
                    .0
                    .iter()
                    .any(|output| output.amount.multiasset.is_some())
                {
                    return Err(JsError::from_str("Multiasset values not supported by RandomImprove. Please use RandomImproveMultiAsset"));
                }
                use rand::Rng;
                let mut rng = rand::thread_rng();
                let mut available_indices =
                    (0..available_inputs.len()).collect::<BTreeSet<usize>>();
                self.cip2_random_improve_by(
                    available_inputs,
                    &mut available_indices,
                    &mut input_total,
                    &mut output_total,
                    |value| Some(value.coin),
                    &mut rng,
                    true,
                )?;
                while input_total.coin < output_total.coin {
                    if available_indices.is_empty() {
                        return Err(JsError::from_str("UTxO Balance Insufficient[x]"));
                    }
                    let i = *available_indices
                        .iter()
                        .nth(rng.gen_range(0..available_indices.len()))
                        .unwrap();
                    available_indices.remove(&i);
                    let input = &available_inputs[i];
                    let input_fee = self.fee_for_input(
                        &input.output.address,
                        &input.input,
                        &input.output.amount,
                    )?;
                    self.add_input(&input.output.address, &input.input, &input.output.amount);
                    input_total = input_total.checked_add(&input.output.amount)?;
                    output_total = output_total.checked_add(&Value::new(&input_fee))?;
                }
            }
            CoinSelectionStrategyCIP2::LargestFirstMultiAsset => {
                let mut available_indices = (0..available_inputs.len()).collect::<Vec<usize>>();
                if let Some(ma) = output_total.multiasset.clone() {
                    for (policy_id, assets) in ma.0.iter() {
                        for (asset_name, _) in assets.0.iter() {
                            self.cip2_largest_first_by(
                                available_inputs,
                                &mut available_indices,
                                &mut input_total,
                                &mut output_total,
                                |value| value.multiasset.as_ref()?.get(policy_id)?.get(asset_name),
                            )?;
                        }
                    }
                }
                self.cip2_largest_first_by(
                    available_inputs,
                    &mut available_indices,
                    &mut input_total,
                    &mut output_total,
                    |value| Some(value.coin),
                )?;
            }
            CoinSelectionStrategyCIP2::RandomImproveMultiAsset => {
                use rand::Rng;
                let mut rng = rand::thread_rng();
                let mut available_indices =
                    (0..available_inputs.len()).collect::<BTreeSet<usize>>();
                if let Some(ma) = output_total.multiasset.clone() {
                    for (policy_id, assets) in ma.0.iter() {
                        for (asset_name, _) in assets.0.iter() {
                            self.cip2_random_improve_by(
                                available_inputs,
                                &mut available_indices,
                                &mut input_total,
                                &mut output_total,
                                |value| value.multiasset.as_ref()?.get(policy_id)?.get(asset_name),
                                &mut rng,
                                false,
                            )?;
                        }
                    }
                }
                self.cip2_random_improve_by(
                    available_inputs,
                    &mut available_indices,
                    &mut input_total,
                    &mut output_total,
                    |value| Some(value.coin),
                    &mut rng,
                    false,
                )?;
                while input_total.coin < output_total.coin {
                    if available_indices.is_empty() {
                        return Err(JsError::from_str("UTxO Balance Insufficient[x]"));
                    }
                    let i = *available_indices
                        .iter()
                        .nth(rng.gen_range(0..available_indices.len()))
                        .unwrap();
                    available_indices.remove(&i);
                    let input = &available_inputs[i];
                    let input_fee = self.fee_for_input(
                        &input.output.address,
                        &input.input,
                        &input.output.amount,
                    )?;
                    self.add_input(&input.output.address, &input.input, &input.output.amount);
                    input_total = input_total.checked_add(&input.output.amount)?;
                    output_total = output_total.checked_add(&Value::new(&input_fee))?;
                }
            }
        }
        Ok(())
    }
    fn cip2_largest_first_by<F>(
        &mut self,
        available_inputs: &Vec<TransactionUnspentOutput>,
        available_indices: &mut Vec<usize>,
        input_total: &mut Value,
        output_total: &mut Value,
        by: F,
    ) -> Result<(), JsError>
    where
        F: Fn(&Value) -> Option<BigNum>,
    {
        let mut relevant_indices = available_indices.clone();
        relevant_indices.retain(|i| by(&available_inputs[*i].output.amount).is_some());
        relevant_indices
            .sort_by_key(|i| by(&available_inputs[*i].output.amount).expect("filtered above"));
        for i in relevant_indices.iter().rev() {
            if by(input_total).unwrap_or(BigNum::zero())
                >= by(output_total).expect("do not call on asset types that aren't in the output")
            {
                break;
            }
            let input = &available_inputs[*i];
            let input_fee =
                self.fee_for_input(&input.output.address, &input.input, &input.output.amount)?;
            self.add_input(&input.output.address, &input.input, &input.output.amount);
            *input_total = input_total.checked_add(&input.output.amount)?;
            *output_total = output_total.checked_add(&Value::new(&input_fee))?;
            available_indices.swap_remove(available_indices.iter().position(|j| i == j).unwrap());
        }
        if by(input_total).unwrap_or(BigNum::zero())
            < by(output_total).expect("do not call on asset types that aren't in the output")
        {
            return Err(JsError::from_str("UTxO Balance Insufficient"));
        }
        Ok(())
    }
    fn cip2_random_improve_by<F>(
        &mut self,
        available_inputs: &Vec<TransactionUnspentOutput>,
        available_indices: &mut BTreeSet<usize>,
        input_total: &mut Value,
        output_total: &mut Value,
        by: F,
        rng: &mut rand::rngs::ThreadRng,
        pure_ada: bool,
    ) -> Result<(), JsError>
    where
        F: Fn(&Value) -> Option<BigNum>,
    {
        use rand::Rng;
        let mut relevant_indices = available_indices
            .iter()
            .filter(|i| by(&available_inputs[**i].output.amount).is_some())
            .cloned()
            .collect::<Vec<usize>>();
        let mut associated_indices: BTreeMap<TransactionOutput, Vec<usize>> = BTreeMap::new();
        let mut outputs = self
            .outputs
            .0
            .iter()
            .filter(|output| by(&output.amount).is_some())
            .cloned()
            .collect::<Vec<TransactionOutput>>();
        outputs.sort_by_key(|output| by(&output.amount).expect("filtered above"));
        let mut available_coins = by(input_total).unwrap_or(BigNum::zero());
        for output in outputs.iter().rev() {
            let mut added = available_coins.clone();
            let needed = by(&output.amount).unwrap();
            while added < needed {
                if relevant_indices.is_empty() {
                    return Err(JsError::from_str("UTxO Balance Insufficient"));
                }
                let random_index = rng.gen_range(0..relevant_indices.len());
                let i = relevant_indices.swap_remove(random_index);
                available_indices.remove(&i);
                let input = &available_inputs[i];
                added = added.checked_add(
                    &by(&input.output.amount)
                        .expect("do not call on asset types that aren't in the output"),
                )?;
                associated_indices
                    .entry(output.clone())
                    .or_default()
                    .push(i);
            }
            available_coins = added.checked_sub(&needed)?;
        }
        if !relevant_indices.is_empty() && pure_ada {
            for output in outputs.iter_mut() {
                let associated = associated_indices.get_mut(output).unwrap();
                for i in associated.iter_mut() {
                    let random_index = rng.gen_range(0..relevant_indices.len());
                    let j: &mut usize = relevant_indices.get_mut(random_index).unwrap();
                    let input = &available_inputs[*i];
                    let new_input = &available_inputs[*j];
                    let cur = from_bignum(&by(&input.output.amount).unwrap_or(BigNum::zero()));
                    let new = from_bignum(&by(&new_input.output.amount).unwrap_or(BigNum::zero()));
                    let min = from_bignum(&by(&output.amount).unwrap_or(BigNum::zero()));
                    let ideal = 2 * min;
                    let max = 3 * min;
                    let move_closer =
                        (ideal as i128 - new as i128).abs() < (ideal as i128 - cur as i128).abs();
                    let not_exceed_max = new < max;
                    if move_closer && not_exceed_max {
                        std::mem::swap(i, j);
                        available_indices.insert(*i);
                        available_indices.remove(j);
                    }
                }
            }
        }
        for output in outputs.iter() {
            if let Some(associated) = associated_indices.get(output) {
                for i in associated.iter() {
                    let input = &available_inputs[*i];
                    let input_fee = self.fee_for_input(
                        &input.output.address,
                        &input.input,
                        &input.output.amount,
                    )?;
                    self.add_input(&input.output.address, &input.input, &input.output.amount);
                    *input_total = input_total.checked_add(&input.output.amount)?;
                    *output_total = output_total.checked_add(&Value::new(&input_fee))?;
                }
            }
        }
        Ok(())
    }
    pub fn set_inputs(&mut self, inputs: &TxInputsBuilder) {
        self.inputs = inputs.clone();
    }
    pub fn set_collateral(&mut self, collateral: &TxInputsBuilder) {
        self.collateral = collateral.clone();
    }
    pub fn set_collateral_return(&mut self, collateral_return: &TransactionOutput) {
        self.collateral_return = Some(collateral_return.clone());
    }
    pub fn set_collateral_return_and_total(
        &mut self,
        collateral_return: &TransactionOutput,
    ) -> Result<(), JsError> {
        let collateral = &self.collateral;
        if collateral.len() == 0 {
            return Err(JsError::from_str(
                "Cannot calculate total collateral value when collateral inputs are missing",
            ));
        }
        let col_input_value: Value = collateral.total_value()?;
        let total_col: Value = col_input_value.checked_sub(&collateral_return.amount())?;
        if total_col.multiasset.is_some() {
            return Err(JsError::from_str(
                "Total collateral value cannot contain assets!",
            ));
        }
        let min_ada = min_ada_for_output(&collateral_return, &self.config.utxo_cost())?;
        if min_ada > collateral_return.amount.coin {
            return Err(JsError::from_str(&format!(
                "Not enough coin to make return on the collateral value!\
                 Increase amount of return coins. \
                 Min ada for return {}, but was {}",
                min_ada, collateral_return.amount.coin
            )));
        }
        self.set_collateral_return(collateral_return);
        self.total_collateral = Some(total_col.coin);
        Ok(())
    }
    pub fn set_total_collateral(&mut self, total_collateral: &Coin) {
        self.total_collateral = Some(total_collateral.clone());
    }
    pub fn set_total_collateral_and_return(
        &mut self,
        total_collateral: &Coin,
        return_address: &Address,
    ) -> Result<(), JsError> {
        let collateral = &self.collateral;
        if collateral.len() == 0 {
            return Err(JsError::from_str(
                "Cannot calculate collateral return when collateral inputs are missing",
            ));
        }
        let col_input_value: Value = collateral.total_value()?;
        let col_return: Value = col_input_value.checked_sub(&Value::new(&total_collateral))?;
        if col_return.multiasset.is_some() || col_return.coin > BigNum::zero() {
            let return_output = TransactionOutput::new(return_address, &col_return);
            let min_ada = min_ada_for_output(&return_output, &self.config.utxo_cost())?;
            if min_ada > col_return.coin {
                return Err(JsError::from_str(&format!(
                    "Not enough coin to make return on the collateral value!\
                 Decrease the total collateral value or add more collateral inputs. \
                 Min ada for return {}, but was {}",
                    min_ada, col_return.coin
                )));
            }
            self.collateral_return = Some(return_output);
        }
        self.set_total_collateral(total_collateral);
        Ok(())
    }
    pub fn add_reference_input(&mut self, reference_input: &TransactionInput) {
        self.reference_inputs.insert(reference_input.clone());
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_key_input(
        &mut self,
        hash: &Ed25519KeyHash,
        input: &TransactionInput,
        amount: &Value,
    ) {
        self.inputs.add_key_input(hash, input, amount);
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_script_input(
        &mut self,
        hash: &ScriptHash,
        input: &TransactionInput,
        amount: &Value,
    ) {
        self.inputs.add_script_input(hash, input, amount);
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_native_script_input(
        &mut self,
        script: &NativeScript,
        input: &TransactionInput,
        amount: &Value,
    ) {
        self.inputs.add_native_script_input(script, input, amount);
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_plutus_script_input(
        &mut self,
        witness: &PlutusWitness,
        input: &TransactionInput,
        amount: &Value,
    ) {
        self.inputs.add_plutus_script_input(witness, input, amount);
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_bootstrap_input(
        &mut self,
        hash: &ByronAddress,
        input: &TransactionInput,
        amount: &Value,
    ) {
        self.inputs.add_bootstrap_input(hash, input, amount);
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_input(&mut self, address: &Address, input: &TransactionInput, amount: &Value) {
        self.inputs.add_input(address, input, amount);
    }
    #[deprecated(
        since = "10.2.0",
        note = "Use `.count_missing_input_scripts` from `TxInputsBuilder`"
    )]
    pub fn count_missing_input_scripts(&self) -> usize {
        self.inputs.count_missing_input_scripts()
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_required_native_input_scripts(&mut self, scripts: &NativeScripts) -> usize {
        self.inputs.add_required_native_input_scripts(scripts)
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn add_required_plutus_input_scripts(&mut self, scripts: &PlutusWitnesses) -> usize {
        self.inputs.add_required_plutus_input_scripts(scripts)
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn get_native_input_scripts(&self) -> Option<NativeScripts> {
        self.inputs.get_native_input_scripts()
    }
    #[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
    pub fn get_plutus_input_scripts(&self) -> Option<PlutusWitnesses> {
        self.inputs.get_plutus_input_scripts()
    }
    pub fn fee_for_input(
        &self,
        address: &Address,
        input: &TransactionInput,
        amount: &Value,
    ) -> Result<Coin, JsError> {
        let mut self_copy = self.clone();
        self_copy.set_fee(&to_bignum(0));
        let fee_before = min_fee(&self_copy)?;
        self_copy.add_input(&address, &input, &amount);
        let fee_after = min_fee(&self_copy)?;
        fee_after.checked_sub(&fee_before)
    }
    pub fn add_output(&mut self, output: &TransactionOutput) -> Result<(), JsError> {
        let value_size = output.amount.to_bytes().len();
        if value_size > self.config.max_value_size as usize {
            return Err(JsError::from_str(&format!(
                "Maximum value size of {} exceeded. Found: {}",
                self.config.max_value_size, value_size
            )));
        }
        let min_ada = min_ada_for_output(&output, &self.config.utxo_cost())?;
        if output.amount().coin() < min_ada {
            Err(JsError::from_str(&format!(
                "Value {} less than the minimum UTXO value {}",
                from_bignum(&output.amount().coin()),
                from_bignum(&min_ada)
            )))
        } else {
            self.outputs.add(output);
            Ok(())
        }
    }
    pub fn fee_for_output(&self, output: &TransactionOutput) -> Result<Coin, JsError> {
        let mut self_copy = self.clone();
        self_copy.set_fee(&to_bignum(0));
        let fee_before = min_fee(&self_copy)?;
        self_copy.add_output(&output)?;
        let fee_after = min_fee(&self_copy)?;
        fee_after.checked_sub(&fee_before)
    }
    pub fn set_fee(&mut self, fee: &Coin) {
        self.fee = Some(fee.clone())
    }
    #[deprecated(
        since = "10.1.0",
        note = "Underlying value capacity of ttl (BigNum u64) bigger then Slot32. Use set_ttl_bignum instead."
    )]
    pub fn set_ttl(&mut self, ttl: Slot32) {
        self.ttl = Some(ttl.into())
    }
    pub fn set_ttl_bignum(&mut self, ttl: &SlotBigNum) {
        self.ttl = Some(ttl.clone())
    }
    #[deprecated(
        since = "10.1.0",
        note = "Underlying value capacity of validity_start_interval (BigNum u64) bigger then Slot32. Use set_validity_start_interval_bignum instead."
    )]
    pub fn set_validity_start_interval(&mut self, validity_start_interval: Slot32) {
        self.validity_start_interval = Some(validity_start_interval.into())
    }
    pub fn set_validity_start_interval_bignum(&mut self, validity_start_interval: SlotBigNum) {
        self.validity_start_interval = Some(validity_start_interval.clone())
    }
    #[deprecated(
        since = "11.4.1",
        note = "Can emit an error if you add a cert with script credential. Use set_certs_builder instead."
    )]
    pub fn set_certs(&mut self, certs: &Certificates) -> Result<(), JsError> {
        let mut builder = CertificatesBuilder::new();
        for cert in &certs.0 {
            builder.add(cert)?;
        }
        self.certs = Some(builder);
        Ok(())
    }
    pub fn set_certs_builder(&mut self, certs: &CertificatesBuilder) {
        self.certs = Some(certs.clone());
    }
    #[deprecated(
        since = "11.4.1",
        note = "Can emit an error if you add a withdrawal with script credential. Use set_withdrawals_builder instead."
    )]
    pub fn set_withdrawals(&mut self, withdrawals: &Withdrawals) -> Result<(), JsError> {
        let mut withdrawals_builder = WithdrawalsBuilder::new();
        for (withdrawal, coin) in &withdrawals.0 {
            withdrawals_builder.add(&withdrawal, &coin)?;
        }
        self.withdrawals = Some(withdrawals_builder);
        Ok(())
    }
    pub fn set_withdrawals_builder(&mut self, withdrawals: &WithdrawalsBuilder) {
        self.withdrawals = Some(withdrawals.clone());
    }
    pub fn set_voting_builder(&mut self, voting_builder: &VotingBuilder) {
        self.voting_procedures = Some(voting_builder.clone());
    }
    pub fn set_voting_proposal_builder(&mut self, voting_proposal_builder: &VotingProposalBuilder) {
        self.voting_proposals = Some(voting_proposal_builder.clone());
    }
    pub fn get_auxiliary_data(&self) -> Option<AuxiliaryData> {
        self.auxiliary_data.clone()
    }
    pub fn set_auxiliary_data(&mut self, auxiliary_data: &AuxiliaryData) {
        self.auxiliary_data = Some(auxiliary_data.clone())
    }
    pub fn set_metadata(&mut self, metadata: &GeneralTransactionMetadata) {
        let mut aux = self
            .auxiliary_data
            .as_ref()
            .cloned()
            .unwrap_or(AuxiliaryData::new());
        aux.set_metadata(metadata);
        self.set_auxiliary_data(&aux);
    }
    pub fn add_metadatum(&mut self, key: &TransactionMetadatumLabel, val: &TransactionMetadatum) {
        let mut metadata = self
            .auxiliary_data
            .as_ref()
            .map(|aux| aux.metadata().as_ref().cloned())
            .unwrap_or(None)
            .unwrap_or(GeneralTransactionMetadata::new());
        metadata.insert(key, val);
        self.set_metadata(&metadata);
    }
    pub fn add_json_metadatum(
        &mut self,
        key: &TransactionMetadatumLabel,
        val: String,
    ) -> Result<(), JsError> {
        self.add_json_metadatum_with_schema(key, val, MetadataJsonSchema::NoConversions)
    }
    pub fn add_json_metadatum_with_schema(
        &mut self,
        key: &TransactionMetadatumLabel,
        val: String,
        schema: MetadataJsonSchema,
    ) -> Result<(), JsError> {
        let metadatum = encode_json_str_to_metadatum(val, schema)?;
        self.add_metadatum(key, &metadatum);
        Ok(())
    }
    pub fn set_mint_builder(&mut self, mint_builder: &MintBuilder) {
        self.mint = Some(mint_builder.clone());
    }
    pub fn get_mint_builder(&self) -> Option<MintBuilder> {
        self.mint.clone()
    }
    #[deprecated(
        since = "11.2.0",
        note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
    )]
    pub fn set_mint(&mut self, mint: &Mint, mint_scripts: &NativeScripts) -> Result<(), JsError> {
        assert_required_mint_scripts(mint, Some(mint_scripts))?;
        let mut scripts_policies = HashMap::new();
        for scipt in &mint_scripts.0 {
            scripts_policies.insert(scipt.hash(), scipt.clone());
        }
        let mut mint_builder = MintBuilder::new();
        for (policy_id, asset_map) in &mint.0 {
            for (asset_name, amount) in &asset_map.0 {
                if let Some(script) = scripts_policies.get(policy_id) {
                    let mint_witness = MintWitness::new_native_script(script);
                    mint_builder.set_asset(&mint_witness, asset_name, amount);
                } else {
                    return Err(JsError::from_str(
                        "Mint policy does not have a matching script",
                    ));
                }
            }
        }
        self.mint = Some(mint_builder);
        Ok(())
    }
    #[deprecated(
        since = "11.2.0",
        note = "Mints are defining by MintBuilder now. Use `.get_mint_builder()` and `.build()` instead."
    )]
    pub fn get_mint(&self) -> Option<Mint> {
        match &self.mint {
            Some(mint) => Some(mint.build()),
            None => None,
        }
    }
    pub fn get_mint_scripts(&self) -> Option<NativeScripts> {
        match &self.mint {
            Some(mint) => Some(mint.get_native_scripts()),
            None => None,
        }
    }
    #[deprecated(
        since = "11.2.0",
        note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
    )]
    pub fn set_mint_asset(&mut self, policy_script: &NativeScript, mint_assets: &MintAssets) {
        let mint_witness = MintWitness::new_native_script(policy_script);
        if let Some(mint) = &mut self.mint {
            for (asset, amount) in mint_assets.0.iter() {
                mint.set_asset(&mint_witness, asset, amount);
            }
        } else {
            let mut mint = MintBuilder::new();
            for (asset, amount) in mint_assets.0.iter() {
                mint.set_asset(&mint_witness, asset, amount);
            }
            self.mint = Some(mint);
        }
    }
    #[deprecated(
        since = "11.2.0",
        note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
    )]
    pub fn add_mint_asset(
        &mut self,
        policy_script: &NativeScript,
        asset_name: &AssetName,
        amount: &Int,
    ) {
        let mint_witness = MintWitness::new_native_script(policy_script);
        if let Some(mint) = &mut self.mint {
            mint.add_asset(&mint_witness, asset_name, &amount);
        } else {
            let mut mint = MintBuilder::new();
            mint.add_asset(&mint_witness, asset_name, &amount);
            self.mint = Some(mint);
        }
    }
    pub fn add_mint_asset_and_output(
        &mut self,
        policy_script: &NativeScript,
        asset_name: &AssetName,
        amount: &Int,
        output_builder: &TransactionOutputAmountBuilder,
        output_coin: &Coin,
    ) -> Result<(), JsError> {
        if !amount.is_positive() {
            return Err(JsError::from_str("Output value must be positive!"));
        }
        let policy_id: PolicyID = policy_script.hash();
        self.add_mint_asset(policy_script, asset_name, amount);
        let multiasset = Mint::new_from_entry(
            &policy_id,
            &MintAssets::new_from_entry(asset_name, amount)?,
        )
        .as_positive_multiasset();
        self.add_output(
            &output_builder
                .with_coin_and_asset(&output_coin, &multiasset)
                .build()?,
        )
    }
    pub fn add_mint_asset_and_output_min_required_coin(
        &mut self,
        policy_script: &NativeScript,
        asset_name: &AssetName,
        amount: &Int,
        output_builder: &TransactionOutputAmountBuilder,
    ) -> Result<(), JsError> {
        if !amount.is_positive() {
            return Err(JsError::from_str("Output value must be positive!"));
        }
        let policy_id: PolicyID = policy_script.hash();
        self.add_mint_asset(policy_script, asset_name, amount);
        let multiasset = Mint::new_from_entry(
            &policy_id,
            &MintAssets::new_from_entry(asset_name, amount)?,
        )
        .as_positive_multiasset();
        self.add_output(
            &output_builder
                .with_asset_and_min_required_coin_by_utxo_cost(
                    &multiasset,
                    &self.config.utxo_cost(),
                )?
                .build()?,
        )
    }
    pub fn add_extra_witness_datum(&mut self, datum: &PlutusData) {
        if let Some(extra_datums) = &mut self.extra_datums {
            extra_datums.add(datum);
        } else {
            let mut extra_datums = PlutusList::new();
            extra_datums.add(datum);
            self.extra_datums = Some(extra_datums);
        }
    }
    pub fn get_extra_witness_datums(&self) -> Option<PlutusList> {
        self.extra_datums.clone()
    }
    pub fn set_donation(&mut self, donation: &Coin) {
        self.donation = Some(donation.clone());
    }
    pub fn get_donation(&self) -> Option<Coin> {
        self.donation.clone()
    }
    pub fn set_current_treasury_value(&mut self, current_treasury_value: &Coin) -> Result<(), JsError> {
        if current_treasury_value == &Coin::zero() {
            return Err(JsError::from_str("Current treasury value cannot be zero!"));
        }
        self.current_treasury_value = Some(current_treasury_value.clone());
        Ok(())
    }
    pub fn get_current_treasury_value(&self) -> Option<Coin> {
        self.current_treasury_value.clone()
    }
    pub fn new(cfg: &TransactionBuilderConfig) -> Self {
        Self {
            config: cfg.clone(),
            inputs: TxInputsBuilder::new(),
            collateral: TxInputsBuilder::new(),
            outputs: TransactionOutputs::new(),
            fee: None,
            ttl: None,
            certs: None,
            withdrawals: None,
            auxiliary_data: None,
            validity_start_interval: None,
            mint: None,
            script_data_hash: None,
            required_signers: Ed25519KeyHashes::new(),
            collateral_return: None,
            total_collateral: None,
            reference_inputs: HashSet::new(),
            extra_datums: None,
            voting_procedures: None,
            voting_proposals: None,
            donation: None,
            current_treasury_value: None,
        }
    }
    pub fn get_reference_inputs(&self) -> TransactionInputs {
        let mut inputs = self.reference_inputs.clone();
        for input in self.inputs.get_ref_inputs().0 {
            inputs.insert(input);
        }
        if let Some(mint) = &self.mint {
            for input in mint.get_ref_inputs().0 {
                inputs.insert(input);
            }
        }
        if let Some(withdrawals) = &self.withdrawals {
            for input in withdrawals.get_ref_inputs().0 {
                inputs.insert(input);
            }
        }
        if let Some(certs) = &self.certs {
            for input in certs.get_ref_inputs().0 {
                inputs.insert(input);
            }
        }
        if let Some(voting_procedures) = &self.voting_procedures {
            for input in voting_procedures.get_ref_inputs().0 {
                inputs.insert(input);
            }
        }
        if let Some(voting_proposals) = &self.voting_proposals {
            for input in voting_proposals.get_ref_inputs().0 {
                inputs.insert(input);
            }
        }
        let vec_inputs = inputs.into_iter().collect();
        TransactionInputs(vec_inputs)
    }
    pub fn get_explicit_input(&self) -> Result<Value, JsError> {
        self.inputs
            .iter()
            .try_fold(Value::zero(), |acc, ref tx_builder_input| {
                acc.checked_add(&tx_builder_input.amount)
            })
    }
    pub fn get_implicit_input(&self) -> Result<Value, JsError> {
        let mut implicit_input = Value::zero();
        if let Some(withdrawals) = &self.withdrawals {
            implicit_input = implicit_input.checked_add(&withdrawals.get_total_withdrawals()?)?;
        }
        if let Some(refunds) = &self.certs {
            implicit_input = implicit_input.checked_add(
                &refunds
                    .get_certificates_refund(&self.config.pool_deposit, &self.config.key_deposit)?,
            )?;
        }
        Ok(implicit_input)
    }
    fn get_mint_as_values(&self) -> (Value, Value) {
        self.mint
            .as_ref()
            .map(|m| {
                (
                    Value::new_from_assets(&m.build().as_positive_multiasset()),
                    Value::new_from_assets(&m.build().as_negative_multiasset()),
                )
            })
            .unwrap_or((Value::zero(), Value::zero()))
    }
    pub fn get_total_input(&self) -> Result<Value, JsError> {
        let (mint_value, _) = self.get_mint_as_values();
        self.get_explicit_input()?
            .checked_add(&self.get_implicit_input()?)?
            .checked_add(&mint_value)
    }
    pub fn get_total_output(&self) -> Result<Value, JsError> {
        let (_, burn_value) = self.get_mint_as_values();
        let mut total = self.get_explicit_output()?
            .checked_add(&Value::new(&self.get_deposit()?))?
            .checked_add(&burn_value)?;
        if let Some(donation) = &self.donation {
            total = total.checked_add(&Value::new(donation))?;
        }
        Ok(total)
    }
    pub fn get_explicit_output(&self) -> Result<Value, JsError> {
        self.outputs
            .0
            .iter()
            .try_fold(Value::new(&to_bignum(0)), |acc, ref output| {
                acc.checked_add(&output.amount())
            })
    }
    pub fn get_deposit(&self) -> Result<Coin, JsError> {
        let mut total_deposit = Coin::zero();
        if let Some(certs) = &self.certs {
            total_deposit =
                total_deposit.checked_add(&certs.get_certificates_deposit(
                    &self.config.pool_deposit,
                    &self.config.key_deposit,
                )?)?;
        }
        if let Some(voting_proposal_builder) = &self.voting_proposals {
            total_deposit = total_deposit.checked_add(
                &voting_proposal_builder.get_total_deposit()?,
            )?;
        }
        Ok(total_deposit)
    }
    pub fn get_fee_if_set(&self) -> Option<Coin> {
        self.fee.clone()
    }
    pub fn add_change_if_needed(&mut self, address: &Address) -> Result<bool, JsError> {
        self.add_change_if_needed_with_optional_script_and_datum(address, None, None)
    }
    pub fn add_change_if_needed_with_datum(
        &mut self,
        address: &Address,
        plutus_data: &OutputDatum,
    ) -> Result<bool, JsError> {
        self.add_change_if_needed_with_optional_script_and_datum(
            address,
            Some(plutus_data.0.clone()),
            None,
        )
    }
    fn add_change_if_needed_with_optional_script_and_datum(
        &mut self,
        address: &Address,
        plutus_data: Option<DataOption>,
        script_ref: Option<ScriptRef>,
    ) -> Result<bool, JsError> {
        let fee = match &self.fee {
            None => self.min_fee(),
            Some(_x) => {
                return Err(JsError::from_str(
                    "Cannot calculate change if fee was explicitly specified",
                ))
            }
        }?;
        let input_total = self.get_total_input()?;
        let output_total = self.get_total_output()?;
        let shortage = get_input_shortage(&input_total, &output_total, &fee)?;
        if let Some(shortage) = shortage {
            return Err(JsError::from_str(&format!(
                "Insufficient input in transaction. {}",
                shortage
            )));
        }
        use std::cmp::Ordering;
        match &input_total.partial_cmp(&output_total.checked_add(&Value::new(&fee))?) {
            Some(Ordering::Equal) => {
                self.set_fee(&input_total.checked_sub(&output_total)?.coin());
                Ok(false)
            }
            Some(Ordering::Less) => Err(JsError::from_str("Insufficient input in transaction")),
            Some(Ordering::Greater) => {
                fn has_assets(ma: Option<MultiAsset>) -> bool {
                    ma.map(|assets| assets.len() > 0).unwrap_or(false)
                }
                let change_estimator = input_total.checked_sub(&output_total)?;
                if has_assets(change_estimator.multiasset()) {
                    fn will_adding_asset_make_output_overflow(
                        output: &TransactionOutput,
                        current_assets: &Assets,
                        asset_to_add: (PolicyID, AssetName, BigNum),
                        max_value_size: u32,
                        data_cost: &DataCost,
                    ) -> Result<bool, JsError> {
                        let (policy, asset_name, value) = asset_to_add;
                        let mut current_assets_clone = current_assets.clone();
                        current_assets_clone.insert(&asset_name, &value);
                        let mut amount_clone = output.amount.clone();
                        let mut val = Value::new(&Coin::zero());
                        let mut ma = MultiAsset::new();
                        ma.insert(&policy, ¤t_assets_clone);
                        val.set_multiasset(&ma);
                        amount_clone = amount_clone.checked_add(&val)?;
                        let mut calc = MinOutputAdaCalculator::new_empty(data_cost)?;
                        calc.set_amount(&val);
                        let min_ada = calc.calculate_ada()?;
                        amount_clone.set_coin(&min_ada);
                        Ok(amount_clone.to_bytes().len() > max_value_size as usize)
                    }
                    fn pack_nfts_for_change(
                        max_value_size: u32,
                        data_cost: &DataCost,
                        change_address: &Address,
                        change_estimator: &Value,
                        plutus_data: &Option<DataOption>,
                        script_ref: &Option<ScriptRef>,
                    ) -> Result<Vec<MultiAsset>, JsError> {
                        let mut change_assets: Vec<MultiAsset> = Vec::new();
                        let mut base_coin = Value::new(&change_estimator.coin());
                        base_coin.set_multiasset(&MultiAsset::new());
                        let mut output = TransactionOutput {
                            address: change_address.clone(),
                            amount: base_coin.clone(),
                            plutus_data: plutus_data.clone(),
                            script_ref: script_ref.clone(),
                            serialization_format: None,
                        };
                        for (policy, assets) in change_estimator.multiasset().unwrap().0.iter() {
                            let mut old_amount = output.amount.clone();
                            let mut val = Value::new(&Coin::zero());
                            let mut next_nft = MultiAsset::new();
                            let asset_names = assets.keys();
                            let mut rebuilt_assets = Assets::new();
                            for n in 0..asset_names.len() {
                                let asset_name = asset_names.get(n);
                                let value = assets.get(&asset_name).unwrap();
                                if will_adding_asset_make_output_overflow(
                                    &output,
                                    &rebuilt_assets,
                                    (policy.clone(), asset_name.clone(), value),
                                    max_value_size,
                                    data_cost,
                                )? {
                                    next_nft.insert(policy, &rebuilt_assets);
                                    val.set_multiasset(&next_nft);
                                    output.amount = output.amount.checked_add(&val)?;
                                    change_assets.push(output.amount.multiasset().unwrap());
                                    base_coin = Value::new(&Coin::zero());
                                    base_coin.set_multiasset(&MultiAsset::new());
                                    output = TransactionOutput {
                                        address: change_address.clone(),
                                        amount: base_coin.clone(),
                                        plutus_data: plutus_data.clone(),
                                        script_ref: script_ref.clone(),
                                        serialization_format: None,
                                    };
                                    old_amount = output.amount.clone();
                                    val = Value::new(&Coin::zero());
                                    next_nft = MultiAsset::new();
                                    rebuilt_assets = Assets::new();
                                }
                                rebuilt_assets.insert(&asset_name, &value);
                            }
                            next_nft.insert(policy, &rebuilt_assets);
                            val.set_multiasset(&next_nft);
                            output.amount = output.amount.checked_add(&val)?;
                            let mut amount_clone = output.amount.clone();
                            let mut calc = MinOutputAdaCalculator::new_empty(data_cost)?;
                            calc.set_amount(&val);
                            let min_ada = calc.calculate_ada()?;
                            amount_clone.set_coin(&min_ada);
                            if amount_clone.to_bytes().len() > max_value_size as usize {
                                output.amount = old_amount;
                                break;
                            }
                        }
                        change_assets.push(output.amount.multiasset().unwrap());
                        Ok(change_assets)
                    }
                    let mut change_left = input_total.checked_sub(&output_total)?;
                    let mut new_fee = fee.clone();
                    let utxo_cost = self.config.utxo_cost();
                    let mut calc = MinOutputAdaCalculator::new_empty(&utxo_cost)?;
                    if let Some(data) = &plutus_data {
                        match data {
                            DataOption::DataHash(data_hash) => calc.set_data_hash(data_hash),
                            DataOption::Data(datum) => calc.set_plutus_data(datum),
                        };
                    }
                    if let Some(script_ref) = &script_ref {
                        calc.set_script_ref(script_ref);
                    }
                    let minimum_utxo_val = calc.calculate_ada()?;
                    while let Some(Ordering::Greater) = change_left
                        .multiasset
                        .as_ref()
                        .map_or_else(|| None, |ma| ma.partial_cmp(&MultiAsset::new()))
                    {
                        let nft_changes = pack_nfts_for_change(
                            self.config.max_value_size,
                            &utxo_cost,
                            address,
                            &change_left,
                            &plutus_data.clone(),
                            &script_ref.clone(),
                        )?;
                        if nft_changes.len() == 0 {
                            return Err(JsError::from_str("NFTs too large for change output"));
                        }
                        let mut change_value = Value::new(&Coin::zero());
                        for nft_change in nft_changes.iter() {
                            change_value.set_multiasset(&nft_change);
                            let mut calc = MinOutputAdaCalculator::new_empty(&utxo_cost)?;
                            let mut fake_change = change_value.clone();
                            fake_change.set_coin(&change_left.coin);
                            calc.set_amount(&fake_change);
                            if let Some(data) = &plutus_data {
                                match data {
                                    DataOption::DataHash(data_hash) => {
                                        calc.set_data_hash(data_hash)
                                    }
                                    DataOption::Data(datum) => calc.set_plutus_data(datum),
                                };
                            }
                            if let Some(script_ref) = &script_ref {
                                calc.set_script_ref(script_ref);
                            }
                            let min_ada = calc.calculate_ada()?;
                            change_value.set_coin(&min_ada);
                            let change_output = TransactionOutput {
                                address: address.clone(),
                                amount: change_value.clone(),
                                plutus_data: plutus_data.clone(),
                                script_ref: script_ref.clone(),
                                serialization_format: None,
                            };
                            let fee_for_change = self.fee_for_output(&change_output)?;
                            new_fee = new_fee.checked_add(&fee_for_change)?;
                            if change_left.coin() < min_ada.checked_add(&new_fee)? {
                                return Err(JsError::from_str("Not enough ADA leftover to include non-ADA assets in a change address"));
                            }
                            change_left = change_left.checked_sub(&change_value)?;
                            self.add_output(&change_output)?;
                        }
                    }
                    change_left = change_left.checked_sub(&Value::new(&new_fee))?;
                    let left_above_minimum = change_left.coin.compare(&minimum_utxo_val) > 0;
                    if self.config.prefer_pure_change && left_above_minimum {
                        let pure_output = TransactionOutput {
                            address: address.clone(),
                            amount: change_left.clone(),
                            plutus_data: plutus_data.clone(),
                            script_ref: script_ref.clone(),
                            serialization_format: None,
                        };
                        let additional_fee = self.fee_for_output(&pure_output)?;
                        let potential_pure_value =
                            change_left.checked_sub(&Value::new(&additional_fee))?;
                        let potential_pure_above_minimum =
                            potential_pure_value.coin.compare(&minimum_utxo_val) > 0;
                        if potential_pure_above_minimum {
                            new_fee = new_fee.checked_add(&additional_fee)?;
                            change_left = Value::zero();
                            self.add_output(&TransactionOutput {
                                address: address.clone(),
                                amount: potential_pure_value.clone(),
                                plutus_data: plutus_data.clone(),
                                script_ref: script_ref.clone(),
                                serialization_format: None,
                            })?;
                        }
                    }
                    self.set_fee(&new_fee);
                    if !change_left.is_zero() {
                        self.outputs.0.last_mut().unwrap().amount = self
                            .outputs
                            .0
                            .last()
                            .unwrap()
                            .amount
                            .checked_add(&change_left)?;
                    }
                    Ok(true)
                } else {
                    let mut calc = MinOutputAdaCalculator::new_empty(&self.config.utxo_cost())?;
                    calc.set_amount(&change_estimator);
                    if let Some(data) = &plutus_data {
                        match data {
                            DataOption::DataHash(data_hash) => calc.set_data_hash(data_hash),
                            DataOption::Data(datum) => calc.set_plutus_data(datum),
                        };
                    }
                    if let Some(script_ref) = &script_ref {
                        calc.set_script_ref(script_ref);
                    }
                    let min_ada = calc.calculate_ada()?;
                    fn burn_extra(
                        builder: &mut TransactionBuilder,
                        burn_amount: &BigNum,
                    ) -> Result<bool, JsError> {
                        builder.set_fee(burn_amount);
                        Ok(false) }
                    match change_estimator.coin() >= min_ada {
                        false => burn_extra(self, &change_estimator.coin()),
                        true => {
                            let fee_for_change = self.fee_for_output(&TransactionOutput {
                                address: address.clone(),
                                amount: change_estimator.clone(),
                                plutus_data: plutus_data.clone(),
                                script_ref: script_ref.clone(),
                                serialization_format: None,
                            })?;
                            let new_fee = fee.checked_add(&fee_for_change)?;
                            match change_estimator.coin()
                                >= min_ada.checked_add(&Value::new(&new_fee).coin())?
                            {
                                false => burn_extra(self, &change_estimator.coin()),
                                true => {
                                    self.set_fee(&new_fee);
                                    self.add_output(&TransactionOutput {
                                        address: address.clone(),
                                        amount: change_estimator
                                            .checked_sub(&Value::new(&new_fee.clone()))?,
                                        plutus_data: plutus_data.clone(),
                                        script_ref: script_ref.clone(),
                                        serialization_format: None,
                                    })?;
                                    Ok(true)
                                }
                            }
                        }
                    }
                }
            }
            None => Err(JsError::from_str(
                "missing input or output for some native asset",
            )),
        }
    }
    pub fn calc_script_data_hash(&mut self, cost_models: &Costmdls) -> Result<(), JsError> {
        let mut used_langs = BTreeSet::new();
        let mut retained_cost_models = Costmdls::new();
        let mut plutus_witnesses = PlutusWitnesses::new();
        if let Some(mut inputs_plutus) = self.inputs.get_plutus_input_scripts() {
            used_langs.append(&mut self.inputs.get_used_plutus_lang_versions());
            plutus_witnesses.0.append(&mut inputs_plutus.0)
        }
        if let Some(mut collateral_plutus) = self.collateral.get_plutus_input_scripts() {
            used_langs.append(&mut self.collateral.get_used_plutus_lang_versions());
            plutus_witnesses.0.append(&mut collateral_plutus.0)
        }
        if let Some(mint_builder) = &self.mint {
            used_langs.append(&mut mint_builder.get_used_plutus_lang_versions());
            plutus_witnesses
                .0
                .append(&mut mint_builder.get_plutus_witnesses().0)
        }
        if let Some(certs_builder) = &self.certs {
            used_langs.append(&mut certs_builder.get_used_plutus_lang_versions());
            plutus_witnesses
                .0
                .append(&mut certs_builder.get_plutus_witnesses().0)
        }
        if let Some(withdrawals_builder) = &self.withdrawals {
            used_langs.append(&mut withdrawals_builder.get_used_plutus_lang_versions());
            plutus_witnesses
                .0
                .append(&mut withdrawals_builder.get_plutus_witnesses().0)
        }
        if let Some(voting_builder) = &self.voting_procedures {
            used_langs.append(&mut voting_builder.get_used_plutus_lang_versions());
            plutus_witnesses
                .0
                .append(&mut voting_builder.get_plutus_witnesses().0)
        }
        if let Some(voting_proposal_builder) = &self.voting_proposals {
            used_langs.append(&mut voting_proposal_builder.get_used_plutus_lang_versions());
            plutus_witnesses
                .0
                .append(&mut voting_proposal_builder.get_plutus_witnesses().0)
        }
        let (_scripts, mut datums, redeemers) = plutus_witnesses.collect();
        for lang in used_langs {
            match cost_models.get(&lang) {
                Some(cost) => {
                    retained_cost_models.insert(&lang, &cost);
                }
                _ => {
                    return Err(JsError::from_str(&format!(
                        "Missing cost model for language version: {:?}",
                        lang
                    )))
                }
            }
        }
        if let Some(extra_datum) = &self.extra_datums {
            if datums.is_none() {
                datums = Some(PlutusList::new());
            }
            for datum in extra_datum {
                if let Some(datums) = &mut datums {
                    datums.add(datum);
                }
            }
        }
        if datums.is_some() || redeemers.len() > 0 || retained_cost_models.len() > 0 {
            self.script_data_hash =
                Some(hash_script_data(&redeemers, &retained_cost_models, datums));
        }
        Ok(())
    }
    pub fn set_script_data_hash(&mut self, hash: &ScriptDataHash) {
        self.script_data_hash = Some(hash.clone());
    }
    pub fn remove_script_data_hash(&mut self) {
        self.script_data_hash = None;
    }
    pub fn add_required_signer(&mut self, key: &Ed25519KeyHash) {
        self.required_signers.add(key);
    }
    fn build_and_size(&self) -> Result<(TransactionBody, usize), JsError> {
        let fee = self
            .fee
            .ok_or_else(|| JsError::from_str("Fee not specified"))?;
        let built = TransactionBody {
            inputs: self.inputs.inputs(),
            outputs: self.outputs.clone(),
            fee,
            ttl: self.ttl,
            certs: self.certs.as_ref().map(|x| x.build()),
            withdrawals: self.withdrawals.as_ref().map(|x| x.build()),
            update: None,
            auxiliary_data_hash: self
                .auxiliary_data
                .as_ref()
                .map(|x| utils::hash_auxiliary_data(x)),
            validity_start_interval: self.validity_start_interval,
            mint: self.mint.as_ref().map(|x| x.build()),
            script_data_hash: self.script_data_hash.clone(),
            collateral: self.collateral.inputs_option(),
            required_signers: self.required_signers.to_option(),
            network_id: None,
            collateral_return: self.collateral_return.clone(),
            total_collateral: self.total_collateral.clone(),
            reference_inputs: self.get_reference_inputs().to_option(),
            voting_procedures: self.voting_procedures.as_ref().map(|x| x.build()),
            voting_proposals: self.voting_proposals.as_ref().map(|x| x.build()),
            donation: self.donation.clone(),
            current_treasury_value: self.current_treasury_value.clone(),
        };
        let full_tx = fake_full_tx(self, built)?;
        let full_tx_size = full_tx.to_bytes().len();
        return Ok((full_tx.body, full_tx_size));
    }
    pub fn full_size(&self) -> Result<usize, JsError> {
        return self.build_and_size().map(|r| r.1);
    }
    pub fn output_sizes(&self) -> Vec<usize> {
        return self.outputs.0.iter().map(|o| o.to_bytes().len()).collect();
    }
    pub fn build(&self) -> Result<TransactionBody, JsError> {
        let (body, full_tx_size) = self.build_and_size()?;
        if full_tx_size > self.config.max_tx_size as usize {
            Err(JsError::from_str(&format!(
                "Maximum transaction size of {} exceeded. Found: {}",
                self.config.max_tx_size, full_tx_size
            )))
        } else {
            Ok(body)
        }
    }
    fn get_combined_native_scripts(&self) -> Option<NativeScripts> {
        let mut ns = NativeScripts::new();
        if let Some(input_scripts) = self.inputs.get_native_input_scripts() {
            input_scripts.0.iter().for_each(|s| {
                ns.add(s);
            });
        }
        if let Some(input_scripts) = self.collateral.get_native_input_scripts() {
            input_scripts.0.iter().for_each(|s| {
                ns.add(s);
            });
        }
        if let Some(mint_builder) = &self.mint {
            mint_builder.get_native_scripts().0.iter().for_each(|s| {
                ns.add(s);
            });
        }
        if let Some(certificates_builder) = &self.certs {
            certificates_builder
                .get_native_scripts()
                .0
                .iter()
                .for_each(|s| {
                    ns.add(s);
                });
        }
        if let Some(withdrawals_builder) = &self.withdrawals {
            withdrawals_builder
                .get_native_scripts()
                .0
                .iter()
                .for_each(|s| {
                    ns.add(s);
                });
        }
        if let Some(voting_builder) = &self.voting_procedures {
            voting_builder.get_native_scripts().0.iter().for_each(|s| {
                ns.add(s);
            });
        }
        if ns.len() > 0 {
            Some(ns)
        } else {
            None
        }
    }
    fn get_combined_plutus_scripts(&self) -> Option<PlutusWitnesses> {
        let mut res = PlutusWitnesses::new();
        if let Some(scripts) = self.inputs.get_plutus_input_scripts() {
            scripts.0.iter().for_each(|s| {
                res.add(s);
            })
        }
        if let Some(scripts) = self.collateral.get_plutus_input_scripts() {
            scripts.0.iter().for_each(|s| {
                res.add(s);
            })
        }
        if let Some(mint_builder) = &self.mint {
            mint_builder.get_plutus_witnesses().0.iter().for_each(|s| {
                res.add(s);
            })
        }
        if let Some(certificates_builder) = &self.certs {
            certificates_builder
                .get_plutus_witnesses()
                .0
                .iter()
                .for_each(|s| {
                    res.add(s);
                })
        }
        if let Some(withdrawals_builder) = &self.withdrawals {
            withdrawals_builder
                .get_plutus_witnesses()
                .0
                .iter()
                .for_each(|s| {
                    res.add(s);
                })
        }
        if let Some(voting_builder) = &self.voting_procedures {
            voting_builder
                .get_plutus_witnesses()
                .0
                .iter()
                .for_each(|s| {
                    res.add(s);
                })
        }
        if let Some(voting_proposal_builder) = &self.voting_proposals {
            voting_proposal_builder
                .get_plutus_witnesses()
                .0
                .iter()
                .for_each(|s| {
                    res.add(s);
                })
        }
        if res.len() > 0 {
            Some(res)
        } else {
            None
        }
    }
    pub(crate) fn get_witness_set(&self) -> TransactionWitnessSet {
        let mut wit = TransactionWitnessSet::new();
        if let Some(scripts) = self.get_combined_native_scripts() {
            wit.set_native_scripts(&scripts);
        }
        let mut all_datums = None;
        if let Some(pw) = self.get_combined_plutus_scripts() {
            let (scripts, datums, redeemers) = pw.collect();
            wit.set_plutus_scripts(&scripts);
            all_datums = datums;
            wit.set_redeemers(&redeemers);
        }
        if let Some(extra_datum) = &self.extra_datums {
            if all_datums.is_none() {
                all_datums = Some(PlutusList::new());
            }
            for datum in extra_datum {
                if let Some(datums) = &mut all_datums {
                    datums.add(datum);
                }
            }
        }
        if let Some(datums) = &all_datums {
            wit.set_plutus_data(datums);
        }
        wit
    }
    fn has_plutus_inputs(&self) -> bool {
        if self.inputs.has_plutus_scripts() {
            return true;
        }
        if self.mint.as_ref().map_or(false, |m| m.has_plutus_scripts()) {
            return true;
        }
        if self
            .certs
            .as_ref()
            .map_or(false, |c| c.has_plutus_scripts())
        {
            return true;
        }
        if self
            .withdrawals
            .as_ref()
            .map_or(false, |w| w.has_plutus_scripts())
        {
            return true;
        }
        if self
            .voting_procedures
            .as_ref()
            .map_or(false, |w| w.has_plutus_scripts())
        {
            return true;
        }
        if self
            .voting_proposals
            .as_ref()
            .map_or(false, |w| w.has_plutus_scripts())
        {
            return true;
        }
        return false;
    }
    pub fn build_tx(&self) -> Result<Transaction, JsError> {
        if self.count_missing_input_scripts() > 0 {
            return Err(JsError::from_str(
                "There are some script inputs added that don't have the corresponding script provided as a witness!",
            ));
        }
        if self.has_plutus_inputs() {
            if self.script_data_hash.is_none() {
                return Err(JsError::from_str(
                    "Plutus inputs are present, but script data hash is not specified",
                ));
            }
            if self.collateral.len() == 0 {
                return Err(JsError::from_str(
                    "Plutus inputs are present, but no collateral inputs are added",
                ));
            }
        }
        self.build_tx_unsafe()
    }
    pub fn build_tx_unsafe(&self) -> Result<Transaction, JsError> {
        Ok(Transaction {
            body: self.build()?,
            witness_set: self.get_witness_set(),
            is_valid: true,
            auxiliary_data: self.auxiliary_data.clone(),
        })
    }
    pub fn min_fee(&self) -> Result<Coin, JsError> {
        let mut self_copy = self.clone();
        self_copy.set_fee(&to_bignum(0x1_00_00_00_00));
        min_fee(&self_copy)
    }
}