zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;

use crate::error::Result;

use super::super::constants::{
    ACTION_RELEASE_SOL, ACTION_RELEASE_SPL, ATA_PROGRAM_ID, CORE_PROGRAM_ID, DEFAULT_EVENT_INDEX,
    DEFAULT_VAA_EXPIRY, DEFAULT_VAA_VERSION, SYSTEM_PROGRAM_ID, SYSVAR_INSTRUCTIONS_PUBKEY,
    TOKEN_BRIDGE_PROGRAM_ID, TOKEN_PROGRAM_ID,
};
use super::helpers::{
    build_transaction, compute_unit_limit_instruction, create_ed25519_verify_instruction,
    parse_hex_32, parse_pubkey, parse_signatures, payload_length_suffix, resolve_recent_blockhash,
};

use super::super::rpc::SolanaRpc;
use super::super::types::{
    ReleaseSolAccounts, ReleaseSolOptions, ReleaseSolResult, ReleaseSplAccounts, ReleaseSplOptions,
    ReleaseSplResult,
};
use super::super::utils::{
    concat_bytes, derive_rate_limit_state_pda, derive_released_transfer_pda,
    derive_router_config_pda, derive_router_signer_pda, derive_token_price_registry_pda,
    derive_token_registration_pda, derive_vault_pda, derive_verified_transfer_pda, encode_u32_le,
    encode_u64_be, encode_u64_le, generate_discriminator, get_ata,
};

