seedelf-cli 0.4.5

Seedelf: A Cardano Stealth Wallet
Documentation
use crate::{
    address,
    assets::Assets,
    constants::{
        CPU_COST_DENOMINATOR, CPU_COST_NUMERATOR, Config, MAINNET_COLLATERAL_UTXO,
        MEM_COST_DENOMINATOR, MEM_COST_NUMERATOR, OVERHEAD_COST, PREPROD_COLLATERAL_UTXO,
        UTXO_COST_PER_BYTE, get_config,
    },
    register::Register,
    schnorr,
};
use pallas_addresses::Address;
use pallas_crypto::hash::Hash;
use pallas_primitives::Fragment;
use pallas_txbuilder::{Input, Output};
use serde_json::Value;

/// Calculates the minimum required UTXO for a given output.
///
/// This function calculates the minimum required UTXO value based on the size of the serialized output.
/// The calculation uses the post-Alonzo (Babbage) form of the output and incorporates
/// the overhead cost and cost per byte.
///
/// # Arguments
///
/// * `output` - An `Output` representing the transaction output.
///
/// # Returns
///
/// * `u64` - The minimum required UTXO value in lovelace.
pub fn calculate_min_required_utxo(output: Output) -> u64 {
    // we need the output in the post alonzo form so we can encode it
    let output_cbor_length: u64 = output
        .build_babbage_raw()
        .unwrap()
        .encode_fragment()
        .unwrap()
        .len()
        .try_into()
        .unwrap();
    // sum the overhead and length times the cost per byte
    (OVERHEAD_COST + output_cbor_length) * UTXO_COST_PER_BYTE
}

/// Creates a collateral input for a transaction based on the network.
///
/// This function selects a pre-defined collateral UTXO based on the network flag
/// and creates an `Input` using the UTXO hash and an index of `0`.
///
/// # Arguments
///
/// * `network_flag` - A boolean flag specifying the network:
///     - `true` for Preprod.
///     - `false` for Mainnet.
///
/// # Returns
///
/// * `Input` - A transaction input constructed from the specified collateral UTXO.
pub fn collateral_input(network_flag: bool) -> Input {
    let utxo: &str = if network_flag {
        PREPROD_COLLATERAL_UTXO
    } else {
        MAINNET_COLLATERAL_UTXO
    };
    Input::new(
        pallas_crypto::hash::Hash::new(
            hex::decode(utxo)
                .expect("Invalid hex string")
                .try_into()
                .expect("Failed to convert to 32-byte array"),
        ),
        0,
    )
}

/// Creates a reference UTXO input for the Seedelf contract.
///
/// This function selects a pre-defined reference UTXO based on the network flag
/// and creates an `Input` using the UTXO hash and a fixed index of `1`.
///
/// # Arguments
///
/// * `network_flag` - A boolean flag specifying the network:
///     - `true` for Preprod.
///     - `false` for Mainnet.
///
/// # Returns
///
/// * `Input` - A transaction input constructed from the specified reference UTXO.
pub fn seedelf_reference_utxo(network_flag: bool, variant: u64) -> Input {
    let config: Config = get_config(variant, network_flag).unwrap_or_else(|| {
        eprintln!("Error: Invalid Variant");
        std::process::exit(1);
    });
    Input::new(
        Hash::new(
            hex::decode(config.reference.seedelf_reference_utxo)
                .expect("Invalid hex string")
                .try_into()
                .expect("Failed to convert to 32-byte array"),
        ),
        1,
    )
}

/// Creates a reference UTXO input for the wallet contract.
///
/// This function selects a pre-defined wallet reference UTXO based on the network flag
/// and creates an `Input` using the UTXO hash and a fixed index of `1`.
///
/// # Arguments
///
/// * `network_flag` - A boolean flag specifying the network:
///     - `true` for Preprod.
///     - `false` for Mainnet.
///
/// # Returns
///
/// * `Input` - A transaction input constructed from the specified wallet reference UTXO.
pub fn wallet_reference_utxo(network_flag: bool, variant: u64) -> Input {
    let config: Config = get_config(variant, network_flag).unwrap_or_else(|| {
        eprintln!("Error: Invalid Variant");
        std::process::exit(1);
    });

    Input::new(
        Hash::new(
            hex::decode(config.reference.wallet_reference_utxo)
                .expect("Invalid hex string")
                .try_into()
                .expect("Failed to convert to 32-byte array"),
        ),
        1,
    )
}

