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