txline 0.3.2

Devnet-only Rust SDK for TxLINE.
Documentation
use std::env;

use solana_sdk::signature::{Keypair, read_keypair_file};
use txline::solana::validation::ValidationSimulationConfig;
use txline::validation::{NDimensionalStrategy, ScoresStatValidationV2};
use txline::{ApiToken, GuestJwt, TxlineClient, TxlineConfig};

pub struct StatV2Run {
    pub client: TxlineClient,
    pub proof: ScoresStatValidationV2,
}

pub async fn fetch_stat_v2(
    flow_name: &str,
    min_stat_keys: usize,
) -> txline::Result<Option<StatV2Run>> {
    let Some((jwt, api_token)) = read_auth() else {
        eprintln!("Set TXLINE_DEVNET_JWT and TXLINE_DEVNET_API_TOKEN first.");
        return Ok(None);
    };
    let Some((fixture_id, seq, stat_keys)) = read_request() else {
        eprintln!(
            "Set TXLINE_FIXTURE_ID, TXLINE_SCORE_SEQ, and TXLINE_STAT_KEYS from a real score record."
        );
        return Ok(None);
    };
    if stat_keys.len() < min_stat_keys {
        eprintln!(
            "{flow_name} needs at least {min_stat_keys} TXLINE_STAT_KEYS; got {}.",
            stat_keys.len()
        );
        return Ok(None);
    }

    let client = TxlineClient::new(TxlineConfig::devnet())?;
    client.set_guest_jwt(jwt);
    client.set_api_token(api_token);

    println!(
        "[{flow_name}] fetching V2 stat validation for fixture {fixture_id}, seq {seq}, stat keys {:?}",
        stat_keys
    );
    let proof = client
        .scores()
        .stat_validation_v2(fixture_id, seq, stat_keys)
        .await?;

    Ok(Some(StatV2Run { client, proof }))
}

pub fn print_proof_summary(flow_name: &str, proof: &ScoresStatValidationV2) -> txline::Result<()> {
    println!(
        "[{flow_name}] requested stat keys by strategy index: {:?}",
        proof.requested_stat_keys()
    );
    println!(
        "[{flow_name}] proof contains {} stat leaves; epoch day {}",
        proof.stats_to_prove().len(),
        proof.epoch_day()?
    );
    Ok(())
}

pub fn read_simulation_keypair() -> txline::Result<Option<Keypair>> {
    if !env::var("TXLINE_VALIDATE_ON_CHAIN").is_ok_and(|value| value == "1") {
        return Ok(None);
    }

    let Some(wallet_path) = read_wallet_path() else {
        eprintln!("Set TXLINE_WALLET or ANCHOR_WALLET to run on-chain simulation.");
        return Ok(None);
    };
    let keypair = read_keypair_file(wallet_path).map_err(|err| {
        txline::TxlineError::Solana(format!("could not read wallet keypair: {err}"))
    })?;
    Ok(Some(keypair))
}

pub fn run_strategy(
    flow_name: &str,
    client: &TxlineClient,
    proof: &ScoresStatValidationV2,
    keypair: Option<&Keypair>,
    subset_len: usize,
    label: &str,
    strategy: &NDimensionalStrategy,
) -> txline::Result<()> {
    println!("[{flow_name}] {label} strategy: {strategy:?}");
    let Some(keypair) = keypair else {
        return Ok(());
    };

    let payload = proof.leading_subset(subset_len)?;
    let result = client.solana().simulate_validate_stat_v2(
        keypair,
        &payload,
        strategy,
        ValidationSimulationConfig::default(),
    )?;
    println!("[{flow_name}] {label} simulation returned {result}");
    Ok(())
}

fn read_auth() -> Option<(GuestJwt, ApiToken)> {
    let jwt = GuestJwt::new(env::var("TXLINE_DEVNET_JWT").ok()?).ok()?;
    let api_token = ApiToken::new(env::var("TXLINE_DEVNET_API_TOKEN").ok()?).ok()?;
    Some((jwt, api_token))
}

fn read_request() -> Option<(i64, i32, Vec<u32>)> {
    let fixture_id = env::var("TXLINE_FIXTURE_ID").ok()?.parse().ok()?;
    let seq = env::var("TXLINE_SCORE_SEQ").ok()?.parse().ok()?;
    let stat_keys = env::var("TXLINE_STAT_KEYS")
        .ok()?
        .split(',')
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::parse::<u32>)
        .collect::<std::result::Result<Vec<_>, _>>()
        .ok()?;
    if stat_keys.is_empty() {
        return None;
    }
    Some((fixture_id, seq, stat_keys))
}

fn read_wallet_path() -> Option<String> {
    env::var("TXLINE_WALLET")
        .ok()
        .or_else(|| env::var("ANCHOR_WALLET").ok())
}