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    CancelPublicAutomation, ClaimUsd, ClaimUsdInstructionArgs, CreateBoard, CreateEpochVault, CreateOneBtcVault,
10    CreatePublicAutomation, CreatePublicAutomationInstructionArgs, CreateSatrushConfig,
11    CreateSatrushConfigInstructionArgs, CreateSatsVault, CreateTreasury, DeployPublic, DeployPublicInstructionArgs,
12    ExecutePublicAutomation, ExecutePublicAutomationInstructionArgs, RotateRound, SettleDeployPublic, SwapRoundStake,
13    SwapRoundStakeInstructionArgs, TopUpPublicAutomation, TopUpPublicAutomationInstructionArgs,
14    UpdateBoardRoundDuration, UpdateBoardRoundDurationInstructionArgs,
15};
16use crate::types::AutomationStrategy;
17use crate::{
18    get_board_address, get_epoch_vault_address, get_epoch_vault_iteration_address, get_event_authority_address,
19    get_miner_address, get_one_btc_vault_address, get_one_btc_vault_iteration_address, get_public_automation_address,
20    get_public_deployment_address, get_round_address, get_satrush_config_address, get_sats_vault_address,
21    get_treasury_address,
22};
23use solana_instruction::{AccountMeta, Instruction};
24use solana_pubkey::Pubkey;
25
26/// SPL Token program.
27pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
28/// SPL Associated Token Account program.
29pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
30/// System program.
31pub const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
32/// SlotHashes sysvar.
33pub const SLOT_HASHES_ID: Pubkey = Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111");
34
35/// Derive the canonical associated token account for `wallet` holding `mint`.
36pub fn get_associated_token_address(wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
37    Pubkey::find_program_address(
38        &[wallet.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
39        &ASSOCIATED_TOKEN_PROGRAM_ID,
40    )
41    .0
42}
43
44/// `create_satrush_config`: the config PDA holding authorities, mints and fee
45/// parameters. `authority` pays and must equal the program's upgrade authority.
46pub fn get_create_satrush_config_instruction(
47    authority: Pubkey,
48    args: CreateSatrushConfigInstructionArgs,
49) -> Instruction {
50    CreateSatrushConfig {
51        authority,
52        satrush_config: get_satrush_config_address().0,
53        system_program: SYSTEM_PROGRAM_ID,
54    }
55    .instruction(args)
56}
57
58/// `create_board`: the board singleton, its USD/BTC pools and the initial round
59/// (id 1). Signed by the config's admin authority.
60pub fn get_create_board_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
61    let board = get_board_address().0;
62    CreateBoard {
63        authority,
64        satrush_config: get_satrush_config_address().0,
65        board,
66        initial_round: get_round_address(1).0,
67        usd_mint,
68        btc_mint,
69        board_usd_ata: get_associated_token_address(&board, &usd_mint),
70        board_btc_ata: get_associated_token_address(&board, &btc_mint),
71        token_program: TOKEN_PROGRAM_ID,
72        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
73        system_program: SYSTEM_PROGRAM_ID,
74    }
75    .instruction()
76}
77
78/// `update_board_round_duration`: overwrite the board's `round_duration` (slots).
79/// Applies to future round activations only; the active round keeps its window.
80/// Signed by the config's admin authority.
81pub fn get_update_board_round_duration_instruction(authority: Pubkey, new_round_duration: u32) -> Instruction {
82    UpdateBoardRoundDuration {
83        authority,
84        satrush_config: get_satrush_config_address().0,
85        board: get_board_address().0,
86    }
87    .instruction(UpdateBoardRoundDurationInstructionArgs { new_round_duration })
88}
89
90/// `create_epoch_vault`: the epoch vault singleton, its USD/BTC pools and the
91/// first iteration (id 1).
92pub fn get_create_epoch_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
93    let epoch_vault = get_epoch_vault_address().0;
94    CreateEpochVault {
95        authority,
96        satrush_config: get_satrush_config_address().0,
97        epoch_vault,
98        epoch_vault_iteration: get_epoch_vault_iteration_address(1).0,
99        usd_mint,
100        btc_mint,
101        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
102        epoch_vault_btc_ata: get_associated_token_address(&epoch_vault, &btc_mint),
103        token_program: TOKEN_PROGRAM_ID,
104        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
105        system_program: SYSTEM_PROGRAM_ID,
106    }
107    .instruction()
108}
109
110/// `create_one_btc_vault`: the 1 BTC vault singleton, its USD/BTC pools and the
111/// first iteration (id 1).
112pub fn get_create_one_btc_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
113    let one_btc_vault = get_one_btc_vault_address().0;
114    CreateOneBtcVault {
115        authority,
116        satrush_config: get_satrush_config_address().0,
117        one_btc_vault,
118        one_btc_vault_iteration: get_one_btc_vault_iteration_address(1).0,
119        usd_mint,
120        btc_mint,
121        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
122        one_btc_vault_btc_ata: get_associated_token_address(&one_btc_vault, &btc_mint),
123        token_program: TOKEN_PROGRAM_ID,
124        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
125        system_program: SYSTEM_PROGRAM_ID,
126    }
127    .instruction()
128}
129
130/// `create_treasury`: the treasury singleton and its USD fee pool.
131pub fn get_create_treasury_instruction(authority: Pubkey, usd_mint: Pubkey) -> Instruction {
132    let treasury = get_treasury_address().0;
133    CreateTreasury {
134        authority,
135        satrush_config: get_satrush_config_address().0,
136        treasury,
137        usd_mint,
138        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
139        token_program: TOKEN_PROGRAM_ID,
140        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
141        system_program: SYSTEM_PROGRAM_ID,
142    }
143    .instruction()
144}
145
146/// `deploy_public`: stake `amount` USD (base units, gross of fees) on the tiles
147/// in `selection_mask` for round `round_id`, as the miner `authority`. The round
148/// must be the board's current one and open for deploys; the miner profile and
149/// deployment record PDAs are created by the instruction.
150pub fn get_deploy_public_instruction(
151    authority: Pubkey,
152    usd_mint: Pubkey,
153    round_id: u32,
154    selection_mask: u32,
155    amount: u64,
156) -> Instruction {
157    let board = get_board_address().0;
158    let epoch_vault = get_epoch_vault_address().0;
159    let one_btc_vault = get_one_btc_vault_address().0;
160    let treasury = get_treasury_address().0;
161
162    DeployPublic {
163        authority,
164        satrush_config: get_satrush_config_address().0,
165        board,
166        usd_mint,
167        board_usd_ata: get_associated_token_address(&board, &usd_mint),
168        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
169        round: get_round_address(round_id).0,
170        public_deployment: get_public_deployment_address(authority, round_id).0,
171        miner: get_miner_address(authority).0,
172        epoch_vault,
173        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
174        one_btc_vault,
175        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
176        treasury,
177        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
178        token_program: TOKEN_PROGRAM_ID,
179        system_program: SYSTEM_PROGRAM_ID,
180    }
181    .instruction(DeployPublicInstructionArgs { selection_mask, amount })
182}
183
184/// `rotate_round`: reveal `current_round_id`'s winning tile and deploy round
185/// `current_round_id + 1` as the board's new active round. Signed by the
186/// config's round authority; valid once the current round's window elapsed.
187pub fn get_rotate_round_instruction(authority: Pubkey, current_round_id: u32) -> Instruction {
188    RotateRound {
189        authority,
190        satrush_config: get_satrush_config_address().0,
191        board: get_board_address().0,
192        current_round: get_round_address(current_round_id).0,
193        next_round: get_round_address(current_round_id + 1).0,
194        slot_hashes: SLOT_HASHES_ID,
195        system_program: SYSTEM_PROGRAM_ID,
196        event_authority: get_event_authority_address().0,
197        program: crate::SATRUSH_ID,
198    }
199    .instruction()
200}
201
202/// `swap_round_stake`: relay a pre-built aggregator route that converts part of
203/// the revealed round's USD into BTC. `swap_data` and `route_accounts` are the
204/// route's opaque instruction data and account list; the on-chain handler
205/// enforces the economics against observed balance deltas. Signed by the
206/// config's round authority.
207#[allow(clippy::too_many_arguments)]
208pub fn get_swap_round_stake_instruction(
209    authority: Pubkey,
210    usd_mint: Pubkey,
211    btc_mint: Pubkey,
212    round_id: u32,
213    min_btc_out: u64,
214    swap_program: Pubkey,
215    swap_data: Vec<u8>,
216    route_accounts: &[AccountMeta],
217) -> Instruction {
218    let board = get_board_address().0;
219    SwapRoundStake {
220        authority,
221        satrush_config: get_satrush_config_address().0,
222        board,
223        round: get_round_address(round_id).0,
224        usd_mint,
225        btc_mint,
226        board_usd_ata: get_associated_token_address(&board, &usd_mint),
227        board_btc_ata: get_associated_token_address(&board, &btc_mint),
228        swap_program,
229        token_program: TOKEN_PROGRAM_ID,
230        event_authority: get_event_authority_address().0,
231        program: crate::SATRUSH_ID,
232    }
233    .instruction_with_remaining_accounts(SwapRoundStakeInstructionArgs { min_btc_out, swap_data }, route_accounts)
234}
235
236/// `settle_deploy_public`: settle `deployment_authority`'s deployment in a
237/// Settled round. `authority` signs (the owner, or the round authority for
238/// automated deployments); `rent_recipient` must be the round authority for
239/// automated deployments and the owner for manual ones.
240pub fn get_settle_deploy_public_instruction(
241    authority: Pubkey,
242    deployment_authority: Pubkey,
243    rent_recipient: Pubkey,
244    usd_mint: Pubkey,
245    btc_mint: Pubkey,
246    round_id: u32,
247) -> Instruction {
248    let board = get_board_address().0;
249    let sats_vault = get_sats_vault_address().0;
250    let public_automation = get_public_automation_address(deployment_authority).0;
251    SettleDeployPublic {
252        authority,
253        rent_recipient,
254        satrush_config: get_satrush_config_address().0,
255        round: get_round_address(round_id).0,
256        board,
257        public_deployment: get_public_deployment_address(deployment_authority, round_id).0,
258        miner: get_miner_address(deployment_authority).0,
259        public_automation,
260        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
261        board_usd_ata: get_associated_token_address(&board, &usd_mint),
262        sats_vault,
263        btc_mint,
264        usd_mint,
265        board_btc_ata: get_associated_token_address(&board, &btc_mint),
266        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
267        token_program: TOKEN_PROGRAM_ID,
268        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
269        system_program: SYSTEM_PROGRAM_ID,
270        event_authority: get_event_authority_address().0,
271        program: crate::SATRUSH_ID,
272    }
273    .instruction()
274}
275
276/// `create_public_automation`: escrow `deposit_usd_amount` and configure the
277/// crank to deploy `per_round_usd_amount` per round with the given strategy.
278/// One automation per authority.
279#[allow(clippy::too_many_arguments)]
280pub fn get_create_public_automation_instruction(
281    authority: Pubkey,
282    usd_mint: Pubkey,
283    strategy: AutomationStrategy,
284    selection_mask: u32,
285    per_round_usd_amount: u64,
286    reload: bool,
287    deposit_usd_amount: u64,
288) -> Instruction {
289    let public_automation = get_public_automation_address(authority).0;
290    CreatePublicAutomation {
291        authority,
292        satrush_config: get_satrush_config_address().0,
293        public_automation,
294        usd_mint,
295        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
296        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
297        miner: get_miner_address(authority).0,
298        token_program: TOKEN_PROGRAM_ID,
299        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
300        system_program: SYSTEM_PROGRAM_ID,
301    }
302    .instruction(CreatePublicAutomationInstructionArgs {
303        strategy,
304        selection_mask,
305        per_round_usd_amount,
306        reload,
307        deposit_usd_amount,
308    })
309}
310
311/// `top_up_public_automation`: move `amount` USD from the authority's account
312/// into the automation's escrow.
313pub fn get_top_up_public_automation_instruction(authority: Pubkey, usd_mint: Pubkey, amount: u64) -> Instruction {
314    let public_automation = get_public_automation_address(authority).0;
315    TopUpPublicAutomation {
316        authority,
317        satrush_config: get_satrush_config_address().0,
318        public_automation,
319        usd_mint,
320        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
321        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
322        token_program: TOKEN_PROGRAM_ID,
323    }
324    .instruction(TopUpPublicAutomationInstructionArgs { amount })
325}
326
327/// `cancel_public_automation`: refund the full escrow balance and close the
328/// automation and its token account. Unconditional; in-flight deployments
329/// settle to the miner profile later.
330pub fn get_cancel_public_automation_instruction(authority: Pubkey, usd_mint: Pubkey) -> Instruction {
331    let public_automation = get_public_automation_address(authority).0;
332    CancelPublicAutomation {
333        authority,
334        satrush_config: get_satrush_config_address().0,
335        public_automation,
336        usd_mint,
337        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
338        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
339        token_program: TOKEN_PROGRAM_ID,
340        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
341        system_program: SYSTEM_PROGRAM_ID,
342    }
343    .instruction()
344}
345
346/// `execute_public_automation`: crank-signed per-round deploy funded from
347/// `automation_authority`'s escrow. `selection_mask` must be `Some` for
348/// Discretionary automations and `None` otherwise.
349pub fn get_execute_public_automation_instruction(
350    crank_authority: Pubkey,
351    automation_authority: Pubkey,
352    usd_mint: Pubkey,
353    round_id: u32,
354    selection_mask: Option<u32>,
355) -> Instruction {
356    let board = get_board_address().0;
357    let epoch_vault = get_epoch_vault_address().0;
358    let one_btc_vault = get_one_btc_vault_address().0;
359    let treasury = get_treasury_address().0;
360    let public_automation = get_public_automation_address(automation_authority).0;
361
362    ExecutePublicAutomation {
363        authority: crank_authority,
364        satrush_config: get_satrush_config_address().0,
365        board,
366        round: get_round_address(round_id).0,
367        public_automation,
368        usd_mint,
369        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
370        board_usd_ata: get_associated_token_address(&board, &usd_mint),
371        public_deployment: get_public_deployment_address(automation_authority, round_id).0,
372        miner: get_miner_address(automation_authority).0,
373        epoch_vault,
374        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
375        one_btc_vault,
376        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
377        treasury,
378        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
379        slot_hashes: SLOT_HASHES_ID,
380        token_program: TOKEN_PROGRAM_ID,
381        system_program: SYSTEM_PROGRAM_ID,
382    }
383    .instruction(ExecutePublicAutomationInstructionArgs { selection_mask })
384}
385
386/// `claim_usd`: withdraw `amount` of the miner `authority`'s unclaimed USD
387/// winnings from the board's USD pool to the authority's USD account. No exit
388/// fee — the full amount transfers.
389pub fn get_claim_usd_instruction(authority: Pubkey, usd_mint: Pubkey, amount: u64) -> Instruction {
390    let board = get_board_address().0;
391    ClaimUsd {
392        authority,
393        satrush_config: get_satrush_config_address().0,
394        board,
395        miner: get_miner_address(authority).0,
396        usd_mint,
397        board_usd_ata: get_associated_token_address(&board, &usd_mint),
398        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
399        token_program: TOKEN_PROGRAM_ID,
400        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
401        system_program: SYSTEM_PROGRAM_ID,
402    }
403    .instruction(ClaimUsdInstructionArgs { amount })
404}
405
406/// `create_sats_vault`: the sats vault singleton and its BTC reserve pool.
407pub fn get_create_sats_vault_instruction(authority: Pubkey, btc_mint: Pubkey) -> Instruction {
408    let sats_vault = get_sats_vault_address().0;
409    CreateSatsVault {
410        authority,
411        satrush_config: get_satrush_config_address().0,
412        sats_vault,
413        btc_mint,
414        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
415        token_program: TOKEN_PROGRAM_ID,
416        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
417        system_program: SYSTEM_PROGRAM_ID,
418    }
419    .instruction()
420}