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