rialo-cdk 0.4.2

Rialo CDK - A comprehensive toolkit for building with the Rialo blockchain
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Utility functions for working with Rialo currency units, networks,
//! wallet configuration, and key conversions.
//!
//! This module provides helper functions for common operations including:
//! - Currency conversion between kelvin and RLO
//! - Formatting amounts with appropriate units
//! - Network URL resolution
//! - Wallet and account identifier parsing
//! - Network validation
//! - Cryptographic key conversions between different formats

#[cfg(feature = "bincode")]
use rialo_s_sdk::signer::Signer;

use crate::constants::{
    KELVIN_PER_RLO, NETWORK_DEVNET, NETWORK_LOCALNET, NETWORK_MAINNET, NETWORK_TESTNET,
    UNIT_KELVIN, UNIT_RLO,
};
#[cfg(feature = "bincode")]
use crate::{
    error::{Result, RialoError},
    keyring::Keyring,
};

/// Converts an amount between different currency units.
///
/// # Arguments
///
/// * `amount` - The amount to convert
/// * `from_unit` - The source unit (either "kelvin" or "RLO")
/// * `to_unit` - The target unit (either "kelvin" or "RLO")
///
/// # Returns
///
/// The converted amount as a floating point number
///
/// # Examples
///
/// ```
/// use rialo_cdk::utils::convert_amount;
/// use rialo_cdk::constants::{UNIT_KELVIN, UNIT_RLO};
///
/// // Convert 1,000,000,000 kelvin to RLO
/// let rol_amount = convert_amount(1_000_000_000, UNIT_KELVIN, UNIT_RLO);
/// assert_eq!(rol_amount, 1.0);
///
/// // Convert 2 RLO to kelvin
/// let kelvin_amount = convert_amount(2, UNIT_RLO, UNIT_KELVIN);
/// assert_eq!(kelvin_amount, 2_000_000_000.0);
/// ```
pub fn convert_amount(amount: u64, from_unit: &str, to_unit: &str) -> f64 {
    match (from_unit, to_unit) {
        (UNIT_KELVIN, UNIT_RLO) => amount as f64 / KELVIN_PER_RLO as f64,
        (UNIT_RLO, UNIT_KELVIN) => amount as f64 * KELVIN_PER_RLO as f64,
        _ => amount as f64, // Same unit or unsupported conversion
    }
}

/// Formats an amount with the specified unit for human-readable display.
///
/// # Arguments
///
/// * `amount` - The amount to format
///
/// # Returns
///
/// A formatted string with the amount and unit
///
/// # Examples
///
/// ```
/// use rialo_cdk::utils::format_amount;
///
/// // Format 1,000,000,000 kelvin as RLO
/// let formatted = format_amount(1_000_000_000);
/// assert_eq!(formatted, "1.000000000 RLO");
/// ```
pub fn format_amount(amount: u64) -> String {
    format!("{:.9} {}", amount as f64 / KELVIN_PER_RLO as f64, "RLO")
}

/// Parses a string in the "wallet.account" format into separate components.
///
/// # Arguments
///
/// * `input` - A string in the format "wallet_name.account_index" or just "wallet_name"
///
/// # Returns
///
/// A tuple containing:
/// - The wallet name as a String
/// - An `Option<usize>` containing the account index if provided, None otherwise
///
/// # Examples
///
/// ```
/// use rialo_cdk::utils::parse_wallet_account;
///
/// // Parse a wallet with an account index
/// let (wallet, account) = parse_wallet_account("my_wallet.2");
/// assert_eq!(wallet, "my_wallet");
/// assert_eq!(account, Some(2));
///
/// // Parse just a wallet name
/// let (wallet, account) = parse_wallet_account("my_wallet");
/// assert_eq!(wallet, "my_wallet");
/// assert_eq!(account, None);
/// ```
pub fn parse_wallet_account(input: &str) -> (String, Option<usize>) {
    if let Some((wallet, account)) = input.split_once('.') {
        if let Ok(index) = account.parse::<usize>() {
            return (wallet.to_string(), Some(index));
        }
    }
    (input.to_string(), None)
}

/// Validates if a given string is a supported network name.
///
/// # Arguments
///
/// * `network` - The network name to validate
///
/// # Returns
///
/// `true` if the network name is valid, `false` otherwise
///
/// # Examples
///
/// ```
/// use rialo_cdk::utils::is_valid_network;
/// use rialo_cdk::constants::NETWORK_MAINNET;
///
/// assert!(is_valid_network(NETWORK_MAINNET));
/// assert!(!is_valid_network("invalid_network"));
/// ```
pub fn is_valid_network(network: &str) -> bool {
    matches!(
        network,
        NETWORK_MAINNET | NETWORK_TESTNET | NETWORK_DEVNET | NETWORK_LOCALNET
    )
}

