streak-api 0.1.1

API for interacting with the STREAK directional markets protocol on Solana
//! Legacy Pyth price account helpers ([`pyth_sdk_solana`] push oracle).
//!
//! Shared in **`streak-api`** so the program entrypoint stays instruction-only and clients/tests can reuse the same parsing rules.

use pyth_sdk_solana::state::SolanaPriceAccount;
use solana_program::{
    account_info::AccountInfo,
    entrypoint::ProgramResult,
    program_error::ProgramError,
};
use steel::AccountInfoValidation;

pub use pyth_sdk_solana::Price;

use crate::consts::{PYTH_MAX_PRICE_AGE_SECS, PYTH_ORACLE_PROGRAM};
use crate::error::StreakError;
use crate::state::Market;

pub fn assert_legacy_pyth_owner(info: &AccountInfo) -> ProgramResult {
    if *info.key == solana_program::pubkey::Pubkey::default() {
        return Err(StreakError::PythInvalidAccount.into());
    }
    if info.owner != &PYTH_ORACLE_PROGRAM {
        return Err(StreakError::PythBadOwner.into());
    }
    Ok(())
}

pub fn load_fresh_price(
    info: &AccountInfo,
    unix_timestamp: i64,
) -> Result<Price, ProgramError> {
    assert_legacy_pyth_owner(info)?;
    let feed = SolanaPriceAccount::account_info_to_feed(info)
        .map_err(|_| StreakError::PythInvalidAccount)?;
    feed.get_price_no_older_than(unix_timestamp, PYTH_MAX_PRICE_AGE_SECS)
        .ok_or(StreakError::OraclePriceStale.into())
}

/// Writes **`open_*`** from Pyth (**must** match **`Market::pyth_price_feed`** pubkey).
pub fn anchor_open_snapshot(
    market: &mut Market,
    pyth_price_feed_info: &AccountInfo,
    unix_timestamp: i64,
) -> Result<(), ProgramError> {
    pyth_price_feed_info.has_address(&market.pyth_price_feed)?;
    let px = load_fresh_price(pyth_price_feed_info, unix_timestamp)?;
    market.open_ref_price = px.price;
    market.open_ref_expo = px.expo;
    market.open_ref_publish_time = px.publish_time;
    Ok(())
}

/// Compare close vs open Pyth aggregates at a common exponent (mid price only).
/// Tie (**`close == open`**) resolves **`SIDE_DOWN`** (strict-up prediction loses on flat move).
pub fn outcome_from_open_close(open: Price, close: Price) -> Result<u8, ProgramError> {
    const EXP: i32 = -8;
    let o = open
        .scale_to_exponent(EXP)
        .ok_or(StreakError::OracleNormalize)?;
    let c = close
        .scale_to_exponent(EXP)
        .ok_or(StreakError::OracleNormalize)?;
    if c.price > o.price {
        Ok(Market::SIDE_UP)
    } else {
        Ok(Market::SIDE_DOWN)
    }
}