streak-api 0.1.2

API for interacting with the STREAK directional markets protocol on Solana
//! Period directional market (`market` PDA). Users fund **`BuyTickets`**, stake with **`PlaceBet`**. **`open_*`** is frozen by **`InitMarket`** (**create inside the anchor window**, or **`InitMarket` crank**) during **`[open_ts, min(open_ts + MARKET_OPEN_ANCHOR_GRACE_SECS, close_ts))`**. Betting uses **`Clock::unix_timestamp` ∈ `[open_ts, close_ts)`**. **`InitMarket`** stores **`open_ts`**, **`close_ts`**, **`pyth_price_feed`**; **`AdminInstantSettlement`** settles after **`close_ts`** vs fresh Pyth; **`ExecutorTreasury`** (**distribute**) pays winners.

use steel::*;

use super::{market_pda, StreakAccount};

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Market {
    pub period: u64,
    /// Inclusive Unix second when betting may begin (**after** open anchor within grace window).
    pub open_ts: i64,
    /// Exclusive Unix second end of betting window (**betting**: **`open_ts ≤ now < close_ts`**).
    pub close_ts: i64,
    /// Commitment to resolution rules (e.g. hash of IPFS CID); UX / audits — does not affect oracle math.
    pub rules_hash: [u8; 32],
    pub total_up: u64,
    pub total_down: u64,
    /// Losing-side stake totals at settle (for pro-rata pot).
    pub loser_pot: u64,
    /// Subsidy from [`Treasury`](super::Treasury)::`daily_jackpot` at settle (`USDC` base units).
    pub reward_pool: u64,
    /// Winning-side stake total at settle.
    pub winning_total: u64,
    /// Legacy Pyth **price account** pubkey (passed at **`InitMarket`**).
    pub pyth_price_feed: Pubkey,
    /// Pyth aggregate (**`price`**) frozen by **`InitMarket`** near **`open_ts`**.
    pub open_ref_price: i64,
    /// Matching Pyth **`publish_time`** (Unix seconds).
    pub open_ref_publish_time: i64,
    /// Pyth aggregate exponent for **`open_ref_price`**.
    pub open_ref_expo: i32,
    pub _pad_expo: [u8; 4],
    pub outcome: u8,
    pub status: u8,
    pub _pad_tail: [u8; 6],
}

impl Market {
    /// Same encoding as [`Position::side`](super::Position::side): resolved **UP** won.
    pub const SIDE_UP: u8 = 0;

    /// Resolved **DOWN** won (includes oracle tie at normalized precision — strict-up wins only).
    pub const SIDE_DOWN: u8 = 1;

    /// Before **`Market`** (**`ADMIN_INSTANT`** / **`FINALIZE`**); replaced at settle by [`Self::SIDE_UP`] or [`Self::SIDE_DOWN`].
    pub const OUTCOME_UNSET: u8 = 255;

    /// Accepting stakes via **`PlaceBet`** while **`STATUS_OPEN`**.
    pub const STATUS_OPEN: u8 = 0;

    /// Resolved; executor **`ExecutorTreasury`** (**distribute**) for winners, or loss rollover on the next **`BuyTickets`** / **`PlaceBet`**.
    pub const STATUS_SETTLED: u8 = 1;

    pub fn pda(period: u64) -> (Pubkey, u8) {
        market_pda(period)
    }

    pub fn init_open(
        period: u64,
        open_ts: i64,
        close_ts: i64,
        pyth_price_feed: Pubkey,
        rules_hash: [u8; 32],
    ) -> Self {
        Self {
            period,
            open_ts,
            close_ts,
            rules_hash,
            total_up: 0,
            total_down: 0,
            loser_pot: 0,
            reward_pool: 0,
            winning_total: 0,
            pyth_price_feed,
            open_ref_price: 0,
            open_ref_publish_time: 0,
            open_ref_expo: 0,
            _pad_expo: [0; 4],
            outcome: Self::OUTCOME_UNSET,
            status: Self::STATUS_OPEN,
            _pad_tail: [0; 6],
        }
    }

    #[inline(always)]
    pub fn has_open_oracle_snapshot(&self) -> bool {
        self.open_ref_publish_time != 0
    }
}

account!(StreakAccount, Market);