1use pallas_primitives::conway;
2use pallas_traverse::MultiEraOutput;
3use serde::{Deserialize, Serialize};
4use serde_with::{serde_as, DisplayFromStr};
5use std::collections::{HashMap, HashSet};
6
7use super::*;
8
9pub type Hash<const N: usize> = pallas_crypto::hash::Hash<N>;
10pub type Address = pallas_addresses::Address;
11pub type Value = pallas_primitives::conway::Value;
12pub type Bytes = pallas_codec::utils::Bytes;
13pub type KeyValuePairs<K, V> = pallas_codec::utils::KeyValuePairs<K, V>;
14pub type NonEmptyKeyValuePairs<K, V> = pallas_codec::utils::NonEmptyKeyValuePairs<K, V>;
15pub type NonEmptySet<T> = pallas_codec::utils::NonEmptySet<T>;
16
17pub type Cbor = Vec<u8>;
18
19#[derive(Debug, Clone, Default)]
20pub struct UtxoSet(HashMap<TxoRef, Cbor>);
21
22impl UtxoSet {
23 pub fn is_empty(&self) -> bool {
24 self.0.is_empty()
25 }
26
27 pub fn iter(&self) -> impl Iterator<Item = (&TxoRef, MultiEraOutput<'_>)> {
28 self.0.iter().map(|(k, v)| {
29 (
30 k,
31 MultiEraOutput::decode(pallas_traverse::Era::Conway, v).unwrap(),
32 )
33 })
34 }
35
36 pub fn refs(&self) -> impl Iterator<Item = &TxoRef> {
37 self.0.keys()
38 }
39
40 pub fn txos(&self) -> impl Iterator<Item = MultiEraOutput<'_>> {
41 self.0
42 .values()
43 .map(|v| MultiEraOutput::decode(pallas_traverse::Era::Conway, v).unwrap())
44 }
45}
46
47impl FromIterator<(TxoRef, Cbor)> for UtxoSet {
48 fn from_iter<T: IntoIterator<Item = (TxoRef, Cbor)>>(iter: T) -> Self {
49 Self(HashMap::from_iter(iter))
50 }
51}
52
53impl From<HashMap<TxoRef, Cbor>> for UtxoSet {
54 fn from(value: HashMap<TxoRef, Cbor>) -> Self {
55 UtxoSet(value)
56 }
57}
58
59#[derive(Clone, Default, Serialize, Deserialize)]
60pub struct UtxoPattern {
61 pub address: Option<AddressPattern>,
62 pub asset: Option<AssetPattern>,
63}
64
65impl From<UtxoPattern> for crate::wit::balius::app::ledger::UtxoPattern {
66 fn from(value: UtxoPattern) -> Self {
67 Self {
68 address: value.address.map(Into::into),
69 asset: value.asset.map(Into::into),
70 }
71 }
72}
73
74#[derive(Clone, Serialize, Deserialize)]
75pub struct AddressPattern {
76 pub exact_address: Vec<u8>,
77}
78
79impl From<AddressPattern> for crate::wit::balius::app::ledger::AddressPattern {
80 fn from(value: AddressPattern) -> Self {
81 Self {
82 exact_address: value.exact_address,
83 }
84 }
85}
86
87#[derive(Clone, Serialize, Deserialize)]
88pub struct AssetPattern {
89 pub policy: Vec<u8>,
90 pub name: Option<Vec<u8>>,
91}
92
93impl From<AssetPattern> for crate::wit::balius::app::ledger::AssetPattern {
94 fn from(value: AssetPattern) -> Self {
95 Self {
96 policy: value.policy,
97 name: value.name,
98 }
99 }
100}
101
102pub trait InputExpr: 'static + Send + Sync {
103 fn eval(&self, ctx: &BuildContext) -> Result<Vec<conway::TransactionInput>, BuildError>;
104}
105
106#[derive(Clone, Serialize, Deserialize)]
107pub enum UtxoSource {
108 Refs(Vec<TxoRef>),
109 Search(UtxoPattern),
110}
111
112impl UtxoSource {
113 pub fn resolve(&self, ctx: &BuildContext) -> Result<UtxoSet, BuildError> {
114 match self {
115 Self::Refs(refs) => ctx.ledger.read_utxos(refs),
116 Self::Search(utxo_pattern) => ctx.ledger.search_utxos(utxo_pattern),
117 }
118 }
119}
120
121#[serde_as]
122#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
123pub struct ReferenceScript {
124 pub ref_txo: conway::TransactionInput,
125 pub hash: Hash<28>,
126 #[serde_as(as = "DisplayFromStr")]
127 pub address: Address,
128}
129
130impl InputExpr for ReferenceScript {
131 fn eval(&self, _: &dsl::BuildContext) -> Result<Vec<conway::TransactionInput>, BuildError> {
132 Ok(vec![self.ref_txo.clone()])
133 }
134}
135
136#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize, Deserialize)]
137pub struct AssetPolicyId(Hash<28>);
138
139impl AssetPolicyId {
140 pub fn new(hash: Hash<28>) -> Self {
141 Self(hash)
142 }
143}
144
145impl From<Hash<28>> for AssetPolicyId {
146 fn from(value: Hash<28>) -> Self {
147 Self(value)
148 }
149}
150
151impl From<AssetPolicyId> for Hash<28> {
152 fn from(value: AssetPolicyId) -> Self {
153 value.0
154 }
155}
156
157impl TryFrom<&str> for AssetPolicyId {
158 type Error = BuildError;
159
160 fn try_from(value: &str) -> Result<Self, Self::Error> {
161 let hash = <Hash<28> as std::str::FromStr>::from_str(value)
162 .map_err(|_| BuildError::MalformedAssetPolicyIdHex)?;
163 Ok(AssetPolicyId(hash))
164 }
165}
166
167impl std::ops::Deref for AssetPolicyId {
168 type Target = Hash<28>;
169
170 fn deref(&self) -> &Self::Target {
171 &self.0
172 }
173}
174
175impl std::fmt::Display for AssetPolicyId {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 write!(f, "{}", hex::encode(self.0))
178 }
179}
180
181#[derive(PartialEq, Eq, Debug, Clone, Hash, Serialize, Deserialize)]
182pub struct AssetName(Bytes);
183
184impl AssetName {
185 pub fn new(name: Bytes) -> Result<Self, BuildError> {
186 if name.len() > 32 {
187 panic!("Asset name too long");
188 }
189
190 Ok(Self(name))
191 }
192}
193
194impl TryFrom<Vec<u8>> for AssetName {
195 type Error = BuildError;
196
197 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
198 Self::new(value.into())
199 }
200}
201
202impl TryFrom<&str> for AssetName {
203 type Error = BuildError;
204
205 fn try_from(value: &str) -> Result<Self, Self::Error> {
206 Self::new(value.as_bytes().to_vec().into())
207 }
208}
209
210impl From<AssetName> for Bytes {
211 fn from(value: AssetName) -> Self {
212 value.0
213 }
214}
215
216impl std::ops::Deref for AssetName {
217 type Target = Bytes;
218
219 fn deref(&self) -> &Self::Target {
220 &self.0
221 }
222}
223
224#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Hash, Clone)]
225pub struct TxoRef {
226 pub hash: Hash<32>,
227 pub index: u64,
228}
229
230impl std::str::FromStr for TxoRef {
231 type Err = BuildError;
232
233 fn from_str(s: &str) -> Result<Self, Self::Err> {
234 let (hash, index) = s.split_once("#").ok_or(BuildError::MalformedTxoRef)?;
235 let hash = Hash::from_str(hash).map_err(|_| BuildError::MalformedTxoRef)?;
236 let index = index.parse().map_err(|_| BuildError::MalformedTxoRef)?;
237 Ok(TxoRef::new(hash, index))
238 }
239}
240
241impl From<crate::wit::balius::app::ledger::TxoRef> for TxoRef {
242 fn from(value: crate::wit::balius::app::ledger::TxoRef) -> Self {
243 Self::new(Hash::from(value.tx_hash.as_slice()), value.tx_index as u64)
244 }
245}
246
247impl From<TxoRef> for crate::wit::balius::app::ledger::TxoRef {
248 fn from(value: TxoRef) -> crate::wit::balius::app::ledger::TxoRef {
249 crate::wit::balius::app::ledger::TxoRef {
250 tx_hash: value.hash.to_vec(),
251 tx_index: value.index as u32,
252 }
253 }
254}
255
256impl TxoRef {
257 pub fn new(hash: Hash<32>, index: u64) -> Self {
258 Self { hash, index }
259 }
260}
261
262impl dsl::InputExpr for TxoRef {
263 fn eval(&self, _: &BuildContext) -> Result<Vec<conway::TransactionInput>, BuildError> {
264 Ok(vec![self.into()])
265 }
266}
267
268impl From<&TxoRef> for conway::TransactionInput {
269 fn from(value: &TxoRef) -> Self {
270 conway::TransactionInput {
271 transaction_id: value.hash,
272 index: value.index,
273 }
274 }
275}
276
277impl InputExpr for UtxoSource {
278 fn eval(&self, ctx: &BuildContext) -> Result<Vec<conway::TransactionInput>, BuildError> {
279 let out = self.resolve(ctx)?.refs().map(|i| i.into()).collect();
280
281 Ok(out)
282 }
283}
284
285pub trait ValueExpr: 'static + Send + Sync {
286 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError>;
287
288 fn eval_as_mint(&self, ctx: &BuildContext) -> Result<conway::Mint, BuildError> {
289 let value = self.eval(ctx)?;
290
291 match value {
292 conway::Value::Multiasset(_, assets) => asset_math::multiasset_coin_to_mint(assets),
293 conway::Value::Coin(_) => Err(BuildError::Conflicting),
294 }
295 }
296
297 fn eval_as_burn(&self, ctx: &BuildContext) -> Result<conway::Mint, BuildError> {
298 let value = self.eval(ctx)?;
299
300 match value {
301 conway::Value::Multiasset(_, assets) => asset_math::multiasset_coin_to_burn(assets),
302 conway::Value::Coin(_) => Err(BuildError::Conflicting),
303 }
304 }
305}
306
307impl ValueExpr for u64 {
308 fn eval(&self, _ctx: &BuildContext) -> Result<conway::Value, BuildError> {
309 Ok(conway::Value::Coin(*self))
310 }
311}
312
313impl<F> ValueExpr for F
314where
315 F: Fn(&BuildContext) -> Result<conway::Value, BuildError> + 'static + Send + Sync,
316{
317 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError> {
318 self(ctx)
319 }
320}
321
322impl<T: ValueExpr> ValueExpr for Option<T> {
323 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError> {
324 match self {
325 Some(v) => v.eval(ctx),
326 None => Err(BuildError::Incomplete),
327 }
328 }
329}
330
331pub struct MinUtxoLovelace;
334
335impl ValueExpr for MinUtxoLovelace {
336 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError> {
337 let parent = match &ctx.parent_output {
338 Some(x) => x,
339 None => return Ok(conway::Value::Coin(0)),
340 };
341
342 let serialized = pallas_codec::minicbor::to_vec(parent).unwrap();
343 let min_lovelace = (160u64 + serialized.len() as u64) * ctx.pparams.coins_per_utxo_byte;
344 let current_value = match parent {
345 conway::PseudoTransactionOutput::PostAlonzo(x) => &x.value,
346 _ => unimplemented!(),
347 };
348
349 let current_lovelace = asset_math::value_coin(current_value);
350
351 if current_lovelace >= min_lovelace {
352 return Ok(current_value.clone());
353 }
354
355 let optimized = asset_math::value_saturating_add_coin(
356 current_value.clone(),
357 (min_lovelace - current_lovelace) as i64,
358 );
359
360 Ok(optimized)
361 }
362}
363
364impl ValueExpr for Box<dyn ValueExpr> {
365 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError> {
366 (**self).eval(ctx)
367 }
368}
369
370impl<T: ValueExpr> ValueExpr for Vec<T> {
371 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError> {
372 let values = self
373 .iter()
374 .map(|v| v.eval(ctx))
375 .collect::<Result<Vec<_>, _>>()?;
376
377 Ok(asset_math::aggregate_values(values))
378 }
379}
380
381pub trait AddressExpr: 'static + Send + Sync {
382 fn eval(&self, ctx: &BuildContext) -> Result<Address, BuildError>;
383}
384
385impl AddressExpr for &'static str {
386 fn eval(&self, _ctx: &BuildContext) -> Result<Address, BuildError> {
387 Address::from_bech32(self).map_err(|_| BuildError::MalformedAddress)
388 }
389}
390
391impl AddressExpr for String {
392 fn eval(&self, _ctx: &BuildContext) -> Result<Address, BuildError> {
393 Address::from_bech32(self).map_err(|_| BuildError::MalformedAddress)
394 }
395}
396
397impl AddressExpr for Address {
398 fn eval(&self, _ctx: &BuildContext) -> Result<Address, BuildError> {
399 Ok(self.clone())
400 }
401}
402
403impl AddressExpr for Box<dyn AddressExpr> {
404 fn eval(&self, ctx: &BuildContext) -> Result<Address, BuildError> {
405 (**self).eval(ctx)
406 }
407}
408
409impl<T: AddressExpr> AddressExpr for Option<T> {
410 fn eval(&self, ctx: &BuildContext) -> Result<Address, BuildError> {
411 match self {
412 Some(v) => v.eval(ctx),
413 None => Err(BuildError::Incomplete),
414 }
415 }
416}
417
418impl<F> AddressExpr for F
419where
420 F: Fn(&BuildContext) -> Result<Address, BuildError> + 'static + Send + Sync,
421{
422 fn eval(&self, ctx: &BuildContext) -> Result<Address, BuildError> {
423 self(ctx)
424 }
425}
426
427pub trait OutputExpr: 'static + Send + Sync {
428 fn eval(&mut self, ctx: &BuildContext) -> Result<conway::TransactionOutput, BuildError>;
429}
430
431pub struct ChangeAddress(pub UtxoSource);
432
433impl AddressExpr for ChangeAddress {
434 fn eval(&self, ctx: &BuildContext) -> Result<Address, BuildError> {
435 let utxo_set = &self.0.resolve(ctx)?;
436
437 if utxo_set.is_empty() {
438 return Err(BuildError::EmptyUtxoSet);
439 }
440
441 let addresses: HashSet<_> = utxo_set
442 .txos()
443 .map(|x| x.address())
444 .collect::<Result<HashSet<_>, _>>()
445 .map_err(|_| BuildError::UtxoDecode)?;
446
447 if addresses.len() > 1 {
448 return Err(BuildError::Conflicting);
449 }
450
451 Ok(addresses.into_iter().next().unwrap())
452 }
453}
454
455pub struct TotalChange;
456
457impl ValueExpr for TotalChange {
458 fn eval(&self, ctx: &BuildContext) -> Result<conway::Value, BuildError> {
459 let change = asset_math::subtract_value(&ctx.total_input, &ctx.spent_output)?;
460 let fee = ctx.estimated_fee;
461 let diff = asset_math::value_saturating_add_coin(change, -(fee as i64));
462 Ok(diff)
463 }
464}
465
466pub struct FeeChangeReturn(pub UtxoSource);
467
468impl OutputExpr for FeeChangeReturn {
469 fn eval(&mut self, ctx: &BuildContext) -> Result<conway::TransactionOutput, BuildError> {
470 OutputBuilder::new()
471 .address(ChangeAddress(self.0.clone()))
472 .with_value(TotalChange)
473 .eval(ctx)
474 }
475}
476
477pub trait PlutusDataExpr: 'static + Send + Sync {
478 fn eval(&self, ctx: &BuildContext) -> Result<conway::PlutusData, BuildError>;
479}
480
481impl PlutusDataExpr for conway::PlutusData {
482 fn eval(&self, _ctx: &BuildContext) -> Result<conway::PlutusData, BuildError> {
483 Ok(self.clone())
484 }
485}
486
487impl<F> PlutusDataExpr for F
488where
489 F: Fn(&BuildContext) -> Result<conway::PlutusData, BuildError> + 'static + Send + Sync,
490{
491 fn eval(&self, ctx: &BuildContext) -> Result<conway::PlutusData, BuildError> {
492 self(ctx)
493 }
494}
495
496impl PlutusDataExpr for Box<dyn PlutusDataExpr> {
497 fn eval(&self, ctx: &BuildContext) -> Result<conway::PlutusData, BuildError> {
498 (**self).eval(ctx)
499 }
500}
501
502impl PlutusDataExpr for () {
503 fn eval(&self, _ctx: &BuildContext) -> Result<conway::PlutusData, BuildError> {
504 Ok(conway::PlutusData::Constr(conway::Constr {
505 tag: 121,
506 any_constructor: None,
507 fields: conway::MaybeIndefArray::Def(vec![]),
508 }))
509 }
510}
511
512pub trait MintExpr: 'static + Send + Sync {
513 fn eval(&self, ctx: &BuildContext) -> Result<Option<conway::Mint>, BuildError>;
514 fn eval_redeemer(&self, ctx: &BuildContext) -> Result<Option<conway::Redeemer>, BuildError>;
515}
516
517#[derive(Default)]
518pub struct MintBuilder {
519 pub assets: Vec<Box<dyn ValueExpr>>,
520 pub burn: Vec<Box<dyn ValueExpr>>,
521 pub redeemer: Option<Box<dyn PlutusDataExpr>>,
522}
523
524impl MintBuilder {
525 pub fn new() -> Self {
526 Self::default()
527 }
528
529 pub fn with_asset(mut self, asset: impl ValueExpr) -> Self {
530 self.assets.push(Box::new(asset));
531 self
532 }
533
534 pub fn with_burn(mut self, burn: impl ValueExpr) -> Self {
535 self.burn.push(Box::new(burn));
536 self
537 }
538
539 pub fn using_redeemer(mut self, redeemer: impl PlutusDataExpr) -> Self {
540 self.redeemer = Some(Box::new(redeemer));
541 self
542 }
543}
544
545impl MintExpr for MintBuilder {
546 fn eval(&self, ctx: &BuildContext) -> Result<Option<primitives::Mint>, BuildError> {
547 let out = HashMap::new();
548
549 let out = self.assets.iter().try_fold(out, |mut acc, v| {
550 let v = v.eval_as_mint(ctx)?;
551 asset_math::fold_multiassets(&mut acc, v);
552 Result::<_, BuildError>::Ok(acc)
553 })?;
554
555 let out = self.burn.iter().try_fold(out, |mut acc, v| {
556 let v = v.eval_as_burn(ctx)?;
557 asset_math::fold_multiassets(&mut acc, v);
558 Result::<_, BuildError>::Ok(acc)
559 })?;
560
561 let mint: Vec<_> = out
562 .into_iter()
563 .filter_map(|(policy, assets)| {
564 let assets = assets.into_iter().collect();
565 Some((policy, NonEmptyKeyValuePairs::from_vec(assets)?))
566 })
567 .collect();
568
569 Ok(NonEmptyKeyValuePairs::from_vec(mint))
570 }
571
572 fn eval_redeemer(&self, ctx: &BuildContext) -> Result<Option<conway::Redeemer>, BuildError> {
573 let Some(mint) = self.eval(ctx)? else {
574 return Ok(None);
575 };
576
577 if mint.is_empty() {
578 return Err(BuildError::Incomplete);
579 }
580
581 if mint.len() > 1 {
582 return Err(BuildError::Conflicting);
583 }
584
585 let (policy, _) = mint.iter().next().unwrap();
586
587 let data = self
588 .redeemer
589 .as_ref()
590 .ok_or(BuildError::Incomplete)?
591 .eval(ctx)?;
592
593 let out = conway::Redeemer {
594 tag: conway::RedeemerTag::Mint,
595 index: ctx.mint_redeemer_index(*policy)?,
596 ex_units: ctx.eval_ex_units(*policy, &data),
597 data,
598 };
599
600 Ok(Some(out))
601 }
602}
603
604pub trait ScriptExpr: 'static + Send + Sync {
605 fn eval(&self, ctx: &BuildContext) -> Result<conway::ScriptRef, BuildError>;
606}
607
608impl ScriptExpr for conway::ScriptRef {
609 fn eval(&self, _ctx: &BuildContext) -> Result<conway::ScriptRef, BuildError> {
610 Ok(self.clone())
611 }
612}
613
614impl ScriptExpr for conway::PlutusScript<3> {
615 fn eval(&self, _ctx: &BuildContext) -> Result<conway::ScriptRef, BuildError> {
616 Ok(conway::ScriptRef::PlutusV3Script(self.clone()))
617 }
618}
619
620#[derive(Default)]
621pub struct OutputBuilder {
622 pub previous: Option<conway::TransactionOutput>,
623 pub address: Option<Box<dyn dsl::AddressExpr>>,
624 pub values: Vec<Box<dyn dsl::ValueExpr>>,
625 pub script: Option<Box<dyn ScriptExpr>>,
626 }
628
629impl OutputBuilder {
630 pub fn new() -> Self {
631 Self::default()
632 }
633
634 pub fn address(mut self, address: impl AddressExpr + 'static) -> Self {
635 self.address = Some(Box::new(address));
636 self
637 }
638
639 pub fn with_value(mut self, value: impl ValueExpr + 'static) -> Self {
640 self.values.push(Box::new(value));
641 self
642 }
643
644 pub fn with_script(mut self, script: impl ScriptExpr + 'static) -> Self {
645 self.script = Some(Box::new(script));
646 self
647 }
648}
649
650impl OutputExpr for OutputBuilder {
651 fn eval(&mut self, ctx: &BuildContext) -> Result<conway::TransactionOutput, BuildError> {
652 let ctx = match &self.previous {
653 Some(x) => &ctx.with_parent_output(x.clone()),
654 None => ctx,
655 };
656
657 let value = self.values.eval(ctx)?;
658
659 let address = self.address.eval(ctx)?.to_vec().into();
660
661 let script_ref = self
662 .script
663 .as_ref()
664 .map(|s| s.eval(ctx))
665 .transpose()?
666 .map(pallas_codec::utils::CborWrap);
667
668 let output = conway::TransactionOutput::PostAlonzo(conway::PostAlonzoTransactionOutput {
669 value,
670 address,
671 script_ref,
672 datum_option: None, });
674
675 self.previous = Some(output.clone());
676
677 Ok(output)
678 }
679}
680
681pub trait TxExpr: 'static + Send + Sync {
682 fn eval_body(&mut self, ctx: &BuildContext) -> Result<conway::TransactionBody, BuildError>;
683 fn eval_witness_set(&mut self, ctx: &BuildContext) -> Result<conway::WitnessSet, BuildError>;
684}
685
686impl<T: TxExpr> TxExpr for &'static mut T {
687 fn eval_body(&mut self, ctx: &BuildContext) -> Result<conway::TransactionBody, BuildError> {
688 (**self).eval_body(ctx)
689 }
690
691 fn eval_witness_set(&mut self, ctx: &BuildContext) -> Result<conway::WitnessSet, BuildError> {
692 (**self).eval_witness_set(ctx)
693 }
694}
695
696impl TxExpr for Box<dyn TxExpr> {
697 fn eval_body(&mut self, ctx: &BuildContext) -> Result<conway::TransactionBody, BuildError> {
698 (**self).eval_body(ctx)
699 }
700
701 fn eval_witness_set(&mut self, ctx: &BuildContext) -> Result<conway::WitnessSet, BuildError> {
702 (**self).eval_witness_set(ctx)
703 }
704}
705
706#[derive(Default)]
707pub struct TxBuilder {
708 pub reference_inputs: Vec<Box<dyn InputExpr>>,
709 pub inputs: Vec<Box<dyn InputExpr>>,
710 pub outputs: Vec<Box<dyn OutputExpr>>,
711 pub mint: Vec<Box<dyn MintExpr>>,
712 pub fee: Option<u64>,
713 }
732
733impl TxBuilder {
734 pub fn new() -> Self {
735 Self::default()
736 }
737
738 pub fn with_reference_input(mut self, input: impl InputExpr) -> Self {
739 self.reference_inputs.push(Box::new(input));
740 self
741 }
742
743 pub fn with_input(mut self, input: impl InputExpr) -> Self {
744 self.inputs.push(Box::new(input));
745 self
746 }
747
748 pub fn with_output(mut self, output: impl OutputExpr) -> Self {
749 self.outputs.push(Box::new(output));
750 self
751 }
752
753 pub fn with_mint(mut self, mint: impl MintExpr) -> Self {
754 self.mint.push(Box::new(mint));
755 self
756 }
757
758 pub fn with_fee(mut self, fee: u64) -> Self {
759 self.fee = Some(fee);
760 self
761 }
762}
763
764impl TxExpr for TxBuilder {
765 fn eval_body(&mut self, ctx: &BuildContext) -> Result<conway::TransactionBody, BuildError> {
766 let out = conway::TransactionBody {
767 inputs: self
768 .inputs
769 .iter()
770 .map(|i| i.eval(ctx))
771 .collect::<Result<Vec<_>, _>>()?
772 .into_iter()
773 .flatten()
774 .collect::<Vec<_>>()
775 .into(),
776 outputs: self
777 .outputs
778 .iter_mut()
779 .map(|o| o.eval(ctx))
780 .collect::<Result<Vec<_>, _>>()?,
781 fee: ctx.estimated_fee,
782 ttl: None,
783 validity_interval_start: None,
784 certificates: None,
785 withdrawals: None,
786 auxiliary_data_hash: None,
787 mint: {
788 let mints = self
789 .mint
790 .iter()
791 .map(|m| m.eval(ctx))
792 .collect::<Result<Vec<_>, _>>()?
793 .into_iter()
794 .flatten();
795
796 asset_math::aggregate_assets(mints)
797 },
798 script_data_hash: None,
799 collateral: None,
800 required_signers: None,
801 network_id: None,
802 collateral_return: None,
803 total_collateral: None,
804 reference_inputs: {
805 let refs: Vec<_> = self
806 .reference_inputs
807 .iter()
808 .map(|i| i.eval(ctx))
809 .collect::<Result<Vec<_>, _>>()?
810 .into_iter()
811 .flatten()
812 .collect();
813
814 NonEmptySet::from_vec(refs)
815 },
816 voting_procedures: None,
817 proposal_procedures: None,
818 treasury_value: None,
819 donation: None,
820 };
821
822 Ok(out)
823 }
824
825 fn eval_witness_set(&mut self, ctx: &BuildContext) -> Result<conway::WitnessSet, BuildError> {
826 let out = conway::WitnessSet {
827 redeemer: {
828 let redeemers: Vec<_> = self
829 .mint
830 .iter()
831 .map(|m| m.eval_redeemer(ctx))
832 .collect::<Result<Vec<_>, _>>()?
833 .into_iter()
834 .flatten()
835 .collect();
836
837 if redeemers.is_empty() {
838 None
839 } else {
840 Some(conway::Redeemers::List(conway::MaybeIndefArray::Def(
841 redeemers,
842 )))
843 }
844 },
845 vkeywitness: None,
846 native_script: None,
847 bootstrap_witness: None,
848 plutus_v1_script: None,
849 plutus_data: None,
850 plutus_v2_script: None,
851 plutus_v3_script: None,
852 };
853
854 Ok(out)
855 }
856}
857
858#[macro_export]
859macro_rules! define_asset_class {
860 ($struct_name:ident, $policy:expr) => {
861 #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
862 pub struct $struct_name($crate::txbuilder::Bytes, u64);
863
864 impl $struct_name {
865 pub fn value(name: $crate::txbuilder::AssetName, quantity: u64) -> Self {
866 Self(name.into(), quantity)
867 }
868 }
869
870 impl $crate::txbuilder::ValueExpr for $struct_name {
871 fn eval(
872 &self,
873 _: &$crate::txbuilder::BuildContext,
874 ) -> std::result::Result<$crate::txbuilder::Value, $crate::txbuilder::BuildError> {
875 let policy = $crate::txbuilder::Hash::from(*$policy);
876 let name = $crate::txbuilder::Bytes::from(self.0.clone());
877 let Ok(amount) = self.1.try_into() else {
878 return Ok($crate::txbuilder::Value::Coin(0));
879 };
880 let asset = $crate::txbuilder::NonEmptyKeyValuePairs::Def(vec![(name, amount)]);
881 let val = $crate::txbuilder::Value::Multiasset(
882 0,
883 $crate::txbuilder::NonEmptyKeyValuePairs::Def(vec![(policy, asset)]),
884 );
885
886 Ok(val)
887 }
888 }
889 };
890}
891
892define_asset_class!(MyAssetClass, b"abcabcababcabcababcabcababca");