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