/// Returns a list of all supported network names.
///
/// # Returns
///
/// A vector of network name string literals
///
/// # Examples
///
/// ```
/// use rialo_cdk::utils::get_supported_networks;
///
/// let networks = get_supported_networks();
/// assert!(networks.contains(&"mainnet"));
/// assert!(networks.contains(&"devnet"));
/// ```
pub fn get_supported_networks() -> Vec<&'static str> {
    vec![
        NETWORK_MAINNET,
        NETWORK_TESTNET,
        NETWORK_DEVNET,
        NETWORK_LOCALNET,
    ]
}

/// Converts a CDK keyring keypair to a Solana SDK keypair.
///
/// The CDK uses ed25519-dalek SigningKey which returns 32 bytes (private key only)
/// from `as_bytes()`, while Solana SDK expects 64 bytes (32 private + 32 public).
/// This function handles the conversion by properly combining the private and public key bytes.
///
/// **Note**: This function requires the `bincode` feature to be enabled.
///
/// # Arguments
///
/// * `keyring` - The CDK keyring containing the keypair to convert
///
/// # Returns
///
/// A Solana SDK keypair that can be used with Solana transactions
///
/// # Errors
///
/// Returns `RialoError::InvalidInput` if:
/// - The keypair bytes have an unexpected length
/// - The Solana keypair creation fails
///
/// # Examples
///
/// ```rust,no_run
/// use rialo_cdk::{utils::convert_keyring_to_solana_keypair, keyring::Keyring};
///
/// async fn example(keyring: &Keyring) -> Result<(), Box<dyn std::error::Error>> {
///     let rialo_s_keypair = convert_keyring_to_solana_keypair(keyring)?;
///     // Use rialo_s_keypair with Solana SDK functions
///     Ok(())
/// }
/// ```
#[cfg(feature = "bincode")]
pub fn convert_keyring_to_solana_keypair(
    keyring: &Keyring,
) -> Result<rialo_s_sdk::signer::keypair::Keypair> {
    let cdk_keypair_bytes = keyring.keypair_bytes();

    tracing::debug!(
        keypair_length = cdk_keypair_bytes.len(),
        "Converting CDK keyring keypair to Solana format"
    );

    // ed25519-dalek SigningKey.as_bytes() returns 32 bytes (private key only)
    // but Solana SDK expects 64 bytes (32 private + 32 public)
    let rialo_s_keypair = if cdk_keypair_bytes.len() == 32 {
        // Extend with public key bytes
        let mut full_keypair_bytes = [0u8; 64];
        full_keypair_bytes[0..32].copy_from_slice(&cdk_keypair_bytes);
        full_keypair_bytes[32..64].copy_from_slice(keyring.pubkey().to_bytes().as_ref());

        rialo_s_sdk::signer::keypair::Keypair::from_bytes(&full_keypair_bytes).map_err(|e| {
            RialoError::InvalidInput(format!(
                "Failed to create Solana keypair from 32+32 bytes: {e}"
            ))
        })?
    } else if cdk_keypair_bytes.len() == 64 {
        // Already in the expected format
        rialo_s_sdk::signer::keypair::Keypair::from_bytes(&cdk_keypair_bytes).map_err(|e| {
            RialoError::InvalidInput(format!(
                "Failed to create Solana keypair from 64 bytes: {e}"
            ))
        })?
    } else {
        return Err(RialoError::InvalidInput(format!(
            "Unexpected keypair byte length: {} (expected 32 or 64)",
            cdk_keypair_bytes.len()
        )));
    };

    tracing::debug!(
        rialo_s_pubkey = %rialo_s_keypair.pubkey(),
        "Successfully converted keypair to Solana format"
    );

    Ok(rialo_s_keypair)
}

/// Deprecated: Use [`convert_keyring_to_solana_keypair`] instead.
#[cfg(feature = "bincode")]
#[deprecated(
    since = "0.2.0",
    note = "Use convert_keyring_to_solana_keypair instead"
)]
pub fn convert_wallet_to_solana_keypair(
    keyring: &Keyring,
) -> Result<rialo_s_sdk::signer::keypair::Keypair> {
    convert_keyring_to_solana_keypair(keyring)
}