Skip to main content

streak_api/
event.rs

1//! On-chain events (`sol_log_data` via Steel `event!` / `.log()`).
2//!
3//! ## Indexer contract
4//!
5//! The off-chain indexer **must subscribe to these events** to reconstruct daily and weekly
6//! leaderboard state. Accounts alone are insufficient for daily rank snapshots because the on-chain
7//! Ledger only maintains rolling weekly aggregates, not per-day history.
8//!
9//! ### Daily leaderboard reconstruction
10//!
11//! For each UTC day the indexer:
12//! 1. Groups `WinRecorded` events by `(player, UTC_day(period))` → daily wins + daily best streak.
13//! 2. Groups `BetRecorded` events by `(player, UTC_day(period))` → daily bet count.
14//! 3. Excludes periods matching any `MarketVoided` event from accuracy calculations.
15//! 4. Uses `StreakBroken` events to confirm streak resets (optional — can also derive from missing
16//!    consecutive `WinRecorded` run).
17//! 5. At UTC midnight, snapshots rank order for each player → feeds into weekly scoring.
18//!
19//! The `period` field maps to a UTC day via the `Market::open_ts` / `Market::close_ts` stored
20//! on the Market PDA (288 periods per day for 5-minute rounds).
21//!
22//! ### Weekly score inputs (all on-chain, readable from events or Ledger state)
23//!
24//! | Component | Weight | Source |
25//! |---|---|---|
26//! | Best streak of the week | 50% | `WinRecorded.win_streak` peak per week |
27//! | Daily leaderboard placement | 30% | Off-chain daily rank snapshots |
28//! | Total correct calls | 10% | `WinRecorded.total_wins` at week end |
29//! | Accuracy rate | 10% | `total_wins / total_bets` (min ~50 bets enforced off-chain) |
30
31use steel::*;
32
33/// Emitted after successful [`Initialize`](crate::instruction::Initialize).
34#[repr(C)]
35#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
36pub struct Initialized {
37    pub admin: Pubkey,
38}
39
40/// Emitted by `ExecutorTreasury` DISTRIBUTE when a win is confirmed for a player.
41///
42/// Contains full stat snapshot at the moment of win for accurate daily/weekly indexing.
43#[repr(C)]
44#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
45pub struct WinRecorded {
46    pub player: Pubkey,
47    pub series_id: u16,
48    pub _pad: [u8; 6],
49    /// Market period that was won (derive UTC day from `Market::open_ts`).
50    pub period: u64,
51    /// Running win streak **after** this win.
52    pub win_streak: u64,
53    /// All-time highest streak ever (updated if this win set a new peak).
54    pub peak_win_streak: u64,
55    /// Lifetime correct calls after this win.
56    pub total_wins: u64,
57    /// Correct calls in the current executor week after this win.
58    pub week_wins: u64,
59    /// Best streak achieved in the current executor week after this win.
60    pub week_peak_streak: u64,
61}
62
63/// Emitted by `PlaceBet` when a player's streak is broken (previous period settled as a loss).
64///
65/// The indexer uses this to confirm streak resets and close the daily streak window.
66#[repr(C)]
67#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
68pub struct StreakBroken {
69    pub player: Pubkey,
70    pub series_id: u16,
71    pub _pad: [u8; 6],
72    /// The period whose settlement caused the streak break.
73    pub period: u64,
74}
75
76/// Emitted by `PlaceBet` when tickets are actually debited (both commit and live windows).
77///
78/// Enables accurate daily bet counting for accuracy-rate scoring.
79/// Void-refunded bets are NOT un-counted here; the indexer excludes voided periods
80/// from accuracy calculations using [`MarketVoided`] events instead.
81#[repr(C)]
82#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
83pub struct BetRecorded {
84    pub player: Pubkey,
85    pub series_id: u16,
86    /// `Market::SIDE_UP` (0) or `Market::SIDE_DOWN` (1).
87    pub side: u8,
88    pub _pad: [u8; 5],
89    pub period: u64,
90    /// Lifetime total bets after this one.
91    pub total_bets: u64,
92    /// Week bets after this one.
93    pub week_bets: u64,
94}
95
96/// Emitted by `AdminVoidMarket` when a market is voided.
97///
98/// The indexer must exclude this `(series_id, period)` from accuracy calculations
99/// and not count any wins/losses for it in the leaderboard.
100#[repr(C)]
101#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
102pub struct MarketVoided {
103    pub series_id: u16,
104    pub _pad: [u8; 6],
105    pub period: u64,
106}
107
108/// Emitted by `AdminRefundVoidPosition` for each refunded position.
109///
110/// Lets the indexer track that the player's ticket was returned and the bet should be excluded.
111#[repr(C)]
112#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
113pub struct VoidRefunded {
114    pub player: Pubkey,
115    pub series_id: u16,
116    pub _pad: [u8; 6],
117    pub period: u64,
118    pub tickets_refunded: u64,
119}
120
121event!(Initialized);
122event!(WinRecorded);
123event!(StreakBroken);
124event!(BetRecorded);
125event!(MarketVoided);
126event!(VoidRefunded);