zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
use crate::crypto::constants::{HashType, KeyType};
use crate::error::{Result, ZeraError};

#[derive(Debug, Clone)]
pub struct BaseWallet {
    pub wallet_type: String,
    pub mnemonic: String,
    pub private_key: String,
    pub address: String,
    pub public_key: String,
    pub coin_type: u32,
    pub symbol: String,
    pub derivation_path: String,
    pub key_type: KeyType,
    pub hash_types: Option<Vec<HashType>>,
}

#[derive(Debug, Clone)]
pub struct Wallet {
    pub wallet_type: &'static str,
    pub mnemonic: String,
    pub private_key: String,
    pub address: String,
    pub public_key: String,
    pub public_key_package: String,
    pub coin_type: u32,
    pub symbol: String,
    pub name: String,
    pub derivation_path: String,
    pub key_type: KeyType,
    pub hash_types: Option<Vec<HashType>>,
    pub extended_private_key: String,
    pub extended_public_key: String,
    pub fingerprint: String,
    pub depth: u8,
    pub index: u32,
}

impl Wallet {
    pub fn secure_clear(&mut self) {
        self.mnemonic.clear();
        self.private_key.clear();
    }
}

#[derive(Debug, Clone)]
pub struct SanitizedWallet {
    pub wallet_type: String,
    pub address: String,
    pub public_key: Option<String>,
    pub coin_type: u32,
    pub symbol: String,
    pub derivation_path: String,
    pub key_type: KeyType,
    pub hash_types: Option<Vec<HashType>>,
    pub mnemonic: &'static str,
    pub private_key: &'static str,
}

pub fn generate_zera_public_key_identifier(
    public_key: &[u8],
    key_type: KeyType,
    hash_types: &[HashType],
) -> Result<String> {
    if public_key.is_empty() {
        return Err(ZeraError::InvalidInput(
            "Public key must be non-empty".into(),
        ));
    }

    let key_prefix = key_type.prefix();
    let public_key_b58 = bs58::encode(public_key).into_string();

    if hash_types.is_empty() {
        return Ok(format!("{key_prefix}_{public_key_b58}"));
    }

    let mut sorted = hash_types.to_vec();
    sorted.sort();
    let hash_prefixes = sorted
        .iter()
        .map(|h| h.prefix())
        .collect::<Vec<_>>()
        .join("_");

    Ok(format!("{key_prefix}_{hash_prefixes}_{public_key_b58}"))
}

#[allow(clippy::too_many_arguments)]
pub fn create_base_wallet(
    wallet_type: &str,
    mnemonic: &str,
    private_key: &str,
    address: &str,
    public_key: &str,
    coin_type: u32,
    symbol: &str,
    derivation_path: &str,
    key_type: KeyType,
    hash_types: Vec<HashType>,
) -> BaseWallet {
    BaseWallet {
        wallet_type: wallet_type.to_string(),
        mnemonic: mnemonic.to_string(),
        private_key: private_key.to_string(),
        address: address.to_string(),
        public_key: public_key.to_string(),
        coin_type,
        symbol: symbol.to_string(),
        derivation_path: derivation_path.to_string(),
        key_type,
        hash_types: if hash_types.is_empty() {
            None
        } else {
            Some(hash_types)
        },
    }
}

pub fn validate_wallet_object(wallet: &BaseWallet) -> bool {
    !wallet.wallet_type.is_empty()
        && !wallet.mnemonic.is_empty()
        && !wallet.private_key.is_empty()
        && !wallet.address.is_empty()
        && !wallet.public_key.is_empty()
        && !wallet.symbol.is_empty()
        && !wallet.derivation_path.is_empty()
}

pub fn sanitize_wallet_for_logging(wallet: &BaseWallet) -> SanitizedWallet {
    let public_key = if wallet.public_key.is_empty() {
        None
    } else {
        Some(format!(
            "{}...",
            &wallet.public_key[..wallet.public_key.len().min(10)]
        ))
    };

    SanitizedWallet {
        wallet_type: wallet.wallet_type.clone(),
        address: wallet.address.clone(),
        public_key,
        coin_type: wallet.coin_type,
        symbol: wallet.symbol.clone(),
        derivation_path: wallet.derivation_path.clone(),
        key_type: wallet.key_type,
        hash_types: wallet.hash_types.clone(),
        mnemonic: "[REDACTED]",
        private_key: "[REDACTED]",
    }
}

pub fn create_wallet_summary(wallet: &BaseWallet) -> String {
    if !validate_wallet_object(wallet) {
        return "Invalid wallet object".to_string();
    }

    let hash_types = wallet
        .hash_types
        .as_ref()
        .filter(|v| !v.is_empty())
        .map(|vals| {
            vals.iter()
                .map(|h| h.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        })
        .unwrap_or_else(|| "none".to_string());

    format!(
        "Wallet Summary:\n  Type: {}\n  Address: {}\n  Key Type: {}\n  Hash Types: {}\n  Derivation Path: {}\n  Coin Type: {}\n  Symbol: {}",
        wallet.wallet_type,
        wallet.address,
        wallet.key_type.as_str(),
        hash_types,
        wallet.derivation_path,
        wallet.coin_type,
        wallet.symbol
    )
}