zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
use zera_proto::zera_guardian::{
    zera_payload, ZeraContractPayload, ZeraMintPayload, ZeraPayload, ZeraReleasePayload,
};
use zera_proto::zera_txn::SmartContractExecuteTxn;

use crate::error::{Result, ZeraError};
use crate::grpc::{UnaryTransport, ValidatorApiClient};
use crate::smart_contracts::send_smart_contract_execute_txn;
use crate::smart_contracts::use_cases::bridge::zera::types::{
    CreateSolOptions, MintSolOptions, ReleaseZeraOptions,
};
use crate::smart_contracts::use_cases::bridge::zera::utils::create_bridge_transaction_with_client;

pub async fn release_zera(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: ReleaseZeraOptions,
) -> Result<SmartContractExecuteTxn> {
    let client = ValidatorApiClient::new(options.grpc_config.clone().unwrap_or_default())?;
    release_zera_with_client(
        to_zera_address,
        public_key_base58_identifier,
        private_key_base58,
        options,
        &client,
    )
    .await
}

pub async fn release_zera_with_client<T>(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: ReleaseZeraOptions,
    client: &ValidatorApiClient<T>,
) -> Result<SmartContractExecuteTxn>
where
    T: UnaryTransport,
{
    if to_zera_address.is_empty() {
        return Err(ZeraError::Validation(
            "toZeraAddress is required".to_string(),
        ));
    }

    let payload = options
        .payload
        .as_ref()
        .ok_or_else(|| ZeraError::Validation("payload is required".to_string()))?;
    let release_payload = expect_release_payload(payload)?;

    let signatures = payload.signatures.join("|");
    let guardian_keys = payload.public_keys.join("|");
    let parameter_value = [
        release_payload.zera_contract_id.as_str(),
        release_payload.amount.as_str(),
        to_zera_address,
        release_payload.tx_signature.as_str(),
        payload.signed_hash.as_str(),
        signatures.as_str(),
        guardian_keys.as_str(),
    ]
    .join(",");

    let fee_id = options
        .fee_id
        .clone()
        .unwrap_or_else(|| release_payload.zera_contract_id.clone());

    create_bridge_transaction_with_client(
        "release_zera",
        &parameter_value,
        public_key_base58_identifier,
        private_key_base58,
        &fee_id,
        options.into(),
        client,
    )
    .await
}

pub async fn release_zera_and_send(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: ReleaseZeraOptions,
) -> Result<String> {
    let grpc_config = options.grpc_config.clone();
    let transaction = release_zera(
        to_zera_address,
        public_key_base58_identifier,
        private_key_base58,
        options,
    )
    .await?;

    send_smart_contract_execute_txn(&transaction, grpc_config).await
}

pub async fn mint_sol(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: MintSolOptions,
) -> Result<SmartContractExecuteTxn> {
    let client = ValidatorApiClient::new(options.grpc_config.clone().unwrap_or_default())?;
    mint_sol_with_client(
        to_zera_address,
        public_key_base58_identifier,
        private_key_base58,
        options,
        &client,
    )
    .await
}

pub async fn mint_sol_with_client<T>(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: MintSolOptions,
    client: &ValidatorApiClient<T>,
) -> Result<SmartContractExecuteTxn>
where
    T: UnaryTransport,
{
    if to_zera_address.is_empty() {
        return Err(ZeraError::Validation(
            "toZeraAddress is required".to_string(),
        ));
    }

    let payload = options
        .payload
        .as_ref()
        .ok_or_else(|| ZeraError::Validation("payload is required".to_string()))?;
    let mint_payload = expect_mint_payload(payload)?;

    let signatures = payload.signatures.join("|");
    let guardian_keys = payload.public_keys.join("|");
    let parameter_value = [
        mint_payload.solana_mint_address.as_str(),
        mint_payload.amount.as_str(),
        to_zera_address,
        mint_payload.usd_price.as_str(),
        mint_payload.tx_signature.as_str(),
        payload.signed_hash.as_str(),
        signatures.as_str(),
        guardian_keys.as_str(),
    ]
    .join(",");

    let fee_id = options
        .fee_id
        .clone()
        .unwrap_or_else(|| "$ZRA+0000".to_string());

    create_bridge_transaction_with_client(
        "mint_sol",
        &parameter_value,
        public_key_base58_identifier,
        private_key_base58,
        &fee_id,
        options.into(),
        client,
    )
    .await
}

