Skip to main content

cml_chain/builders/
output_builder.rs

1use cml_core::ArithmeticError;
2
3use crate::{
4    address::Address,
5    assets::{Coin, MultiAsset, Value},
6    crypto::hash::hash_plutus_data,
7    min_ada::min_ada_required,
8    plutus::PlutusData,
9    transaction::{DatumOption, ScriptRef, TransactionOutput},
10};
11
12#[derive(Debug, thiserror::Error)]
13pub enum OutputBuilderError {
14    #[error("Address missing")]
15    AddressMissing,
16    #[error("Value missing")]
17    AmountMissing,
18    #[error("Min ADA error: {0:?}")]
19    MinAdaError(#[from] ArithmeticError),
20}
21
22/// We introduce a builder-pattern format for creating transaction outputs
23/// This is because:
24/// 1. Some fields (i.e. data hash) are optional, and we can't easily expose Option<> in WASM
25/// 2. Some fields like amounts have many ways it could be set (some depending on other field values being known)
26/// 3. Easier to adapt as the output format gets more complicated in future Cardano releases
27
28#[derive(Clone, Debug, Default)]
29pub struct TransactionOutputBuilder {
30    pub address: Option<Address>,
31    pub datum: Option<DatumOption>,
32    pub communication_datum: Option<PlutusData>,
33    pub script_ref: Option<ScriptRef>,
34}
35
36impl TransactionOutputBuilder {
37    pub fn new() -> Self {
38        // explicit new for consistency with WASM
39        Self::default()
40    }
41
42    pub fn with_address(mut self, address: Address) -> Self {
43        self.address = Some(address);
44        self
45    }
46
47    /// A communication datum is one where the data hash is used in the tx output
48    /// Yet the full datum is included in the witness of the same transaction
49    pub fn with_communication_data(mut self, datum: PlutusData) -> Self {
50        self.datum = Some(DatumOption::new_hash(hash_plutus_data(&datum)));
51        self.communication_datum = Some(datum);
52        self
53    }
54    pub fn with_data(mut self, datum: DatumOption) -> Self {
55        self.datum = Some(datum);
56        self.communication_datum = None;
57        self
58    }
59
60    pub fn with_reference_script(mut self, script_ref: ScriptRef) -> Self {
61        self.script_ref = Some(script_ref);
62        self
63    }
64
65    pub fn next(self) -> Result<TransactionOutputAmountBuilder, OutputBuilderError> {
66        Ok(TransactionOutputAmountBuilder {
67            address: self.address.ok_or(OutputBuilderError::AddressMissing)?,
68            amount: None,
69            datum: self.datum,
70            script_ref: self.script_ref,
71            communication_datum: self.communication_datum,
72        })
73    }
74}
75
76#[derive(Clone, Debug)]
77pub struct TransactionOutputAmountBuilder {
78    address: Address,
79    amount: Option<Value>,
80    datum: Option<DatumOption>,
81    script_ref: Option<ScriptRef>,
82    communication_datum: Option<PlutusData>,
83}
84
85impl TransactionOutputAmountBuilder {
86    pub fn with_value<T: Into<Value>>(mut self, amount: T) -> Self {
87        self.amount = Some(amount.into());
88        self
89    }
90
91    pub fn with_asset_and_min_required_coin(
92        self,
93        multiasset: MultiAsset,
94        coins_per_utxo_byte: Coin,
95    ) -> Result<Self, OutputBuilderError> {
96        let mut min_output = TransactionOutput::new(
97            self.address.clone(),
98            self.amount.clone().unwrap_or_else(|| Value::from(0)),
99            self.datum.clone(),
100            self.script_ref.clone(),
101        );
102        let min_possible_coin = min_ada_required(&min_output, coins_per_utxo_byte)?;
103
104        let check_output = &mut min_output;
105        check_output.set_amount(Value::new(min_possible_coin, multiasset.clone()));
106
107        let required_coin = min_ada_required(check_output, coins_per_utxo_byte)?;
108
109        Ok(self.with_value(Value::new(required_coin, multiasset)))
110    }
111
112    pub fn build(self) -> Result<SingleOutputBuilderResult, OutputBuilderError> {
113        let output = TransactionOutput::new(
114            self.address,
115            self.amount.ok_or(OutputBuilderError::AmountMissing)?,
116            self.datum,
117            self.script_ref,
118        );
119        Ok(SingleOutputBuilderResult {
120            output,
121            communication_datum: self.communication_datum,
122        })
123    }
124}
125
126#[derive(Clone, Debug)]
127pub struct SingleOutputBuilderResult {
128    pub output: TransactionOutput,
129    pub communication_datum: Option<PlutusData>,
130}
131
132impl SingleOutputBuilderResult {
133    pub fn new(output: TransactionOutput) -> SingleOutputBuilderResult {
134        Self {
135            output,
136            communication_datum: None,
137        }
138    }
139}