Skip to main content

pallas_txbuilder/transaction/
model.rs

1use pallas_addresses::Address as PallasAddress;
2use pallas_crypto::{
3    hash::{Hash, Hasher},
4    key::ed25519,
5};
6use pallas_primitives::{conway, Fragment, NonEmptySet};
7use pallas_wallet::PrivateKey;
8
9use std::{collections::HashMap, ops::Deref};
10
11use serde::{Deserialize, Serialize};
12
13use crate::{scriptdata, TxBuilderError};
14
15use super::{
16    AssetName, Bytes, Bytes32, Bytes64, DatumBytes, DatumHash, Hash28, PolicyId, PubKeyHash,
17    PublicKey, ScriptBytes, ScriptHash, Signature, TransactionStatus, TxHash,
18};
19
20// TODO: Don't make wrapper types public
21#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
22pub struct StagingTransaction {
23    pub version: String,
24    pub status: TransactionStatus,
25    pub inputs: Option<Vec<Input>>,
26    pub reference_inputs: Option<Vec<Input>>,
27    pub outputs: Option<Vec<Output>>,
28    pub fee: Option<u64>,
29    pub mint: Option<MintAssets>,
30    pub valid_from_slot: Option<u64>,
31    pub invalid_from_slot: Option<u64>,
32    pub network_id: Option<u8>,
33    pub collateral_inputs: Option<Vec<Input>>,
34    pub collateral_output: Option<Output>,
35    pub disclosed_signers: Option<Vec<PubKeyHash>>,
36    pub scripts: Option<HashMap<ScriptHash, Script>>,
37    pub datums: Option<HashMap<DatumHash, DatumBytes>>,
38    pub redeemers: Option<Redeemers>,
39    pub script_data_hash: Option<Bytes32>,
40    pub signature_amount_override: Option<u8>,
41    pub change_address: Option<Address>,
42    pub language_view: Option<scriptdata::LanguageView>,
43    // pub certificates: TODO
44    // pub withdrawals: TODO
45    // pub updates: TODO
46    // pub auxiliary_data: TODO
47    // pub phase_2_valid: TODO
48}
49
50impl StagingTransaction {
51    pub fn new() -> Self {
52        Self {
53            version: String::from("v1"),
54            status: TransactionStatus::Staging,
55            ..Default::default()
56        }
57    }
58
59    pub fn input(mut self, input: Input) -> Self {
60        let mut txins = self.inputs.unwrap_or_default();
61        txins.push(input);
62        self.inputs = Some(txins);
63        self
64    }
65
66    pub fn remove_input(mut self, input: Input) -> Self {
67        let mut txins = self.inputs.unwrap_or_default();
68        txins.retain(|x| *x != input);
69        self.inputs = Some(txins);
70        self
71    }
72
73    pub fn reference_input(mut self, input: Input) -> Self {
74        let mut ref_txins = self.reference_inputs.unwrap_or_default();
75        ref_txins.push(input);
76        self.reference_inputs = Some(ref_txins);
77        self
78    }
79
80    pub fn remove_reference_input(mut self, input: Input) -> Self {
81        let mut ref_txins = self.reference_inputs.unwrap_or_default();
82        ref_txins.retain(|x| *x != input);
83        self.reference_inputs = Some(ref_txins);
84        self
85    }
86
87    pub fn output(mut self, output: Output) -> Self {
88        let mut txouts = self.outputs.unwrap_or_default();
89        txouts.push(output);
90        self.outputs = Some(txouts);
91        self
92    }
93
94    pub fn remove_output(mut self, index: usize) -> Self {
95        let mut txouts = self.outputs.unwrap_or_default();
96        txouts.remove(index);
97        self.outputs = Some(txouts);
98        self
99    }
100
101    pub fn fee(mut self, fee: u64) -> Self {
102        self.fee = Some(fee);
103        self
104    }
105
106    pub fn clear_fee(mut self) -> Self {
107        self.fee = None;
108        self
109    }
110
111    pub fn mint_asset(
112        mut self,
113        policy: Hash<28>,
114        name: Vec<u8>,
115        amount: i64,
116    ) -> Result<Self, TxBuilderError> {
117        if name.len() > 32 {
118            return Err(TxBuilderError::AssetNameTooLong);
119        }
120
121        let mut mint = self.mint.map(|x| x.0).unwrap_or_default();
122
123        mint.entry(Hash28(*policy))
124            .and_modify(|policy_map| {
125                policy_map
126                    .entry(name.clone().into())
127                    .and_modify(|asset_map| {
128                        *asset_map += amount;
129                    })
130                    .or_insert(amount);
131            })
132            .or_insert_with(|| {
133                let mut map: HashMap<Bytes, i64> = HashMap::new();
134                map.insert(name.clone().into(), amount);
135                map
136            });
137
138        self.mint = Some(MintAssets(mint));
139
140        Ok(self)
141    }
142
143    pub fn remove_mint_asset(mut self, policy: Hash<28>, name: Vec<u8>) -> Self {
144        let mut mint = if let Some(mint) = self.mint {
145            mint.0
146        } else {
147            return self;
148        };
149
150        if let Some(assets) = mint.get_mut(&Hash28(*policy)) {
151            assets.remove(&name.into());
152            if assets.is_empty() {
153                mint.remove(&Hash28(*policy));
154            }
155        }
156
157        self.mint = Some(MintAssets(mint));
158
159        self
160    }
161
162    pub fn valid_from_slot(mut self, slot: u64) -> Self {
163        self.valid_from_slot = Some(slot);
164        self
165    }
166
167    pub fn clear_valid_from_slot(mut self) -> Self {
168        self.valid_from_slot = None;
169        self
170    }
171
172    pub fn invalid_from_slot(mut self, slot: u64) -> Self {
173        self.invalid_from_slot = Some(slot);
174        self
175    }
176
177    pub fn clear_invalid_from_slot(mut self) -> Self {
178        self.invalid_from_slot = None;
179        self
180    }
181
182    pub fn network_id(mut self, id: u8) -> Self {
183        self.network_id = Some(id);
184        self
185    }
186
187    pub fn clear_network_id(mut self) -> Self {
188        self.network_id = None;
189        self
190    }
191
192    pub fn collateral_input(mut self, input: Input) -> Self {
193        let mut coll_ins = self.collateral_inputs.unwrap_or_default();
194        coll_ins.push(input);
195        self.collateral_inputs = Some(coll_ins);
196        self
197    }
198
199    pub fn remove_collateral_input(mut self, input: Input) -> Self {
200        let mut coll_ins = self.collateral_inputs.unwrap_or_default();
201        coll_ins.retain(|x| *x != input);
202        self.collateral_inputs = Some(coll_ins);
203        self
204    }
205
206    pub fn collateral_output(mut self, output: Output) -> Self {
207        self.collateral_output = Some(output);
208        self
209    }
210
211    pub fn clear_collateral_output(mut self) -> Self {
212        self.collateral_output = None;
213        self
214    }
215
216    pub fn disclosed_signer(mut self, pub_key_hash: Hash<28>) -> Self {
217        let mut disclosed_signers = self.disclosed_signers.unwrap_or_default();
218        disclosed_signers.push(Hash28(*pub_key_hash));
219        self.disclosed_signers = Some(disclosed_signers);
220        self
221    }
222
223    pub fn remove_disclosed_signer(mut self, pub_key_hash: Hash<28>) -> Self {
224        let mut disclosed_signers = self.disclosed_signers.unwrap_or_default();
225        disclosed_signers.retain(|x| *x != Hash28(*pub_key_hash));
226        self.disclosed_signers = Some(disclosed_signers);
227        self
228    }
229
230    pub fn script(mut self, language: ScriptKind, bytes: Vec<u8>) -> Self {
231        let mut scripts = self.scripts.unwrap_or_default();
232
233        let hash = match language {
234            ScriptKind::Native => Hasher::<224>::hash_tagged(bytes.as_ref(), 0),
235            ScriptKind::PlutusV1 => Hasher::<224>::hash_tagged(bytes.as_ref(), 1),
236            ScriptKind::PlutusV2 => Hasher::<224>::hash_tagged(bytes.as_ref(), 2),
237            ScriptKind::PlutusV3 => Hasher::<224>::hash_tagged(bytes.as_ref(), 3),
238        };
239
240        scripts.insert(
241            Hash28(*hash),
242            Script {
243                kind: language,
244                bytes: bytes.into(),
245            },
246        );
247
248        self.scripts = Some(scripts);
249        self
250    }
251
252    pub fn remove_script_by_hash(mut self, script_hash: Hash<28>) -> Self {
253        let mut scripts = self.scripts.unwrap_or_default();
254
255        scripts.remove(&Hash28(*script_hash));
256
257        self.scripts = Some(scripts);
258        self
259    }
260
261    pub fn datum(mut self, datum: Vec<u8>) -> Self {
262        let mut datums = self.datums.unwrap_or_default();
263
264        let hash = Hasher::<256>::hash_cbor(&datum);
265
266        datums.insert(Bytes32(*hash), datum.into());
267        self.datums = Some(datums);
268        self
269    }
270
271    pub fn remove_datum(mut self, datum: Vec<u8>) -> Self {
272        let mut datums = self.datums.unwrap_or_default();
273
274        let hash = Hasher::<256>::hash_cbor(&datum);
275
276        datums.remove(&Bytes32(*hash));
277        self.datums = Some(datums);
278        self
279    }
280
281    pub fn remove_datum_by_hash(mut self, datum_hash: Hash<32>) -> Self {
282        let mut datums = self.datums.unwrap_or_default();
283
284        datums.remove(&Bytes32(*datum_hash));
285        self.datums = Some(datums);
286        self
287    }
288
289    pub fn language_view(mut self, plutus_version: ScriptKind, cost_model: Vec<i64>) -> Self {
290        self.language_view = match plutus_version {
291            ScriptKind::PlutusV1 => Some(scriptdata::LanguageView(0, cost_model)),
292            ScriptKind::PlutusV2 => Some(scriptdata::LanguageView(1, cost_model)),
293            ScriptKind::PlutusV3 => Some(scriptdata::LanguageView(2, cost_model)),
294            ScriptKind::Native => None,
295        };
296
297        self
298    }
299
300    pub fn add_spend_redeemer(
301        mut self,
302        input: Input,
303        plutus_data: Vec<u8>,
304        ex_units: Option<ExUnits>,
305    ) -> Self {
306        let mut rdmrs = self.redeemers.map(|x| x.0).unwrap_or_default();
307
308        rdmrs.insert(
309            RedeemerPurpose::Spend(input),
310            (plutus_data.into(), ex_units),
311        );
312
313        self.redeemers = Some(Redeemers(rdmrs));
314
315        self
316    }
317
318    pub fn remove_spend_redeemer(mut self, input: Input) -> Self {
319        let mut rdmrs = self.redeemers.map(|x| x.0).unwrap_or_default();
320
321        rdmrs.remove(&RedeemerPurpose::Spend(input));
322
323        self.redeemers = Some(Redeemers(rdmrs));
324
325        self
326    }
327
328    pub fn add_mint_redeemer(
329        mut self,
330        policy: Hash<28>,
331        plutus_data: Vec<u8>,
332        ex_units: Option<ExUnits>,
333    ) -> Self {
334        let mut rdmrs = self.redeemers.map(|x| x.0).unwrap_or_default();
335
336        rdmrs.insert(
337            RedeemerPurpose::Mint(Hash28(*policy)),
338            (plutus_data.into(), ex_units),
339        );
340
341        self.redeemers = Some(Redeemers(rdmrs));
342
343        self
344    }
345
346    pub fn remove_mint_redeemer(mut self, policy: Hash<28>) -> Self {
347        let mut rdmrs = self.redeemers.map(|x| x.0).unwrap_or_default();
348
349        rdmrs.remove(&RedeemerPurpose::Mint(Hash28(*policy)));
350
351        self.redeemers = Some(Redeemers(rdmrs));
352
353        self
354    }
355
356    pub fn signature_amount_override(mut self, amount: u8) -> Self {
357        self.signature_amount_override = Some(amount);
358        self
359    }
360
361    pub fn clear_signature_amount_override(mut self) -> Self {
362        self.signature_amount_override = None;
363        self
364    }
365
366    pub fn change_address(mut self, address: PallasAddress) -> Self {
367        self.change_address = Some(Address(address));
368        self
369    }
370
371    pub fn clear_change_address(mut self) -> Self {
372        self.change_address = None;
373        self
374    }
375}
376
377// TODO: Don't want our wrapper types in fields public
378#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Hash, Clone)]
379pub struct Input {
380    pub tx_hash: TxHash,
381    pub txo_index: u64,
382}
383
384impl Input {
385    pub fn new(tx_hash: Hash<32>, txo_index: u64) -> Self {
386        Self {
387            tx_hash: Bytes32(*tx_hash),
388            txo_index,
389        }
390    }
391}
392
393// TODO: Don't want our wrapper types in fields public
394#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
395pub struct Output {
396    pub address: Address,
397    pub lovelace: u64,
398    pub assets: Option<OutputAssets>,
399    pub datum: Option<Datum>,
400    pub script: Option<Script>,
401}
402
403impl Output {
404    pub fn new(address: PallasAddress, lovelace: u64) -> Self {
405        Self {
406            address: Address(address),
407            lovelace,
408            assets: None,
409            datum: None,
410            script: None,
411        }
412    }
413
414    pub fn add_asset(
415        mut self,
416        policy: Hash<28>,
417        name: Vec<u8>,
418        amount: u64,
419    ) -> Result<Self, TxBuilderError> {
420        if name.len() > 32 {
421            return Err(TxBuilderError::AssetNameTooLong);
422        }
423
424        let mut assets = self.assets.map(|x| x.0).unwrap_or_default();
425
426        assets
427            .entry(Hash28(*policy))
428            .and_modify(|policy_map| {
429                policy_map
430                    .entry(name.clone().into())
431                    .and_modify(|asset_map| {
432                        *asset_map += amount;
433                    })
434                    .or_insert(amount);
435            })
436            .or_insert_with(|| {
437                let mut map: HashMap<Bytes, u64> = HashMap::new();
438                map.insert(name.clone().into(), amount);
439                map
440            });
441
442        self.assets = Some(OutputAssets(assets));
443
444        Ok(self)
445    }
446
447    pub fn set_inline_datum(mut self, plutus_data: Vec<u8>) -> Self {
448        self.datum = Some(Datum {
449            kind: DatumKind::Inline,
450            bytes: plutus_data.into(),
451        });
452
453        self
454    }
455
456    pub fn set_datum_hash(mut self, datum_hash: Hash<32>) -> Self {
457        self.datum = Some(Datum {
458            kind: DatumKind::Hash,
459            bytes: datum_hash.to_vec().into(),
460        });
461
462        self
463    }
464
465    pub fn set_inline_script(mut self, language: ScriptKind, bytes: Vec<u8>) -> Self {
466        self.script = Some(Script {
467            kind: language,
468            bytes: bytes.into(),
469        });
470
471        self
472    }
473}
474
475#[derive(PartialEq, Eq, Debug, Clone, Default)]
476pub struct OutputAssets(HashMap<PolicyId, HashMap<AssetName, u64>>);
477
478impl Deref for OutputAssets {
479    type Target = HashMap<PolicyId, HashMap<Bytes, u64>>;
480
481    fn deref(&self) -> &Self::Target {
482        &self.0
483    }
484}
485
486impl OutputAssets {
487    pub fn from_map(map: HashMap<PolicyId, HashMap<Bytes, u64>>) -> Self {
488        Self(map)
489    }
490}
491
492#[derive(PartialEq, Eq, Debug, Clone, Default)]
493pub struct MintAssets(HashMap<PolicyId, HashMap<AssetName, i64>>);
494
495impl Deref for MintAssets {
496    type Target = HashMap<PolicyId, HashMap<Bytes, i64>>;
497
498    fn deref(&self) -> &Self::Target {
499        &self.0
500    }
501}
502
503impl MintAssets {
504    pub fn new() -> Self {
505        MintAssets(HashMap::new())
506    }
507
508    pub fn from_map(map: HashMap<PolicyId, HashMap<Bytes, i64>>) -> Self {
509        Self(map)
510    }
511}
512
513#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy)]
514#[serde(rename_all = "snake_case")]
515pub enum ScriptKind {
516    Native,
517    PlutusV1,
518    PlutusV2,
519    PlutusV3,
520}
521
522#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
523pub struct Script {
524    pub kind: ScriptKind,
525    pub bytes: ScriptBytes,
526}
527
528impl Script {
529    pub fn new(kind: ScriptKind, bytes: Vec<u8>) -> Self {
530        Self {
531            kind,
532            bytes: bytes.into(),
533        }
534    }
535}
536
537#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
538#[serde(rename_all = "snake_case")]
539pub enum DatumKind {
540    Hash,
541    Inline,
542}
543
544#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
545pub struct Datum {
546    pub kind: DatumKind,
547    pub bytes: DatumBytes,
548}
549
550#[derive(PartialEq, Eq, Hash, Debug, Clone)]
551pub enum RedeemerPurpose {
552    Spend(Input),
553    Mint(PolicyId),
554    // Reward TODO
555    // Cert TODO
556}
557
558#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
559pub struct ExUnits {
560    pub mem: u64,
561    pub steps: u64,
562}
563
564#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default, Clone)]
565pub struct Redeemers(HashMap<RedeemerPurpose, (Bytes, Option<ExUnits>)>);
566
567impl Deref for Redeemers {
568    type Target = HashMap<RedeemerPurpose, (Bytes, Option<ExUnits>)>;
569
570    fn deref(&self) -> &Self::Target {
571        &self.0
572    }
573}
574
575impl Redeemers {
576    pub fn from_map(map: HashMap<RedeemerPurpose, (Bytes, Option<ExUnits>)>) -> Self {
577        Self(map)
578    }
579}
580
581#[derive(PartialEq, Eq, Debug, Clone)]
582pub struct Address(pub PallasAddress);
583
584impl Deref for Address {
585    type Target = PallasAddress;
586
587    fn deref(&self) -> &Self::Target {
588        &self.0
589    }
590}
591
592impl From<PallasAddress> for Address {
593    fn from(value: PallasAddress) -> Self {
594        Self(value)
595    }
596}
597
598#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
599#[serde(rename_all = "snake_case")]
600pub enum BuilderEra {
601    Babbage,
602    Conway,
603}
604
605#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
606pub struct BuiltTransaction {
607    pub version: String,
608    pub era: BuilderEra,
609    pub status: TransactionStatus,
610    pub tx_hash: TxHash,
611    pub tx_bytes: Bytes,
612    pub signatures: Option<HashMap<PublicKey, Signature>>,
613}
614
615impl BuiltTransaction {
616    pub fn sign(mut self, private_key: PrivateKey) -> Result<Self, TxBuilderError> {
617        let pubkey: [u8; 32] = private_key
618            .public_key()
619            .as_ref()
620            .try_into()
621            .map_err(|_| TxBuilderError::MalformedKey)?;
622
623        let signature: [u8; ed25519::Signature::SIZE] = private_key
624            .sign(self.tx_hash.0)
625            .as_ref()
626            .try_into()
627            .unwrap();
628
629        match self.era {
630            BuilderEra::Conway => {
631                let mut new_sigs = self.signatures.unwrap_or_default();
632
633                new_sigs.insert(Bytes32(pubkey), Bytes64(signature));
634
635                self.signatures = Some(new_sigs);
636
637                // TODO: chance for serialisation round trip issues?
638                let mut tx = conway::Tx::decode_fragment(&self.tx_bytes.0)
639                    .map_err(|_| TxBuilderError::CorruptedTxBytes)?;
640
641                let mut vkey_witnesses = tx
642                    .transaction_witness_set
643                    .vkeywitness
644                    .map(|x| x.to_vec())
645                    .unwrap_or_default();
646
647                vkey_witnesses.push(conway::VKeyWitness {
648                    vkey: Vec::from(pubkey.as_ref()).into(),
649                    signature: Vec::from(signature.as_ref()).into(),
650                });
651
652                tx.transaction_witness_set.vkeywitness =
653                    Some(NonEmptySet::from_vec(vkey_witnesses).unwrap());
654
655                self.tx_bytes = tx.encode_fragment().unwrap().into();
656            }
657            _ => return Err(TxBuilderError::UnsupportedEra),
658        }
659
660        Ok(self)
661    }
662
663    pub fn add_signature(
664        mut self,
665        pub_key: ed25519::PublicKey,
666        signature: [u8; 64],
667    ) -> Result<Self, TxBuilderError> {
668        match self.era {
669            BuilderEra::Conway => {
670                let mut new_sigs = self.signatures.unwrap_or_default();
671
672                new_sigs.insert(
673                    Bytes32(
674                        pub_key
675                            .as_ref()
676                            .try_into()
677                            .map_err(|_| TxBuilderError::MalformedKey)?,
678                    ),
679                    Bytes64(signature),
680                );
681
682                self.signatures = Some(new_sigs);
683
684                // TODO: chance for serialisation round trip issues?
685                let mut tx = conway::Tx::decode_fragment(&self.tx_bytes.0)
686                    .map_err(|_| TxBuilderError::CorruptedTxBytes)?;
687
688                let mut vkey_witnesses = tx
689                    .transaction_witness_set
690                    .vkeywitness
691                    .map(|x| x.to_vec())
692                    .unwrap_or_default();
693
694                vkey_witnesses.push(conway::VKeyWitness {
695                    vkey: Vec::from(pub_key.as_ref()).into(),
696                    signature: Vec::from(signature.as_ref()).into(),
697                });
698
699                tx.transaction_witness_set.vkeywitness =
700                    Some(NonEmptySet::from_vec(vkey_witnesses).unwrap());
701
702                self.tx_bytes = tx.encode_fragment().unwrap().into();
703            }
704            _ => return Err(TxBuilderError::UnsupportedEra),
705        }
706
707        Ok(self)
708    }
709
710    pub fn remove_signature(mut self, pub_key: ed25519::PublicKey) -> Result<Self, TxBuilderError> {
711        match self.era {
712            BuilderEra::Conway => {
713                let mut new_sigs = self.signatures.unwrap_or_default();
714
715                let pk = Bytes32(
716                    pub_key
717                        .as_ref()
718                        .try_into()
719                        .map_err(|_| TxBuilderError::MalformedKey)?,
720                );
721
722                new_sigs.remove(&pk);
723
724                self.signatures = Some(new_sigs);
725
726                // TODO: chance for serialisation round trip issues?
727                let mut tx = conway::Tx::decode_fragment(&self.tx_bytes.0)
728                    .map_err(|_| TxBuilderError::CorruptedTxBytes)?;
729
730                let mut vkey_witnesses = tx
731                    .transaction_witness_set
732                    .vkeywitness
733                    .map(|x| x.to_vec())
734                    .unwrap_or_default();
735
736                vkey_witnesses.retain(|x| *x.vkey != pk.0.to_vec());
737
738                tx.transaction_witness_set.vkeywitness =
739                    Some(NonEmptySet::from_vec(vkey_witnesses).unwrap());
740
741                self.tx_bytes = tx.encode_fragment().unwrap().into();
742            }
743            _ => return Err(TxBuilderError::UnsupportedEra),
744        }
745
746        Ok(self)
747    }
748}