pub async fn mint_sol_and_send(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: MintSolOptions,
) -> Result<String> {
    let grpc_config = options.grpc_config.clone();
    let transaction = mint_sol(
        to_zera_address,
        public_key_base58_identifier,
        private_key_base58,
        options,
    )
    .await?;

    send_smart_contract_execute_txn(&transaction, grpc_config).await
}

pub async fn create_sol(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: CreateSolOptions,
) -> Result<SmartContractExecuteTxn> {
    let client = ValidatorApiClient::new(options.grpc_config.clone().unwrap_or_default())?;
    create_sol_with_client(
        to_zera_address,
        public_key_base58_identifier,
        private_key_base58,
        options,
        &client,
    )
    .await
}

pub async fn create_sol_with_client<T>(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: CreateSolOptions,
    client: &ValidatorApiClient<T>,
) -> Result<SmartContractExecuteTxn>
where
    T: UnaryTransport,
{
    if to_zera_address.is_empty() {
        return Err(ZeraError::Validation(
            "toZeraAddress is required".to_string(),
        ));
    }

    let payload = options
        .payload
        .as_ref()
        .ok_or_else(|| ZeraError::Validation("payload is required".to_string()))?;
    let contract_payload = expect_contract_payload(payload)?;

    let signatures = payload.signatures.join("|");
    let guardian_keys = payload.public_keys.join("|");
    let parameter_value = [
        contract_payload.symbol.as_str(),
        contract_payload.name.as_str(),
        contract_payload.denomination.as_str(),
        to_zera_address,
        contract_payload.amount.as_str(),
        contract_payload.solana_mint_address.as_str(),
        contract_payload.uri.as_str(),
        contract_payload.solana_authorized_address.as_str(),
        contract_payload.tx_signature.as_str(),
        payload.signed_hash.as_str(),
        signatures.as_str(),
        guardian_keys.as_str(),
        contract_payload.usd_price.as_str(),
    ]
    .join(",");

    let fee_id = options
        .fee_id
        .clone()
        .unwrap_or_else(|| "$ZRA+0000".to_string());

    create_bridge_transaction_with_client(
        "create_sol",
        &parameter_value,
        public_key_base58_identifier,
        private_key_base58,
        &fee_id,
        options.into(),
        client,
    )
    .await
}

pub async fn create_sol_and_send(
    to_zera_address: &str,
    public_key_base58_identifier: &str,
    private_key_base58: &str,
    options: CreateSolOptions,
) -> Result<String> {
    let grpc_config = options.grpc_config.clone();
    let transaction = create_sol(
        to_zera_address,
        public_key_base58_identifier,
        private_key_base58,
        options,
    )
    .await?;

    send_smart_contract_execute_txn(&transaction, grpc_config).await
}

fn payload_case_name(payload: &ZeraPayload) -> &'static str {
    match payload.payload {
        None => "none",
        Some(zera_payload::Payload::ContractPayload(_)) => "contractPayload",
        Some(zera_payload::Payload::MintPayload(_)) => "mintPayload",
        Some(zera_payload::Payload::ReleasePayload(_)) => "releasePayload",
    }
}

fn expect_release_payload(payload: &ZeraPayload) -> Result<&ZeraReleasePayload> {
    match payload.payload.as_ref() {
        Some(zera_payload::Payload::ReleasePayload(release_payload)) => Ok(release_payload),
        _ => Err(ZeraError::Validation(format!(
            "Expected releasePayload, got: {}",
            payload_case_name(payload)
        ))),
    }
}

fn expect_mint_payload(payload: &ZeraPayload) -> Result<&ZeraMintPayload> {
    match payload.payload.as_ref() {
        Some(zera_payload::Payload::MintPayload(mint_payload)) => Ok(mint_payload),
        _ => Err(ZeraError::Validation(format!(
            "Expected mintPayload, got: {}",
            payload_case_name(payload)
        ))),
    }
}

fn expect_contract_payload(payload: &ZeraPayload) -> Result<&ZeraContractPayload> {
    match payload.payload.as_ref() {
        Some(zera_payload::Payload::ContractPayload(contract_payload)) => Ok(contract_payload),
        _ => Err(ZeraError::Validation(format!(
            "Expected contractPayload, got: {}",
            payload_case_name(payload)
        ))),
    }
}