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, pub(crate) input_ref_script_size: Option<usize>,
10}
11
12#[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 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 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 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 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 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 pub fn get_plutus_input_scripts(&self) -> Option<PlutusWitnesses> {
324 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}