antegen-thread-program 5.2.0

Solana program for Antegen - automation and scheduling threads
Documentation
use anchor_lang::AccountDeserialize;
use antegen_fiber_program::state::Fiber;
use litesvm::LiteSVM;
use solana_sdk::pubkey::Pubkey;

use super::setup::{FIBER_PROGRAM_ID, PROGRAM_ID};

// PDA seeds (must match program constants)
const SEED_CONFIG: &[u8] = b"thread_config";
const SEED_THREAD: &[u8] = b"thread";
const SEED_THREAD_FIBER: &[u8] = b"thread_fiber";

/// Derive the config PDA.
pub fn config_pda() -> (Pubkey, u8) {
    Pubkey::find_program_address(&[SEED_CONFIG], &PROGRAM_ID)
}

/// Derive a thread PDA.
pub fn thread_pda(authority: &Pubkey, id: &[u8]) -> (Pubkey, u8) {
    Pubkey::find_program_address(&[SEED_THREAD, authority.as_ref(), id], &PROGRAM_ID)
}

/// Derive a fiber PDA (owned by Fiber Program).
pub fn fiber_pda(thread: &Pubkey, index: u8) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[SEED_THREAD_FIBER, thread.as_ref(), &[index]],
        &FIBER_PROGRAM_ID,
    )
}

/// Deserialize a Thread account from the SVM.
pub fn deserialize_thread(svm: &LiteSVM, pubkey: &Pubkey) -> antegen_thread_program::state::Thread {
    let account = svm.get_account(pubkey).expect("Thread account not found");
    antegen_thread_program::state::Thread::try_deserialize(&mut account.data.as_slice())
        .expect("Failed to deserialize Thread")
}

/// Deserialize a ThreadConfig account from the SVM.
pub fn deserialize_config(
    svm: &LiteSVM,
    pubkey: &Pubkey,
) -> antegen_thread_program::state::ThreadConfig {
    let account = svm.get_account(pubkey).expect("Config account not found");
    antegen_thread_program::state::ThreadConfig::try_deserialize(&mut account.data.as_slice())
        .expect("Failed to deserialize ThreadConfig")
}

/// Deserialize a fiber account, returning a unified V1 view regardless of
/// on-disk shape. Legacy fibers get `version = 0` and `lookup_tables = []`
/// so existing tests can keep using dot-field access.
pub fn deserialize_fiber(
    svm: &LiteSVM,
    pubkey: &Pubkey,
) -> antegen_fiber_program::state::FiberVersionedState {
    let account = svm.get_account(pubkey).expect("Fiber account not found");
    let read = Fiber::try_deserialize(&mut account.data.as_slice())
        .expect("Failed to deserialize fiber account");
    match read {
        Fiber::Legacy(s) => antegen_fiber_program::state::FiberVersionedState {
            version: 0,
            thread: s.thread,
            compiled_instruction: s.compiled_instruction,
            last_executed: s.last_executed,
            exec_count: s.exec_count,
            priority_fee: s.priority_fee,
            lookup_tables: Vec::new(),
        },
        Fiber::V1(s) => s,
    }
}

/// Deserialize a fiber account as a `Fiber`, preserving whether it's
/// a legacy or V1 account on disk. Use when the test specifically cares
/// about the on-chain shape (e.g. lookup_table presence).
pub fn deserialize_fiber_any(svm: &LiteSVM, pubkey: &Pubkey) -> Fiber {
    let account = svm.get_account(pubkey).expect("Fiber account not found");
    Fiber::try_deserialize(&mut account.data.as_slice())
        .expect("Failed to deserialize fiber account")
}

/// Check if an account exists and has non-zero data.
pub fn account_exists(svm: &LiteSVM, pubkey: &Pubkey) -> bool {
    svm.get_account(pubkey)
        .map(|a| !a.data.is_empty() && a.lamports > 0)
        .unwrap_or(false)
}

/// Get account lamports.
pub fn get_balance(svm: &LiteSVM, pubkey: &Pubkey) -> u64 {
    svm.get_account(pubkey).map(|a| a.lamports).unwrap_or(0)
}