zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
use zera_proto::zera_txn::{
    CoinDenomination, ContractFees, ContractType, ExpenseRatio, Governance, KeyValuePair,
    MaxSupplyRelease, PreMintWallet, RestrictedKey, TokenCompliance,
};

use crate::crypto::address::generate_address_from_public_key;
use crate::error::{Result, ZeraError};
use crate::types::RpcConfig;
use crate::utils::validation::is_valid_contract_id;

#[derive(Debug, Clone, PartialEq)]
pub struct CreateContractOptions {
    pub contract_version: u64,
    pub symbol: String,
    pub name: String,
    pub contract_type: ContractType,
    pub contract_id: String,
    pub public_key_base58_identifier: String,
    pub private_key_base58: String,
    pub governance: Option<Governance>,
    pub restricted_keys: Vec<RestrictedKey>,
    pub max_supply: Option<String>,
    pub contract_fees: Option<ContractFees>,
    pub premint_wallets: Vec<PreMintWallet>,
    pub coin_denomination: CoinDenomination,
    pub custom_parameters: Vec<KeyValuePair>,
    pub expense_ratio: Vec<ExpenseRatio>,
    pub update_contract_fees: Option<bool>,
    pub update_expense_ratio: Option<bool>,
    pub quash_threshold: Option<u32>,
    pub token_compliance: Vec<TokenCompliance>,
    pub kyc_status: Option<bool>,
    pub immutable_kyc_status: Option<bool>,
    pub max_supply_release: Vec<MaxSupplyRelease>,
    pub fee_id: Option<String>,
    pub fee_amount_parts: Option<String>,
    pub memo: Option<String>,
    pub grpc_config: Option<RpcConfig>,
    pub nonce: Option<u64>,
}

pub type BuildContractOptions = CreateContractOptions;

#[derive(Debug, Clone, PartialEq)]
pub struct UpdateContractOptions {
    pub contract_id: String,
    pub contract_version: u64,
    pub public_key_base58_identifier: String,
    pub private_key_base58: String,
    pub name: Option<String>,
    pub governance: Option<Governance>,
    pub restricted_keys: Vec<RestrictedKey>,
    pub contract_fees: Option<ContractFees>,
    pub custom_parameters: Vec<KeyValuePair>,
    pub expense_ratio: Vec<ExpenseRatio>,
    pub token_compliance: Vec<TokenCompliance>,
    pub kyc_status: Option<bool>,
    pub immutable_kyc_status: Option<bool>,
    pub quash_threshold: Option<u32>,
    pub fee_id: Option<String>,
    pub fee_amount_parts: Option<String>,
    pub memo: Option<String>,
    pub grpc_config: Option<RpcConfig>,
    pub nonce: Option<u64>,
}

pub fn validate_create_contract_options(
    contract_id: &str,
    symbol: &str,
    name: &str,
    contract_version: u64,
    contract_type: Option<ContractType>,
) -> Result<()> {
    if !is_valid_contract_id(contract_id) {
        return Err(ZeraError::Validation(
            "ContractId must be provided and follow the format $[letters]+[4 digits] (e.g., $ZRA+0000)"
                .to_string(),
        ));
    }
    if symbol.trim().is_empty() {
        return Err(ZeraError::Validation(
            "Symbol must be provided and non-empty".to_string(),
        ));
    }
    if name.trim().is_empty() {
        return Err(ZeraError::Validation(
            "Name must be provided and non-empty".to_string(),
        ));
    }
    if contract_type.is_none() {
        return Err(ZeraError::Validation(
            "Contract type must be provided (TOKEN=0, NFT=1, SBT=2)".to_string(),
        ));
    }
    let _ = contract_version;
    Ok(())
}

pub fn validate_update_contract_options(contract_id: &str, contract_version: u64) -> Result<()> {
    if !is_valid_contract_id(contract_id) {
        return Err(ZeraError::Validation(
            "ContractId must be provided and follow the format $[letters]+[4 digits] (e.g., $ZRA+0000)"
                .to_string(),
        ));
    }
    if contract_version < 1 {
        return Err(ZeraError::Validation(
            "Contract version must be at least 1 for updates".to_string(),
        ));
    }
    Ok(())
}

pub fn validate_key_pair(
    public_key_base58_identifier: &str,
    private_key_base58: &str,
) -> Result<()> {
    if public_key_base58_identifier.is_empty() {
        return Err(ZeraError::Validation(
            "Public key identifier is required".to_string(),
        ));
    }
    if private_key_base58.is_empty() {
        return Err(ZeraError::Validation("Private key is required".to_string()));
    }
    generate_address_from_public_key(public_key_base58_identifier).map_err(|error| {
        ZeraError::Validation(format!("Invalid public key identifier: {error}"))
    })?;
    Ok(())
}

pub fn validate_premint_wallets(
    contract_type: ContractType,
    premint_wallets: &[PreMintWallet],
) -> Result<()> {
    if !premint_wallets.is_empty() && contract_type != ContractType::Token {
        let type_name = match contract_type {
            ContractType::Nft => "NFT",
            ContractType::Sbt => "SBT",
            ContractType::Token => "TOKEN",
        };
        return Err(ZeraError::Validation(format!(
            "Premint wallets can only be used with TOKEN contract type. Current contract type is {type_name} ({}). Please remove premintWallets or change contract type to TOKEN.",
            contract_type as i32
        )));
    }
    Ok(())
}