/// Generates the SeedElf token name.
///
/// This function constructs a token name by concatenating a prefix, a provided label,
/// the smallest input's transaction index (formatted as hex), and its transaction hash.
/// The smallest input is determined lexicographically based on its transaction hash
/// and index. The result is a byte vector derived from the concatenated string.
///
/// # Arguments
///
/// * `label` - A string label to include in the token name.
/// * `inputs` - An optional reference to a vector of `Input` structs. The smallest input is selected
///              based on lexicographical order of the transaction hash and the index.
///
/// # Returns
///
/// * `Vec<u8>` - A vector of bytes representing the constructed token name.
pub fn seedelf_token_name(label: String, inputs: Option<&Vec<Input>>) -> Vec<u8> {
    let mut label_hex: String = hex::encode(label);
    label_hex.truncate(30);
    // find the smallest input, first in lexicogrpahical order
    let smallest_input: &Input = inputs
        .and_then(|inputs| {
            inputs.iter().min_by(|a, b| {
                a.tx_hash
                    .0
                    .cmp(&b.tx_hash.0)
                    .then(a.txo_index.cmp(&b.txo_index))
            })
        })
        .unwrap();
    // format the tx index
    let formatted_index: String = format!("{:02x}", smallest_input.txo_index);
    let tx_hash_hex: String = hex::encode(smallest_input.tx_hash.0);
    let prefix: String = "5eed0e1f".to_string();
    let concatenated: String = format!("{}{}{}{}", prefix, label_hex, formatted_index, tx_hash_hex);
    hex::decode(&concatenated[..64.min(concatenated.len())]).unwrap()
}

/// Computes the computation fee for a transaction.
///
/// This function calculates the total computation fee based on the memory and CPU units consumed.
/// It applies a cost model where memory and CPU costs are scaled using pre-defined numerators
/// and denominators.
///
/// # Arguments
///
/// * `mem_units` - The number of memory units consumed.
/// * `cpu_units` - The number of CPU units consumed.
///
/// # Returns
///
/// * `u64` - The total computation fee as a sum of the memory and CPU costs.
pub fn computation_fee(mem_units: u64, cpu_units: u64) -> u64 {
    (MEM_COST_NUMERATOR * mem_units / MEM_COST_DENOMINATOR)
        + (CPU_COST_NUMERATOR * cpu_units / CPU_COST_DENOMINATOR)
}

/// Extracts CPU and memory budgets from a JSON value.
///
/// This function parses a JSON structure to extract CPU and memory usage budgets
/// from an array located under the `"result"` key. Each item in the array is expected
/// to have a `"budget"` object containing `"cpu"` and `"memory"` fields.
///
/// # Arguments
///
/// * `value` - A reference to a `serde_json::Value` containing the JSON data.
///
/// # Returns
///
/// * `Vec<(u64, u64)>` - A vector of tuples, where each tuple contains:
///     - `u64` - CPU budget.
///     - `u64` - Memory budget.
pub fn extract_budgets(value: &Value) -> Vec<(u64, u64)> {
    let mut budgets: Vec<(u64, u64)> = Vec::new();

    // Ensure the value contains the expected "result" array
    if let Some(result_array) = value.get("result").and_then(|r| r.as_array()) {
        for item in result_array {
            if let Some(budget) = item.get("budget") {
                if let (Some(cpu), Some(memory)) = (
                    budget.get("cpu").and_then(|c| c.as_u64()),
                    budget.get("memory").and_then(|m| m.as_u64()),
                ) {
                    budgets.push((cpu, memory));
                }
            }
        }
    }

    budgets
}

