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#[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 }
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#[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#[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 }
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 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 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 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}