pub async fn build_release_spl_transaction(
    options: ReleaseSplOptions,
    payer: &Pubkey,
    rpc: Option<&dyn SolanaRpc>,
) -> Result<ReleaseSplResult> {
    let recipient = parse_pubkey(&options.recipient, "recipient")?;
    let mint = parse_pubkey(&options.mint, "mint")?;
    let txn_hash = parse_hex_32(&options.txn_id, "txnId")?;
    let expected_hash = parse_hex_32(&options.expected_hash, "expectedHash")?;

    let (router_signer, _) = derive_router_signer_pda();
    let (router_config, _) = derive_router_config_pda();
    let (used_marker, _) = derive_verified_transfer_pda(&expected_hash);
    let (redeemed_marker, _) = derive_released_transfer_pda(&expected_hash);
    let (rate_limit_state, _) = derive_rate_limit_state_pda();
    let (token_registration, _) = derive_token_registration_pda(&mint);
    let recipient_ata = get_ata(&recipient, &mint);
    let vault_ata = get_ata(&router_signer, &mint);

    let (signatures, public_keys) = parse_signatures(&options.signatures)?;
    let mut signature_instructions = Vec::with_capacity(signatures.len());
    for (signature, public_key) in signatures.iter().zip(public_keys.iter()) {
        signature_instructions.push(create_ed25519_verify_instruction(
            &expected_hash,
            signature,
            public_key,
        )?);
    }

    let version = [DEFAULT_VAA_VERSION];
    let action = [ACTION_RELEASE_SPL];
    let expiry = encode_u64_le(DEFAULT_VAA_EXPIRY);
    let event_index = encode_u32_le(DEFAULT_EVENT_INDEX);
    let timestamp = encode_u64_le(options.timestamp);
    let amount_be = encode_u64_be(options.amount);
    let amount_le = encode_u64_le(options.amount);
    let usd_price_nano_be = encode_u64_be(options.usd_price_nano);
    let usd_price_nano_le = encode_u64_le(options.usd_price_nano);
    let liquidity_usd_nano_be = encode_u64_be(options.liquidity_usd_nano);
    let liquidity_usd_nano_le = encode_u64_le(options.liquidity_usd_nano);
    let tier = [options.tier];

    let payload = concat_bytes(&[
        amount_be.as_ref(),
        recipient.as_ref(),
        mint.as_ref(),
        usd_price_nano_be.as_ref(),
        liquidity_usd_nano_be.as_ref(),
        tier.as_ref(),
    ]);
    let payload_len = encode_u32_le(payload.len() as u32);
    let payload_tail = payload_length_suffix(&payload);

    let core_discriminator = generate_discriminator("global:post_verified_transfer");
    let core_data = concat_bytes(&[
        core_discriminator.as_ref(),
        version.as_ref(),
        action.as_ref(),
        timestamp.as_ref(),
        expiry.as_ref(),
        txn_hash.as_slice(),
        event_index.as_ref(),
        payload_len.as_ref(),
        payload.as_slice(),
        payload_tail.as_ref(),
    ]);
    let core_instruction = Instruction {
        program_id: *CORE_PROGRAM_ID,
        accounts: vec![
            AccountMeta::new(router_config, false),
            AccountMeta::new_readonly(*SYSVAR_INSTRUCTIONS_PUBKEY, false),
            AccountMeta::new(used_marker, false),
            AccountMeta::new(*payer, true),
            AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
            AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
        ],
        data: core_data,
    };

    let token_discriminator = generate_discriminator("global:release_spl");
    let token_data = concat_bytes(&[
        token_discriminator.as_ref(),
        version.as_ref(),
        timestamp.as_ref(),
        expiry.as_ref(),
        txn_hash.as_slice(),
        event_index.as_ref(),
        amount_le.as_ref(),
        usd_price_nano_le.as_ref(),
        liquidity_usd_nano_le.as_ref(),
        tier.as_ref(),
    ]);
    let token_instruction = Instruction {
        program_id: *TOKEN_BRIDGE_PROGRAM_ID,
        accounts: vec![
            AccountMeta::new_readonly(*CORE_PROGRAM_ID, false),
            AccountMeta::new_readonly(router_config, false),
            AccountMeta::new(*payer, true),
            AccountMeta::new_readonly(mint, false),
            AccountMeta::new_readonly(router_signer, false),
            AccountMeta::new(vault_ata, false),
            AccountMeta::new_readonly(recipient, false),
            AccountMeta::new(recipient_ata, false),
            AccountMeta::new_readonly(*ATA_PROGRAM_ID, false),
            AccountMeta::new_readonly(used_marker, false),
            AccountMeta::new(redeemed_marker, false),
            AccountMeta::new(rate_limit_state, false),
            AccountMeta::new(token_registration, false),
            AccountMeta::new_readonly(*TOKEN_PROGRAM_ID, false),
            AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
            AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
        ],
        data: token_data,
    };

    let recent_blockhash = resolve_recent_blockhash(rpc).await?;

    let mut verify_instructions = Vec::new();
    if !signature_instructions.is_empty() {
        verify_instructions.push(compute_unit_limit_instruction(400_000));
    }
    verify_instructions.extend(signature_instructions.iter().cloned());
    verify_instructions.push(core_instruction.clone());
    let verify_transaction = build_transaction(payer, verify_instructions, recent_blockhash);

    let release_transaction =
        build_transaction(payer, vec![token_instruction.clone()], recent_blockhash);

    let mut combined_instructions = Vec::new();
    if !signature_instructions.is_empty() {
        combined_instructions.push(compute_unit_limit_instruction(400_000));
    }
    combined_instructions.extend(signature_instructions.iter().cloned());
    combined_instructions.push(core_instruction.clone());
    combined_instructions.push(token_instruction.clone());
    let transaction = build_transaction(payer, combined_instructions, recent_blockhash);

    Ok(ReleaseSplResult {
        core_instruction,
        token_instruction,
        signature_instructions,
        transaction,
        verify_transaction,
        release_transaction,
        accounts: ReleaseSplAccounts {
            recipient_ata,
            vault_ata,
            used_marker,
            redeemed_marker,
        },
    })
}

