Skip to main content

streak_api/
sdk.rs

1//! Off-chain instruction builders. Fixed account order mirrors on-chain `process_*` comments.
2
3use solana_program::pubkey::Pubkey;
4use spl_associated_token_account::{
5    get_associated_token_address, get_associated_token_address_with_program_id,
6};
7use steel::*;
8
9use crate::{
10    consts::{EXECUTOR_ADDRESS, FEE_COLLECTOR, USDC_MAINNET_MINT},
11    instruction::{AdminPayout, AdminRouteFees, BuyTicket, Initialize, PlaceBet},
12    state::{Treasury, User},
13};
14
15#[inline(always)]
16pub fn program_id() -> Pubkey {
17    crate::ID
18}
19
20// ── ATA helpers ──────────────────────────────────────────────────────────────
21
22pub fn treasury_usdc_ata() -> Pubkey {
23    get_associated_token_address(&Treasury::pda().0, &USDC_MAINNET_MINT)
24}
25
26pub fn treasury_usdc_ata_with_token_program(token_program: &Pubkey) -> Pubkey {
27    get_associated_token_address_with_program_id(
28        &Treasury::pda().0,
29        &USDC_MAINNET_MINT,
30        token_program,
31    )
32}
33
34pub fn associated_usdc_ata(owner: Pubkey, token_program: &Pubkey) -> Pubkey {
35    get_associated_token_address_with_program_id(&owner, &USDC_MAINNET_MINT, token_program)
36}
37
38pub fn usdc_ata(owner: Pubkey) -> Pubkey {
39    get_associated_token_address(&owner, &USDC_MAINNET_MINT)
40}
41
42pub fn fee_collector_usdc_ata_with_token_program(token_program: &Pubkey) -> Pubkey {
43    get_associated_token_address_with_program_id(&FEE_COLLECTOR, &USDC_MAINNET_MINT, token_program)
44}
45
46// ── Instruction builders ─────────────────────────────────────────────────────
47
48/// One-time treasury initialization.
49pub fn initialize(payer: Pubkey, token_program: Pubkey) -> Instruction {
50    let treasury_address = Treasury::pda().0;
51    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
52    Instruction {
53        program_id: crate::ID,
54        accounts: vec![
55            AccountMeta::new(payer, true),
56            AccountMeta::new(treasury_address, false),
57            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
58            AccountMeta::new(treasury_ata, false),
59            AccountMeta::new_readonly(system_program::ID, false),
60            AccountMeta::new_readonly(token_program, false),
61            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
62        ],
63        data: Initialize {}.to_bytes(),
64    }
65}
66
67/// Purchase gaming credits (tickets) with USDC.
68///
69/// Splits `amount` on-chain (70/15/10/5): 95% goes to the treasury, 5% goes directly
70/// to `FEE_COLLECTOR` (never touches the treasury). Idempotently creates the
71/// `FEE_COLLECTOR` USDC ATA if it doesn't exist yet.
72/// Credits the user's `User` PDA `balance` by `amount`.
73pub fn buy_ticket(user: Pubkey, amount: u64, token_program: Pubkey) -> Instruction {
74    let user_ata = associated_usdc_ata(user, &token_program);
75    let treasury_address = Treasury::pda().0;
76    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
77    let user_pda = User::pda(&user).0;
78    let fee_collector_ata = fee_collector_usdc_ata_with_token_program(&token_program);
79    Instruction {
80        program_id: crate::ID,
81        accounts: vec![
82            AccountMeta::new(user, true),
83            AccountMeta::new(user_ata, false),
84            AccountMeta::new(treasury_address, false),
85            AccountMeta::new(treasury_ata, false),
86            AccountMeta::new(user_pda, false),
87            AccountMeta::new(FEE_COLLECTOR, false),
88            AccountMeta::new(fee_collector_ata, false),
89            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
90            AccountMeta::new_readonly(token_program, false),
91            AccountMeta::new_readonly(system_program::ID, false),
92            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
93        ],
94        data: BuyTicket {
95            amount: amount.to_le_bytes(),
96        }
97        .to_bytes(),
98    }
99}
100
101/// Spend ticket credits on a prediction. User signs; `User.balance` is debited on-chain.
102/// The indexer reads the `BetPlaced` event to record the bet in the DB.
103pub fn place_bet(
104    user: Pubkey,
105    amount: u64,
106    side: u8,
107    series_id: u16,
108    period: u64,
109) -> Instruction {
110    let user_pda = User::pda(&user).0;
111    Instruction {
112        program_id: crate::ID,
113        accounts: vec![
114            AccountMeta::new(user, true),
115            AccountMeta::new(user_pda, false),
116        ],
117        data: PlaceBet {
118            amount: amount.to_le_bytes(),
119            side,
120            _pad: [0; 1],
121            series_id: series_id.to_le_bytes(),
122            _pad2: [0; 4],
123            period: period.to_le_bytes(),
124        }
125        .to_bytes(),
126    }
127}
128
129/// Route `amount` µUSDC of claimed DBC / DAMM v2 fees into the treasury.
130pub fn route_fees(executor: Pubkey, amount: u64, token_program: Pubkey) -> Instruction {
131    let treasury_address = Treasury::pda().0;
132    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
133    let executor_ata = associated_usdc_ata(executor, &token_program);
134    let fee_collector_ata = fee_collector_usdc_ata_with_token_program(&token_program);
135    Instruction {
136        program_id: crate::ID,
137        accounts: vec![
138            AccountMeta::new(executor, true),
139            AccountMeta::new(executor_ata, false),
140            AccountMeta::new(treasury_address, false),
141            AccountMeta::new(treasury_ata, false),
142            AccountMeta::new(fee_collector_ata, false),
143            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
144            AccountMeta::new_readonly(token_program, false),
145        ],
146        data: AdminRouteFees {
147            amount: amount.to_le_bytes(),
148        }
149        .to_bytes(),
150    }
151}
152
153/// Convenience: `route_fees` signed by the canonical `EXECUTOR_ADDRESS`.
154pub fn route_fees_default_executor(amount: u64, token_program: Pubkey) -> Instruction {
155    route_fees(EXECUTOR_ADDRESS, amount, token_program)
156}
157
158/// Pay `amount` µUSDC from the treasury to `recipient_ata`.
159pub fn payout(
160    executor: Pubkey,
161    recipient_ata: Pubkey,
162    amount: u64,
163    series_id: u16,
164    period: u64,
165    token_program: Pubkey,
166) -> Instruction {
167    let treasury_address = Treasury::pda().0;
168    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
169    Instruction {
170        program_id: crate::ID,
171        accounts: vec![
172            AccountMeta::new(executor, true),
173            AccountMeta::new(treasury_address, false),
174            AccountMeta::new(treasury_ata, false),
175            AccountMeta::new(recipient_ata, false),
176            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
177            AccountMeta::new_readonly(token_program, false),
178        ],
179        data: AdminPayout {
180            amount: amount.to_le_bytes(),
181            series_id: series_id.to_le_bytes(),
182            _pad_ix: [0; 6],
183            period: period.to_le_bytes(),
184        }
185        .to_bytes(),
186    }
187}