zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
use std::time::{SystemTime, UNIX_EPOCH};

use zera_proto::zera_txn::SmartContractExecuteTxn;

use crate::api::get_token_info_for_single_with_client;
use crate::contract::shared::validate_key_pair;
use crate::error::{Result, ZeraError};
use crate::grpc::{UnaryTransport, ValidatorApiClient};
use crate::sign::sign_with_key;
use crate::smart_contracts::{
    build_smart_contract_execute_txn_with_client, BuildSmartContractExecuteOptions,
    ExecuteParameter, ParameterType,
};
use crate::types::RpcConfig;
use crate::utils::amount::to_smallest_units;

use super::types::DexOptions;

pub const DEX_CONTRACT_NAME: &str = "zera_dex_proxy";
pub const DEX_INSTANCE: u32 = 1;
pub const DEFAULT_LOCK_DURATION: u64 = 60;

pub fn compute_lock_timestamp(duration_seconds: Option<u64>) -> String {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();
    (now + duration_seconds.unwrap_or(DEFAULT_LOCK_DURATION)).to_string()
}

pub async fn resolve_amount(
    amount: &str,
    contract_id: &str,
    grpc_config: Option<RpcConfig>,
) -> Result<String> {
    let client = ValidatorApiClient::new(grpc_config.unwrap_or_default())?;
    resolve_amount_with_client(amount, contract_id, &client).await
}

pub async fn resolve_amount_with_client<T>(
    amount: &str,
    contract_id: &str,
    client: &ValidatorApiClient<T>,
) -> Result<String>
where
    T: UnaryTransport,
{
    let token_info = get_token_info_for_single_with_client(contract_id, client).await?;
    to_smallest_units(
        amount,
        contract_id,
        Some(token_info.denomination.as_str()),
        None,
    )
}

pub(crate) fn fee_id_or_default(options: &DexOptions) -> String {
    options
        .fee_id
        .clone()
        .unwrap_or_else(|| "$ZRA+0000".to_string())
}

pub(crate) fn validate_signing_inputs(
    public_key_base58_identifier: &str,
    private_key_base58: &str,
) -> Result<()> {
    if public_key_base58_identifier.is_empty() {
        return Err(ZeraError::Validation(
            "publicKeyBase58Identifier is required".to_string(),
        ));
    }
    if private_key_base58.is_empty() {
        return Err(ZeraError::Validation(
            "privateKeyBase58 is required".to_string(),
        ));
    }
    validate_key_pair(public_key_base58_identifier, private_key_base58)
}

pub(crate) async fn create_dex_transaction_with_client<T>(
    action_name: &str,
    parameter_value: String,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: DexOptions,
    client: &ValidatorApiClient<T>,
) -> Result<SmartContractExecuteTxn>
where
    T: UnaryTransport,
{
    validate_signing_inputs(public_key_base58_identifier, private_key_base58)?;

    let parameters = vec![
        ExecuteParameter {
            parameter_type: ParameterType::String.as_str().to_string(),
            value: action_name.as_bytes().to_vec(),
        },
        ExecuteParameter {
            parameter_type: ParameterType::String.as_str().to_string(),
            value: parameter_value.into_bytes(),
        },
    ];

    let mut txn = build_smart_contract_execute_txn_with_client(
        DEX_CONTRACT_NAME,
        DEX_INSTANCE,
        "execute",
        &parameters,
        public_key_base58_identifier,
        BuildSmartContractExecuteOptions {
            memo: options.memo.clone(),
            grpc_config: None,
            gas_fee_in_usd: options.gas_fee_in_usd,
            overestimate_percent: options.overestimate_percent,
            nonce: options.nonce,
            fee_id: Some(fee_id_or_default(&options)),
            fee_amount_parts: options.fee_amount_usd.clone(),
        },
        client,
    )
    .await?;

    sign_with_key(&mut txn, private_key_base58, public_key_base58_identifier)?;

    Ok(txn)
}