/// Calculates the total computation fee for a list of CPU and memory budgets.
///
/// This function iterates through a vector of `(CPU, Memory)` tuples and computes
/// the fee for each pair using the `computation_fee` function. The resulting fees
/// are summed to produce the total computation fee.
///
/// # Arguments
///
/// * `budgets` - A vector of tuples where each tuple contains:
///     - `u64` - CPU units.
///     - `u64` - Memory units.
///
/// # Returns
///
/// * `u64` - The total computation fee for all provided budgets.
pub fn total_computation_fee(budgets: Vec<(u64, u64)>) -> u64 {
    let mut fee: u64 = 0;
    for (cpu, mem) in budgets.into_iter() {
        fee += computation_fee(mem, cpu);
    }
    fee
}

/// Calculates the minimum lovelace required for a SeedElf transaction.
///
/// This function constructs a staged transaction output that includes:
/// - A long token name.
/// - Inline datum.
/// - A specific asset tied to a SeedElf policy ID.
///
/// The function then calculates the minimum required lovelace using the
/// `calculate_min_required_utxo` function.
///
/// # Returns
///
/// * `u64` - The minimum lovelace required for the transaction output.
pub fn seedelf_minimum_lovelace() -> u64 {
    // a very long token name
    let token_name: Vec<u8> = [
        94, 237, 14, 31, 1, 66, 250, 134, 20, 230, 198, 12, 121, 19, 73, 107, 154, 156, 226, 154,
        138, 103, 76, 134, 93, 156, 23, 169, 169, 167, 201, 55,
    ]
    .to_vec();
    let policy_id: Vec<u8> = [
        94, 237, 14, 31, 1, 66, 250, 134, 20, 230, 198, 12, 121, 19, 73, 107, 154, 156, 226, 154,
        138, 103, 76, 134, 93, 156, 23, 169,
    ]
    .to_vec();
    let staging_output: Output = Output::new(address::wallet_contract(true, 1), 5_000_000)
        .set_inline_datum(
            Register::create(schnorr::random_scalar())
                .rerandomize()
                .to_vec(),
        )
        .add_asset(
            Hash::new(policy_id.try_into().expect("Not Correct Length")),
            token_name,
            1,
        )
        .unwrap();

    // use the staging output to calculate the minimum required lovelace
    calculate_min_required_utxo(staging_output)
}

/// Calculates the minimum lovelace required for a wallet transaction output with assets.
///
/// This function constructs a staged transaction output that includes:
/// - Inline datum.
/// - A list of assets (policy IDs, token names, and amounts).
///
/// The function iterates over the provided `Assets` to add each asset to the output
/// and then calculates the minimum required lovelace using `calculate_min_required_utxo`.
///
/// # Arguments
///
/// * `tokens` - An `Assets` struct containing a list of assets (policy ID, token name, and amount).
///
/// # Returns
///
/// * `u64` - The minimum lovelace required for the transaction output.
pub fn wallet_minimum_lovelace_with_assets(tokens: Assets) -> u64 {
    let mut staging_output: Output = Output::new(address::wallet_contract(true, 1), 5_000_000)
        .set_inline_datum(
            Register::create(schnorr::random_scalar())
                .rerandomize()
                .to_vec(),
        );

    for asset in tokens.items {
        staging_output = staging_output
            .add_asset(asset.policy_id, asset.token_name, asset.amount)
            .unwrap();
    }

    // use the staging output to calculate the minimum required lovelace
    calculate_min_required_utxo(staging_output)
}

/// Calculates the minimum lovelace required for a given address with assets.
///
/// This function constructs a transaction output for the specified address with a base
/// lovelace amount and iterates over the provided list of assets to add them to the output.
/// It then calculates the minimum required lovelace using `calculate_min_required_utxo`.
///
/// # Arguments
///
/// * `address` - A string slice containing the Bech32-encoded address.
/// * `tokens` - An `Assets` struct containing a list of assets (policy ID, token name, and amount).
///
/// # Returns
///
/// * `u64` - The minimum lovelace required for the transaction output.
pub fn address_minimum_lovelace_with_assets(address: &str, tokens: Assets) -> u64 {
    let addr: Address = Address::from_bech32(address).unwrap();
    let mut staging_output: Output = Output::new(addr, 5_000_000);

    for asset in tokens.items {
        staging_output = staging_output
            .add_asset(asset.policy_id, asset.token_name, asset.amount)
            .unwrap();
    }

    // use the staging output to calculate the minimum required lovelace
    calculate_min_required_utxo(staging_output)
}