tx_bakery/
tx_info_builder.rs

1//! Transaction Info builder
2
3use num_bigint::BigInt;
4use plutus_ledger_api::v3::{
5    address::Credential,
6    assoc_map::AssocMap,
7    crypto::{LedgerBytes, PaymentPubKeyHash},
8    datum::{Datum, DatumHash},
9    interval::Interval,
10    redeemer::Redeemer,
11    transaction::{
12        GovernanceActionId, POSIXTimeRange, ProposalProcedure, ScriptPurpose, TransactionHash,
13        TransactionInfo, TransactionInput, TransactionOutput, TxCert, TxInInfo, Vote, Voter,
14    },
15    value::{AssetClass, Lovelace, Value},
16};
17use std::collections::{BTreeMap, BTreeSet};
18
19/// Simple TransactionInfo builder
20pub struct TxScaffold {
21    inputs: BTreeMap<TransactionInput, TxScaffoldInput>,
22    reference_inputs: BTreeMap<TransactionInput, TransactionOutput>,
23    outputs: Vec<TransactionOutput>,
24    mint: Vec<(AssetClass, i64, Redeemer)>,
25    tx_certs: Vec<TxCert>,
26    withdrawals: BTreeMap<Credential, u64>,
27    valid_range: POSIXTimeRange,
28    signatories: BTreeSet<PaymentPubKeyHash>,
29    votes: BTreeMap<Voter, BTreeMap<GovernanceActionId, Vote>>,
30    proposal_procedures: Vec<ProposalProcedure>,
31    current_treasury_amount: Option<BigInt>,
32    treasury_donation: Option<BigInt>,
33}
34
35/// Input to a transaction
36pub enum TxScaffoldInput {
37    /// Input from a public key wallet
38    PubKeyInput { output: TransactionOutput },
39    /// Input from a validator address with the attached datum and redeemer
40    ScriptInput {
41        output: TransactionOutput,
42        datum: Option<DatumFromWitness>,
43        redeemer: Redeemer,
44    },
45}
46
47/// Datum and its hash
48pub type DatumFromWitness = (DatumHash, Datum);
49
50impl TxScaffoldInput {
51    fn output(&self) -> TransactionOutput {
52        match self {
53            TxScaffoldInput::PubKeyInput { output } => output.clone(),
54            TxScaffoldInput::ScriptInput { output, .. } => output.clone(),
55        }
56    }
57}
58
59impl Default for TxScaffold {
60    fn default() -> Self {
61        TxScaffold {
62            inputs: BTreeMap::new(),
63            reference_inputs: BTreeMap::new(),
64            outputs: Vec::new(),
65            mint: Vec::new(),
66            tx_certs: Vec::new(),
67            withdrawals: BTreeMap::new(),
68            valid_range: Interval::Always.into(),
69            signatories: BTreeSet::new(),
70            votes: BTreeMap::new(),
71            proposal_procedures: Vec::new(),
72            current_treasury_amount: None,
73            treasury_donation: None,
74        }
75    }
76}
77
78impl TxScaffold {
79    /// Start an empty scaffold
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    /// Add a transaction input
85    pub fn add_input(mut self, reference: TransactionInput, input: TxScaffoldInput) -> Self {
86        self.inputs.insert(reference, input);
87        self
88    }
89
90    /// Add an inpup from a public key wallet
91    pub fn add_pub_key_input(
92        mut self,
93        reference: TransactionInput,
94        output: TransactionOutput,
95    ) -> Self {
96        self.inputs
97            .insert(reference, TxScaffoldInput::PubKeyInput { output });
98        self
99    }
100
101    /// Add a input from a validator address
102    pub fn add_script_input(
103        mut self,
104        reference: TransactionInput,
105        output: TransactionOutput,
106        datum: Option<DatumFromWitness>,
107        redeemer: Redeemer,
108    ) -> Self {
109        self.inputs.insert(
110            reference,
111            TxScaffoldInput::ScriptInput {
112                output,
113                datum,
114                redeemer,
115            },
116        );
117        self
118    }
119
120    /// Add multiple transaction inputs
121    pub fn add_inputs(mut self, mut inputs: BTreeMap<TransactionInput, TxScaffoldInput>) -> Self {
122        self.inputs.append(&mut inputs);
123        self
124    }
125
126    /// Add a reference input
127    pub fn add_reference_input(
128        mut self,
129        reference: TransactionInput,
130        output: TransactionOutput,
131    ) -> Self {
132        self.reference_inputs.insert(reference, output);
133        self
134    }
135
136    /// Add multiple reference inputs
137    pub fn add_reference_inputs(
138        mut self,
139        mut inputs: BTreeMap<TransactionInput, TransactionOutput>,
140    ) -> Self {
141        self.reference_inputs.append(&mut inputs);
142        self
143    }
144
145    /// Add a transaction output
146    /// Output order will be preserved
147    pub fn add_output(mut self, output: TransactionOutput) -> Self {
148        self.outputs.push(output);
149        self
150    }
151
152    /// Add multiple transaction outputs
153    pub fn add_outputs(mut self, mut outputs: Vec<TransactionOutput>) -> Self {
154        self.outputs.append(&mut outputs);
155        self
156    }
157
158    /// Add new minted tokens with their corresponding redeemer
159    pub fn add_mint(mut self, asset_class: AssetClass, amount: i64, redeemer: Redeemer) -> Self {
160        self.mint.push((asset_class, amount, redeemer));
161        self
162    }
163
164    /// Add a Tx Cert
165    pub fn add_txcert(mut self, dcert: TxCert) -> Self {
166        self.tx_certs.push(dcert);
167        self
168    }
169
170    /// Add multiple TxCerts
171    pub fn add_dcerts(mut self, mut dcerts: Vec<TxCert>) -> Self {
172        self.tx_certs.append(&mut dcerts);
173        self
174    }
175
176    /// Add a withdrawal
177    pub fn add_withdrawals(mut self, mut withdrawals: BTreeMap<Credential, u64>) -> Self {
178        self.withdrawals.append(&mut withdrawals);
179        self
180    }
181
182    /// Add multiple withdrawals
183    pub fn add_withdrawal(mut self, staking_credential: Credential, amount: u64) -> Self {
184        self.withdrawals.insert(staking_credential, amount);
185        self
186    }
187
188    /// Set the validity range of the transaction
189    pub fn set_valid_range(mut self, valid_range: POSIXTimeRange) -> Self {
190        self.valid_range = valid_range;
191        self
192    }
193
194    /// Add a required signer of the transaction
195    pub fn add_signatory(mut self, signatory: PaymentPubKeyHash) -> Self {
196        self.signatories.insert(signatory);
197        self
198    }
199
200    /// Add multiple required signers of the transaction
201    pub fn add_signatories(mut self, mut signatories: BTreeSet<PaymentPubKeyHash>) -> Self {
202        self.signatories.append(&mut signatories);
203        self
204    }
205
206    pub fn add_votes(
207        mut self,
208        mut votes: BTreeMap<Voter, BTreeMap<GovernanceActionId, Vote>>,
209    ) -> Self {
210        self.votes.append(&mut votes);
211        self
212    }
213
214    pub fn add_proposal_procedures(
215        mut self,
216        mut proposal_procedures: Vec<ProposalProcedure>,
217    ) -> Self {
218        self.proposal_procedures.append(&mut proposal_procedures);
219        self
220    }
221
222    pub fn add_current_treasury_amount(mut self, current_treasury_amount: Option<BigInt>) -> Self {
223        self.current_treasury_amount = current_treasury_amount;
224        self
225    }
226    pub fn add_treasury_donation(mut self, treasury_donation: Option<BigInt>) -> Self {
227        self.treasury_donation = treasury_donation;
228        self
229    }
230
231    /// Build a TransactionInfo
232    pub fn build(self) -> TransactionInfo {
233        TransactionInfo {
234            inputs: self
235                .inputs
236                .iter()
237                .map(|(reference, scaffold_in)| {
238                    TxInInfo {
239                        reference: reference.clone(),
240                        output: scaffold_in.output(),
241                    }
242                    .clone()
243                })
244                .collect(),
245            reference_inputs: self
246                .reference_inputs
247                .iter()
248                .map(|(reference, output)| TxInInfo {
249                    reference: reference.clone(),
250                    output: output.clone(),
251                })
252                .collect(),
253            outputs: self.outputs,
254            fee: Lovelace(BigInt::ZERO),
255            mint: self
256                .mint
257                .iter()
258                .fold(Value::new(), |others, (asset_class, amount, _red)| {
259                    others
260                        + Value::token_value(
261                            &asset_class.currency_symbol,
262                            &asset_class.token_name,
263                            &BigInt::from(*amount),
264                        )
265                }),
266            tx_certs: self.tx_certs,
267            wdrl: AssocMap(
268                self.withdrawals
269                    .into_iter()
270                    .map(|(credential, amount)| (credential, Lovelace(BigInt::from(amount))))
271                    .collect(),
272            ),
273            valid_range: self.valid_range,
274            signatories: self.signatories.into_iter().collect(),
275            redeemers: AssocMap(
276                self.inputs
277                    .iter()
278                    .filter_map(|(reference, scaffold_in)| match scaffold_in {
279                        TxScaffoldInput::ScriptInput { redeemer, .. } => {
280                            Some((ScriptPurpose::Spending(reference.clone()), redeemer.clone()))
281                        }
282                        TxScaffoldInput::PubKeyInput { .. } => None,
283                    })
284                    .chain(self.mint.iter().map(|(asset_class, _amount, red)| {
285                        (
286                            ScriptPurpose::Minting(asset_class.currency_symbol.clone()),
287                            red.clone(),
288                        )
289                    }))
290                    .collect(),
291            ),
292            datums: AssocMap(
293                self.inputs
294                    .into_iter()
295                    .filter_map(|(_reference, scaffold_in)| match scaffold_in {
296                        TxScaffoldInput::ScriptInput { datum, .. } => datum,
297                        TxScaffoldInput::PubKeyInput { .. } => None,
298                    })
299                    .collect(),
300            ),
301            id: TransactionHash(LedgerBytes(Vec::new())),
302            votes: AssocMap(
303                self.votes
304                    .into_iter()
305                    .map(|(voter, votes)| (voter, AssocMap(votes.into_iter().collect())))
306                    .collect(),
307            ),
308            proposal_procedures: self.proposal_procedures,
309            current_treasury_amount: self.current_treasury_amount.map(Lovelace),
310            treasury_donation: self.treasury_donation.map(Lovelace),
311        }
312    }
313}