streak-api 0.1.5

API for interacting with the STREAK directional markets protocol on Solana
Documentation
//! On-chain events (`sol_log_data` via Steel `event!` / `.log()`).
//!
//! ## Indexer contract
//!
//! The off-chain indexer **must subscribe to these events** to reconstruct daily and weekly
//! leaderboard state. Accounts alone are insufficient for daily rank snapshots because the on-chain
//! Ledger only maintains rolling weekly aggregates, not per-day history.
//!
//! ### Daily leaderboard reconstruction
//!
//! For each UTC day the indexer:
//! 1. Groups `WinRecorded` events by `(player, UTC_day(period))` → daily wins + daily best streak.
//! 2. Groups `BetRecorded` events by `(player, UTC_day(period))` → daily bet count.
//! 3. Excludes periods matching any `MarketVoided` event from accuracy calculations.
//! 4. Uses `StreakBroken` events to confirm streak resets (optional — can also derive from missing
//!    consecutive `WinRecorded` run).
//! 5. At UTC midnight, snapshots rank order for each player → feeds into weekly scoring.
//!
//! The `period` field maps to a UTC day via the `Market::open_ts` / `Market::close_ts` stored
//! on the Market PDA (288 periods per day for 5-minute rounds).
//!
//! ### Weekly score inputs (all on-chain, readable from events or Ledger state)
//!
//! | Component | Weight | Source |
//! |---|---|---|
//! | Best streak of the week | 50% | `WinRecorded.win_streak` peak per week |
//! | Daily leaderboard placement | 30% | Off-chain daily rank snapshots |
//! | Total correct calls | 10% | `WinRecorded.total_wins` at week end |
//! | Accuracy rate | 10% | `total_wins / total_bets` (min ~50 bets enforced off-chain) |

use steel::*;

/// Emitted after successful [`Initialize`](crate::instruction::Initialize).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Initialized {
    pub admin: Pubkey,
}

/// Emitted by `ExecutorTreasury` DISTRIBUTE when a win is confirmed for a player.
///
/// Contains full stat snapshot at the moment of win for accurate daily/weekly indexing.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct WinRecorded {
    pub player: Pubkey,
    pub series_id: u16,
    pub _pad: [u8; 6],
    /// Market period that was won (derive UTC day from `Market::open_ts`).
    pub period: u64,
    /// Running win streak **after** this win.
    pub win_streak: u64,
    /// All-time highest streak ever (updated if this win set a new peak).
    pub peak_win_streak: u64,
    /// Lifetime correct calls after this win.
    pub total_wins: u64,
    /// Correct calls in the current executor week after this win.
    pub week_wins: u64,
    /// Best streak achieved in the current executor week after this win.
    pub week_peak_streak: u64,
}

/// Emitted by `PlaceBet` when a player's streak is broken (previous period settled as a loss).
///
/// The indexer uses this to confirm streak resets and close the daily streak window.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct StreakBroken {
    pub player: Pubkey,
    pub series_id: u16,
    pub _pad: [u8; 6],
    /// The period whose settlement caused the streak break.
    pub period: u64,
}

/// Emitted by `PlaceBet` when tickets are actually debited (both commit and live windows).
///
/// Enables accurate daily bet counting for accuracy-rate scoring.
/// Void-refunded bets are NOT un-counted here; the indexer excludes voided periods
/// from accuracy calculations using [`MarketVoided`] events instead.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct BetRecorded {
    pub player: Pubkey,
    pub series_id: u16,
    /// `Market::SIDE_UP` (0) or `Market::SIDE_DOWN` (1).
    pub side: u8,
    pub _pad: [u8; 5],
    pub period: u64,
    /// Lifetime total bets after this one.
    pub total_bets: u64,
    /// Week bets after this one.
    pub week_bets: u64,
}

/// Emitted by `AdminVoidMarket` when a market is voided.
///
/// The indexer must exclude this `(series_id, period)` from accuracy calculations
/// and not count any wins/losses for it in the leaderboard.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct MarketVoided {
    pub series_id: u16,
    pub _pad: [u8; 6],
    pub period: u64,
}

/// Emitted by `AdminRefundVoidPosition` for each refunded position.
///
/// Lets the indexer track that the player's ticket was returned and the bet should be excluded.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct VoidRefunded {
    pub player: Pubkey,
    pub series_id: u16,
    pub _pad: [u8; 6],
    pub period: u64,
    pub tickets_refunded: u64,
}

event!(Initialized);
event!(WinRecorded);
event!(StreakBroken);
event!(BetRecorded);
event!(MarketVoided);
event!(VoidRefunded);