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, system_program};
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, AdminSetTreasuryPools, BuyTicket, Initialize},
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: 95% → treasury_ata (daily/weekly/buyback), 5% → fee_collector_ata
70/// directly (user signs both transfers; team share never touches the treasury).
71/// Credits the user's `User` PDA `balance` by the full `amount`.
72///
73/// **Pre-requisite:** the `FEE_COLLECTOR` USDC ATA must exist before calling this.
74pub fn buy_ticket(user: Pubkey, amount: u64, token_program: Pubkey) -> Instruction {
75    let user_ata = associated_usdc_ata(user, &token_program);
76    let treasury_address = Treasury::pda().0;
77    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
78    let user_pda = User::pda(&user).0;
79    let fee_collector_ata = fee_collector_usdc_ata_with_token_program(&token_program);
80    Instruction {
81        program_id: crate::ID,
82        accounts: vec![
83            AccountMeta::new(user, true),
84            AccountMeta::new(user_ata, false),
85            AccountMeta::new(treasury_address, false),
86            AccountMeta::new(treasury_ata, false),
87            AccountMeta::new(user_pda, 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        ],
93        data: BuyTicket {
94            amount: amount.to_le_bytes(),
95        }
96        .to_bytes(),
97    }
98}
99
100/// Route `amount` µUSDC of claimed DBC / DAMM v2 fees into the treasury.
101pub fn route_fees(executor: Pubkey, amount: u64, token_program: Pubkey) -> Instruction {
102    let treasury_address = Treasury::pda().0;
103    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
104    let executor_ata = associated_usdc_ata(executor, &token_program);
105    let fee_collector_ata = fee_collector_usdc_ata_with_token_program(&token_program);
106    Instruction {
107        program_id: crate::ID,
108        accounts: vec![
109            AccountMeta::new(executor, true),
110            AccountMeta::new(executor_ata, false),
111            AccountMeta::new(treasury_address, false),
112            AccountMeta::new(treasury_ata, false),
113            AccountMeta::new(fee_collector_ata, false),
114            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
115            AccountMeta::new_readonly(token_program, false),
116        ],
117        data: AdminRouteFees {
118            amount: amount.to_le_bytes(),
119        }
120        .to_bytes(),
121    }
122}
123
124/// Convenience: `route_fees` signed by the canonical `EXECUTOR_ADDRESS`.
125pub fn route_fees_default_executor(amount: u64, token_program: Pubkey) -> Instruction {
126    route_fees(EXECUTOR_ADDRESS, amount, token_program)
127}
128
129/// Pay `amount` µUSDC from the treasury to `recipient_ata`.
130///
131/// `pool` selects which treasury accounting counter to debit (`PAYOUT_POOL_*`).
132/// Use `PAYOUT_POOL_NONE` for per-round bet winner payouts.
133pub fn payout(
134    executor: Pubkey,
135    recipient_ata: Pubkey,
136    amount: u64,
137    series_id: u16,
138    period: u64,
139    token_program: Pubkey,
140    pool: u8,
141) -> Instruction {
142    let treasury_address = Treasury::pda().0;
143    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
144    Instruction {
145        program_id: crate::ID,
146        accounts: vec![
147            AccountMeta::new(executor, true),
148            AccountMeta::new(treasury_address, false),
149            AccountMeta::new(treasury_ata, false),
150            AccountMeta::new(recipient_ata, false),
151            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
152            AccountMeta::new_readonly(token_program, false),
153        ],
154        data: AdminPayout {
155            amount: amount.to_le_bytes(),
156            series_id: series_id.to_le_bytes(),
157            pool,
158            _pad_ix: [0; 5],
159            period: period.to_le_bytes(),
160        }
161        .to_bytes(),
162    }
163}
164
165/// Overwrite treasury pool counters (executor-only reconcile).
166pub fn set_treasury_pools(
167    executor: Pubkey,
168    daily_jackpot: u64,
169    weekly_jackpot: u64,
170    buyback: u64,
171    stability_reserve: u64,
172    token_program: Pubkey,
173) -> Instruction {
174    let treasury_address = Treasury::pda().0;
175    let treasury_ata = treasury_usdc_ata_with_token_program(&token_program);
176    Instruction {
177        program_id: crate::ID,
178        accounts: vec![
179            AccountMeta::new(executor, true),
180            AccountMeta::new(treasury_address, false),
181            AccountMeta::new_readonly(treasury_ata, false),
182            AccountMeta::new_readonly(USDC_MAINNET_MINT, false),
183            AccountMeta::new_readonly(token_program, false),
184            AccountMeta::new_readonly(system_program::ID, false),
185        ],
186        data: AdminSetTreasuryPools {
187            daily_jackpot: daily_jackpot.to_le_bytes(),
188            weekly_jackpot: weekly_jackpot.to_le_bytes(),
189            buyback: buyback.to_le_bytes(),
190            stability_reserve: stability_reserve.to_le_bytes(),
191        }
192        .to_bytes(),
193    }
194}