whisky-csl 1.0.13

Wrapper around the cardano-serialization-lib for easier transaction building, heavily inspired by cardano-cli APIs
Documentation
use cardano_serialization_lib::{self as csl};
use whisky_common::*;

use super::CSLParser;

impl CSLParser {
    pub fn get_outputs(&self) -> &Vec<Output> {
        &self.tx_body.outputs
    }

    pub fn extract_output_utxos(tx_hex: &str) -> Result<Vec<UTxO>, WError> {
        let tx = csl::FixedTransaction::from_hex(tx_hex)
            .map_err(WError::from_err("extract_output_utxos"))?;
        let outputs = tx.body().outputs();
        let tx_hash = tx.transaction_hash().to_hex();
        let whisky_outputs = csl_outputs_to_outputs(&outputs)?;
        let mut output_utxos = Vec::new();
        for (index, output) in whisky_outputs.iter().enumerate() {
            output_utxos.push(output_to_utxo(output, &tx_hash, index as u32)?);
        }
        Ok(output_utxos)
    }

    pub fn extract_output_cbors(tx_hex: &str) -> Result<Vec<String>, WError> {
        let mut output_cbors = Vec::new();
        let csl_tx = csl::FixedTransaction::from_hex(tx_hex)
            .map_err(WError::from_err("extract_output_cbors"))?;
        let csl_outputs = csl_tx.body().outputs();
        let len = csl_outputs.len();
        for i in 0..len {
            let output = csl_outputs.get(i);
            output_cbors.push(output.to_hex());
        }
        Ok(output_cbors)
    }

    pub(super) fn extract_outputs(&mut self) -> Result<(), WError> {
        let outputs = self.csl_tx_body.outputs();
        self.tx_body.outputs = csl_outputs_to_outputs(&outputs)?;
        Ok(())
    }
}

fn output_to_utxo(output: &Output, tx_hash: &String, index: u32) -> Result<UTxO, WError> {
    let datum = match &output.datum {
        Some(Datum::Inline(s)) => Some(s.clone()),
        Some(Datum::Embedded(s)) => Some(s.clone()),
        _ => None,
    };
    let data_hash = match &output.datum {
        Some(Datum::Hash(s)) => Some(s.clone()),
        _ => None,
    };
    let script_ref = match &output.reference_script {
        Some(script) => Some(output_reference_script_to_script_source(&script)?),
        None => None,
    };
    let script_hash = match &script_ref {
        Some(script) => {
            if script.is_native_script() {
                Some(script.native_script().unwrap().hash().to_hex())
            } else {
                Some(script.plutus_script().unwrap().hash().to_hex())
            }
        }
        None => None,
    };
    Ok(UTxO {
        input: UtxoInput {
            tx_hash: tx_hash.clone(),
            output_index: index as u32,
        },
        output: UtxoOutput {
            address: output.address.clone(),
            amount: output.amount.clone(),
            data_hash,
            plutus_data: datum,
            script_ref: script_ref.map(|script| script.to_hex()),
            script_hash: script_hash,
        },
    })
}

fn output_reference_script_to_script_source(
    output_reference_script: &OutputScriptSource,
) -> Result<csl::ScriptRef, WError> {
    match output_reference_script {
        OutputScriptSource::ProvidedScriptSource(script) => {
            let language_version = match script.language_version {
                LanguageVersion::V1 => csl::Language::new_plutus_v1(),
                LanguageVersion::V2 => csl::Language::new_plutus_v2(),
                LanguageVersion::V3 => csl::Language::new_plutus_v3(),
            };
            let script_ref = csl::ScriptRef::new_plutus_script(
                &csl::PlutusScript::from_hex_with_version(&script.script_cbor, &language_version)
                    .map_err(|e| {
                    WError::new(
                        "output_reference_script_to_script_source",
                        &format!("Failed to convert script to plutus script: {:?}", e),
                    )
                })?,
            );
            Ok(script_ref)
        }
        OutputScriptSource::ProvidedSimpleScriptSource(script) => {
            let script_ref = csl::ScriptRef::new_native_script(
                &csl::NativeScript::from_hex(&script.script_cbor).map_err(|e| {
                    WError::new(
                        "output_reference_script_to_script_source",
                        &format!("Failed to convert script to native script: {:?}", e),
                    )
                })?,
            );
            Ok(script_ref)
        }
    }
}

fn csl_outputs_to_outputs(outputs: &csl::TransactionOutputs) -> Result<Vec<Output>, WError> {
    let mut result = Vec::new();

    for i in 0..outputs.len() {
        let output = outputs.get(i);
        let mut value: Vec<Asset> = vec![];

        value.push(Asset::new_from_str(
            "lovelace",
            &output.amount().coin().to_str(),
        ));

        if let Some(multi_asset) = output.amount().multiasset() {
            for policy_id_index in 0..multi_asset.keys().len() {
                let policy_id = multi_asset.keys().get(policy_id_index);
                let assets = multi_asset.get(&policy_id).ok_or_else(|| {
                    WError::new(
                        "csl_outputs_to_outputs",
                        &format!("Failed to get assets for policy ID: {}", policy_id.to_hex()),
                    )
                })?;
                for asset_index in 0..assets.keys().len() {
                    let asset_name = assets.keys().get(asset_index);
                    let asset_quantity = assets.get(&asset_name).ok_or_else(|| {
                        WError::new(
                            "csl_outputs_to_outputs",
                            &format!(
                                "Failed to get quantity for asset: {}",
                                asset_name.to_string()
                            ),
                        )
                    })?;
                    let asset_name_hex = hex::encode(asset_name.name());
                    let concated_name = policy_id.to_hex() + &asset_name_hex;

                    value.push(Asset::new_from_str(
                        &concated_name,
                        &asset_quantity.to_str(),
                    ));
                }
            }
        }

        let datum: Option<Datum> = if let Some(csl_datum) = output.plutus_data() {
            Some(Datum::Inline(csl_datum.to_hex()))
        } else {
            output
                .data_hash()
                .map(|csl_datum_hash| Datum::Hash(csl_datum_hash.to_hex()))
        };

        let reference_script: Option<OutputScriptSource> = match output.script_ref() {
            Some(csl_script_ref) => {
                if let Some(plutus_script) = csl_script_ref.plutus_script() {
                    let language_version = match plutus_script.language_version().kind() {
                        csl::LanguageKind::PlutusV1 => LanguageVersion::V1,
                        csl::LanguageKind::PlutusV2 => LanguageVersion::V2,
                        csl::LanguageKind::PlutusV3 => LanguageVersion::V3,
                    };
                    Some(OutputScriptSource::ProvidedScriptSource(
                        ProvidedScriptSource {
                            script_cbor: plutus_script.to_hex(),
                            language_version,
                        },
                    ))
                } else if let Some(native_script) = csl_script_ref.native_script() {
                    Some(OutputScriptSource::ProvidedSimpleScriptSource(
                        ProvidedSimpleScriptSource {
                            script_cbor: native_script.to_hex(),
                        },
                    ))
                } else {
                    None
                }
            }
            None => None,
        };

        result.push(Output {
            address: output.address().to_bech32(None).map_err(|e| {
                WError::new(
                    "csl_outputs_to_outputs",
                    &format!("Failed to convert address to bech32: {:?}", e),
                )
            })?,
            amount: value,
            datum,
            reference_script,
        });
    }
    Ok(result)
}