cardano_serialization_lib/builders/
tx_inputs_builder.rs

1use crate::*;
2use hashlink::LinkedHashMap;
3use std::collections::{BTreeMap, BTreeSet};
4
5#[derive(Clone, Debug)]
6pub(crate) struct TxBuilderInput {
7    pub(crate) input: TransactionInput,
8    pub(crate) amount: Value, // we need to keep track of the amount in the inputs for input selection
9    pub(crate) input_ref_script_size: Option<usize>,
10}
11
12// We need to know how many of each type of witness will be in the transaction so we can calculate the tx fee
13#[derive(Clone, Debug)]
14pub struct InputsRequiredWitness {
15    vkeys: Ed25519KeyHashes,
16    scripts: LinkedHashMap<ScriptHash, LinkedHashMap<TransactionInput, Option<ScriptWitnessType>>>,
17    bootstraps: BTreeSet<Vec<u8>>,
18}
19
20#[wasm_bindgen]
21#[derive(Clone, Debug)]
22pub struct TxInputsBuilder {
23    inputs: BTreeMap<TransactionInput, (TxBuilderInput, Option<ScriptHash>)>,
24    required_witnesses: InputsRequiredWitness,
25}
26
27pub(crate) fn get_bootstraps(inputs: &TxInputsBuilder) -> BTreeSet<Vec<u8>> {
28    inputs.required_witnesses.bootstraps.clone()
29}
30
31#[wasm_bindgen]
32impl TxInputsBuilder {
33    pub fn new() -> Self {
34        Self {
35            inputs: BTreeMap::new(),
36            required_witnesses: InputsRequiredWitness {
37                vkeys: Ed25519KeyHashes::new(),
38                scripts: LinkedHashMap::new(),
39                bootstraps: BTreeSet::new(),
40            },
41        }
42    }
43
44    pub fn add_regular_utxo(&mut self, utxo: &TransactionUnspentOutput) -> Result<(), JsError> {
45        let input = &utxo.input;
46        let output = &utxo.output;
47
48        let address = &output.address;
49        let address_kind = address.kind();
50        if address_kind == AddressKind::Malformed || address_kind == AddressKind::Reward {
51            return Err(JsError::from_str(&BuilderError::RegularAddressTypeMismatch.as_str()));
52        }
53
54        let ref_script_size = output.script_ref.as_ref().map(|x| x.to_unwrapped_bytes().len());
55        self.add_regular_input_extended(&output.address, input, &output.amount, ref_script_size)
56    }
57
58    pub fn add_plutus_script_utxo(&mut self, utxo: &TransactionUnspentOutput, witness: &PlutusWitness) -> Result<(), JsError> {
59        let input = &utxo.input;
60        let output = &utxo.output;
61        let address = &output.address;
62        let address_kind = address.kind();
63        if address_kind == AddressKind::Malformed || address_kind == AddressKind::Reward || address_kind == AddressKind::Byron{
64            return Err(JsError::from_str(&BuilderError::ScriptAddressTypeMismatch.as_str()));
65        }
66
67        let payment_cred = address.payment_cred();
68        if let Some(payment_cred) = payment_cred {
69            if !payment_cred.has_script_hash() {
70                return Err(JsError::from_str(&BuilderError::ScriptAddressCredentialMismatch.as_str()));
71            }
72        }
73
74        let ref_script_size = output.script_ref.as_ref().map(|x| x.to_unwrapped_bytes().len());
75        let hash = witness.script.script_hash();
76
77        self.add_script_input(&hash, input, &output.amount, ref_script_size);
78        let witness = ScriptWitnessType::PlutusScriptWitness(witness.clone());
79        self.insert_input_with_witness(&hash, input, &witness);
80        Ok(())
81    }
82
83    pub fn add_native_script_utxo(&mut self, utxo: &TransactionUnspentOutput, witness: &NativeScriptSource) -> Result<(), JsError> {
84        let input = &utxo.input;
85        let output = &utxo.output;
86        let address = &output.address;
87        let address_kind = address.kind();
88        if address_kind == AddressKind::Malformed || address_kind == AddressKind::Reward || address_kind == AddressKind::Byron{
89            return Err(JsError::from_str(&BuilderError::ScriptAddressTypeMismatch.as_str()));
90        }
91
92        let payment_cred = address.payment_cred();
93        if let Some(payment_cred) = payment_cred {
94            if !payment_cred.has_script_hash() {
95                return Err(JsError::from_str(&BuilderError::ScriptAddressCredentialMismatch.as_str()));
96            }
97        }
98
99        let ref_script_size = output.script_ref.as_ref().map(|x| x.to_unwrapped_bytes().len());
100        let hash = witness.script_hash();
101
102        self.add_script_input(&hash, input, &output.amount, ref_script_size);
103        let witness = ScriptWitnessType::NativeScriptWitness(witness.0.clone());
104        self.insert_input_with_witness(&hash, input, &witness);
105        Ok(())
106    }
107
108    /// We have to know what kind of inputs these are to know what kind of mock witnesses to create since
109    /// 1) mock witnesses have different lengths depending on the type which changes the expecting fee
110    /// 2) Witnesses are a set so we need to get rid of duplicates to avoid over-estimating the fee
111    pub fn add_key_input(
112        &mut self,
113        hash: &Ed25519KeyHash,
114        input: &TransactionInput,
115        amount: &Value,
116    ) {
117        self.add_key_input_extended(hash, input, amount, None);
118    }
119
120    fn add_key_input_extended(
121        &mut self,
122        hash: &Ed25519KeyHash,
123        input: &TransactionInput,
124        amount: &Value,
125        input_ref_script_size: Option<usize>,
126    ) {
127        let inp = TxBuilderInput {
128            input: input.clone(),
129            amount: amount.clone(),
130            input_ref_script_size,
131        };
132        self.push_input((inp, None));
133        self.required_witnesses.vkeys.add_move(hash.clone());
134    }
135
136    fn add_script_input(&mut self, hash: &ScriptHash, input: &TransactionInput, amount: &Value, input_ref_script_size: Option<usize>) {
137        let inp = TxBuilderInput {
138            input: input.clone(),
139            amount: amount.clone(),
140            input_ref_script_size,
141        };
142        self.push_input((inp, Some(hash.clone())));
143        self.insert_input_with_empty_witness(hash, input);
144    }
145
146    /// This method will add the input to the builder and also register the required native script witness
147    pub fn add_native_script_input(
148        &mut self,
149        script: &NativeScriptSource,
150        input: &TransactionInput,
151        amount: &Value,
152    ) {
153        let hash = script.script_hash();
154        self.add_script_input(&hash, input, amount, None);
155        let witness = ScriptWitnessType::NativeScriptWitness(script.0.clone());
156        self.insert_input_with_witness(&hash, input, &witness);
157    }
158
159    /// This method will add the input to the builder and also register the required plutus witness
160    pub fn add_plutus_script_input(
161        &mut self,
162        witness: &PlutusWitness,
163        input: &TransactionInput,
164        amount: &Value,
165    ) {
166        let hash = witness.script.script_hash();
167        self.add_script_input(&hash, input, amount, None);
168        let witness = ScriptWitnessType::PlutusScriptWitness(witness.clone());
169        self.insert_input_with_witness(&hash, input, &witness);
170    }
171
172    pub fn add_bootstrap_input(
173        &mut self,
174        address: &ByronAddress,
175        input: &TransactionInput,
176        amount: &Value,
177    ) {
178        self.add_bootstrap_input_extended(address, input, amount, None);
179    }
180
181    fn add_bootstrap_input_extended(
182        &mut self,
183        address: &ByronAddress,
184        input: &TransactionInput,
185        amount: &Value,
186        input_ref_script_size: Option<usize>,
187    ) {
188        let inp = TxBuilderInput {
189            input: input.clone(),
190            amount: amount.clone(),
191            input_ref_script_size,
192        };
193        self.push_input((inp, None));
194        self.required_witnesses.bootstraps.insert(address.to_bytes());
195    }
196
197
198    /// Adds non script input, in case of script or reward address input it will return an error
199    pub fn add_regular_input(
200        &mut self,
201        address: &Address,
202        input: &TransactionInput,
203        amount: &Value,
204    ) -> Result<(), JsError> {
205        self.add_regular_input_extended(address, input, amount, None)
206    }
207
208    fn add_regular_input_extended(
209        &mut self,
210        address: &Address,
211        input: &TransactionInput,
212        amount: &Value,
213        input_ref_script_size: Option<usize>,
214    ) -> Result<(), JsError> {
215        match &address.0 {
216            AddrType::Base(base_addr) => match &base_addr.payment.0 {
217                CredType::Key(key) => {
218                    self.add_key_input_extended(key, input, amount, input_ref_script_size);
219                    Ok(())
220                }
221                CredType::Script(_) => Err(JsError::from_str(
222                    &BuilderError::RegularInputIsScript.as_str(),
223                )),
224            },
225            AddrType::Enterprise(ent_aaddr) => match &ent_aaddr.payment.0 {
226                CredType::Key(key) => {
227                    self.add_key_input_extended(key, input, amount, input_ref_script_size);
228                    Ok(())
229                }
230                CredType::Script(_) => Err(JsError::from_str(
231                    &BuilderError::RegularInputIsScript.as_str(),
232                )),
233            },
234            AddrType::Ptr(ptr_addr) => match &ptr_addr.payment.0 {
235                CredType::Key(key) => {
236                    self.add_key_input_extended(key, input, amount, input_ref_script_size);
237                    Ok(())
238                }
239                CredType::Script(_) => Err(JsError::from_str(
240                    &BuilderError::RegularInputIsScript.as_str(),
241                )),
242            },
243            AddrType::Byron(byron_addr) => {
244                self.add_bootstrap_input_extended(byron_addr, input, amount, input_ref_script_size);
245                Ok(())
246            }
247            AddrType::Reward(_) => Err(JsError::from_str(
248                &BuilderError::RegularInputIsFromRewardAddress.as_str(),
249            )),
250            AddrType::Malformed(_) => {
251                Err(JsError::from_str(&BuilderError::MalformedAddress.as_str()))
252            }
253        }
254    }
255
256    pub fn get_ref_inputs(&self) -> TransactionInputs {
257        let mut inputs = Vec::new();
258        for wintess in self
259            .required_witnesses
260            .scripts
261            .iter()
262            .flat_map(|(_, tx_wits)| tx_wits.values())
263            .filter_map(|wit| wit.as_ref())
264        {
265            match wintess {
266                ScriptWitnessType::NativeScriptWitness(NativeScriptSourceEnum::RefInput(
267                    input, _, _, _,
268                )) => {
269                    inputs.push(input.clone());
270                }
271                ScriptWitnessType::PlutusScriptWitness(plutus_witness) => {
272                    if let Some(DatumSourceEnum::RefInput(input)) = &plutus_witness.datum {
273                        inputs.push(input.clone());
274                    }
275                    if let PlutusScriptSourceEnum::RefInput(script_ref, _) = &plutus_witness.script
276                    {
277                        inputs.push(script_ref.input_ref.clone());
278                    }
279                }
280                _ => (),
281            }
282        }
283        TransactionInputs::from_vec(inputs)
284    }
285
286
287    /// Returns a copy of the current script input witness scripts in the builder
288    pub fn get_native_input_scripts(&self) -> Option<NativeScripts> {
289        let mut scripts = NativeScripts::new();
290        self.required_witnesses
291            .scripts
292            .values()
293            .flat_map(|v| v)
294            .for_each(|tx_in_with_wit| {
295                if let Some(ScriptWitnessType::NativeScriptWitness(
296                    NativeScriptSourceEnum::NativeScript(s, _),
297                )) = tx_in_with_wit.1
298                {
299                    scripts.add(&s);
300                }
301            });
302        if scripts.len() > 0 {
303            Some(scripts)
304        } else {
305            None
306        }
307    }
308
309    pub(crate) fn get_used_plutus_lang_versions(&self) -> BTreeSet<Language> {
310        let mut used_langs = BTreeSet::new();
311        for input_with_wit in self.required_witnesses.scripts.values() {
312            for (_, script_wit) in input_with_wit {
313                if let Some(ScriptWitnessType::PlutusScriptWitness(plutus_witness)) = script_wit {
314                    used_langs.insert(plutus_witness.script.language());
315                }
316            }
317        }
318        used_langs
319    }
320
321    /// Returns a copy of the current plutus input witness scripts in the builder.
322    /// NOTE: each plutus witness will be cloned with a specific corresponding input index
323    pub fn get_plutus_input_scripts(&self) -> Option<PlutusWitnesses> {
324        /*
325         * === EXPLANATION ===
326         * The `Redeemer` object contains the `.index` field which is supposed to point
327         * exactly to the index of the corresponding input in the inputs array. We want to
328         * simplify and automate this as much as possible for the user to not have to care about it.
329         *
330         * For this we store the script hash along with the input, when it was registered, and
331         * now we create a map of script hashes to their input indexes.
332         *
333         * The registered witnesses are then each cloned with the new correct redeemer input index.
334         * To avoid incorrect redeemer tag we also set the `tag` field to `spend`.
335         */
336        let tag = RedeemerTag::new_spend();
337        let script_hash_index_map: BTreeMap<&TransactionInput, BigNum> = self
338            .inputs
339            .values()
340            .enumerate()
341            .fold(BTreeMap::new(), |mut m, (i, (tx_in, hash_option))| {
342                if hash_option.is_some() {
343                    m.insert(&tx_in.input, (i as u64).into());
344                }
345                m
346            });
347        let mut scripts = PlutusWitnesses::new();
348        self.required_witnesses
349            .scripts
350            .iter()
351            .flat_map(|x| x.1)
352            .for_each(|(hash, option)| {
353                if let Some(ScriptWitnessType::PlutusScriptWitness(s)) = option {
354                    if let Some(idx) = script_hash_index_map.get(&hash) {
355                        scripts.add(&s.clone_with_redeemer_index_and_tag(&idx, &tag));
356                    }
357                }
358            });
359        if scripts.len() > 0 {
360            Some(scripts)
361        } else {
362            None
363        }
364    }
365
366    pub(crate) fn has_plutus_scripts(&self) -> bool {
367        self.required_witnesses.scripts.values().any(|x| {
368            x.iter()
369                .any(|(_, w)| matches!(w, Some(ScriptWitnessType::PlutusScriptWitness(_))))
370        })
371    }
372
373    pub(crate) fn iter(&self) -> impl std::iter::Iterator<Item = &TxBuilderInput> + '_ {
374        self.inputs.values().map(|(i, _)| i)
375    }
376
377    pub fn len(&self) -> usize {
378        self.inputs.len()
379    }
380
381    pub fn add_required_signer(&mut self, key: &Ed25519KeyHash) {
382        self.required_witnesses.vkeys.add_move(key.clone());
383    }
384
385    pub fn add_required_signers(&mut self, keys: &RequiredSigners) {
386        self.required_witnesses.vkeys.extend(keys);
387    }
388
389    pub fn total_value(&self) -> Result<Value, JsError> {
390        let mut res = Value::zero();
391        for (inp, _) in self.inputs.values() {
392            res = res.checked_add(&inp.amount)?;
393        }
394        Ok(res)
395    }
396
397    pub fn inputs(&self) -> TransactionInputs {
398        TransactionInputs::from_vec(
399            self.inputs
400                .values()
401                .map(|(ref tx_builder_input, _)| tx_builder_input.input.clone())
402                .collect(),
403        )
404    }
405
406    pub fn inputs_option(&self) -> Option<TransactionInputs> {
407        if self.len() > 0 {
408            Some(self.inputs())
409        } else {
410            None
411        }
412    }
413
414    pub(crate) fn get_script_ref_inputs_with_size(
415        &self,
416    ) -> impl Iterator<Item = (&TransactionInput, usize)> {
417        self.required_witnesses
418            .scripts
419            .iter()
420            .flat_map(|(_, tx_wits)| tx_wits.iter())
421            .filter_map(|(_, wit)| wit.as_ref())
422            .filter_map(|wit| wit.get_script_ref_input_with_size())
423    }
424
425    pub(crate) fn get_inputs_with_ref_script_size(
426        &self,
427    ) -> impl Iterator<Item = (&TransactionInput, usize)> {
428        self.inputs.iter().filter_map(|(tx_in, (tx_builder_input, _))| {
429            if let Some(size) = tx_builder_input.input_ref_script_size {
430                Some((tx_in, size))
431            } else {
432                None
433            }
434        })
435    }
436
437    #[allow(dead_code)]
438    pub(crate) fn get_required_signers(&self) -> Ed25519KeyHashes {
439        self.into()
440    }
441
442    pub(crate) fn has_inputs(&self) -> bool {
443        !self.inputs.is_empty()
444    }
445
446    pub(crate) fn has_input(&self, input: &TransactionInput) -> bool {
447        self.inputs.contains_key(input)
448    }
449
450    fn push_input(&mut self, e: (TxBuilderInput, Option<ScriptHash>)) {
451        self.inputs.insert(e.0.input.clone(), e);
452    }
453
454    fn insert_input_with_witness(
455        &mut self,
456        script_hash: &ScriptHash,
457        input: &TransactionInput,
458        witness: &ScriptWitnessType,
459    ) {
460        let script_inputs = self
461            .required_witnesses
462            .scripts
463            .entry(script_hash.clone())
464            .or_insert(LinkedHashMap::new());
465        script_inputs.insert(input.clone(), Some(witness.clone()));
466    }
467
468    fn insert_input_with_empty_witness(
469        &mut self,
470        script_hash: &ScriptHash,
471        input: &TransactionInput,
472    ) {
473        let script_inputs = self
474            .required_witnesses
475            .scripts
476            .entry(script_hash.clone())
477            .or_insert(LinkedHashMap::new());
478        script_inputs.insert(input.clone(), None);
479    }
480}
481
482impl From<&TxInputsBuilder> for Ed25519KeyHashes {
483    fn from(inputs: &TxInputsBuilder) -> Self {
484        let mut set = inputs.required_witnesses.vkeys.clone();
485        inputs
486            .required_witnesses
487            .scripts
488            .values()
489            .flat_map(|tx_wits| tx_wits.values())
490            .for_each(|swt: &Option<ScriptWitnessType>| {
491                match swt {
492                    Some(ScriptWitnessType::NativeScriptWitness(script_source)) => {
493                        if let Some(signers) = script_source.required_signers() {
494                            set.extend_move(signers);
495                        }
496                    }
497                    Some(ScriptWitnessType::PlutusScriptWitness(script_source)) => {
498                        if let Some(signers) = script_source.get_required_signers() {
499                            set.extend_move(signers);
500                        }
501                    }
502                    None => (),
503                }
504            });
505        set
506    }
507}