whisky_csl/tx_parser/
outputs.rs

1use cardano_serialization_lib::{self as csl};
2use whisky_common::*;
3
4use super::CSLParser;
5
6impl CSLParser {
7    pub fn get_outputs(&self) -> &Vec<Output> {
8        &self.tx_body.outputs
9    }
10
11    pub fn extract_output_utxos(tx_hex: &str) -> Result<Vec<UTxO>, WError> {
12        let tx = csl::FixedTransaction::from_hex(tx_hex)
13            .map_err(WError::from_err("extract_output_utxos"))?;
14        let outputs = tx.body().outputs();
15        let tx_hash = tx.transaction_hash().to_hex();
16        let whisky_outputs = csl_outputs_to_outputs(&outputs)?;
17        let mut output_utxos = Vec::new();
18        for (index, output) in whisky_outputs.iter().enumerate() {
19            output_utxos.push(output_to_utxo(output, &tx_hash, index as u32)?);
20        }
21        Ok(output_utxos)
22    }
23
24    pub fn extract_output_cbors(tx_hex: &str) -> Result<Vec<String>, WError> {
25        let mut output_cbors = Vec::new();
26        let csl_tx = csl::FixedTransaction::from_hex(tx_hex)
27            .map_err(WError::from_err("extract_output_cbors"))?;
28        let csl_outputs = csl_tx.body().outputs();
29        let len = csl_outputs.len();
30        for i in 0..len {
31            let output = csl_outputs.get(i);
32            output_cbors.push(output.to_hex());
33        }
34        Ok(output_cbors)
35    }
36
37    pub(super) fn extract_outputs(&mut self) -> Result<(), WError> {
38        let outputs = self.csl_tx_body.outputs();
39        self.tx_body.outputs = csl_outputs_to_outputs(&outputs)?;
40        Ok(())
41    }
42}
43
44fn output_to_utxo(output: &Output, tx_hash: &String, index: u32) -> Result<UTxO, WError> {
45    let datum = match &output.datum {
46        Some(Datum::Inline(s)) => Some(s.clone()),
47        Some(Datum::Embedded(s)) => Some(s.clone()),
48        _ => None,
49    };
50    let data_hash = match &output.datum {
51        Some(Datum::Hash(s)) => Some(s.clone()),
52        _ => None,
53    };
54    let script_ref = match &output.reference_script {
55        Some(script) => Some(output_reference_script_to_script_source(&script)?),
56        None => None,
57    };
58    let script_hash = match &script_ref {
59        Some(script) => {
60            if script.is_native_script() {
61                Some(script.native_script().unwrap().hash().to_hex())
62            } else {
63                Some(script.plutus_script().unwrap().hash().to_hex())
64            }
65        }
66        None => None,
67    };
68    Ok(UTxO {
69        input: UtxoInput {
70            tx_hash: tx_hash.clone(),
71            output_index: index as u32,
72        },
73        output: UtxoOutput {
74            address: output.address.clone(),
75            amount: output.amount.clone(),
76            data_hash,
77            plutus_data: datum,
78            script_ref: script_ref.map(|script| script.to_hex()),
79            script_hash: script_hash,
80        },
81    })
82}
83
84fn output_reference_script_to_script_source(
85    output_reference_script: &OutputScriptSource,
86) -> Result<csl::ScriptRef, WError> {
87    match output_reference_script {
88        OutputScriptSource::ProvidedScriptSource(script) => {
89            let language_version = match script.language_version {
90                LanguageVersion::V1 => csl::Language::new_plutus_v1(),
91                LanguageVersion::V2 => csl::Language::new_plutus_v2(),
92                LanguageVersion::V3 => csl::Language::new_plutus_v3(),
93            };
94            let script_ref = csl::ScriptRef::new_plutus_script(
95                &csl::PlutusScript::from_hex_with_version(&script.script_cbor, &language_version)
96                    .map_err(|e| {
97                    WError::new(
98                        "output_reference_script_to_script_source",
99                        &format!("Failed to convert script to plutus script: {:?}", e),
100                    )
101                })?,
102            );
103            Ok(script_ref)
104        }
105        OutputScriptSource::ProvidedSimpleScriptSource(script) => {
106            let script_ref = csl::ScriptRef::new_native_script(
107                &csl::NativeScript::from_hex(&script.script_cbor).map_err(|e| {
108                    WError::new(
109                        "output_reference_script_to_script_source",
110                        &format!("Failed to convert script to native script: {:?}", e),
111                    )
112                })?,
113            );
114            Ok(script_ref)
115        }
116    }
117}
118
119fn csl_outputs_to_outputs(outputs: &csl::TransactionOutputs) -> Result<Vec<Output>, WError> {
120    let mut result = Vec::new();
121
122    for i in 0..outputs.len() {
123        let output = outputs.get(i);
124        let mut value: Vec<Asset> = vec![];
125
126        value.push(Asset::new_from_str(
127            "lovelace",
128            &output.amount().coin().to_str(),
129        ));
130
131        if let Some(multi_asset) = output.amount().multiasset() {
132            for policy_id_index in 0..multi_asset.keys().len() {
133                let policy_id = multi_asset.keys().get(policy_id_index);
134                let assets = multi_asset.get(&policy_id).ok_or_else(|| {
135                    WError::new(
136                        "csl_outputs_to_outputs",
137                        &format!("Failed to get assets for policy ID: {}", policy_id.to_hex()),
138                    )
139                })?;
140                for asset_index in 0..assets.keys().len() {
141                    let asset_name = assets.keys().get(asset_index);
142                    let asset_quantity = assets.get(&asset_name).ok_or_else(|| {
143                        WError::new(
144                            "csl_outputs_to_outputs",
145                            &format!(
146                                "Failed to get quantity for asset: {}",
147                                asset_name.to_string()
148                            ),
149                        )
150                    })?;
151                    let asset_name_hex = hex::encode(asset_name.name());
152                    let concated_name = policy_id.to_hex() + &asset_name_hex;
153
154                    value.push(Asset::new_from_str(
155                        &concated_name,
156                        &asset_quantity.to_str(),
157                    ));
158                }
159            }
160        }
161
162        let datum: Option<Datum> = if let Some(csl_datum) = output.plutus_data() {
163            Some(Datum::Inline(csl_datum.to_hex()))
164        } else {
165            output
166                .data_hash()
167                .map(|csl_datum_hash| Datum::Hash(csl_datum_hash.to_hex()))
168        };
169
170        let reference_script: Option<OutputScriptSource> = match output.script_ref() {
171            Some(csl_script_ref) => {
172                if let Some(plutus_script) = csl_script_ref.plutus_script() {
173                    let language_version = match plutus_script.language_version().kind() {
174                        csl::LanguageKind::PlutusV1 => LanguageVersion::V1,
175                        csl::LanguageKind::PlutusV2 => LanguageVersion::V2,
176                        csl::LanguageKind::PlutusV3 => LanguageVersion::V3,
177                    };
178                    Some(OutputScriptSource::ProvidedScriptSource(
179                        ProvidedScriptSource {
180                            script_cbor: plutus_script.to_hex(),
181                            language_version,
182                        },
183                    ))
184                } else if let Some(native_script) = csl_script_ref.native_script() {
185                    Some(OutputScriptSource::ProvidedSimpleScriptSource(
186                        ProvidedSimpleScriptSource {
187                            script_cbor: native_script.to_hex(),
188                        },
189                    ))
190                } else {
191                    None
192                }
193            }
194            None => None,
195        };
196
197        result.push(Output {
198            address: output.address().to_bech32(None).map_err(|e| {
199                WError::new(
200                    "csl_outputs_to_outputs",
201                    &format!("Failed to convert address to bech32: {:?}", e),
202                )
203            })?,
204            amount: value,
205            datum,
206            reference_script,
207        });
208    }
209    Ok(result)
210}