Skip to main content

satrush_client/
builders.rs

1//! Environment-free instruction builders.
2//!
3//! Thin composition helpers over the generated instruction structs: they derive
4//! every PDA and associated token account from the program's fixed seeds, so a
5//! caller only supplies the signing authority and the mint addresses. Shared by
6//! the LiteSVM test-suite and the `satrush-cli` binary.
7
8use crate::instructions::{
9    Claim, CreateBoard, CreateEpochVault, CreateOneBtcVault, CreateSatrushConfig, CreateSatrushConfigInstructionArgs,
10    CreateSatsVault, CreateTreasury, DeployPublic, DeployPublicInstructionArgs, RotateRound, SwapRoundStake,
11    SwapRoundStakeInstructionArgs,
12};
13use crate::{
14    get_board_address, get_epoch_vault_address, get_epoch_vault_iteration_address, get_miner_address,
15    get_one_btc_vault_address, get_one_btc_vault_iteration_address, get_public_deployment_address, get_round_address,
16    get_satrush_config_address, get_sats_vault_address, get_treasury_address,
17};
18use solana_instruction::{AccountMeta, Instruction};
19use solana_pubkey::Pubkey;
20
21/// SPL Token program.
22pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
23/// SPL Associated Token Account program.
24pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
25/// System program.
26pub const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
27/// SlotHashes sysvar.
28pub const SLOT_HASHES_ID: Pubkey = Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111");
29
30/// Derive the canonical associated token account for `wallet` holding `mint`.
31pub fn get_associated_token_address(wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
32    Pubkey::find_program_address(
33        &[wallet.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
34        &ASSOCIATED_TOKEN_PROGRAM_ID,
35    )
36    .0
37}
38
39/// `create_satrush_config`: the config PDA holding authorities, mints and fee
40/// parameters. `authority` pays and must equal the program's upgrade authority.
41pub fn get_create_satrush_config_instruction(
42    authority: Pubkey,
43    args: CreateSatrushConfigInstructionArgs,
44) -> Instruction {
45    CreateSatrushConfig {
46        authority,
47        satrush_config: get_satrush_config_address().0,
48        system_program: SYSTEM_PROGRAM_ID,
49    }
50    .instruction(args)
51}
52
53/// `create_board`: the board singleton, its USD/BTC pools and the initial round
54/// (id 1). Signed by the config's admin authority.
55pub fn get_create_board_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
56    let board = get_board_address().0;
57    CreateBoard {
58        authority,
59        satrush_config: get_satrush_config_address().0,
60        board,
61        initial_round: get_round_address(1).0,
62        usd_mint,
63        btc_mint,
64        board_usd_ata: get_associated_token_address(&board, &usd_mint),
65        board_btc_ata: get_associated_token_address(&board, &btc_mint),
66        token_program: TOKEN_PROGRAM_ID,
67        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
68        system_program: SYSTEM_PROGRAM_ID,
69    }
70    .instruction()
71}
72
73/// `create_epoch_vault`: the epoch vault singleton, its USD/BTC pools and the
74/// first iteration (id 1).
75pub fn get_create_epoch_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
76    let epoch_vault = get_epoch_vault_address().0;
77    CreateEpochVault {
78        authority,
79        satrush_config: get_satrush_config_address().0,
80        epoch_vault,
81        epoch_vault_iteration: get_epoch_vault_iteration_address(1).0,
82        usd_mint,
83        btc_mint,
84        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
85        epoch_vault_btc_ata: get_associated_token_address(&epoch_vault, &btc_mint),
86        token_program: TOKEN_PROGRAM_ID,
87        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
88        system_program: SYSTEM_PROGRAM_ID,
89    }
90    .instruction()
91}
92
93/// `create_one_btc_vault`: the 1 BTC vault singleton, its USD/BTC pools and the
94/// first iteration (id 1).
95pub fn get_create_one_btc_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
96    let one_btc_vault = get_one_btc_vault_address().0;
97    CreateOneBtcVault {
98        authority,
99        satrush_config: get_satrush_config_address().0,
100        one_btc_vault,
101        one_btc_vault_iteration: get_one_btc_vault_iteration_address(1).0,
102        usd_mint,
103        btc_mint,
104        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
105        one_btc_vault_btc_ata: get_associated_token_address(&one_btc_vault, &btc_mint),
106        token_program: TOKEN_PROGRAM_ID,
107        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
108        system_program: SYSTEM_PROGRAM_ID,
109    }
110    .instruction()
111}
112
113/// `create_treasury`: the treasury singleton and its USD fee pool.
114pub fn get_create_treasury_instruction(authority: Pubkey, usd_mint: Pubkey) -> Instruction {
115    let treasury = get_treasury_address().0;
116    CreateTreasury {
117        authority,
118        satrush_config: get_satrush_config_address().0,
119        treasury,
120        usd_mint,
121        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
122        token_program: TOKEN_PROGRAM_ID,
123        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
124        system_program: SYSTEM_PROGRAM_ID,
125    }
126    .instruction()
127}
128
129/// `deploy_public`: stake `amount` USD (base units, gross of fees) on the tiles
130/// in `selection_mask` for round `round_id`, as the miner `authority`. The round
131/// must be the board's current one and open for deploys; the miner profile and
132/// deployment record PDAs are created by the instruction.
133pub fn get_deploy_public_instruction(
134    authority: Pubkey,
135    usd_mint: Pubkey,
136    round_id: u32,
137    selection_mask: u32,
138    amount: u64,
139) -> Instruction {
140    let board = get_board_address().0;
141    let epoch_vault = get_epoch_vault_address().0;
142    let one_btc_vault = get_one_btc_vault_address().0;
143    let treasury = get_treasury_address().0;
144
145    DeployPublic {
146        authority,
147        satrush_config: get_satrush_config_address().0,
148        board,
149        usd_mint,
150        board_usd_ata: get_associated_token_address(&board, &usd_mint),
151        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
152        round: get_round_address(round_id).0,
153        public_deployment: get_public_deployment_address(authority, round_id).0,
154        miner: get_miner_address(authority).0,
155        epoch_vault,
156        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
157        one_btc_vault,
158        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
159        treasury,
160        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
161        token_program: TOKEN_PROGRAM_ID,
162        system_program: SYSTEM_PROGRAM_ID,
163    }
164    .instruction(DeployPublicInstructionArgs { selection_mask, amount })
165}
166
167/// `rotate_round`: reveal `current_round_id`'s winning tile and deploy round
168/// `current_round_id + 1` as the board's new active round. Signed by the
169/// config's round authority; valid once the current round's window elapsed.
170pub fn get_rotate_round_instruction(authority: Pubkey, current_round_id: u32) -> Instruction {
171    RotateRound {
172        authority,
173        satrush_config: get_satrush_config_address().0,
174        board: get_board_address().0,
175        current_round: get_round_address(current_round_id).0,
176        next_round: get_round_address(current_round_id + 1).0,
177        slot_hashes: SLOT_HASHES_ID,
178        system_program: SYSTEM_PROGRAM_ID,
179    }
180    .instruction()
181}
182
183/// `swap_round_stake`: relay a pre-built aggregator route that converts part of
184/// the revealed round's USD into BTC. `swap_data` and `route_accounts` are the
185/// route's opaque instruction data and account list; the on-chain handler
186/// enforces the economics against observed balance deltas. Signed by the
187/// config's round authority.
188#[allow(clippy::too_many_arguments)]
189pub fn get_swap_round_stake_instruction(
190    authority: Pubkey,
191    usd_mint: Pubkey,
192    btc_mint: Pubkey,
193    round_id: u32,
194    min_btc_out: u64,
195    swap_program: Pubkey,
196    swap_data: Vec<u8>,
197    route_accounts: &[AccountMeta],
198) -> Instruction {
199    let board = get_board_address().0;
200    SwapRoundStake {
201        authority,
202        satrush_config: get_satrush_config_address().0,
203        board,
204        round: get_round_address(round_id).0,
205        usd_mint,
206        btc_mint,
207        board_usd_ata: get_associated_token_address(&board, &usd_mint),
208        board_btc_ata: get_associated_token_address(&board, &btc_mint),
209        swap_program,
210        token_program: TOKEN_PROGRAM_ID,
211    }
212    .instruction_with_remaining_accounts(SwapRoundStakeInstructionArgs { min_btc_out, swap_data }, route_accounts)
213}
214
215/// `claim`: settle the miner `authority`'s deployment in a Settled round —
216/// credit winnings (USD + sats vault shares) and hashrate points to the miner
217/// profile, then close the deployment account, returning its rent. Valid for
218/// losing deployments too (they still earn hashrate points).
219pub fn get_claim_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey, round_id: u32) -> Instruction {
220    let board = get_board_address().0;
221    let sats_vault = get_sats_vault_address().0;
222    Claim {
223        authority,
224        satrush_config: get_satrush_config_address().0,
225        round: get_round_address(round_id).0,
226        board,
227        public_deployment: get_public_deployment_address(authority, round_id).0,
228        miner: get_miner_address(authority).0,
229        sats_vault,
230        btc_mint,
231        usd_mint,
232        board_btc_ata: get_associated_token_address(&board, &btc_mint),
233        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
234        token_program: TOKEN_PROGRAM_ID,
235        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
236        system_program: SYSTEM_PROGRAM_ID,
237    }
238    .instruction()
239}
240
241/// `create_sats_vault`: the sats vault singleton and its BTC reserve pool.
242pub fn get_create_sats_vault_instruction(authority: Pubkey, btc_mint: Pubkey) -> Instruction {
243    let sats_vault = get_sats_vault_address().0;
244    CreateSatsVault {
245        authority,
246        satrush_config: get_satrush_config_address().0,
247        sats_vault,
248        btc_mint,
249        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
250        token_program: TOKEN_PROGRAM_ID,
251        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
252        system_program: SYSTEM_PROGRAM_ID,
253    }
254    .instruction()
255}