pub async fn build_release_sol_transaction(
    options: ReleaseSolOptions,
    payer: &Pubkey,
    rpc: Option<&dyn SolanaRpc>,
) -> Result<ReleaseSolResult> {
    let recipient = parse_pubkey(&options.recipient, "recipient")?;
    let txn_hash = parse_hex_32(&options.txn_id, "txnId")?;
    let expected_hash = parse_hex_32(&options.expected_hash, "expectedHash")?;

    let (vault, _) = derive_vault_pda();
    let (router_config, _) = derive_router_config_pda();
    let (used_marker, _) = derive_verified_transfer_pda(&expected_hash);
    let (redeemed_marker, _) = derive_released_transfer_pda(&expected_hash);
    let (rate_limit_state, _) = derive_rate_limit_state_pda();
    let (token_price_registry, _) = derive_token_price_registry_pda();

    let (signatures, public_keys) = parse_signatures(&options.signatures)?;
    let mut signature_instructions = Vec::with_capacity(signatures.len());
    for (signature, public_key) in signatures.iter().zip(public_keys.iter()) {
        signature_instructions.push(create_ed25519_verify_instruction(
            &expected_hash,
            signature,
            public_key,
        )?);
    }

    let version = [DEFAULT_VAA_VERSION];
    let action = [ACTION_RELEASE_SOL];
    let expiry = encode_u64_le(DEFAULT_VAA_EXPIRY);
    let event_index = encode_u32_le(DEFAULT_EVENT_INDEX);
    let timestamp = encode_u64_le(options.timestamp);
    let amount_be = encode_u64_be(options.amount);
    let amount_le = encode_u64_le(options.amount);
    let usd_amount_be = encode_u64_be(options.usd_amount);
    let usd_amount_le = encode_u64_le(options.usd_amount);

    let payload = concat_bytes(&[
        amount_be.as_ref(),
        recipient.as_ref(),
        usd_amount_be.as_ref(),
    ]);
    let payload_len = encode_u32_le(payload.len() as u32);
    let payload_tail = payload_length_suffix(&payload);

    let core_discriminator = generate_discriminator("global:post_verified_transfer");
    let core_data = concat_bytes(&[
        core_discriminator.as_ref(),
        version.as_ref(),
        action.as_ref(),
        timestamp.as_ref(),
        expiry.as_ref(),
        txn_hash.as_slice(),
        event_index.as_ref(),
        payload_len.as_ref(),
        payload.as_slice(),
        payload_tail.as_ref(),
    ]);
    let core_instruction = Instruction {
        program_id: *CORE_PROGRAM_ID,
        accounts: vec![
            AccountMeta::new(router_config, false),
            AccountMeta::new_readonly(*SYSVAR_INSTRUCTIONS_PUBKEY, false),
            AccountMeta::new(used_marker, false),
            AccountMeta::new(*payer, true),
            AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
            AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
        ],
        data: core_data,
    };

    let token_discriminator = generate_discriminator("global:release_sol");
    let token_data = concat_bytes(&[
        token_discriminator.as_ref(),
        version.as_ref(),
        timestamp.as_ref(),
        expiry.as_ref(),
        txn_hash.as_slice(),
        event_index.as_ref(),
        amount_le.as_ref(),
        recipient.as_ref(),
        usd_amount_le.as_ref(),
    ]);
    let token_instruction = Instruction {
        program_id: *TOKEN_BRIDGE_PROGRAM_ID,
        accounts: vec![
            AccountMeta::new_readonly(*CORE_PROGRAM_ID, false),
            AccountMeta::new_readonly(router_config, false),
            AccountMeta::new(*payer, true),
            AccountMeta::new(vault, false),
            AccountMeta::new_readonly(used_marker, false),
            AccountMeta::new(redeemed_marker, false),
            AccountMeta::new(recipient, false),
            AccountMeta::new(rate_limit_state, false),
            AccountMeta::new(token_price_registry, false),
            AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
            AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
        ],
        data: token_data,
    };

    let recent_blockhash = resolve_recent_blockhash(rpc).await?;

    let mut verify_instructions = Vec::new();
    if !signature_instructions.is_empty() {
        verify_instructions.push(compute_unit_limit_instruction(400_000));
    }
    verify_instructions.extend(signature_instructions.iter().cloned());
    verify_instructions.push(core_instruction.clone());
    let verify_transaction = build_transaction(payer, verify_instructions, recent_blockhash);

    let release_transaction =
        build_transaction(payer, vec![token_instruction.clone()], recent_blockhash);

    let mut combined_instructions = Vec::new();
    if !signature_instructions.is_empty() {
        combined_instructions.push(compute_unit_limit_instruction(400_000));
    }
    combined_instructions.extend(signature_instructions.iter().cloned());
    combined_instructions.push(core_instruction.clone());
    combined_instructions.push(token_instruction.clone());
    let transaction = build_transaction(payer, combined_instructions, recent_blockhash);

    Ok(ReleaseSolResult {
        core_instruction,
        token_instruction,
        signature_instructions,
        transaction,
        verify_transaction,
        release_transaction,
        accounts: ReleaseSolAccounts {
            vault,
            used_marker,
            redeemed_marker,
        },
    })
}