whisky_pallas/tx_builder/
core_pallas.rs

1use std::collections::HashMap;
2
3use pallas::codec::utils::PositiveCoin;
4use pallas::ledger::primitives::conway::{
5    LanguageView, Redeemer as PallasRedeemer, RedeemerTag as PallasRedeemerTag, ScriptData,
6    Value as PallasValue, WitnessSet as PallasWitnessSet,
7};
8use pallas::ledger::primitives::Fragment;
9use whisky_common::{get_cost_models_from_network, MintItem, Output, PubKeyTxIn, RefTxIn};
10use whisky_common::{
11    Certificate as WhiskyCertificate,
12    Certificate::{BasicCertificate, ScriptCertificate, SimpleScriptCertificate},
13    CertificateType,
14    DatumSource::{self, InlineDatumSource, ProvidedDatumSource},
15    LanguageVersion,
16    ScriptSource::{self, InlineScriptSource, ProvidedScriptSource},
17    SimpleScriptTxInParameter::{InlineSimpleScriptSource, ProvidedSimpleScriptSource},
18    TxBuilderBody, TxIn, Vote as WhiskyVote, WError, Withdrawal as WhiskyWithdrawal,
19    Withdrawal::{PlutusScriptWithdrawal, PubKeyWithdrawal, SimpleScriptWithdrawal},
20};
21
22use crate::utils::{calculate_fee, required_signatures_to_mock_witnesses};
23use crate::{
24    converter::{bytes_from_bech32, convert_value},
25    wrapper::{
26        transaction_body::{
27            Anchor, Certificate, CertificateKind, DRep, DRepKind, Datum, DatumKind, GovActionId,
28            MultiassetNonZeroInt, MultiassetPositiveCoin, NetworkId, NetworkIdKind, PoolMetadata,
29            Relay, RelayKind, RequiredSigners, RewardAccount, ScriptRef, ScriptRefKind,
30            StakeCredential, StakeCredentialKind, Transaction, TransactionBody, TransactionInput,
31            TransactionOutput, Value, Vote, VoteKind, Voter, VoterKind, VotingProdecedure,
32        },
33        witness_set::{
34            native_script::NativeScript,
35            plutus_data::PlutusData,
36            plutus_script::PlutusScript,
37            redeemer::{ExUnits, Redeemer, RedeemerTag},
38            witness_set::WitnessSet,
39        },
40    },
41};
42
43#[derive(Clone, Debug)]
44pub struct CorePallas {
45    pub protocol_params: whisky_common::Protocol,
46
47    // Required info for balancing transaction
48    pub inputs_map: HashMap<TransactionInput, Value>,
49    pub collaterals_map: HashMap<TransactionInput, Value>,
50    pub script_source_ref_inputs: Vec<RefTxIn>,
51    pub total_script_size: usize,
52    pub required_signatures_vec: Vec<String>,
53
54    // Required info for generating witness set
55    pub native_scripts_vec: Vec<NativeScript>,
56    pub plutus_v1_scripts_vec: Vec<PlutusScript<1>>,
57    pub plutus_v2_scripts_vec: Vec<PlutusScript<2>>,
58    pub plutus_v3_scripts_vec: Vec<PlutusScript<3>>,
59    pub plutus_v1_used: bool,
60    pub plutus_v2_used: bool,
61    pub plutus_v3_used: bool,
62    pub input_redeemers_vec: Vec<(TransactionInput, Redeemer)>,
63    pub certificate_redeemers_vec: Vec<(Certificate, Redeemer)>,
64    pub withdrawal_redeemers_vec: Vec<(RewardAccount, Redeemer)>,
65    pub mint_redeemers_vec: Vec<(String, Redeemer)>,
66    pub vote_redeemers_vec: Vec<(Voter, Redeemer)>,
67    pub plutus_data_vec: Vec<PlutusData>,
68
69    // Potential reference inputs (shouldn't overlap with actual inputs)
70    pub ref_inputs_vec: Vec<TransactionInput>,
71}
72
73impl CorePallas {
74    pub fn new(protocol_params: whisky_common::Protocol) -> Self {
75        Self {
76            protocol_params,
77            inputs_map: HashMap::new(),
78            collaterals_map: HashMap::new(),
79            script_source_ref_inputs: vec![],
80            total_script_size: 0,
81            required_signatures_vec: vec![],
82            native_scripts_vec: vec![],
83            plutus_v1_scripts_vec: vec![],
84            plutus_v2_scripts_vec: vec![],
85            plutus_v3_scripts_vec: vec![],
86            plutus_v1_used: false,
87            plutus_v2_used: false,
88            plutus_v3_used: false,
89            input_redeemers_vec: vec![],
90            certificate_redeemers_vec: vec![],
91            withdrawal_redeemers_vec: vec![],
92            mint_redeemers_vec: vec![],
93            vote_redeemers_vec: vec![],
94            plutus_data_vec: vec![],
95            ref_inputs_vec: vec![],
96        }
97    }
98
99    fn process_inputs(
100        &mut self,
101        whisky_inputs: Vec<TxIn>,
102    ) -> Result<Vec<TransactionInput>, WError> {
103        let mut inputs: Vec<TransactionInput> = vec![];
104        for tx_in in whisky_inputs.clone() {
105            match tx_in {
106                TxIn::PubKeyTxIn(pub_key_tx_in) => {
107                    let input = TransactionInput::new(
108                        &pub_key_tx_in.tx_in.tx_hash,
109                        pub_key_tx_in.tx_in.tx_index.into(),
110                    )?;
111                    let asset_vec = pub_key_tx_in.tx_in.amount.clone().ok_or_else(|| {
112                        WError::new("WhiskyPallas - Adding inputs:", "Input amount is missing")
113                    })?;
114                    let value = convert_value(&asset_vec)?;
115                    self.inputs_map.insert(input.clone(), value);
116                    self.required_signatures_vec
117                        .push(bytes_from_bech32(&pub_key_tx_in.tx_in.address.unwrap())?);
118                    inputs.push(input);
119                }
120                TxIn::SimpleScriptTxIn(simple_script_tx_in) => {
121                    let input = TransactionInput::new(
122                        &simple_script_tx_in.tx_in.tx_hash,
123                        simple_script_tx_in.tx_in.tx_index.into(),
124                    )?;
125                    let asset_vec = simple_script_tx_in.tx_in.amount.clone().ok_or_else(|| {
126                        WError::new("WhiskyPallas - Adding inputs:", "Input amount is missing")
127                    })?;
128                    let value = convert_value(&asset_vec)?;
129                    self.inputs_map.insert(input.clone(), value);
130                    inputs.push(input);
131
132                    match &simple_script_tx_in.simple_script_tx_in {
133                        ProvidedSimpleScriptSource(provided_simple_script_source) => {
134                            self.native_scripts_vec.push(NativeScript::new_from_hex(
135                                &provided_simple_script_source.script_cbor.clone(),
136                            )?);
137                        }
138                        InlineSimpleScriptSource(inline_simple_script_source) => {
139                            self.ref_inputs_vec.push(TransactionInput::new(
140                                &inline_simple_script_source.ref_tx_in.tx_hash,
141                                inline_simple_script_source.ref_tx_in.tx_index.into(),
142                            )?)
143                        }
144                    }
145                }
146                TxIn::ScriptTxIn(script_tx_in) => {
147                    let input = TransactionInput::new(
148                        &script_tx_in.tx_in.tx_hash,
149                        script_tx_in.tx_in.tx_index.into(),
150                    )?;
151                    let asset_vec = script_tx_in.tx_in.amount.clone().ok_or_else(|| {
152                        WError::new("WhiskyPallas - Adding inputs:", "Input amount is missing")
153                    })?;
154                    let value = convert_value(&asset_vec)?;
155                    self.inputs_map.insert(input.clone(), value);
156                    inputs.push(input.clone());
157
158                    let script_source = script_tx_in
159                        .script_tx_in
160                        .script_source
161                        .clone()
162                        .ok_or_else(|| {
163                            WError::new(
164                                "WhiskyPallas - Adding inputs",
165                                "Script source is missing from script input",
166                            )
167                        })?;
168
169                    let datum_source =
170                        script_tx_in
171                            .script_tx_in
172                            .datum_source
173                            .clone()
174                            .ok_or_else(|| {
175                                WError::new(
176                                    "WhiskyPallas - Adding inputs",
177                                    "Datum source is missing from script input",
178                                )
179                            })?;
180
181                    let redeemer = script_tx_in.script_tx_in.redeemer.clone().ok_or_else(|| {
182                        WError::new(
183                            "WhiskyPallas - Adding inputs",
184                            "Redeemer is missing from script input",
185                        )
186                    })?;
187
188                    self.process_script_source(script_source)?;
189
190                    self.process_datum_source(datum_source)?;
191
192                    self.input_redeemers_vec.push((
193                        input.clone(),
194                        Redeemer::new(
195                            RedeemerTag::Spend,
196                            0,
197                            PlutusData::new(redeemer.data)?,
198                            ExUnits {
199                                mem: redeemer.ex_units.mem,
200                                steps: redeemer.ex_units.steps,
201                            },
202                        )?,
203                    ));
204                }
205            }
206        }
207        inputs.sort_by(|a, b| {
208            a.inner
209                .transaction_id
210                .cmp(&b.inner.transaction_id)
211                .then(a.inner.index.cmp(&b.inner.index))
212        });
213        Ok(inputs)
214    }
215
216    fn process_outputs(
217        &mut self,
218        whisky_outputs: Vec<Output>,
219    ) -> Result<Vec<TransactionOutput<'static>>, WError> {
220        let mut outputs: Vec<TransactionOutput> = vec![];
221        let whisky_outputs = whisky_outputs.clone();
222        for output in &whisky_outputs {
223            let datum: Option<Datum> = match &output.datum {
224                Some(datum_source) => match datum_source {
225                    whisky_common::Datum::Inline(datum_str) => Some(Datum::new(DatumKind::Data {
226                        plutus_data_hex: datum_str.to_string(),
227                    })?),
228                    whisky_common::Datum::Hash(datum_str) => {
229                        let datum = Datum::new(DatumKind::Data {
230                            plutus_data_hex: datum_str.to_string(),
231                        })?;
232
233                        let datum_hash_str = datum.hash()?;
234                        Some(Datum::new(DatumKind::Hash {
235                            datum_hash: datum_hash_str,
236                        })?)
237                    }
238                    whisky_common::Datum::Embedded(datum_str) => {
239                        let datum = Datum::new(DatumKind::Data {
240                            plutus_data_hex: datum_str.to_string(),
241                        })?;
242                        self.plutus_data_vec
243                            .push(PlutusData::new(datum_str.to_string())?);
244
245                        let datum_hash_str = datum.hash()?;
246                        Some(Datum::new(DatumKind::Hash {
247                            datum_hash: datum_hash_str,
248                        })?)
249                    }
250                },
251                None => None,
252            };
253
254            let script_ref = match &output.reference_script {
255                Some(script_source) => match script_source {
256                    whisky_common::OutputScriptSource::ProvidedScriptSource(
257                        provided_script_source,
258                    ) => {
259                        let plutus_script = match provided_script_source.language_version {
260                            LanguageVersion::V1 => ScriptRef::new(ScriptRefKind::PlutusV1Script {
261                                plutus_v1_script_hex: provided_script_source.script_cbor.clone(),
262                            })?,
263                            LanguageVersion::V2 => ScriptRef::new(ScriptRefKind::PlutusV2Script {
264                                plutus_v2_script_hex: provided_script_source.script_cbor.clone(),
265                            })?,
266                            LanguageVersion::V3 => ScriptRef::new(ScriptRefKind::PlutusV3Script {
267                                plutus_v3_script_hex: provided_script_source.script_cbor.clone(),
268                            })?,
269                        };
270                        Some(plutus_script)
271                    }
272                    whisky_common::OutputScriptSource::ProvidedSimpleScriptSource(
273                        provided_simple_script_source,
274                    ) => {
275                        let native_script = ScriptRef::new(ScriptRefKind::NativeScript {
276                            native_script_hex: provided_simple_script_source.script_cbor.clone(),
277                        })?;
278                        Some(native_script)
279                    }
280                },
281                None => None,
282            };
283            outputs.push(TransactionOutput::new(
284                &bytes_from_bech32(&output.address)?,
285                convert_value(&output.amount.clone())?,
286                datum,
287                script_ref,
288            )?);
289        }
290        Ok(outputs)
291    }
292
293    fn process_certificate_type(
294        &mut self,
295        cert_type: &CertificateType,
296    ) -> Result<Certificate, WError> {
297        match cert_type {
298            CertificateType::RegisterStake(register_stake) => {
299                let stake_cred = RewardAccount::from_bech32(&register_stake.stake_key_address)?
300                    .to_stake_cred()?;
301                match stake_cred.inner {
302                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
303                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
304                        self.required_signatures_vec.push(hash.to_string());
305                    }
306                }
307                Ok(Certificate::new(CertificateKind::StakeRegistration {
308                    stake_credential: stake_cred,
309                }))?
310            }
311            CertificateType::DeregisterStake(deregister_stake) => {
312                let stake_cred = RewardAccount::from_bech32(&deregister_stake.stake_key_address)?
313                    .to_stake_cred()?;
314                match stake_cred.inner {
315                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
316                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
317                        self.required_signatures_vec.push(hash.to_string());
318                    }
319                }
320                Ok(Certificate::new(CertificateKind::StakeDeregistration {
321                    stake_credential: stake_cred,
322                }))?
323            }
324            CertificateType::DelegateStake(delegate_stake) => {
325                let stake_cred = RewardAccount::from_bech32(&delegate_stake.stake_key_address)?
326                    .to_stake_cred()?;
327                match stake_cred.inner {
328                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
329                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
330                        self.required_signatures_vec.push(hash.to_string());
331                    }
332                }
333                Ok(Certificate::new(CertificateKind::StakeDelegation {
334                    stake_credential: stake_cred,
335                    pool_key_hash: delegate_stake.pool_id.clone(),
336                }))?
337            }
338            CertificateType::RegisterPool(register_pool) => {
339                let mut relays: Vec<Relay> = vec![];
340                for relay in &register_pool.pool_params.relays {
341                    match relay {
342                        whisky_common::Relay::SingleHostAddr(single_host_addr) => {
343                            relays.push(Relay::new(RelayKind::SingleHostAddr(
344                                single_host_addr.port.map(|p| p.into()),
345                                single_host_addr.ipv4.clone(),
346                                single_host_addr.ipv6.clone(),
347                            ))?);
348                        }
349                        whisky_common::Relay::SingleHostName(single_host_name) => {
350                            relays.push(Relay::new(RelayKind::SingleHostName(
351                                single_host_name.port.map(|p| p.into()),
352                                single_host_name.domain_name.clone(),
353                            ))?);
354                        }
355                        whisky_common::Relay::MultiHostName(multi_host_name) => {
356                            relays.push(Relay::new(RelayKind::MultiHostName(
357                                multi_host_name.domain_name.clone(),
358                            ))?);
359                        }
360                    }
361                }
362                // TODO: convert pool operator and owners from bech32 if needed
363                Ok(Certificate::new(CertificateKind::PoolRegistration {
364                    operator: register_pool.pool_params.operator.clone(),
365                    vrf_keyhash: register_pool.pool_params.vrf_key_hash.clone(),
366                    pledge: register_pool
367                        .pool_params
368                        .pledge
369                        .clone()
370                        .parse::<u64>()
371                        .map_err(|e| {
372                            WError::new(
373                                "Certificate - Pool Registration: Invalid pledge amount",
374                                &e.to_string(),
375                            )
376                        })?,
377                    cost: register_pool
378                        .pool_params
379                        .cost
380                        .clone()
381                        .parse::<u64>()
382                        .map_err(|e| {
383                            WError::new(
384                                "Certificate - Pool Registration: Invalid cost amount",
385                                &e.to_string(),
386                            )
387                        })?,
388                    margin: register_pool.pool_params.margin,
389                    reward_account: RewardAccount::new(bytes_from_bech32(
390                        &register_pool.pool_params.reward_address,
391                    )?)?,
392                    pool_owners: register_pool.pool_params.owners.clone(),
393                    relays,
394                    pool_metadata: register_pool
395                        .pool_params
396                        .metadata
397                        .clone()
398                        .map(|metadata| PoolMetadata::new(metadata.url, metadata.hash).unwrap()),
399                }))?
400            }
401            CertificateType::RetirePool(retire_pool) => {
402                // TODO: convert pool id from bech32 if needed
403                Ok(Certificate::new(CertificateKind::PoolRetirement {
404                    pool_key_hash: retire_pool.pool_id.clone(),
405                    epoch: retire_pool.epoch.into(),
406                }))?
407            }
408            CertificateType::VoteDelegation(vote_delegation) => {
409                let drep: DRep = match &vote_delegation.drep {
410                    whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
411                    whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
412                    whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
413                };
414                let stake_cred = RewardAccount::from_bech32(&vote_delegation.stake_key_address)?
415                    .to_stake_cred()?;
416                match stake_cred.inner {
417                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
418                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
419                        self.required_signatures_vec.push(hash.to_string());
420                    }
421                }
422                Ok(Certificate::new(CertificateKind::VoteDeleg {
423                    stake_credential: stake_cred,
424                    drep,
425                }))?
426            }
427            CertificateType::StakeAndVoteDelegation(stake_and_vote_delegation) => {
428                let drep: DRep = match &stake_and_vote_delegation.drep {
429                    whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
430                    whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
431                    whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
432                };
433                let stake_cred =
434                    RewardAccount::from_bech32(&stake_and_vote_delegation.stake_key_address)?
435                        .to_stake_cred()?;
436                match stake_cred.inner {
437                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
438                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
439                        self.required_signatures_vec.push(hash.to_string());
440                    }
441                }
442                Ok(Certificate::new(CertificateKind::StakeVoteDeleg {
443                    stake_credential: stake_cred,
444                    pool_key_hash: stake_and_vote_delegation.pool_key_hash.clone(),
445                    drep,
446                }))?
447            }
448            CertificateType::StakeRegistrationAndDelegation(stake_registration_and_delegation) => {
449                let stake_cred = RewardAccount::from_bech32(
450                    &stake_registration_and_delegation.stake_key_address,
451                )?
452                .to_stake_cred()?;
453                match stake_cred.inner {
454                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
455                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
456                        self.required_signatures_vec.push(hash.to_string());
457                    }
458                }
459                Ok(Certificate::new(CertificateKind::StakeRegDeleg {
460                    stake_credential: stake_cred,
461                    pool_key_hash: stake_registration_and_delegation.pool_key_hash.clone(),
462                    amount: stake_registration_and_delegation.coin.into(),
463                }))?
464            }
465            CertificateType::VoteRegistrationAndDelegation(vote_registration_and_delegation) => {
466                let drep: DRep = match &vote_registration_and_delegation.drep {
467                    whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
468                    whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
469                    whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
470                };
471                let stake_cred = RewardAccount::from_bech32(
472                    &vote_registration_and_delegation.stake_key_address,
473                )?
474                .to_stake_cred()?;
475                match stake_cred.inner {
476                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
477                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
478                        self.required_signatures_vec.push(hash.to_string());
479                    }
480                }
481                Ok(Certificate::new(CertificateKind::VoteRegDeleg {
482                    stake_credential: stake_cred,
483                    drep,
484                    amount: vote_registration_and_delegation.coin.into(),
485                }))?
486            }
487            CertificateType::StakeVoteRegistrationAndDelegation(
488                stake_vote_registration_and_delegation,
489            ) => {
490                let drep: DRep = match &stake_vote_registration_and_delegation.drep {
491                    whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
492                    whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
493                    whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
494                };
495                let stake_cred = RewardAccount::from_bech32(
496                    &stake_vote_registration_and_delegation.stake_key_address,
497                )?
498                .to_stake_cred()?;
499                match stake_cred.inner {
500                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
501                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
502                        self.required_signatures_vec.push(hash.to_string());
503                    }
504                }
505                Ok(Certificate::new(CertificateKind::StakeVoteRegDeleg {
506                    stake_credential: stake_cred,
507                    pool_key_hash: stake_vote_registration_and_delegation.pool_key_hash.clone(),
508                    drep,
509                    amount: stake_vote_registration_and_delegation.coin.into(),
510                }))?
511            }
512            CertificateType::CommitteeHotAuth(committee_hot_auth) => {
513                let committee_cold_cred =
514                    RewardAccount::from_bech32(&committee_hot_auth.committee_cold_key_address)?
515                        .to_stake_cred()?;
516                match committee_cold_cred.inner {
517                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
518                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
519                        self.required_signatures_vec.push(hash.to_string());
520                    }
521                }
522                Ok(Certificate::new(CertificateKind::AuthCommitteeHot {
523                    committee_cold_cred,
524                    committee_hot_cred: RewardAccount::from_bech32(
525                        &committee_hot_auth.committee_hot_key_address,
526                    )?
527                    .to_stake_cred()?,
528                }))?
529            }
530            CertificateType::CommitteeColdResign(committee_cold_resign) => {
531                let anchor: Option<Anchor> = match &committee_cold_resign.anchor {
532                    Some(anchor_data) => Some(Anchor::new(
533                        anchor_data.anchor_url.clone(),
534                        anchor_data.anchor_data_hash.clone(),
535                    )?),
536                    None => None,
537                };
538                let committee_cold_cred =
539                    RewardAccount::from_bech32(&committee_cold_resign.committee_cold_key_address)?
540                        .to_stake_cred()?;
541                match committee_cold_cred.inner {
542                    pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
543                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
544                        self.required_signatures_vec.push(hash.to_string());
545                    }
546                }
547                Ok(Certificate::new(CertificateKind::ResignCommitteeCold {
548                    committee_cold_cred,
549                    anchor,
550                }))?
551            }
552            CertificateType::DRepRegistration(drep_registration) => {
553                let drep: DRep = DRep::from_bech32(&drep_registration.drep_id)?;
554                let drep_cred = match &drep.inner {
555                    pallas::ledger::primitives::conway::DRep::Key(hash) => {
556                        self.required_signatures_vec.push(hash.to_string());
557                        StakeCredential::new(StakeCredentialKind::KeyHash {
558                            key_hash_hex: hash.to_string(),
559                        })?
560                    }
561                    pallas::ledger::primitives::conway::DRep::Script(hash) => {
562                        StakeCredential::new(StakeCredentialKind::ScriptHash {
563                            script_hash_hex: hash.to_string(),
564                        })?
565                    }
566                    _ => {
567                        return Err(WError::new(
568                            "Certificate - DRep Registration:",
569                            "DRep must be either Key or Script type",
570                        ));
571                    }
572                };
573                Ok(Certificate::new(CertificateKind::RegDRepCert {
574                    drep_cred,
575                    amount: drep_registration.coin.into(),
576                    anchor: drep_registration.anchor.clone().map(|anchor_data| {
577                        Anchor::new(anchor_data.anchor_url, anchor_data.anchor_data_hash).unwrap()
578                    }),
579                }))?
580            }
581            CertificateType::DRepDeregistration(drep_deregistration) => {
582                let drep: DRep = DRep::from_bech32(&drep_deregistration.drep_id)?;
583                let drep_cred = match &drep.inner {
584                    pallas::ledger::primitives::conway::DRep::Key(hash) => {
585                        self.required_signatures_vec.push(hash.to_string());
586                        StakeCredential::new(StakeCredentialKind::KeyHash {
587                            key_hash_hex: hash.to_string(),
588                        })?
589                    }
590                    pallas::ledger::primitives::conway::DRep::Script(hash) => {
591                        StakeCredential::new(StakeCredentialKind::ScriptHash {
592                            script_hash_hex: hash.to_string(),
593                        })?
594                    }
595                    _ => {
596                        return Err(WError::new(
597                            "Certificate - DRep Deregistration:",
598                            "DRep must be either Key or Script type",
599                        ));
600                    }
601                };
602                Ok(Certificate::new(CertificateKind::UnRegDRepCert {
603                    drep_cred,
604                    amount: drep_deregistration.coin.into(),
605                }))?
606            }
607            CertificateType::DRepUpdate(drep_update) => {
608                let drep: DRep = DRep::from_bech32(&drep_update.drep_id)?;
609                let drep_cred = match &drep.inner {
610                    pallas::ledger::primitives::conway::DRep::Key(hash) => {
611                        self.required_signatures_vec.push(hash.to_string());
612                        StakeCredential::new(StakeCredentialKind::KeyHash {
613                            key_hash_hex: hash.to_string(),
614                        })?
615                    }
616                    pallas::ledger::primitives::conway::DRep::Script(hash) => {
617                        StakeCredential::new(StakeCredentialKind::ScriptHash {
618                            script_hash_hex: hash.to_string(),
619                        })?
620                    }
621                    _ => {
622                        return Err(WError::new(
623                            "Certificate - DRep Update:",
624                            "DRep must be either Key or Script type",
625                        ));
626                    }
627                };
628
629                let anchor: Option<Anchor> = match &drep_update.anchor {
630                    Some(anchor_data) => Some(Anchor::new(
631                        anchor_data.anchor_url.clone(),
632                        anchor_data.anchor_data_hash.clone(),
633                    )?),
634                    None => None,
635                };
636                Ok(Certificate::new(CertificateKind::UpdateDRepCert {
637                    drep_cred,
638                    anchor,
639                }))?
640            }
641        }
642    }
643
644    fn process_certificates(
645        &mut self,
646        whisky_certificates: Vec<WhiskyCertificate>,
647    ) -> Result<Option<Vec<Certificate>>, WError> {
648        let mut certificates: Vec<Certificate> = vec![];
649
650        for cert in whisky_certificates.clone() {
651            match cert {
652                BasicCertificate(certificate_type) => {
653                    certificates.push(self.process_certificate_type(&certificate_type)?);
654                }
655                ScriptCertificate(script_certificate) => {
656                    let cert = self.process_certificate_type(&script_certificate.cert)?;
657                    let script_source =
658                        script_certificate.script_source.clone().ok_or_else(|| {
659                            WError::new(
660                                "WhiskyPallas - Processing certificates:",
661                                "Script source is missing from script certificate",
662                            )
663                        })?;
664                    self.process_script_source(script_source)?;
665
666                    let redeemer = script_certificate.redeemer.clone().ok_or_else(|| {
667                        WError::new(
668                            "WhiskyPallas - Processing certificates:",
669                            "Redeemer is missing from script certificate",
670                        )
671                    })?;
672                    self.certificate_redeemers_vec.push((
673                        cert.clone(),
674                        Redeemer::new(
675                            RedeemerTag::Cert,
676                            0,
677                            PlutusData::new(redeemer.data)?,
678                            ExUnits {
679                                mem: redeemer.ex_units.mem,
680                                steps: redeemer.ex_units.steps,
681                            },
682                        )?,
683                    ));
684
685                    certificates.push(cert);
686                }
687                SimpleScriptCertificate(simple_script_certificate) => {
688                    match simple_script_certificate.simple_script_source {
689                        Some(simple_script_source) => match simple_script_source {
690                            whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
691                                provided_simple_script_source,
692                            ) => {
693                                self.native_scripts_vec.push(NativeScript::new_from_hex(
694                                    &provided_simple_script_source.script_cbor,
695                                )?);
696                            }
697                            whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
698                                inline_simple_script_source,
699                            ) => self.ref_inputs_vec.push(TransactionInput::new(
700                                &inline_simple_script_source.ref_tx_in.tx_hash,
701                                inline_simple_script_source.ref_tx_in.tx_index.into(),
702                            )?),
703                        },
704                        None => {
705                            return Err(WError::new(
706                                "WhiskyPallas - Processing certificates:",
707                                "Simple script source is missing from simple script certificate",
708                            ));
709                        }
710                    };
711                    certificates
712                        .push(self.process_certificate_type(&simple_script_certificate.cert)?);
713                }
714            }
715        }
716        if certificates.is_empty() {
717            Ok(None)
718        } else {
719            Ok(Some(certificates))
720        }
721    }
722
723    fn process_withdrawals(
724        &mut self,
725        whisky_withdrawals: Vec<WhiskyWithdrawal>,
726    ) -> Result<Option<Vec<(RewardAccount, u64)>>, WError> {
727        let mut withdrawals: Vec<(RewardAccount, u64)> = vec![];
728        for withdrawal in whisky_withdrawals.clone() {
729            match withdrawal {
730                PubKeyWithdrawal(pub_key_withdrawal) => {
731                    let reward_account_bytes = bytes_from_bech32(&pub_key_withdrawal.address)?;
732                    self.required_signatures_vec
733                        .push(reward_account_bytes.clone());
734                    let reward_account = RewardAccount::new(reward_account_bytes)?;
735                    withdrawals.push((reward_account, pub_key_withdrawal.coin));
736                }
737                PlutusScriptWithdrawal(plutus_script_withdrawal) => {
738                    let reward_account_bytes =
739                        bytes_from_bech32(&plutus_script_withdrawal.address)?;
740                    let reward_account = RewardAccount::new(reward_account_bytes)?;
741                    withdrawals.push((reward_account.clone(), plutus_script_withdrawal.coin));
742                    let script_source =
743                        plutus_script_withdrawal
744                            .script_source
745                            .clone()
746                            .ok_or_else(|| {
747                                WError::new(
748                                    "WhiskyPallas - Processing withdrawals:",
749                                    "Script source is missing from plutus script withdrawal",
750                                )
751                            })?;
752                    self.process_script_source(script_source)?;
753
754                    let redeemer = plutus_script_withdrawal.redeemer.clone().ok_or_else(|| {
755                        WError::new(
756                            "WhiskyPallas - Processing withdrawals:",
757                            "Redeemer is missing from plutus script withdrawal",
758                        )
759                    })?;
760                    self.withdrawal_redeemers_vec.push((
761                        reward_account.clone(),
762                        Redeemer::new(
763                            RedeemerTag::Reward,
764                            0,
765                            PlutusData::new(redeemer.data)?,
766                            ExUnits {
767                                mem: redeemer.ex_units.mem,
768                                steps: redeemer.ex_units.steps,
769                            },
770                        )?,
771                    ));
772                }
773                SimpleScriptWithdrawal(simple_script_withdrawal) => {
774                    let reward_account_bytes =
775                        bytes_from_bech32(&simple_script_withdrawal.address)?;
776                    let reward_account = RewardAccount::new(reward_account_bytes)?;
777                    withdrawals.push((reward_account.clone(), simple_script_withdrawal.coin));
778                    match &simple_script_withdrawal.script_source {
779                        Some(simple_script_source) => match simple_script_source {
780                            whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
781                                provided_simple_script_source,
782                            ) => {
783                                self.native_scripts_vec.push(NativeScript::new_from_hex(
784                                    &provided_simple_script_source.script_cbor,
785                                )?);
786                            }
787                            whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
788                                inline_simple_script_source,
789                            ) => self.ref_inputs_vec.push(TransactionInput::new(
790                                &inline_simple_script_source.ref_tx_in.tx_hash,
791                                inline_simple_script_source.ref_tx_in.tx_index.into(),
792                            )?),
793                        },
794                        None => {
795                            return Err(WError::new(
796                                "WhiskyPallas - Processing withdrawals:",
797                                "Simple script source is missing from simple script withdrawal",
798                            ));
799                        }
800                    };
801                }
802            }
803        }
804        Ok(if withdrawals.is_empty() {
805            None
806        } else {
807            Some(withdrawals)
808        })
809    }
810
811    fn process_mints(
812        &mut self,
813        whisky_mints: Vec<MintItem>,
814    ) -> Result<Option<MultiassetNonZeroInt>, WError> {
815        let mut mints: Vec<(String, Vec<(String, i64)>)> = vec![];
816        for mint in whisky_mints.clone() {
817            match mint {
818                whisky_common::MintItem::ScriptMint(script_mint) => {
819                    let mint_param = script_mint.mint;
820                    let existing_policy =
821                        mints.iter_mut().find(|mint| mint.0 == mint_param.policy_id);
822                    if existing_policy.is_some() {
823                        let policy_mint = existing_policy.unwrap();
824                        policy_mint.1.push((
825                            mint_param.asset_name,
826                            mint_param.amount.try_into().map_err(|_| {
827                                WError::new(
828                                    "WhiskyPallas - Processing mints:",
829                                    "Invalid mint amount",
830                                )
831                            })?,
832                        ));
833                    } else {
834                        mints.push((
835                            mint_param.policy_id.clone(),
836                            vec![(
837                                mint_param.asset_name,
838                                mint_param.amount.try_into().map_err(|_| {
839                                    WError::new(
840                                        "WhiskyPallas - Processing mints:",
841                                        "Invalid mint amount",
842                                    )
843                                })?,
844                            )],
845                        ));
846                    }
847
848                    let script_source = script_mint.script_source.clone().ok_or_else(|| {
849                        WError::new(
850                            "WhiskyPallas - Processing mints:",
851                            "Script source is missing from script mint",
852                        )
853                    })?;
854                    self.process_script_source(script_source)?;
855
856                    let redeemer = script_mint.redeemer.clone().ok_or_else(|| {
857                        WError::new(
858                            "WhiskyPallas - Processing mints:",
859                            "Redeemer is missing from script mint",
860                        )
861                    })?;
862                    self.mint_redeemers_vec.push((
863                        mint_param.policy_id.clone(),
864                        Redeemer::new(
865                            RedeemerTag::Mint,
866                            0,
867                            PlutusData::new(redeemer.data)?,
868                            ExUnits {
869                                mem: redeemer.ex_units.mem,
870                                steps: redeemer.ex_units.steps,
871                            },
872                        )?,
873                    ));
874                }
875                whisky_common::MintItem::SimpleScriptMint(simple_script_mint) => {
876                    let mint_param = simple_script_mint.mint;
877                    let existing_policy =
878                        mints.iter_mut().find(|mint| mint.0 == mint_param.policy_id);
879                    if existing_policy.is_some() {
880                        let policy_mint = existing_policy.unwrap();
881                        policy_mint.1.push((
882                            mint_param.asset_name,
883                            mint_param.amount.try_into().map_err(|_| {
884                                WError::new(
885                                    "WhiskyPallas - Processing mints:",
886                                    "Invalid mint amount",
887                                )
888                            })?,
889                        ));
890                    } else {
891                        mints.push((
892                            mint_param.policy_id.clone(),
893                            vec![(
894                                mint_param.asset_name,
895                                mint_param.amount.try_into().map_err(|_| {
896                                    WError::new(
897                                        "WhiskyPallas - Processing mints:",
898                                        "Invalid mint amount",
899                                    )
900                                })?,
901                            )],
902                        ));
903                    }
904
905                    match &simple_script_mint.script_source {
906                        Some(simple_script_source) => match simple_script_source {
907                            whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
908                                provided_simple_script_source,
909                            ) => {
910                                self.native_scripts_vec.push(NativeScript::new_from_hex(
911                                    &provided_simple_script_source.script_cbor,
912                                )?);
913                            }
914                            whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
915                                inline_simple_script_source,
916                            ) => self.ref_inputs_vec.push(TransactionInput::new(
917                                &inline_simple_script_source.ref_tx_in.tx_hash,
918                                inline_simple_script_source.ref_tx_in.tx_index.into(),
919                            )?),
920                        },
921                        None => {
922                            return Err(WError::new(
923                                "WhiskyPallas - Processing mints:",
924                                "Simple script source is missing from simple script mint",
925                            ));
926                        }
927                    };
928                }
929            }
930        }
931        Ok(if mints.is_empty() {
932            None
933        } else {
934            Some(MultiassetNonZeroInt::new(mints)?)
935        })
936    }
937
938    fn process_collaterals(
939        &mut self,
940        whisky_collaterals: Vec<PubKeyTxIn>,
941    ) -> Result<Option<Vec<TransactionInput>>, WError> {
942        let mut collaterals: Vec<TransactionInput> = vec![];
943        for collateral in whisky_collaterals.clone() {
944            self.required_signatures_vec
945                .push(bytes_from_bech32(&collateral.tx_in.address.unwrap())?);
946            let transaction_input =
947                TransactionInput::new(&collateral.tx_in.tx_hash, collateral.tx_in.tx_index.into())?;
948            collaterals.push(transaction_input.clone());
949            self.collaterals_map.insert(
950                transaction_input.clone(),
951                convert_value(&collateral.tx_in.amount.unwrap())?,
952            );
953        }
954        Ok(if collaterals.is_empty() {
955            None
956        } else {
957            Some(collaterals)
958        })
959    }
960
961    fn process_required_signers(
962        &mut self,
963        whisky_required_signers: Vec<String>,
964    ) -> Result<Option<RequiredSigners>, WError> {
965        let mut required_signers: Vec<String> = vec![];
966        for signer in whisky_required_signers.clone() {
967            self.required_signatures_vec.push(signer.clone());
968            required_signers.push(signer);
969        }
970        Ok(if required_signers.is_empty() {
971            None
972        } else {
973            Some(RequiredSigners::new(required_signers)?)
974        })
975    }
976
977    fn process_total_collateral(
978        &mut self,
979        whisky_total_collateral: Option<String>,
980    ) -> Result<Option<u64>, WError> {
981        if let Some(total_collateral) = whisky_total_collateral.clone() {
982            Ok(Some(total_collateral.parse::<u64>().map_err(|e| {
983                WError::new(
984                    "WhiskyPallas - Processing total collateral:",
985                    &format!("Failed to parse total collateral: {}", e.to_string()),
986                )
987            })?))
988        } else {
989            Ok(None)
990        }
991    }
992
993    fn process_script_source(&mut self, script_source: ScriptSource) -> Result<(), WError> {
994        match script_source {
995            ProvidedScriptSource(provided_script_source) => {
996                match provided_script_source.language_version {
997                    LanguageVersion::V1 => {
998                        self.plutus_v1_scripts_vec
999                            .push(PlutusScript::<1>::new(provided_script_source.script_cbor)?);
1000                        self.plutus_v1_used = true;
1001                    }
1002                    LanguageVersion::V2 => {
1003                        self.plutus_v2_scripts_vec
1004                            .push(PlutusScript::<2>::new(provided_script_source.script_cbor)?);
1005                        self.plutus_v2_used = true;
1006                    }
1007                    LanguageVersion::V3 => {
1008                        self.plutus_v3_scripts_vec
1009                            .push(PlutusScript::<3>::new(provided_script_source.script_cbor)?);
1010                        self.plutus_v3_used = true;
1011                    }
1012                }
1013            }
1014            InlineScriptSource(inline_script_source) => {
1015                self.ref_inputs_vec.push(TransactionInput::new(
1016                    &inline_script_source.ref_tx_in.tx_hash,
1017                    inline_script_source.ref_tx_in.tx_index.into(),
1018                )?);
1019                self.script_source_ref_inputs
1020                    .push(inline_script_source.ref_tx_in.clone());
1021                match inline_script_source.language_version {
1022                    LanguageVersion::V1 => {
1023                        self.plutus_v1_used = true;
1024                    }
1025                    LanguageVersion::V2 => {
1026                        self.plutus_v2_used = true;
1027                    }
1028                    LanguageVersion::V3 => {
1029                        self.plutus_v3_used = true;
1030                    }
1031                }
1032            }
1033        };
1034        Ok(())
1035    }
1036
1037    fn process_vote_type(
1038        &mut self,
1039        vote_type: &whisky_common::VoteType,
1040    ) -> Result<(Voter, Vec<(GovActionId, VotingProdecedure)>), WError> {
1041        let voter = match &vote_type.voter {
1042            whisky_common::Voter::ConstitutionalCommitteeHotCred(credential) => {
1043                match credential {
1044                    whisky_common::Credential::KeyHash(key_hash_hex) => {
1045                        self.required_signatures_vec.push(key_hash_hex.clone());
1046                        Voter::new(VoterKind::ConstitutionalCommitteKey {
1047                            key_hash: key_hash_hex.clone(),
1048                        })
1049                    }
1050                    whisky_common::Credential::ScriptHash(script_hash_hex) => {
1051                        Voter::new(VoterKind::ConstitutionalCommitteScript {
1052                            script_hash: script_hash_hex.clone(),
1053                        })
1054                    }
1055                }
1056            }?,
1057            whisky_common::Voter::DRepId(drep_id) => {
1058                let drep = DRep::from_bech32(&drep_id)?;
1059                match drep.inner {
1060                    pallas::ledger::primitives::conway::DRep::Key(hash) => {
1061                        self.required_signatures_vec.push(hash.to_string());
1062                        Voter::new(VoterKind::DrepKey {
1063                            key_hash: hash.to_string(),
1064                        })
1065                    }
1066                    pallas::ledger::primitives::conway::DRep::Script(hash) => {
1067                        Voter::new(VoterKind::DrepScript {
1068                            script_hash: hash.to_string(),
1069                        })
1070                    }
1071                    _ => {
1072                        return Err(WError::new(
1073                            "Voting Procedure - Voter:",
1074                            "DRep must be either Key or Script type",
1075                        ));
1076                    }
1077                }
1078            }?,
1079            whisky_common::Voter::StakingPoolKeyHash(reward_account) => {
1080                let stake_cred = RewardAccount::from_bech32(&reward_account)?.to_stake_cred()?;
1081                match stake_cred.inner {
1082                    pallas::ledger::primitives::StakeCredential::ScriptHash(hash) => {
1083                        Voter::new(VoterKind::StakePoolKey {
1084                            pool_key_hash: hash.to_string(),
1085                        })
1086                    }
1087                    pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
1088                        self.required_signatures_vec.push(hash.to_string());
1089                        Voter::new(VoterKind::StakePoolKey {
1090                            pool_key_hash: hash.to_string(),
1091                        })
1092                    }
1093                }
1094            }?,
1095        };
1096
1097        let gov_action_id = GovActionId::new(
1098            &vote_type.gov_action_id.tx_hash,
1099            vote_type.gov_action_id.tx_index.into(),
1100        )?;
1101
1102        let voting_procedure = VotingProdecedure::new(
1103            match &vote_type.voting_procedure.vote_kind {
1104                whisky_common::VoteKind::No => Vote::new(VoteKind::No)?,
1105                whisky_common::VoteKind::Yes => Vote::new(VoteKind::Yes)?,
1106                whisky_common::VoteKind::Abstain => Vote::new(VoteKind::Abstain)?,
1107            },
1108            match &vote_type.voting_procedure.anchor {
1109                Some(anchor_data) => Some(Anchor::new(
1110                    anchor_data.anchor_url.clone(),
1111                    anchor_data.anchor_data_hash.clone(),
1112                )?),
1113                None => None,
1114            },
1115        );
1116        Ok((voter, vec![(gov_action_id, voting_procedure)]))
1117    }
1118
1119    fn process_voting_procedures(
1120        &mut self,
1121        whisky_votes: Vec<WhiskyVote>,
1122    ) -> Result<Option<Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)>>, WError> {
1123        let mut voting_procedures: Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)> = vec![];
1124
1125        for vote in whisky_votes.clone() {
1126            match vote {
1127                whisky_common::Vote::BasicVote(vote_type) => {
1128                    let (voter, procedures) = self.process_vote_type(&vote_type)?;
1129                    // Check if voter already exists in voting_procedures
1130                    if let Some(existing_voter) =
1131                        voting_procedures.iter_mut().find(|(v, _)| *v == voter)
1132                    {
1133                        existing_voter.1.extend(procedures);
1134                    } else {
1135                        voting_procedures.push((voter, procedures));
1136                    }
1137                }
1138                whisky_common::Vote::ScriptVote(script_vote) => {
1139                    let (voter, procedures) = self.process_vote_type(&script_vote.vote)?;
1140                    // Check if voter already exists in voting_procedures
1141                    if let Some(existing_voter) =
1142                        voting_procedures.iter_mut().find(|(v, _)| *v == voter)
1143                    {
1144                        existing_voter.1.extend(procedures);
1145                    } else {
1146                        voting_procedures.push((voter.clone(), procedures));
1147                    }
1148
1149                    let script_source = script_vote.script_source.clone().ok_or_else(|| {
1150                        WError::new(
1151                            "WhiskyPallas - Processing voting procedures:",
1152                            "Script source is missing from script vote",
1153                        )
1154                    })?;
1155                    self.process_script_source(script_source)?;
1156
1157                    let redeemer = script_vote.redeemer.clone().ok_or_else(|| {
1158                        WError::new(
1159                            "WhiskyPallas - Processing voting procedures:",
1160                            "Redeemer is missing from script vote",
1161                        )
1162                    })?;
1163                    self.vote_redeemers_vec.push((
1164                        voter.clone(),
1165                        Redeemer::new(
1166                            RedeemerTag::Vote,
1167                            0,
1168                            PlutusData::new(redeemer.data)?,
1169                            ExUnits {
1170                                mem: redeemer.ex_units.mem,
1171                                steps: redeemer.ex_units.steps,
1172                            },
1173                        )?,
1174                    ));
1175                }
1176                whisky_common::Vote::SimpleScriptVote(simple_script_vote) => {
1177                    let (voter, procedures) = self.process_vote_type(&simple_script_vote.vote)?;
1178                    // Check if voter already exists in voting_procedures
1179                    if let Some(existing_voter) =
1180                        voting_procedures.iter_mut().find(|(v, _)| *v == voter)
1181                    {
1182                        existing_voter.1.extend(procedures);
1183                    } else {
1184                        voting_procedures.push((voter, procedures));
1185                    }
1186
1187                    match &simple_script_vote.simple_script_source {
1188                        Some(simple_script_source) => match simple_script_source {
1189                            whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
1190                                provided_simple_script_source,
1191                            ) => {
1192                                self.native_scripts_vec.push(NativeScript::new_from_hex(
1193                                    &provided_simple_script_source.script_cbor,
1194                                )?);
1195                            }
1196                            whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
1197                                inline_simple_script_source,
1198                            ) => self.ref_inputs_vec.push(TransactionInput::new(
1199                                &inline_simple_script_source.ref_tx_in.tx_hash,
1200                                inline_simple_script_source.ref_tx_in.tx_index.into(),
1201                            )?),
1202                        },
1203                        None => {
1204                            return Err(WError::new(
1205                                "WhiskyPallas - Processing voting procedures:",
1206                                "Simple script source is missing from simple script vote",
1207                            ));
1208                        }
1209                    };
1210                }
1211            }
1212        }
1213
1214        if voting_procedures.is_empty() {
1215            Ok(None)
1216        } else {
1217            Ok(Some(voting_procedures))
1218        }
1219    }
1220
1221    fn process_reference_inputs(
1222        &mut self,
1223        whisky_ref_inputs: Vec<RefTxIn>,
1224        whisky_inputs: Vec<TxIn>,
1225    ) -> Result<Option<Vec<TransactionInput>>, WError> {
1226        for ref_input in whisky_ref_inputs.clone() {
1227            self.ref_inputs_vec.push(TransactionInput::new(
1228                &ref_input.tx_hash,
1229                ref_input.tx_index.into(),
1230            )?);
1231            self.script_source_ref_inputs.push(ref_input.clone());
1232        }
1233        let final_ref_inputs: Vec<TransactionInput> = self
1234            .ref_inputs_vec
1235            .clone()
1236            .iter()
1237            .filter(|ref_input| {
1238                // Check if the input exists in tx_builder_body
1239                whisky_inputs
1240                    .iter()
1241                    .find(|input| {
1242                        input.to_utxo().input.tx_hash == ref_input.inner.transaction_id.to_string()
1243                            && input.to_utxo().input.output_index == ref_input.inner.index as u32
1244                    })
1245                    .is_none()
1246            })
1247            .cloned()
1248            .collect();
1249        for pallas_ref_input in final_ref_inputs.iter() {
1250            let Some(script_source_tx_in) =
1251                self.script_source_ref_inputs
1252                    .iter()
1253                    .find(|script_source_tx_in| {
1254                        script_source_tx_in.tx_hash
1255                            == pallas_ref_input.inner.transaction_id.to_string()
1256                            && script_source_tx_in.tx_index == pallas_ref_input.inner.index as u32
1257                    })
1258            else {
1259                continue;
1260            };
1261            let Some(script_size) = script_source_tx_in.script_size else {
1262                continue;
1263            };
1264            self.total_script_size += script_size;
1265        }
1266        Ok(Some(final_ref_inputs))
1267    }
1268
1269    fn process_datum_source(&mut self, datum_source: DatumSource) -> Result<(), WError> {
1270        match datum_source {
1271            ProvidedDatumSource(provided_datum_source) => {
1272                self.plutus_data_vec
1273                    .push(PlutusData::new(provided_datum_source.data)?);
1274            }
1275            InlineDatumSource(inline_datum_source) => {
1276                self.ref_inputs_vec.push(TransactionInput::new(
1277                    &inline_datum_source.tx_hash,
1278                    inline_datum_source.tx_index.into(),
1279                )?)
1280            }
1281        };
1282        Ok(())
1283    }
1284
1285    fn process_witness_set(
1286        &'_ mut self,
1287        tx_inputs: Vec<TransactionInput>,
1288        certificates: Option<Vec<Certificate>>,
1289        withdrawals: Option<Vec<(RewardAccount, u64)>>,
1290        mints: Option<MultiassetNonZeroInt>,
1291        votes: Option<Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)>>,
1292    ) -> Result<WitnessSet<'_>, WError> {
1293        let native_scripts = if self.native_scripts_vec.is_empty() {
1294            None
1295        } else {
1296            Some(self.native_scripts_vec.clone())
1297        };
1298        let plutus_v1_scripts = if self.plutus_v1_scripts_vec.is_empty() {
1299            None
1300        } else {
1301            Some(self.plutus_v1_scripts_vec.clone())
1302        };
1303        let plutus_v2_scripts = if self.plutus_v2_scripts_vec.is_empty() {
1304            None
1305        } else {
1306            Some(self.plutus_v2_scripts_vec.clone())
1307        };
1308        let plutus_v3_scripts = if self.plutus_v3_scripts_vec.is_empty() {
1309            None
1310        } else {
1311            Some(self.plutus_v3_scripts_vec.clone())
1312        };
1313        let plutus_data = if self.plutus_data_vec.is_empty() {
1314            None
1315        } else {
1316            Some(self.plutus_data_vec.clone())
1317        };
1318        let mut redeemers: Vec<PallasRedeemer> = vec![];
1319        // Update redeemer indexes for input redeemers
1320        for (input, redeemer) in self.input_redeemers_vec.clone() {
1321            // Find the index of the input in the transaction inputs
1322            let index = tx_inputs.iter().position(|tx_input| {
1323                tx_input.inner.transaction_id.to_string() == input.inner.transaction_id.to_string()
1324                    && tx_input.inner.index == input.inner.index
1325            });
1326            if let Some(idx) = index {
1327                redeemers.push(PallasRedeemer {
1328                    tag: PallasRedeemerTag::Spend,
1329                    index: idx as u32,
1330                    data: redeemer.inner.data.clone(),
1331                    ex_units: redeemer.inner.ex_units.clone(),
1332                })
1333            } else {
1334                return Err(WError::new(
1335                    "WhiskyPallas - Processing witness set:",
1336                    "Input for redeemer not found in transaction inputs",
1337                ));
1338            }
1339        }
1340        // Update redeemer indexes for certificate redeemers
1341        let certificates = certificates.unwrap_or_default();
1342        for (cert, redeemer) in self.certificate_redeemers_vec.clone() {
1343            // Find the index of the certificate in the transaction body certificates
1344            let index = certificates.iter().position(|c| c == &cert);
1345            if let Some(idx) = index {
1346                redeemers.push(PallasRedeemer {
1347                    tag: PallasRedeemerTag::Cert,
1348                    index: idx as u32,
1349                    data: redeemer.inner.data.clone(),
1350                    ex_units: redeemer.inner.ex_units.clone(),
1351                })
1352            } else {
1353                return Err(WError::new(
1354                    "WhiskyPallas - Processing witness set:",
1355                    "Certificate for redeemer not found in transaction certificates",
1356                ));
1357            }
1358        }
1359        let withdrawals = withdrawals.unwrap_or_default();
1360        // Update redeemer indexes for withdrawal redeemers
1361        for (reward_account, redeemer) in self.withdrawal_redeemers_vec.clone() {
1362            // Find the index of the withdrawal in the transaction body withdrawals
1363            let index = withdrawals.iter().position(|w| w.0 == reward_account);
1364            if let Some(idx) = index {
1365                redeemers.push(PallasRedeemer {
1366                    tag: PallasRedeemerTag::Reward,
1367                    index: idx as u32,
1368                    data: redeemer.inner.data.clone(),
1369                    ex_units: redeemer.inner.ex_units.clone(),
1370                })
1371            } else {
1372                return Err(WError::new(
1373                    "WhiskyPallas - Processing witness set:",
1374                    "Withdrawal for redeemer not found in transaction withdrawals",
1375                ));
1376            }
1377        }
1378        let mints = mints.unwrap_or(MultiassetNonZeroInt::new(vec![])?);
1379        // Update redeemer indexes for mint redeemers
1380        for (policy_id, redeemer) in self.mint_redeemers_vec.clone() {
1381            // Find the index of the mint in the transaction body mints
1382            let index = mints
1383                .inner
1384                .keys()
1385                .position(|pid| pid.to_string() == policy_id);
1386
1387            if let Some(idx) = index {
1388                redeemers.push(PallasRedeemer {
1389                    tag: PallasRedeemerTag::Mint,
1390                    index: idx as u32,
1391                    data: redeemer.inner.data.clone(),
1392                    ex_units: redeemer.inner.ex_units.clone(),
1393                })
1394            } else {
1395                return Err(WError::new(
1396                    "WhiskyPallas - Processing witness set:",
1397                    "Mint for redeemer not found in transaction mints",
1398                ));
1399            }
1400        }
1401
1402        // Update redeemer indexes for vote redeemers
1403        let votes = votes.unwrap_or_default();
1404        for (voter, redeemer) in self.vote_redeemers_vec.clone() {
1405            // Find the index of the vote in the transaction body votes
1406            let index = votes.iter().position(|(v, _)| *v == voter);
1407            if let Some(idx) = index {
1408                redeemers.push(PallasRedeemer {
1409                    tag: PallasRedeemerTag::Vote,
1410                    index: idx as u32,
1411                    data: redeemer.inner.data.clone(),
1412                    ex_units: redeemer.inner.ex_units.clone(),
1413                })
1414            } else {
1415                return Err(WError::new(
1416                    "WhiskyPallas - Processing witness set:",
1417                    "Vote for redeemer not found in transaction votes",
1418                ));
1419            }
1420        }
1421
1422        WitnessSet::new(
1423            None,
1424            native_scripts,
1425            None,
1426            plutus_v1_scripts,
1427            plutus_data,
1428            if redeemers.is_empty() {
1429                None
1430            } else {
1431                Some(
1432                    redeemers
1433                        .iter()
1434                        .map(|redeemer| Redeemer {
1435                            inner: redeemer.clone(),
1436                        })
1437                        .collect(),
1438                )
1439            },
1440            plutus_v2_scripts,
1441            plutus_v3_scripts,
1442        )
1443    }
1444
1445    pub fn build_tx(
1446        &mut self,
1447        tx_builder_body: TxBuilderBody,
1448        balanced: bool,
1449    ) -> Result<String, WError> {
1450        let inputs = self.process_inputs(tx_builder_body.inputs.clone())?;
1451        let mut outputs = self.process_outputs(tx_builder_body.outputs)?;
1452        let ttl = tx_builder_body.validity_range.invalid_hereafter;
1453        let certificates = self.process_certificates(tx_builder_body.certificates)?;
1454        let withdrawals = self.process_withdrawals(tx_builder_body.withdrawals)?;
1455        let validity_interval_start = tx_builder_body.validity_range.invalid_before;
1456        let mints = self.process_mints(tx_builder_body.mints)?;
1457        let collaterals = self.process_collaterals(tx_builder_body.collaterals)?;
1458        let required_signers =
1459            self.process_required_signers(tx_builder_body.required_signatures)?;
1460        let network = tx_builder_body
1461            .network
1462            .clone()
1463            .unwrap_or(whisky_common::Network::Mainnet);
1464        let network_id = match tx_builder_body.network.clone() {
1465            Some(network) => match network {
1466                whisky_common::Network::Mainnet => Some(NetworkId::new(NetworkIdKind::Mainnet)),
1467                _ => Some(NetworkId::new(NetworkIdKind::Testnet)),
1468            },
1469            None => None,
1470        };
1471        let total_collateral = self.process_total_collateral(tx_builder_body.total_collateral)?;
1472        let reference_inputs = self
1473            .process_reference_inputs(tx_builder_body.reference_inputs, tx_builder_body.inputs)?;
1474        let voting_procedures = self.process_voting_procedures(tx_builder_body.votes)?;
1475        let cost_models = get_cost_models_from_network(&network);
1476        let plutus_version: Option<u8> = if self.plutus_v3_used {
1477            Some(2)
1478        } else if self.plutus_v2_used {
1479            Some(1)
1480        } else if self.plutus_v1_used {
1481            Some(0)
1482        } else {
1483            None
1484        };
1485        let total_script_size = self.total_script_size;
1486        let protocol_params = self.protocol_params.clone();
1487        let inputs_map = self.inputs_map.clone();
1488        let required_signatures_vec = self.required_signatures_vec.clone();
1489        let witness_set = self.process_witness_set(
1490            inputs.clone(),
1491            certificates.clone(),
1492            withdrawals.clone(),
1493            mints.clone(),
1494            voting_procedures.clone(),
1495        )?;
1496        let script_data_hash = match plutus_version {
1497            Some(version) => {
1498                let cost_model = match version {
1499                    0 => cost_models.get(0),
1500                    1 => cost_models.get(1),
1501                    2 => cost_models.get(2),
1502                    _ => None,
1503                };
1504                let language_view = cost_model.map(|cm| LanguageView(version, cm.clone()));
1505                Some(
1506                    ScriptData::build_for(&witness_set.inner, &language_view)
1507                        .unwrap()
1508                        .hash()
1509                        .to_string(),
1510                )
1511            }
1512            None => None,
1513        };
1514        let fee = match tx_builder_body.fee {
1515            Some(fee) => fee.parse::<u64>().map_err(|e| {
1516                WError::new(
1517                    "WhiskyPallas - Building transaction:",
1518                    &format!("Failed to parse fee: {}", e.to_string()),
1519                )
1520            })?,
1521            None => {
1522                let mock_tx_body = TransactionBody::new(
1523                    inputs.clone(),
1524                    outputs.clone(),
1525                    18446744073709551615, // Max u64 as placeholder fee
1526                    ttl,
1527                    certificates.clone(),
1528                    withdrawals.clone(),
1529                    None,
1530                    validity_interval_start,
1531                    mints.clone(),
1532                    script_data_hash.clone(),
1533                    collaterals.clone(),
1534                    required_signers.clone(),
1535                    network_id.clone(),
1536                    None,
1537                    total_collateral,
1538                    reference_inputs.clone(),
1539                    voting_procedures.clone(),
1540                    None, // Proposals are currently not supported
1541                    None, // Treasury donations are currently not supported
1542                    None, // Treasury donations are currently not supported
1543                )?;
1544                let mock_witness_set = PallasWitnessSet {
1545                    vkeywitness: required_signatures_to_mock_witnesses(
1546                        required_signatures_vec.clone(),
1547                    ),
1548                    native_script: witness_set.inner.native_script.clone(),
1549                    bootstrap_witness: witness_set.inner.bootstrap_witness.clone(),
1550                    plutus_v1_script: witness_set.inner.plutus_v1_script.clone(),
1551                    plutus_data: witness_set.inner.plutus_data.clone(),
1552                    redeemer: witness_set.inner.redeemer.clone(),
1553                    plutus_v2_script: witness_set.inner.plutus_v2_script.clone(),
1554                    plutus_v3_script: witness_set.inner.plutus_v3_script.clone(),
1555                };
1556                let mock_tx = Transaction::new(
1557                    mock_tx_body,
1558                    WitnessSet {
1559                        inner: mock_witness_set,
1560                    },
1561                    true,
1562                    None,
1563                )?;
1564                calculate_fee(mock_tx, total_script_size, protocol_params.clone())?
1565            }
1566        };
1567
1568        if balanced {
1569            let mut change_value: Value = Value::new(0, None);
1570            for (_, value) in inputs_map {
1571                change_value = change_value.add(&value)?;
1572            }
1573            for (_, amount) in withdrawals.clone().unwrap_or_default() {
1574                change_value = change_value.add(&Value::new(amount, None))?;
1575            }
1576            let mut positive_mint_value = Value::new(0, None);
1577            let mut negative_mint_value = Value::new(0, None);
1578            if let Some(ma_non_zero_int) = mints.clone() {
1579                for (policy_id, assets) in ma_non_zero_int.inner {
1580                    for (asset_name, amount) in assets {
1581                        if i64::from(amount) > 0 {
1582                            positive_mint_value = positive_mint_value.add(&Value::new(
1583                                0,
1584                                Some(MultiassetPositiveCoin::new(vec![(
1585                                    policy_id.to_string(),
1586                                    vec![(
1587                                        asset_name.to_string(),
1588                                        u64::try_from(i64::from(amount)).map_err(|_| {
1589                                            WError::new(
1590                                                "WhiskyPallas - Building transaction:",
1591                                                "Invalid mint amount while balancing change output",
1592                                            )
1593                                        })?,
1594                                    )],
1595                                )])?),
1596                            ))?;
1597                        } else {
1598                            negative_mint_value = negative_mint_value.add(&Value::new(
1599                                0,
1600                                Some(MultiassetPositiveCoin::new(vec![(
1601                                    policy_id.to_string(),
1602                                    vec![(
1603                                        asset_name.to_string(),
1604                                        u64::try_from(-i64::from(amount)).map_err(|_| {
1605                                            WError::new(
1606                                                "WhiskyPallas - Building transaction:",
1607                                                "Invalid mint amount while balancing change output",
1608                                            )
1609                                        })?,
1610                                    )],
1611                                )])?),
1612                            ))?;
1613                        }
1614                    }
1615                }
1616            }
1617            change_value = change_value.add(&positive_mint_value)?;
1618            change_value = change_value.sub(&negative_mint_value).map_err(|_| {
1619                WError::new(
1620                    "WhiskyPallas - Balancing transaction",
1621                    "Burn values not covered by inputs",
1622                )
1623            })?;
1624            for output in outputs.iter() {
1625                let output_value = match &output.inner {
1626                    pallas::ledger::primitives::babbage::GenTransactionOutput::PostAlonzo(
1627                        tx_out,
1628                    ) => tx_out.value.clone(),
1629                    pallas::ledger::primitives::babbage::GenTransactionOutput::Legacy(tx_out) => {
1630                        let legacy_value = &tx_out.amount;
1631                        let coerced_value: PallasValue = match legacy_value {
1632                            pallas::ledger::primitives::alonzo::Value::Coin(coin) => {
1633                                PallasValue::Coin(coin.clone())
1634                            }
1635                            pallas::ledger::primitives::alonzo::Value::Multiasset(
1636                                coin,
1637                                asset_map,
1638                            ) => {
1639                                let converted_asset_map = asset_map
1640                                    .into_iter()
1641                                    .map(|(policy_id, assets)| {
1642                                        let converted_assets = assets
1643                                            .into_iter()
1644                                            .map(|(asset_name, amount)| {
1645                                                (
1646                                                    asset_name.clone(),
1647                                                    PositiveCoin::try_from(amount.clone()).unwrap(),
1648                                                )
1649                                            })
1650                                            .collect();
1651                                        (policy_id.clone(), converted_assets)
1652                                    })
1653                                    .collect();
1654                                PallasValue::Multiasset(coin.clone(), converted_asset_map)
1655                            }
1656                        };
1657                        coerced_value
1658                    }
1659                };
1660                change_value = change_value
1661                    .sub(&Value {
1662                        inner: output_value,
1663                    })
1664                    .map_err(|e| {
1665                        WError::new(
1666                            "WhiskyPallas - Building transaction:",
1667                            &format!(
1668                                "Error while balancing change output, inputs less than outputs: {}",
1669                                e.to_string()
1670                            ),
1671                        )
1672                    })?;
1673            }
1674            if let Some(certs) = certificates.clone() {
1675                for cert in certs {
1676                    match cert.inner {
1677                        pallas::ledger::primitives::conway::Certificate::StakeRegistration(_) => {
1678                            change_value.sub(&Value::new(protocol_params.key_deposit, None))?;
1679                        }
1680                        pallas::ledger::primitives::conway::Certificate::StakeDeregistration(_) => {
1681                            change_value =
1682                                change_value.add(&Value::new(protocol_params.key_deposit, None))?;
1683                        }
1684                        pallas::ledger::primitives::conway::Certificate::StakeDelegation(_, _) => {}
1685                        pallas::ledger::primitives::conway::Certificate::PoolRegistration {
1686                            operator: _,
1687                            vrf_keyhash: _,
1688                            pledge: _,
1689                            cost: _,
1690                            margin: _,
1691                            reward_account: _,
1692                            pool_owners: _,
1693                            relays: _,
1694                            pool_metadata: _,
1695                        } => {
1696                            change_value = change_value
1697                                .sub(&Value::new(protocol_params.pool_deposit, None))?;
1698                        }
1699                        pallas::ledger::primitives::conway::Certificate::PoolRetirement(_, _) => {
1700                            change_value = change_value
1701                                .add(&Value::new(protocol_params.pool_deposit, None))?;
1702                        }
1703                        pallas::ledger::primitives::conway::Certificate::Reg(_, coin) => {
1704                            change_value = change_value.sub(&Value::new(coin, None))?;
1705                        }
1706                        pallas::ledger::primitives::conway::Certificate::UnReg(_, coin) => {
1707                            change_value = change_value.add(&Value::new(coin, None))?;
1708                        }
1709                        pallas::ledger::primitives::conway::Certificate::VoteDeleg(_, _) => {}
1710                        pallas::ledger::primitives::conway::Certificate::StakeVoteDeleg(
1711                            _,
1712                            _,
1713                            _,
1714                        ) => {}
1715                        pallas::ledger::primitives::conway::Certificate::StakeRegDeleg(
1716                            _,
1717                            _,
1718                            coin,
1719                        ) => {
1720                            change_value = change_value.sub(&Value::new(coin, None))?;
1721                        }
1722                        pallas::ledger::primitives::conway::Certificate::VoteRegDeleg(
1723                            _,
1724                            _,
1725                            coin,
1726                        ) => {
1727                            change_value = change_value.sub(&Value::new(coin, None))?;
1728                        }
1729                        pallas::ledger::primitives::conway::Certificate::StakeVoteRegDeleg(
1730                            _,
1731                            _,
1732                            _,
1733                            coin,
1734                        ) => {
1735                            change_value = change_value.sub(&Value::new(coin, None))?;
1736                        }
1737                        pallas::ledger::primitives::conway::Certificate::AuthCommitteeHot(_, _) => {
1738                        }
1739                        pallas::ledger::primitives::conway::Certificate::ResignCommitteeCold(
1740                            _,
1741                            _,
1742                        ) => {}
1743                        pallas::ledger::primitives::conway::Certificate::RegDRepCert(
1744                            _,
1745                            coin,
1746                            _,
1747                        ) => {
1748                            change_value = change_value.sub(&Value::new(coin, None))?;
1749                        }
1750                        pallas::ledger::primitives::conway::Certificate::UnRegDRepCert(_, coin) => {
1751                            change_value = change_value.add(&Value::new(coin, None))?;
1752                        }
1753                        pallas::ledger::primitives::conway::Certificate::UpdateDRepCert(_, _) => {}
1754                    }
1755                }
1756            }
1757            change_value = change_value.sub(&Value::new(fee, None)).map_err(|e| {
1758                WError::new(
1759                    "WhiskyPallas - Building transaction:",
1760                    &format!(
1761                        "Error while balancing change output, inputs less than outputs + fee: {}",
1762                        e.to_string()
1763                    ),
1764                )
1765            })?;
1766            outputs.push(TransactionOutput::new(
1767                &bytes_from_bech32(&tx_builder_body.change_address)?,
1768                change_value,
1769                None,
1770                None,
1771            )?)
1772        }
1773
1774        let tx_body = TransactionBody::new(
1775            inputs,
1776            outputs,
1777            fee,
1778            ttl,
1779            certificates,
1780            withdrawals,
1781            None,
1782            validity_interval_start,
1783            mints,
1784            script_data_hash,
1785            collaterals,
1786            required_signers,
1787            network_id,
1788            None,
1789            total_collateral,
1790            reference_inputs,
1791            voting_procedures,
1792            None, // Proposals are currently not supported
1793            None, // Treasury donations are currently not supported
1794            None, // Treasury donations are currently not supported
1795        )?;
1796        let transaction_bytes = Transaction::new(tx_body, witness_set, true, None)?
1797            .inner
1798            .encode_fragment()
1799            .map_err(|e| {
1800                WError::new(
1801                    "WhiskyPallas - Building transaction:",
1802                    &format!("Encoding failed at Transaction: {}", e.to_string()),
1803                )
1804            })?;
1805        Ok(hex::encode(transaction_bytes))
1806    }
1807}