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
30pub(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 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 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 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 LargestFirst,
163 RandomImprove,
165 LargestFirstMultiAsset,
167 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, 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) ref_script_coins_per_byte: Option<UnitInterval>, 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>, key_deposit: Option<Coin>, max_value_size: Option<u32>, max_tx_size: Option<u32>, data_cost: Option<DataCost>, ex_unit_prices: Option<ExUnitPrices>, ref_script_coins_per_byte: Option<UnitInterval>, 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 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 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>, 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 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 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 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 let mut available_indices = (0..available_inputs.len()).collect::<Vec<usize>>();
513 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 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 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 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 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 relevant_indices
610 .sort_by_key(|i| by(&available_inputs[*i].output.amount).expect("filtered above"));
611
612 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 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 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 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 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 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 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 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 #[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 #[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 #[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 #[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 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 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 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 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 #[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 #[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 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 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 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 pub fn fee_for_output(&self, output: &TransactionOutput) -> Result<Coin, JsError> {
1118 let mut self_copy = self.clone();
1119
1120 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 pub fn set_fee(&mut self, fee: &Coin) {
1137 self.fee_request = TxBuilderFee::Exactly(fee.clone());
1138 }
1139
1140 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(
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(
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(
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(
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 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 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 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 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 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(
1330 since = "11.2.0",
1331 note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
1332 )]
1333 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(
1366 since = "11.2.0",
1367 note = "Mints are defining by MintBuilder now. Use `.get_mint_builder()` and `.build()` instead."
1368 )]
1369 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 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(
1389 since = "11.2.0",
1390 note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
1391 )]
1392 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(
1416 since = "11.2.0",
1417 note = "Mints are defining by MintBuilder now. Use `.set_mint_builder()` and `MintBuilder` instead."
1418 )]
1419 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 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 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 for item in self.inputs.get_inputs_with_ref_script_size() {
1677 add_to_map(item, &mut sizes_map)?
1678 }
1679
1680 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 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 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 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 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 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 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 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 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 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, ¤t_assets_clone);
1898 val.set_multiasset(&ma);
1899 amount_clone = amount_clone.checked_add(&val)?;
1900
1901 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 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 for (policy, assets) in change_estimator.multiasset().unwrap().0.iter() {
1941 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 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 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 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 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 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 return Err(JsError::from_str("NFTs too large for change output"));
2053 }
2054 for nft_change in nft_changes.iter() {
2055 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 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 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 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 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 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 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) }
2169 match change_estimator.coin() >= min_ada {
2170 false => burn_extra(self, &change_estimator.coin()),
2171 true => {
2172 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 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 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 pub fn set_script_data_hash(&mut self, hash: &ScriptDataHash) {
2304 self.script_data_hash = Some(hash.clone());
2305 }
2306
2307 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 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 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 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 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 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 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}