cml_chain/builders/
input_builder.rs

1use crate::builders::witness_builder::{InputAggregateWitnessData, PartialPlutusWitness};
2
3use super::{
4    tx_builder::TransactionUnspentOutput,
5    utils::required_wits_from_required_signers,
6    witness_builder::{NativeScriptWitnessInfo, RequiredWitnessSet},
7};
8
9use crate::{
10    address::Address,
11    certs::StakeCredential,
12    crypto::hash::hash_plutus_data,
13    plutus::PlutusData,
14    transaction::{TransactionInput, TransactionOutput},
15    NativeScript, RequiredSigners,
16};
17
18#[derive(Debug, thiserror::Error)]
19pub enum InputBuilderError {
20    #[error("UTXO address was not a payment key: {0:?}")]
21    UTXOAddressNotPayment(Box<Address>),
22    #[error("Missing the following witnesses for the input: {0:?}")]
23    MissingWitnesses(Box<RequiredWitnessSet>),
24}
25
26pub fn input_required_wits(
27    utxo_info: &TransactionOutput,
28    required_witnesses: &mut RequiredWitnessSet,
29) {
30    if let Some(cred) = utxo_info.address().payment_cred() {
31        match cred {
32            StakeCredential::PubKey { hash, .. } => {
33                required_witnesses.add_vkey_key_hash(*hash);
34            }
35            StakeCredential::Script { hash, .. } => {
36                required_witnesses.add_script_hash(*hash);
37                if let Some(data_hash) = utxo_info.datum_hash() {
38                    required_witnesses.add_plutus_datum_hash(*data_hash);
39                    // note: redeemer is required as well
40                    // but we can't know the index, so we rely on the tx builder to satisfy this requirement
41                }
42            }
43        }
44    };
45    if let Address::Byron(byron) = utxo_info.address() {
46        required_witnesses.add_bootstrap(byron.clone());
47    }
48}
49
50#[derive(Clone, Debug)]
51pub struct InputBuilderResult {
52    pub input: TransactionInput,
53    pub utxo_info: TransactionOutput,
54    pub aggregate_witness: Option<InputAggregateWitnessData>,
55    pub required_wits: RequiredWitnessSet,
56}
57
58#[derive(Clone)]
59pub struct SingleInputBuilder {
60    input: TransactionInput,
61    utxo_info: TransactionOutput,
62}
63
64impl SingleInputBuilder {
65    pub fn new(input: TransactionInput, utxo_info: TransactionOutput) -> Self {
66        Self { input, utxo_info }
67    }
68
69    pub fn payment_key(self) -> Result<InputBuilderResult, InputBuilderError> {
70        let mut required_wits = RequiredWitnessSet::default();
71        input_required_wits(&self.utxo_info, &mut required_wits);
72        let required_wits_left = required_wits.clone();
73
74        if !required_wits_left.scripts.is_empty() {
75            return Err(InputBuilderError::UTXOAddressNotPayment(Box::new(
76                self.utxo_info.address().clone(),
77            )));
78        }
79
80        Ok(InputBuilderResult {
81            input: self.input,
82            utxo_info: self.utxo_info,
83            aggregate_witness: None,
84            required_wits,
85        })
86    }
87
88    pub fn native_script(
89        self,
90        native_script: NativeScript,
91        witness_info: NativeScriptWitnessInfo,
92    ) -> Result<InputBuilderResult, InputBuilderError> {
93        let mut required_wits = RequiredWitnessSet::default();
94        input_required_wits(&self.utxo_info, &mut required_wits);
95        let mut required_wits_left = required_wits.clone();
96
97        let script_hash = &native_script.hash();
98
99        // check the user provided all the required witnesses
100        required_wits_left.scripts.remove(script_hash);
101
102        if !required_wits_left.scripts.is_empty() {
103            return Err(InputBuilderError::MissingWitnesses(Box::new(
104                required_wits_left,
105            )));
106        }
107
108        Ok(InputBuilderResult {
109            input: self.input,
110            utxo_info: self.utxo_info,
111            aggregate_witness: Some(InputAggregateWitnessData::NativeScript(
112                native_script,
113                witness_info,
114            )),
115            required_wits,
116        })
117    }
118
119    pub fn plutus_script(
120        self,
121        partial_witness: PartialPlutusWitness,
122        required_signers: RequiredSigners,
123        datum: PlutusData,
124    ) -> Result<InputBuilderResult, InputBuilderError> {
125        self.plutus_script_inner(partial_witness, required_signers, Some(datum))
126    }
127
128    pub fn plutus_script_inline_datum(
129        self,
130        partial_witness: PartialPlutusWitness,
131        required_signers: RequiredSigners,
132    ) -> Result<InputBuilderResult, InputBuilderError> {
133        self.plutus_script_inner(partial_witness, required_signers, None)
134    }
135
136    fn plutus_script_inner(
137        self,
138        partial_witness: PartialPlutusWitness,
139        required_signers: RequiredSigners,
140        datum: Option<PlutusData>,
141    ) -> Result<InputBuilderResult, InputBuilderError> {
142        let mut required_wits = required_wits_from_required_signers(&required_signers);
143        input_required_wits(&self.utxo_info, &mut required_wits);
144        let mut required_wits_left = required_wits.clone();
145
146        // no way to know these at this time
147        required_wits_left.vkeys.clear();
148
149        let script_hash = partial_witness.script.hash();
150
151        // check the user provided all the required witnesses
152        required_wits_left.scripts.remove(&script_hash);
153        if let Some(datum) = &datum {
154            required_wits_left
155                .plutus_data
156                .remove(&hash_plutus_data(datum));
157        }
158
159        if required_wits_left.len() > 0 {
160            return Err(InputBuilderError::MissingWitnesses(Box::new(
161                required_wits_left,
162            )));
163        }
164
165        Ok(InputBuilderResult {
166            input: self.input,
167            utxo_info: self.utxo_info,
168            aggregate_witness: Some(InputAggregateWitnessData::PlutusScript(
169                partial_witness,
170                required_signers,
171                datum,
172            )),
173            required_wits,
174        })
175    }
176}
177
178impl From<TransactionUnspentOutput> for SingleInputBuilder {
179    fn from(utxo: TransactionUnspentOutput) -> Self {
180        Self {
181            input: utxo.input,
182            utxo_info: utxo.output,
183        }
184    }
185}