cml_chain_wasm/builders/
tx_builder.rs

1use cml_chain::builders::tx_builder::{ChangeSelectionAlgo, CoinSelectionStrategyCIP2};
2use cml_core_wasm::{impl_wasm_cbor_event_serialize_api, impl_wasm_conversions};
3use cml_crypto_wasm::Ed25519KeyHash;
4use wasm_bindgen::prelude::{wasm_bindgen, JsError};
5
6use crate::{
7    address::Address,
8    assets::Mint,
9    auxdata::AuxiliaryData,
10    builders::{
11        certificate_builder::CertificateBuilderResult, input_builder::InputBuilderResult,
12        mint_builder::MintBuilderResult, output_builder::SingleOutputBuilderResult,
13        proposal_builder::ProposalBuilderResult, redeemer_builder::RedeemerWitnessKey,
14        vote_builder::VoteBuilderResult, withdrawal_builder::WithdrawalBuilderResult,
15        witness_builder::TransactionWitnessSetBuilder,
16    },
17    crypto::{BootstrapWitness, Vkeywitness},
18    fees::LinearFee,
19    plutus::{CostModels, ExUnitPrices, ExUnits, Redeemers},
20    transaction::{Transaction, TransactionBody, TransactionInput, TransactionOutput},
21    Coin, NetworkId, Slot, Value, Withdrawals,
22};
23
24#[wasm_bindgen]
25#[derive(Clone, Debug)]
26pub struct TransactionUnspentOutput(cml_chain::builders::tx_builder::TransactionUnspentOutput);
27
28impl_wasm_conversions!(
29    cml_chain::builders::tx_builder::TransactionUnspentOutput,
30    TransactionUnspentOutput
31);
32
33impl_wasm_cbor_event_serialize_api!(TransactionUnspentOutput);
34
35#[wasm_bindgen]
36impl TransactionUnspentOutput {
37    pub fn new(input: &TransactionInput, output: &TransactionOutput) -> Self {
38        cml_chain::builders::tx_builder::TransactionUnspentOutput::new(
39            input.clone().into(),
40            output.clone().into(),
41        )
42        .into()
43    }
44
45    pub fn input(&self) -> TransactionInput {
46        self.0.input.clone().into()
47    }
48
49    pub fn output(&self) -> TransactionOutput {
50        self.0.output.clone().into()
51    }
52}
53
54#[wasm_bindgen]
55#[derive(Clone, Debug)]
56pub struct TransactionBuilderConfig(cml_chain::builders::tx_builder::TransactionBuilderConfig);
57
58impl_wasm_conversions!(
59    cml_chain::builders::tx_builder::TransactionBuilderConfig,
60    TransactionBuilderConfig
61);
62
63#[wasm_bindgen]
64#[derive(Clone, Debug, Default)]
65pub struct TransactionBuilderConfigBuilder(
66    cml_chain::builders::tx_builder::TransactionBuilderConfigBuilder,
67);
68
69impl_wasm_conversions!(
70    cml_chain::builders::tx_builder::TransactionBuilderConfigBuilder,
71    TransactionBuilderConfigBuilder
72);
73
74#[wasm_bindgen]
75impl TransactionBuilderConfigBuilder {
76    pub fn new() -> Self {
77        // we have to provide new to expose it to WASM builds
78        Self::default()
79    }
80
81    pub fn fee_algo(&self, fee_algo: &LinearFee) -> Self {
82        self.0.clone().fee_algo(fee_algo.clone().into()).into()
83    }
84
85    pub fn coins_per_utxo_byte(&self, coins_per_utxo_byte: Coin) -> Self {
86        self.0
87            .clone()
88            .coins_per_utxo_byte(coins_per_utxo_byte)
89            .into()
90    }
91
92    pub fn pool_deposit(&self, pool_deposit: u64) -> Self {
93        self.0.clone().pool_deposit(pool_deposit).into()
94    }
95
96    pub fn key_deposit(&self, key_deposit: u64) -> Self {
97        self.0.clone().key_deposit(key_deposit).into()
98    }
99
100    pub fn max_value_size(&self, max_value_size: u32) -> Self {
101        self.0.clone().max_value_size(max_value_size).into()
102    }
103
104    pub fn max_tx_size(&self, max_tx_size: u32) -> Self {
105        self.0.clone().max_tx_size(max_tx_size).into()
106    }
107
108    pub fn prefer_pure_change(&self, prefer_pure_change: bool) -> Self {
109        self.0.clone().prefer_pure_change(prefer_pure_change).into()
110    }
111
112    pub fn ex_unit_prices(&self, ex_unit_prices: &ExUnitPrices) -> Self {
113        self.0
114            .clone()
115            .ex_unit_prices(ex_unit_prices.clone().into())
116            .into()
117    }
118
119    pub fn cost_models(&self, cost_models: &CostModels) -> Self {
120        self.0
121            .clone()
122            .cost_models(cost_models.clone().into())
123            .into()
124    }
125
126    pub fn collateral_percentage(&self, collateral_percentage: u32) -> Self {
127        self.0
128            .clone()
129            .collateral_percentage(collateral_percentage)
130            .into()
131    }
132
133    pub fn max_collateral_inputs(&self, max_collateral_inputs: u32) -> Self {
134        self.0
135            .clone()
136            .max_collateral_inputs(max_collateral_inputs)
137            .into()
138    }
139
140    pub fn build(&self) -> Result<TransactionBuilderConfig, JsError> {
141        self.0.clone().build().map(Into::into).map_err(Into::into)
142    }
143}
144
145#[wasm_bindgen]
146#[derive(Clone, Debug)]
147pub struct TransactionBuilder(cml_chain::builders::tx_builder::TransactionBuilder);
148
149impl_wasm_conversions!(
150    cml_chain::builders::tx_builder::TransactionBuilder,
151    TransactionBuilder
152);
153
154#[wasm_bindgen]
155impl TransactionBuilder {
156    /// This automatically selects and adds inputs from {inputs} consisting of just enough to cover
157    /// the outputs that have already been added.
158    /// This should be called after adding all certs/outputs/etc and will be an error otherwise.
159    /// Uses CIP2: https://github.com/cardano-foundation/CIPs/blob/master/CIP-0002/CIP-0002.md
160    /// Adding a change output must be called after via TransactionBuilder::add_change_if_needed()
161    /// This function, diverging from CIP2, takes into account fees and will attempt to add additional
162    /// inputs to cover the minimum fees. This does not, however, set the txbuilder's fee.
163    pub fn select_utxos(&mut self, strategy: CoinSelectionStrategyCIP2) -> Result<(), JsError> {
164        self.0.select_utxos(strategy).map_err(Into::into)
165    }
166
167    pub fn add_input(&mut self, result: &InputBuilderResult) -> Result<(), JsError> {
168        self.0.add_input(result.clone().into()).map_err(Into::into)
169    }
170
171    pub fn add_utxo(&mut self, result: &InputBuilderResult) {
172        self.0.add_utxo(result.clone().into())
173    }
174
175    /// calculates how much the fee would increase if you added a given output
176    pub fn fee_for_input(&self, result: &InputBuilderResult) -> Result<Coin, JsError> {
177        self.0.fee_for_input(result.as_ref()).map_err(Into::into)
178    }
179
180    /// Add a reference input. Must be called BEFORE adding anything (inputs, certs, etc) that refer to this reference input.
181    pub fn add_reference_input(&mut self, utxo: &TransactionUnspentOutput) {
182        self.0.add_reference_input(utxo.clone().into())
183    }
184
185    /// Add explicit output via a TransactionOutput object
186    pub fn add_output(
187        &mut self,
188        builder_result: &SingleOutputBuilderResult,
189    ) -> Result<(), JsError> {
190        self.0
191            .add_output(builder_result.clone().into())
192            .map_err(Into::into)
193    }
194
195    /// calculates how much the fee would increase if you added a given output
196    pub fn fee_for_output(&self, builder: &SingleOutputBuilderResult) -> Result<Coin, JsError> {
197        self.0.fee_for_output(builder.as_ref()).map_err(Into::into)
198    }
199
200    pub fn set_fee(&mut self, fee: Coin) {
201        self.0.set_fee(fee)
202    }
203
204    pub fn set_donation(&mut self, donation: Coin) {
205        self.0.set_donation(donation)
206    }
207
208    pub fn set_current_treasury_value(&mut self, current_treasury_value: Coin) {
209        self.0.set_current_treasury_value(current_treasury_value)
210    }
211
212    pub fn set_ttl(&mut self, ttl: Slot) {
213        self.0.set_ttl(ttl)
214    }
215
216    pub fn set_validity_start_interval(&mut self, validity_start_interval: Slot) {
217        self.0.set_validity_start_interval(validity_start_interval)
218    }
219
220    pub fn add_cert(&mut self, result: &CertificateBuilderResult) {
221        self.0.add_cert(result.clone().into())
222    }
223
224    pub fn add_proposal(&mut self, result: ProposalBuilderResult) {
225        self.0.add_proposal(result.clone().into())
226    }
227
228    pub fn add_vote(&mut self, result: VoteBuilderResult) {
229        self.0.add_vote(result.clone().into())
230    }
231
232    pub fn get_withdrawals(&self) -> Option<Withdrawals> {
233        self.0.get_withdrawals().map(|wd| wd.into())
234    }
235
236    pub fn add_withdrawal(&mut self, result: &WithdrawalBuilderResult) {
237        self.0.add_withdrawal(result.clone().into())
238    }
239
240    pub fn get_auxiliary_data(&self) -> Option<AuxiliaryData> {
241        self.0.get_auxiliary_data().map(|aux| aux.into())
242    }
243
244    pub fn set_auxiliary_data(&mut self, new_aux_data: &AuxiliaryData) {
245        self.0.set_auxiliary_data(new_aux_data.clone().into())
246    }
247
248    pub fn add_auxiliary_data(&mut self, new_aux_data: &AuxiliaryData) {
249        self.0.add_auxiliary_data(new_aux_data.clone().into())
250    }
251
252    pub fn add_mint(&mut self, result: &MintBuilderResult) -> Result<(), JsError> {
253        self.0.add_mint(result.clone().into()).map_err(Into::into)
254    }
255
256    /// Returns a copy of the current mint state in the builder
257    pub fn get_mint(&self) -> Option<Mint> {
258        self.0.get_mint().map(|m| m.into())
259    }
260
261    pub fn new(cfg: &TransactionBuilderConfig) -> Self {
262        cml_chain::builders::tx_builder::TransactionBuilder::new(cfg.clone().into()).into()
263    }
264
265    pub fn add_collateral(&mut self, result: &InputBuilderResult) -> Result<(), JsError> {
266        self.0
267            .add_collateral(result.clone().into())
268            .map_err(Into::into)
269    }
270
271    pub fn add_required_signer(&mut self, hash: &Ed25519KeyHash) {
272        self.0.add_required_signer(hash.clone().into())
273    }
274
275    pub fn set_network_id(&mut self, network_id: &NetworkId) {
276        self.0.set_network_id(network_id.clone().into())
277    }
278
279    pub fn network_id(&self) -> Option<NetworkId> {
280        self.0.network_id().map(Into::into)
281    }
282
283    /// does not include refunds or withdrawals
284    pub fn get_explicit_input(&self) -> Result<Value, JsError> {
285        self.0
286            .get_explicit_input()
287            .map(Into::into)
288            .map_err(Into::into)
289    }
290
291    /// withdrawals and refunds
292    pub fn get_implicit_input(&self) -> Result<Value, JsError> {
293        self.0
294            .get_implicit_input()
295            .map(Into::into)
296            .map_err(Into::into)
297    }
298
299    /// Return explicit input plus implicit input plus mint
300    pub fn get_total_input(&self) -> Result<Value, JsError> {
301        self.0.get_total_input().map(Into::into).map_err(Into::into)
302    }
303
304    /// Return explicit output plus implicit output plus burn (does not consider fee directly)
305    pub fn get_total_output(&self) -> Result<Value, JsError> {
306        self.0
307            .get_total_output()
308            .map(Into::into)
309            .map_err(Into::into)
310    }
311
312    /// does not include fee
313    pub fn get_explicit_output(&self) -> Result<Value, JsError> {
314        self.0
315            .get_explicit_output()
316            .map(Into::into)
317            .map_err(Into::into)
318    }
319
320    pub fn get_deposit(&self) -> Result<Coin, JsError> {
321        self.0.get_deposit().map_err(Into::into)
322    }
323
324    pub fn get_fee_if_set(&self) -> Option<Coin> {
325        self.0.get_fee_if_set()
326    }
327
328    pub fn set_collateral_return(&mut self, output: &TransactionOutput) {
329        self.0.set_collateral_return(output.clone().into())
330    }
331
332    pub fn full_size(&self) -> Result<usize, JsError> {
333        self.0.full_size().map_err(Into::into)
334    }
335
336    pub fn output_sizes(&self) -> Vec<usize> {
337        self.0.output_sizes()
338    }
339
340    // TODO: switch from ChangeSelectionAlgo to ChangeSelectionBuilder
341    /// Builds the transaction and moves to the next step redeemer units can be added and a draft tx can
342    /// be evaluated
343    /// NOTE: is_valid set to true
344    pub fn build_for_evaluation(
345        &self,
346        algo: ChangeSelectionAlgo,
347        change_address: &Address,
348    ) -> Result<TxRedeemerBuilder, JsError> {
349        self.0
350            .build_for_evaluation(algo, change_address.as_ref())
351            .map(Into::into)
352            .map_err(Into::into)
353    }
354
355    // TODO: switch from ChangeSelectionAlgo to ChangeSelectionBuilder
356    /// Builds the transaction and moves to the next step where any real witness can be added
357    /// NOTE: is_valid set to true
358    pub fn build(
359        &mut self,
360        algo: ChangeSelectionAlgo,
361        change_address: &Address,
362    ) -> Result<SignedTxBuilder, JsError> {
363        self.0
364            .build(algo, change_address.as_ref())
365            .map(Into::into)
366            .map_err(Into::into)
367    }
368
369    /// used to override the exunit values initially provided when adding inputs
370    pub fn set_exunits(&mut self, redeemer: &RedeemerWitnessKey, ex_units: &ExUnits) {
371        self.0
372            .set_exunits((*redeemer).into(), ex_units.clone().into())
373    }
374
375    /// warning: sum of all parts of a transaction must equal 0. You cannot just set the fee to the min value and forget about it
376    /// warning: min_fee may be slightly larger than the actual minimum fee (ex: a few lovelaces)
377    /// this is done to simplify the library code, but can be fixed later
378    pub fn min_fee(&self, script_calulation: bool) -> Result<Coin, JsError> {
379        self.0.min_fee(script_calulation).map_err(Into::into)
380    }
381
382    /// Warning: this function will mutate the /fee/ field
383    /// Make sure to call this function last after setting all other tx-body properties
384    /// Editing inputs, outputs, mint, etc. after change been calculated
385    /// might cause a mismatch in calculated fee versus the required fee
386    pub fn add_change_if_needed(
387        &mut self,
388        address: &Address,
389        include_exunits: bool,
390    ) -> Result<bool, JsError> {
391        cml_chain::builders::tx_builder::add_change_if_needed(
392            &mut self.0,
393            address.as_ref(),
394            include_exunits,
395        )
396        .map_err(Into::into)
397    }
398}
399
400#[wasm_bindgen]
401#[derive(Debug, Clone)]
402pub struct TxRedeemerBuilder(cml_chain::builders::tx_builder::TxRedeemerBuilder);
403
404impl_wasm_conversions!(
405    cml_chain::builders::tx_builder::TxRedeemerBuilder,
406    TxRedeemerBuilder
407);
408
409#[wasm_bindgen]
410impl TxRedeemerBuilder {
411    /// Builds the transaction and moves to the next step where any real witness can be added
412    /// NOTE: is_valid set to true
413    /// Will NOT require you to have set required signers & witnesses
414    pub fn build(&self) -> Result<Redeemers, JsError> {
415        self.0.build().map(Into::into).map_err(Into::into)
416    }
417
418    /// used to override the exunit values initially provided when adding inputs
419    pub fn set_exunits(&mut self, redeemer: &RedeemerWitnessKey, ex_units: &ExUnits) {
420        self.0
421            .set_exunits((*redeemer).into(), ex_units.clone().into())
422    }
423
424    /// Transaction body with a dummy values for redeemers & script_data_hash
425    /// Used for calculating exunits or required signers
426    pub fn draft_body(&self) -> TransactionBody {
427        self.0.draft_body().into()
428    }
429
430    pub fn auxiliary_data(&self) -> Option<AuxiliaryData> {
431        self.0.auxiliary_data().map(Into::into)
432    }
433
434    /// Transaction body with a dummy values for redeemers & script_data_hash and padded with dummy witnesses
435    /// Used for calculating exunits
436    /// note: is_valid set to true
437    pub fn draft_tx(&self) -> Result<Transaction, JsError> {
438        self.0.draft_tx().map(Into::into).map_err(Into::into)
439    }
440}
441
442#[wasm_bindgen]
443#[derive(Debug, Clone)]
444pub struct SignedTxBuilder(cml_chain::builders::tx_builder::SignedTxBuilder);
445
446impl_wasm_conversions!(
447    cml_chain::builders::tx_builder::SignedTxBuilder,
448    SignedTxBuilder
449);
450
451#[wasm_bindgen]
452impl SignedTxBuilder {
453    pub fn new_with_data(
454        body: &TransactionBody,
455        witness_set: &TransactionWitnessSetBuilder,
456        is_valid: bool,
457        auxiliary_data: &AuxiliaryData,
458    ) -> SignedTxBuilder {
459        cml_chain::builders::tx_builder::SignedTxBuilder::new_with_data(
460            body.clone().into(),
461            witness_set.clone().into(),
462            is_valid,
463            auxiliary_data.clone().into(),
464        )
465        .into()
466    }
467
468    pub fn new_without_data(
469        body: &TransactionBody,
470        witness_set: &TransactionWitnessSetBuilder,
471        is_valid: bool,
472    ) -> SignedTxBuilder {
473        cml_chain::builders::tx_builder::SignedTxBuilder::new_without_data(
474            body.clone().into(),
475            witness_set.clone().into(),
476            is_valid,
477        )
478        .into()
479    }
480
481    /**
482     * Builds the final transaction and checks that all witnesses are there
483     */
484    pub fn build_checked(&self) -> Result<Transaction, JsError> {
485        self.0
486            .clone()
487            .build_checked()
488            .map(Into::into)
489            .map_err(Into::into)
490    }
491
492    /**
493     * Builds the transaction without doing any witness checks.
494     *
495     * This can be useful if other witnesses will be added later.
496     * e.g. CIP30 signing takes a Transaction with possible witnesses
497     * to send to the wallet to fill in the missing ones.
498     */
499    pub fn build_unchecked(&self) -> Transaction {
500        self.0.clone().build_unchecked().into()
501    }
502
503    // Note: we only allow adding vkey & bootstraps at this stage
504    // This is because other witness kinds increase the tx size
505    // so they should have been added during the TransactionBuilder step
506    //
507    // However, if you manually set the fee during the TransactionBuilder step
508    // to allow adding some extra witnesses later,
509    // use `build_unchecked`
510    //
511    // Note: can't easily check inside the `add_vkey` or `add_bootstrap` functions if the user added a wrong witness
512    // This is because scripts may require keys that weren't known exactly during the tx building phase
513    pub fn add_vkey(&mut self, vkey: &Vkeywitness) {
514        self.0.add_vkey(vkey.clone().into())
515    }
516
517    pub fn add_bootstrap(&mut self, bootstrap: &BootstrapWitness) {
518        self.0.add_bootstrap(bootstrap.clone().into())
519    }
520
521    pub fn body(&self) -> TransactionBody {
522        self.0.body().into()
523    }
524
525    pub fn witness_set(&self) -> TransactionWitnessSetBuilder {
526        self.0.witness_set().into()
527    }
528
529    pub fn is_valid(&self) -> bool {
530        self.0.is_valid()
531    }
532
533    pub fn auxiliary_data(&self) -> Option<AuxiliaryData> {
534        self.0.auxiliary_data().map(|aux| aux.into())
535    }
536}