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, ClaimGrubstakeAirdrop, ClaimSats, ClaimSatsInstructionArgs, ClaimToken,
10    ClaimTokenInstructionArgs, ClaimUsd, ClaimUsdInstructionArgs, CloseRound, CreateBoard, CreateEpochVault,
11    CreateGrubstakeAirdrop, CreateGrubstakeAirdropInstructionArgs, CreateOneBtcVault, CreatePublicAutomation,
12    CreatePublicAutomationInstructionArgs, CreateSatrushConfig, CreateSatrushConfigInstructionArgs, CreateSatsVault,
13    CreateTokenVault, CreateTreasury, DeployPublic, DeployPublicInstructionArgs, DepositGrubstake,
14    DepositGrubstakeInstructionArgs, ExchangeAffiliatePoints, ExchangeAffiliatePointsInstructionArgs,
15    ExecutePublicAutomation, ExecutePublicAutomationInstructionArgs, MigrateBoard, MigrateMiner, MigrateSatrushConfig,
16    MigrateSatrushConfigInstructionArgs, MigrateTreasury, ReclaimGrubstakeAirdrop, ReclaimGrubstakeAutomation,
17    ReclaimMinerGrubstake, RotateRound, SetAffiliateRate, SetAffiliateRateInstructionArgs, SetMinerTag,
18    SetMinerTagInstructionArgs, SettleDeployPublic, SwapRoundStake, SwapRoundStakeInstructionArgs,
19    TopUpPublicAutomation, TopUpPublicAutomationInstructionArgs, UpdateBoardRoundDuration,
20    UpdateBoardRoundDurationInstructionArgs, UpdateDeployFees, UpdateDeployFeesInstructionArgs,
21    UpdateDeploymentSettleGraceDuration, UpdateDeploymentSettleGraceDurationInstructionArgs,
22    UpdateEpochVaultIterationDuration, UpdateEpochVaultIterationDurationInstructionArgs, UpdateMinDeployUsdAmount,
23    UpdateMinDeployUsdAmountInstructionArgs, UpdateStrikeTriggerModulus, UpdateStrikeTriggerModulusInstructionArgs,
24    UpdateUnclaimedHashrateBps, UpdateUnclaimedHashrateBpsInstructionArgs, WithdrawGrubstake,
25    WithdrawGrubstakeInstructionArgs,
26};
27use crate::types::AutomationStrategy;
28use crate::{
29    get_affiliate_address, get_affiliate_tag_address, get_board_address, get_epoch_vault_address,
30    get_epoch_vault_iteration_address, get_event_authority_address, get_miner_address, get_one_btc_vault_address,
31    get_one_btc_vault_iteration_address, get_public_automation_address, get_public_deployment_address,
32    get_round_address, get_satrush_config_address, get_sats_vault_address, get_token_vault_address,
33    get_treasury_address,
34};
35use solana_instruction::{AccountMeta, Instruction};
36use solana_pubkey::Pubkey;
37
38/// SPL Token program.
39pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
40/// SPL Associated Token Account program.
41pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
42/// System program.
43pub const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
44/// SlotHashes sysvar.
45pub const SLOT_HASHES_ID: Pubkey = Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111");
46
47/// Derive the canonical associated token account for `wallet` holding `mint`.
48pub fn get_associated_token_address(wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
49    Pubkey::find_program_address(
50        &[wallet.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
51        &ASSOCIATED_TOKEN_PROGRAM_ID,
52    )
53    .0
54}
55
56/// `create_satrush_config`: the config PDA holding authorities, mints and fee
57/// parameters. `authority` pays and must equal the program's upgrade authority.
58pub fn get_create_satrush_config_instruction(
59    authority: Pubkey,
60    args: CreateSatrushConfigInstructionArgs,
61) -> Instruction {
62    CreateSatrushConfig {
63        authority,
64        satrush_config: get_satrush_config_address().0,
65        system_program: SYSTEM_PROGRAM_ID,
66    }
67    .instruction(args)
68}
69
70/// `create_board`: the board singleton, its USD/BTC pools and the initial round
71/// (id 1). Signed by the config's admin authority.
72pub fn get_create_board_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
73    let board = get_board_address().0;
74    CreateBoard {
75        authority,
76        satrush_config: get_satrush_config_address().0,
77        board,
78        initial_round: get_round_address(1).0,
79        usd_mint,
80        btc_mint,
81        board_usd_ata: get_associated_token_address(&board, &usd_mint),
82        board_btc_ata: get_associated_token_address(&board, &btc_mint),
83        token_program: TOKEN_PROGRAM_ID,
84        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
85        system_program: SYSTEM_PROGRAM_ID,
86    }
87    .instruction()
88}
89
90/// `update_board_round_duration`: overwrite the board's `round_duration` (slots).
91/// Applies to future round activations only; the active round keeps its window.
92/// Signed by the config's admin authority.
93pub fn get_update_board_round_duration_instruction(authority: Pubkey, new_round_duration: u32) -> Instruction {
94    UpdateBoardRoundDuration {
95        authority,
96        satrush_config: get_satrush_config_address().0,
97        board: get_board_address().0,
98    }
99    .instruction(UpdateBoardRoundDurationInstructionArgs { new_round_duration })
100}
101
102/// `update_min_deploy_usd_amount`: overwrite the config's minimum gross deploy
103/// size (USD mint base units). Applies to every subsequent manual deploy,
104/// automation creation and automation execution. Signed by the config's admin
105/// authority.
106pub fn get_update_min_deploy_usd_amount_instruction(authority: Pubkey, new_min_deploy_usd_amount: u64) -> Instruction {
107    UpdateMinDeployUsdAmount {
108        authority,
109        satrush_config: get_satrush_config_address().0,
110    }
111    .instruction(UpdateMinDeployUsdAmountInstructionArgs {
112        new_min_deploy_usd_amount,
113    })
114}
115
116/// `update_unclaimed_hashrate_bps`: overwrite the config's deferred hashrate
117/// bonus ratio (bps of each settled play's reward). Signed by the config's
118/// admin authority.
119pub fn get_update_unclaimed_hashrate_bps_instruction(
120    authority: Pubkey,
121    new_unclaimed_hashrate_bps: u32,
122) -> Instruction {
123    UpdateUnclaimedHashrateBps {
124        authority,
125        satrush_config: get_satrush_config_address().0,
126    }
127    .instruction(UpdateUnclaimedHashrateBpsInstructionArgs {
128        new_unclaimed_hashrate_bps,
129    })
130}
131
132/// `update_deploy_fees`: atomically overwrite the five deploy-time fee legs.
133/// Each leg must be at least 1 bps and the legs must sum to exactly 600 bps
134/// (6%); the split may vary but the total never changes. Signed by the
135/// config's admin authority.
136pub fn get_update_deploy_fees_instruction(
137    authority: Pubkey,
138    new_strike_fee_bps: u32,
139    new_epoch_fee_bps: u32,
140    new_one_btc_fee_bps: u32,
141    new_protocol_fee_bps: u32,
142    new_buybacks_fee_bps: u32,
143) -> Instruction {
144    UpdateDeployFees {
145        authority,
146        satrush_config: get_satrush_config_address().0,
147    }
148    .instruction(UpdateDeployFeesInstructionArgs {
149        new_strike_fee_bps,
150        new_epoch_fee_bps,
151        new_one_btc_fee_bps,
152        new_protocol_fee_bps,
153        new_buybacks_fee_bps,
154    })
155}
156
157/// `update_epoch_vault_iteration_duration`: overwrite the config's epoch vault
158/// iteration length (slots). Applies to the currently accumulating iteration
159/// immediately. Signed by the config's admin authority.
160pub fn get_update_epoch_vault_iteration_duration_instruction(
161    authority: Pubkey,
162    new_iteration_duration: u64,
163) -> Instruction {
164    UpdateEpochVaultIterationDuration {
165        authority,
166        satrush_config: get_satrush_config_address().0,
167    }
168    .instruction(UpdateEpochVaultIterationDurationInstructionArgs { new_iteration_duration })
169}
170
171/// `update_strike_trigger_modulus`: admin retunes the Sat Strike odds (the
172/// jackpot fires when `rng % modulus == 0`). Signed by the config's admin
173/// authority.
174pub fn get_update_strike_trigger_modulus_instruction(authority: Pubkey, new_modulus: u16) -> Instruction {
175    UpdateStrikeTriggerModulus {
176        authority,
177        satrush_config: get_satrush_config_address().0,
178    }
179    .instruction(UpdateStrikeTriggerModulusInstructionArgs { new_modulus })
180}
181
182/// `update_deployment_settle_grace_duration`: admin retunes the settle grace
183/// window (slots after a round settles before stale deployments may be
184/// force-cleaned; 0 disables cleanup).
185pub fn get_update_deployment_settle_grace_duration_instruction(
186    authority: Pubkey,
187    new_grace_duration: u64,
188) -> Instruction {
189    UpdateDeploymentSettleGraceDuration {
190        authority,
191        satrush_config: get_satrush_config_address().0,
192    }
193    .instruction(UpdateDeploymentSettleGraceDurationInstructionArgs { new_grace_duration })
194}
195
196/// `create_epoch_vault`: the epoch vault singleton, its USD/BTC pools and the
197/// first iteration (id 1).
198pub fn get_create_epoch_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
199    let epoch_vault = get_epoch_vault_address().0;
200    CreateEpochVault {
201        authority,
202        satrush_config: get_satrush_config_address().0,
203        epoch_vault,
204        epoch_vault_iteration: get_epoch_vault_iteration_address(1).0,
205        usd_mint,
206        btc_mint,
207        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
208        epoch_vault_btc_ata: get_associated_token_address(&epoch_vault, &btc_mint),
209        token_program: TOKEN_PROGRAM_ID,
210        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
211        system_program: SYSTEM_PROGRAM_ID,
212    }
213    .instruction()
214}
215
216/// `create_one_btc_vault`: the 1 BTC vault singleton, its USD/BTC pools and the
217/// first iteration (id 1).
218pub fn get_create_one_btc_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
219    let one_btc_vault = get_one_btc_vault_address().0;
220    CreateOneBtcVault {
221        authority,
222        satrush_config: get_satrush_config_address().0,
223        one_btc_vault,
224        one_btc_vault_iteration: get_one_btc_vault_iteration_address(1).0,
225        usd_mint,
226        btc_mint,
227        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
228        one_btc_vault_btc_ata: get_associated_token_address(&one_btc_vault, &btc_mint),
229        token_program: TOKEN_PROGRAM_ID,
230        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
231        system_program: SYSTEM_PROGRAM_ID,
232    }
233    .instruction()
234}
235
236/// `create_treasury`: the treasury singleton and its USD fee pool.
237pub fn get_create_treasury_instruction(authority: Pubkey, usd_mint: Pubkey) -> Instruction {
238    let treasury = get_treasury_address().0;
239    CreateTreasury {
240        authority,
241        satrush_config: get_satrush_config_address().0,
242        treasury,
243        usd_mint,
244        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
245        token_program: TOKEN_PROGRAM_ID,
246        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
247        system_program: SYSTEM_PROGRAM_ID,
248    }
249    .instruction()
250}
251
252/// `deploy_public`: stake `amount` USD (base units; all fees are deducted from
253/// it, so the miner parts with exactly `amount`) on the tiles
254/// in `selection_mask` for round `round_id`, as the miner `authority`. The round
255/// must be the board's current one and open for deploys; the miner profile and
256/// deployment record PDAs are created by the instruction.
257pub fn get_deploy_public_instruction(
258    authority: Pubkey,
259    usd_mint: Pubkey,
260    round_id: u32,
261    selection_mask: u32,
262    amount: u64,
263) -> Instruction {
264    let funding_usd_ata = get_associated_token_address(&authority, &usd_mint);
265    build_deploy_public_instruction(authority, usd_mint, round_id, selection_mask, amount, funding_usd_ata, false, None)
266}
267
268/// `deploy_public` carrying the affiliate account of `affiliate_authority`:
269/// binds the miner when this deploy creates it, and accrues the affiliate's
270/// rate share of the deploy's protocol fee leg
271/// (`protocol_fee × rate_bps / 10_000`) as points.
272pub fn get_deploy_public_with_affiliate_instruction(
273    authority: Pubkey,
274    usd_mint: Pubkey,
275    round_id: u32,
276    selection_mask: u32,
277    amount: u64,
278    affiliate_authority: Pubkey,
279) -> Instruction {
280    let funding_usd_ata = get_associated_token_address(&authority, &usd_mint);
281    build_deploy_public_instruction(
282        authority,
283        usd_mint,
284        round_id,
285        selection_mask,
286        amount,
287        funding_usd_ata,
288        false,
289        Some(get_affiliate_address(affiliate_authority).0),
290    )
291}
292
293/// `deploy_public` funded from the miner's grubstake balance: the stake is
294/// pulled from the miner PDA's USD ATA and no hashrate is earned; settled USD
295/// winnings return to the grubstake.
296pub fn get_deploy_public_grubstake_instruction(
297    authority: Pubkey,
298    usd_mint: Pubkey,
299    round_id: u32,
300    selection_mask: u32,
301    amount: u64,
302) -> Instruction {
303    let miner = get_miner_address(authority).0;
304    let funding_usd_ata = get_associated_token_address(&miner, &usd_mint);
305    build_deploy_public_instruction(authority, usd_mint, round_id, selection_mask, amount, funding_usd_ata, true, None)
306}
307
308/// Grubstake-funded `deploy_public` carrying the affiliate account of
309/// `affiliate_authority`. The affiliate must match the miner's bound tag;
310/// bonus-funded deploys accrue no points.
311pub fn get_deploy_public_grubstake_with_affiliate_instruction(
312    authority: Pubkey,
313    usd_mint: Pubkey,
314    round_id: u32,
315    selection_mask: u32,
316    amount: u64,
317    affiliate_authority: Pubkey,
318) -> Instruction {
319    let miner = get_miner_address(authority).0;
320    let funding_usd_ata = get_associated_token_address(&miner, &usd_mint);
321    build_deploy_public_instruction(
322        authority,
323        usd_mint,
324        round_id,
325        selection_mask,
326        amount,
327        funding_usd_ata,
328        true,
329        Some(get_affiliate_address(affiliate_authority).0),
330    )
331}
332
333#[allow(clippy::too_many_arguments)]
334fn build_deploy_public_instruction(
335    authority: Pubkey,
336    usd_mint: Pubkey,
337    round_id: u32,
338    selection_mask: u32,
339    amount: u64,
340    funding_usd_ata: Pubkey,
341    is_grubstake_funded: bool,
342    affiliate: Option<Pubkey>,
343) -> Instruction {
344    let board = get_board_address().0;
345
346    DeployPublic {
347        authority,
348        satrush_config: get_satrush_config_address().0,
349        board,
350        usd_mint,
351        board_usd_ata: get_associated_token_address(&board, &usd_mint),
352        authority_usd_ata: funding_usd_ata,
353        round: get_round_address(round_id).0,
354        public_deployment: get_public_deployment_address(authority, round_id).0,
355        miner: get_miner_address(authority).0,
356        affiliate,
357        token_program: TOKEN_PROGRAM_ID,
358        system_program: SYSTEM_PROGRAM_ID,
359        event_authority: get_event_authority_address().0,
360        program: crate::SATRUSH_ID,
361    }
362    .instruction_with_remaining_accounts(
363        DeployPublicInstructionArgs {
364            selection_mask,
365            amount,
366            is_grubstake_funded,
367        },
368        // Always carried: when this deploy is the round's first entry it arms
369        // the round rotor via CPI, and the caller cannot know in advance.
370        &crate::rng::get_rng_remaining_accounts(),
371    )
372}
373
374/// `rotate_round`: reveal `current_round_id`'s winning tile, sweep the round's
375/// accumulated fee legs from the board pool to the epoch/1 BTC/treasury pools
376/// (plus, on a strike trigger, the strike skim's USD and BTC legs to the epoch
377/// vault), and deploy round `current_round_id + 1` as the board's new active
378/// round. Signed by the config's round authority; valid once the round's
379/// window elapsed.
380pub fn get_rotate_round_instruction(
381    authority: Pubkey,
382    usd_mint: Pubkey,
383    btc_mint: Pubkey,
384    token_mint: Pubkey,
385    oracle_pool: Pubkey,
386    current_round_id: u32,
387) -> Instruction {
388    let board = get_board_address().0;
389    let epoch_vault = get_epoch_vault_address().0;
390    let one_btc_vault = get_one_btc_vault_address().0;
391    let treasury = get_treasury_address().0;
392
393    let mut instruction = RotateRound {
394        authority,
395        satrush_config: get_satrush_config_address().0,
396        board,
397        current_round: get_round_address(current_round_id).0,
398        next_round: get_round_address(current_round_id + 1).0,
399        usd_mint,
400        btc_mint,
401        board_usd_ata: get_associated_token_address(&board, &usd_mint),
402        board_btc_ata: get_associated_token_address(&board, &btc_mint),
403        epoch_vault,
404        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
405        epoch_vault_btc_ata: get_associated_token_address(&epoch_vault, &btc_mint),
406        one_btc_vault,
407        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
408        treasury,
409        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
410        round_rotor: crate::rng::get_rotor_address(crate::rng::ROUND_ROTOR_TAG).0,
411        token_program: TOKEN_PROGRAM_ID,
412        system_program: SYSTEM_PROGRAM_ID,
413        event_authority: get_event_authority_address().0,
414        program: crate::SATRUSH_ID,
415    }
416    .instruction();
417    // The token-side accounts ride as remaining accounts (the on-chain
418    // `try_accounts` frame is at the 4 KB ceiling); the handler pins every
419    // address. Order matters.
420    instruction
421        .accounts
422        .extend(get_rotate_round_remaining_accounts(token_mint, oracle_pool));
423    instruction
424}
425
426/// The nine remaining accounts `rotate_round` takes, in order: the board's
427/// token ATA, the epoch vault's token ATA, the token mint, then the
428/// satrush-mint program, its config, mine, oracle and authority PDAs, and the
429/// Orca pool it prices the token on.
430pub fn get_rotate_round_remaining_accounts(token_mint: Pubkey, oracle_pool: Pubkey) -> Vec<AccountMeta> {
431    let board = get_board_address().0;
432    let epoch_vault = get_epoch_vault_address().0;
433    vec![
434        AccountMeta::new(get_associated_token_address(&board, &token_mint), false),
435        AccountMeta::new(get_associated_token_address(&epoch_vault, &token_mint), false),
436        AccountMeta::new(token_mint, false),
437        AccountMeta::new_readonly(crate::mint::SATRUSH_MINT_PROGRAM_ID, false),
438        AccountMeta::new_readonly(crate::mint::get_satmint_config_address().0, false),
439        AccountMeta::new(crate::mint::get_satmint_mine_address().0, false),
440        AccountMeta::new(crate::mint::get_satmint_oracle_address().0, false),
441        AccountMeta::new_readonly(crate::mint::get_satmint_authority_address().0, false),
442        AccountMeta::new_readonly(oracle_pool, false),
443    ]
444}
445
446/// `swap_round_stake`: relay a pre-built aggregator route that converts part of
447/// the revealed round's USD into BTC. `swap_data` and `route_accounts` are the
448/// route's opaque instruction data and account list; the on-chain handler
449/// enforces the economics against observed balance deltas. Signed by the
450/// config's round authority.
451#[allow(clippy::too_many_arguments)]
452pub fn get_swap_round_stake_instruction(
453    authority: Pubkey,
454    usd_mint: Pubkey,
455    btc_mint: Pubkey,
456    round_id: u32,
457    min_btc_out: u64,
458    swap_program: Pubkey,
459    swap_data: Vec<u8>,
460    route_accounts: &[AccountMeta],
461) -> Instruction {
462    let board = get_board_address().0;
463    SwapRoundStake {
464        authority,
465        satrush_config: get_satrush_config_address().0,
466        board,
467        round: get_round_address(round_id).0,
468        usd_mint,
469        btc_mint,
470        board_usd_ata: get_associated_token_address(&board, &usd_mint),
471        board_btc_ata: get_associated_token_address(&board, &btc_mint),
472        swap_program,
473        token_program: TOKEN_PROGRAM_ID,
474        event_authority: get_event_authority_address().0,
475        program: crate::SATRUSH_ID,
476    }
477    .instruction_with_remaining_accounts(SwapRoundStakeInstructionArgs { min_btc_out, swap_data }, route_accounts)
478}
479
480/// `settle_deploy_public`: settle `deployment_authority`'s deployment in a
481/// Settled round. `authority` signs (the owner, or the round authority for
482/// automated deployments); `rent_recipient` must be the round authority for
483/// automated deployments and the owner for manual ones. Deployments carrying
484/// affiliate points (a referred, wallet-funded play) must be settled with
485/// `get_settle_deploy_public_with_affiliate_instruction` instead.
486pub fn get_settle_deploy_public_instruction(
487    authority: Pubkey,
488    deployment_authority: Pubkey,
489    rent_recipient: Pubkey,
490    usd_mint: Pubkey,
491    btc_mint: Pubkey,
492    token_mint: Pubkey,
493    round_id: u32,
494) -> Instruction {
495    build_settle_deploy_public_instruction(
496        authority,
497        deployment_authority,
498        rent_recipient,
499        usd_mint,
500        btc_mint,
501        token_mint,
502        round_id,
503        None,
504    )
505}
506
507/// `settle_deploy_public` carrying `affiliate` — the miner's bound affiliate
508/// account (`Miner::affiliate`), credited with the points the deployment
509/// froze at deploy. Required for any deployment whose
510/// `affiliate_points_usd_amount` is non-zero; ignored by the program
511/// otherwise.
512#[allow(clippy::too_many_arguments)]
513pub fn get_settle_deploy_public_with_affiliate_instruction(
514    authority: Pubkey,
515    deployment_authority: Pubkey,
516    rent_recipient: Pubkey,
517    usd_mint: Pubkey,
518    btc_mint: Pubkey,
519    token_mint: Pubkey,
520    round_id: u32,
521    affiliate: Pubkey,
522) -> Instruction {
523    build_settle_deploy_public_instruction(
524        authority,
525        deployment_authority,
526        rent_recipient,
527        usd_mint,
528        btc_mint,
529        token_mint,
530        round_id,
531        Some(affiliate),
532    )
533}
534
535#[allow(clippy::too_many_arguments)]
536fn build_settle_deploy_public_instruction(
537    authority: Pubkey,
538    deployment_authority: Pubkey,
539    rent_recipient: Pubkey,
540    usd_mint: Pubkey,
541    btc_mint: Pubkey,
542    token_mint: Pubkey,
543    round_id: u32,
544    affiliate: Option<Pubkey>,
545) -> Instruction {
546    let board = get_board_address().0;
547    let sats_vault = get_sats_vault_address().0;
548    let token_vault = get_token_vault_address().0;
549    let public_automation = get_public_automation_address(deployment_authority).0;
550    let miner = get_miner_address(deployment_authority).0;
551    let mut instruction = SettleDeployPublic {
552        authority,
553        rent_recipient,
554        satrush_config: get_satrush_config_address().0,
555        round: get_round_address(round_id).0,
556        board,
557        public_deployment: get_public_deployment_address(deployment_authority, round_id).0,
558        miner,
559        public_automation,
560        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
561        miner_usd_ata: get_associated_token_address(&miner, &usd_mint),
562        affiliate,
563        board_usd_ata: get_associated_token_address(&board, &usd_mint),
564        sats_vault,
565        btc_mint,
566        usd_mint,
567        board_btc_ata: get_associated_token_address(&board, &btc_mint),
568        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
569        token_vault,
570        token_program: TOKEN_PROGRAM_ID,
571        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
572        system_program: SYSTEM_PROGRAM_ID,
573        event_authority: get_event_authority_address().0,
574        program: crate::SATRUSH_ID,
575    }
576    .instruction();
577    // The token accounts ride as remaining accounts (the on-chain
578    // `try_accounts` frame is at the 4 KB ceiling); the handler pins every
579    // address. Order matters.
580    instruction
581        .accounts
582        .extend(get_settle_deploy_public_remaining_accounts(token_mint));
583    instruction
584}
585
586/// The three remaining accounts `settle_deploy_public` takes, in order: the
587/// token mint, the board's token ATA, and the token vault's token ATA.
588pub fn get_settle_deploy_public_remaining_accounts(token_mint: Pubkey) -> Vec<AccountMeta> {
589    let board = get_board_address().0;
590    let token_vault = get_token_vault_address().0;
591    vec![
592        AccountMeta::new_readonly(token_mint, false),
593        AccountMeta::new(get_associated_token_address(&board, &token_mint), false),
594        AccountMeta::new(get_associated_token_address(&token_vault, &token_mint), false),
595    ]
596}
597
598/// `create_public_automation`: escrow `deposit_usd_amount` and configure the
599/// crank to deploy `per_round_usd_amount` per round with the given strategy.
600/// One automation per authority.
601#[allow(clippy::too_many_arguments)]
602pub fn get_create_public_automation_instruction(
603    authority: Pubkey,
604    usd_mint: Pubkey,
605    strategy: AutomationStrategy,
606    selection_mask: u32,
607    per_round_usd_amount: u64,
608    reload: bool,
609    deposit_usd_amount: u64,
610) -> Instruction {
611    let funding_usd_ata = get_associated_token_address(&authority, &usd_mint);
612    build_create_public_automation_instruction(
613        authority,
614        usd_mint,
615        strategy,
616        selection_mask,
617        per_round_usd_amount,
618        reload,
619        deposit_usd_amount,
620        funding_usd_ata,
621        false,
622        None,
623    )
624}
625
626/// `create_public_automation` carrying the affiliate account of
627/// `affiliate_authority`: binds the miner when this creation initializes it.
628/// The deposit itself accrues no points — volume accrues at execution.
629#[allow(clippy::too_many_arguments)]
630pub fn get_create_public_automation_with_affiliate_instruction(
631    authority: Pubkey,
632    usd_mint: Pubkey,
633    strategy: AutomationStrategy,
634    selection_mask: u32,
635    per_round_usd_amount: u64,
636    reload: bool,
637    deposit_usd_amount: u64,
638    affiliate_authority: Pubkey,
639) -> Instruction {
640    let funding_usd_ata = get_associated_token_address(&authority, &usd_mint);
641    build_create_public_automation_instruction(
642        authority,
643        usd_mint,
644        strategy,
645        selection_mask,
646        per_round_usd_amount,
647        reload,
648        deposit_usd_amount,
649        funding_usd_ata,
650        false,
651        Some(get_affiliate_address(affiliate_authority).0),
652    )
653}
654
655/// `create_public_automation` funded from the miner's grubstake balance: the
656/// deposit is pulled from the miner PDA's USD ATA, and every deployment the
657/// automation creates plays as grubstake-funded.
658#[allow(clippy::too_many_arguments)]
659pub fn get_create_public_automation_grubstake_instruction(
660    authority: Pubkey,
661    usd_mint: Pubkey,
662    strategy: AutomationStrategy,
663    selection_mask: u32,
664    per_round_usd_amount: u64,
665    reload: bool,
666    deposit_usd_amount: u64,
667) -> Instruction {
668    let miner = get_miner_address(authority).0;
669    let funding_usd_ata = get_associated_token_address(&miner, &usd_mint);
670    build_create_public_automation_instruction(
671        authority,
672        usd_mint,
673        strategy,
674        selection_mask,
675        per_round_usd_amount,
676        reload,
677        deposit_usd_amount,
678        funding_usd_ata,
679        true,
680        None,
681    )
682}
683
684#[allow(clippy::too_many_arguments)]
685fn build_create_public_automation_instruction(
686    authority: Pubkey,
687    usd_mint: Pubkey,
688    strategy: AutomationStrategy,
689    selection_mask: u32,
690    per_round_usd_amount: u64,
691    reload: bool,
692    deposit_usd_amount: u64,
693    funding_usd_ata: Pubkey,
694    is_grubstake_funded: bool,
695    affiliate: Option<Pubkey>,
696) -> Instruction {
697    let public_automation = get_public_automation_address(authority).0;
698    CreatePublicAutomation {
699        authority,
700        satrush_config: get_satrush_config_address().0,
701        public_automation,
702        usd_mint,
703        authority_usd_ata: funding_usd_ata,
704        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
705        miner: get_miner_address(authority).0,
706        affiliate,
707        token_program: TOKEN_PROGRAM_ID,
708        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
709        system_program: SYSTEM_PROGRAM_ID,
710    }
711    .instruction(CreatePublicAutomationInstructionArgs {
712        strategy,
713        selection_mask,
714        per_round_usd_amount,
715        reload,
716        deposit_usd_amount,
717        is_grubstake_funded,
718    })
719}
720
721/// `top_up_public_automation`: move `amount` USD from the authority's account
722/// into the automation's escrow. For grubstake automations use
723/// `get_top_up_public_automation_grubstake_instruction` — the program only
724/// accepts the matching funding source.
725pub fn get_top_up_public_automation_instruction(authority: Pubkey, usd_mint: Pubkey, amount: u64) -> Instruction {
726    let funding_usd_ata = get_associated_token_address(&authority, &usd_mint);
727    build_top_up_public_automation_instruction(authority, usd_mint, amount, funding_usd_ata)
728}
729
730/// `top_up_public_automation` for a grubstake automation: `amount` moves from
731/// the miner PDA's USD ATA (debiting the grubstake balance) into the escrow.
732pub fn get_top_up_public_automation_grubstake_instruction(
733    authority: Pubkey,
734    usd_mint: Pubkey,
735    amount: u64,
736) -> Instruction {
737    let miner = get_miner_address(authority).0;
738    let funding_usd_ata = get_associated_token_address(&miner, &usd_mint);
739    build_top_up_public_automation_instruction(authority, usd_mint, amount, funding_usd_ata)
740}
741
742fn build_top_up_public_automation_instruction(
743    authority: Pubkey,
744    usd_mint: Pubkey,
745    amount: u64,
746    funding_usd_ata: Pubkey,
747) -> Instruction {
748    let public_automation = get_public_automation_address(authority).0;
749    TopUpPublicAutomation {
750        authority,
751        satrush_config: get_satrush_config_address().0,
752        public_automation,
753        miner: get_miner_address(authority).0,
754        usd_mint,
755        authority_usd_ata: funding_usd_ata,
756        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
757        token_program: TOKEN_PROGRAM_ID,
758    }
759    .instruction(TopUpPublicAutomationInstructionArgs { amount })
760}
761
762/// `cancel_public_automation`: refund the full escrow balance and close the
763/// automation and its token account. Unconditional; in-flight deployments
764/// settle to the miner profile later. Grubstake escrows refund into the
765/// miner's grubstake instead of the wallet.
766pub fn get_cancel_public_automation_instruction(authority: Pubkey, usd_mint: Pubkey) -> Instruction {
767    let public_automation = get_public_automation_address(authority).0;
768    let miner = get_miner_address(authority).0;
769    CancelPublicAutomation {
770        authority,
771        satrush_config: get_satrush_config_address().0,
772        public_automation,
773        miner,
774        usd_mint,
775        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
776        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
777        miner_usd_ata: get_associated_token_address(&miner, &usd_mint),
778        token_program: TOKEN_PROGRAM_ID,
779        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
780        system_program: SYSTEM_PROGRAM_ID,
781    }
782    .instruction()
783}
784
785/// `execute_public_automation`: crank-signed per-round deploy funded from
786/// `automation_authority`'s escrow. `selection_mask` must be `Some` for
787/// Discretionary automations and `None` otherwise.
788pub fn get_execute_public_automation_instruction(
789    crank_authority: Pubkey,
790    automation_authority: Pubkey,
791    usd_mint: Pubkey,
792    round_id: u32,
793    selection_mask: Option<u32>,
794) -> Instruction {
795    let board = get_board_address().0;
796    let public_automation = get_public_automation_address(automation_authority).0;
797
798    ExecutePublicAutomation {
799        authority: crank_authority,
800        satrush_config: get_satrush_config_address().0,
801        board,
802        round: get_round_address(round_id).0,
803        public_automation,
804        usd_mint,
805        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
806        board_usd_ata: get_associated_token_address(&board, &usd_mint),
807        public_deployment: get_public_deployment_address(automation_authority, round_id).0,
808        miner: get_miner_address(automation_authority).0,
809        affiliate: None,
810        slot_hashes: SLOT_HASHES_ID,
811        token_program: TOKEN_PROGRAM_ID,
812        system_program: SYSTEM_PROGRAM_ID,
813        event_authority: get_event_authority_address().0,
814        program: crate::SATRUSH_ID,
815    }
816    .instruction_with_remaining_accounts(
817        ExecutePublicAutomationInstructionArgs { selection_mask },
818        // Same first-entry rotor arming as the manual deploy builders.
819        &crate::rng::get_rng_remaining_accounts(),
820    )
821}
822
823/// `execute_public_automation` carrying the affiliate account of
824/// `affiliate_authority`, so a bound miner's wallet-funded automation deploy
825/// accrues points.
826pub fn get_execute_public_automation_with_affiliate_instruction(
827    crank_authority: Pubkey,
828    automation_authority: Pubkey,
829    usd_mint: Pubkey,
830    round_id: u32,
831    selection_mask: Option<u32>,
832    affiliate_authority: Pubkey,
833) -> Instruction {
834    let board = get_board_address().0;
835    let public_automation = get_public_automation_address(automation_authority).0;
836
837    ExecutePublicAutomation {
838        authority: crank_authority,
839        satrush_config: get_satrush_config_address().0,
840        board,
841        round: get_round_address(round_id).0,
842        public_automation,
843        usd_mint,
844        automation_usd_ata: get_associated_token_address(&public_automation, &usd_mint),
845        board_usd_ata: get_associated_token_address(&board, &usd_mint),
846        public_deployment: get_public_deployment_address(automation_authority, round_id).0,
847        miner: get_miner_address(automation_authority).0,
848        affiliate: Some(get_affiliate_address(affiliate_authority).0),
849        slot_hashes: SLOT_HASHES_ID,
850        token_program: TOKEN_PROGRAM_ID,
851        system_program: SYSTEM_PROGRAM_ID,
852        event_authority: get_event_authority_address().0,
853        program: crate::SATRUSH_ID,
854    }
855    .instruction_with_remaining_accounts(
856        ExecutePublicAutomationInstructionArgs { selection_mask },
857        &crate::rng::get_rng_remaining_accounts(),
858    )
859}
860
861/// `claim_usd`: withdraw `amount` of the miner `authority`'s unclaimed USD
862/// winnings from the board's USD pool to the authority's USD account. No exit
863/// fee — the full amount transfers.
864pub fn get_claim_usd_instruction(authority: Pubkey, usd_mint: Pubkey, amount: u64) -> Instruction {
865    let board = get_board_address().0;
866    ClaimUsd {
867        authority,
868        satrush_config: get_satrush_config_address().0,
869        board,
870        miner: get_miner_address(authority).0,
871        usd_mint,
872        board_usd_ata: get_associated_token_address(&board, &usd_mint),
873        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
874        token_program: TOKEN_PROGRAM_ID,
875        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
876        system_program: SYSTEM_PROGRAM_ID,
877    }
878    .instruction(ClaimUsdInstructionArgs { amount })
879}
880
881/// `claim_sats`: redeem `shares` of the miner's sats vault shares for BTC and,
882/// coupled to it, the same fraction of their token vault shares for SatRush
883/// tokens. The exit fee is withheld in each vault; the net BTC and tokens
884/// transfer to the authority's token accounts (created if needed).
885pub fn get_claim_sats_instruction(authority: Pubkey, btc_mint: Pubkey, token_mint: Pubkey, shares: u64) -> Instruction {
886    let sats_vault = get_sats_vault_address().0;
887    let token_vault = get_token_vault_address().0;
888    ClaimSats {
889        authority,
890        satrush_config: get_satrush_config_address().0,
891        sats_vault,
892        token_vault,
893        miner: get_miner_address(authority).0,
894        btc_mint,
895        token_mint,
896        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
897        token_vault_token_ata: get_associated_token_address(&token_vault, &token_mint),
898        authority_btc_ata: get_associated_token_address(&authority, &btc_mint),
899        authority_token_ata: get_associated_token_address(&authority, &token_mint),
900        token_program: TOKEN_PROGRAM_ID,
901        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
902        system_program: SYSTEM_PROGRAM_ID,
903        event_authority: get_event_authority_address().0,
904        program: crate::SATRUSH_ID,
905    }
906    .instruction(ClaimSatsInstructionArgs { shares })
907}
908
909/// `claim_token`: the mirror of `claim_sats` with the token side exact —
910/// redeem `token_shares` for SatRush tokens plus the same fraction of the
911/// miner's sats vault shares for BTC (zero for a miner who never won BTC).
912pub fn get_claim_token_instruction(
913    authority: Pubkey,
914    token_mint: Pubkey,
915    btc_mint: Pubkey,
916    token_shares: u64,
917) -> Instruction {
918    let sats_vault = get_sats_vault_address().0;
919    let token_vault = get_token_vault_address().0;
920    ClaimToken {
921        authority,
922        satrush_config: get_satrush_config_address().0,
923        token_vault,
924        sats_vault,
925        miner: get_miner_address(authority).0,
926        token_mint,
927        btc_mint,
928        token_vault_token_ata: get_associated_token_address(&token_vault, &token_mint),
929        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
930        authority_token_ata: get_associated_token_address(&authority, &token_mint),
931        authority_btc_ata: get_associated_token_address(&authority, &btc_mint),
932        token_program: TOKEN_PROGRAM_ID,
933        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
934        system_program: SYSTEM_PROGRAM_ID,
935        event_authority: get_event_authority_address().0,
936        program: crate::SATRUSH_ID,
937    }
938    .instruction(ClaimTokenInstructionArgs { token_shares })
939}
940
941/// `migrate_miner`: extend a 115-byte v1 miner account to the v2 layout
942/// (`affiliate` Pubkey + reserved tail, version stamped to 2). Signed by the
943/// config's admin authority, who also funds the rent top-up.
944pub fn get_migrate_miner_instruction(authority: Pubkey, miner: Pubkey) -> Instruction {
945    MigrateMiner {
946        authority,
947        satrush_config: get_satrush_config_address().0,
948        miner,
949        system_program: SYSTEM_PROGRAM_ID,
950    }
951    .instruction()
952}
953
954/// `migrate_satrush_config`: grow the live v1 config to v2, stamping the
955/// token mint. Signed by the admin authority (read from the v1 bytes).
956pub fn get_migrate_satrush_config_instruction(authority: Pubkey, token_mint: Pubkey) -> Instruction {
957    MigrateSatrushConfig {
958        authority,
959        satrush_config: get_satrush_config_address().0,
960        system_program: SYSTEM_PROGRAM_ID,
961    }
962    .instruction(MigrateSatrushConfigInstructionArgs { token_mint })
963}
964
965/// `migrate_board`: grow the live v1 board to v2 (strike token counters).
966/// Requires a migrated config. Signed by the config's admin authority.
967pub fn get_migrate_board_instruction(authority: Pubkey) -> Instruction {
968    MigrateBoard {
969        authority,
970        satrush_config: get_satrush_config_address().0,
971        board: get_board_address().0,
972        system_program: SYSTEM_PROGRAM_ID,
973    }
974    .instruction()
975}
976
977/// `migrate_treasury`: grow the live v1 treasury to v2 (staking buybacks
978/// counter). Requires a migrated config. Signed by the config's admin authority.
979pub fn get_migrate_treasury_instruction(authority: Pubkey) -> Instruction {
980    MigrateTreasury {
981        authority,
982        satrush_config: get_satrush_config_address().0,
983        treasury: get_treasury_address().0,
984        system_program: SYSTEM_PROGRAM_ID,
985    }
986    .instruction()
987}
988
989/// `create_sats_vault`: the sats vault singleton and its BTC reserve pool.
990pub fn get_create_sats_vault_instruction(authority: Pubkey, btc_mint: Pubkey) -> Instruction {
991    let sats_vault = get_sats_vault_address().0;
992    CreateSatsVault {
993        authority,
994        satrush_config: get_satrush_config_address().0,
995        sats_vault,
996        btc_mint,
997        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
998        token_program: TOKEN_PROGRAM_ID,
999        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
1000        system_program: SYSTEM_PROGRAM_ID,
1001    }
1002    .instruction()
1003}
1004
1005/// `create_token_vault`: the token vault singleton and the three token
1006/// accounts the reward flow needs (board, epoch vault, vault reserves).
1007pub fn get_create_token_vault_instruction(authority: Pubkey, token_mint: Pubkey) -> Instruction {
1008    let token_vault = get_token_vault_address().0;
1009    let board = get_board_address().0;
1010    let epoch_vault = get_epoch_vault_address().0;
1011    CreateTokenVault {
1012        authority,
1013        satrush_config: get_satrush_config_address().0,
1014        token_vault,
1015        board,
1016        epoch_vault,
1017        token_mint,
1018        token_vault_token_ata: get_associated_token_address(&token_vault, &token_mint),
1019        board_token_ata: get_associated_token_address(&board, &token_mint),
1020        epoch_vault_token_ata: get_associated_token_address(&epoch_vault, &token_mint),
1021        token_program: TOKEN_PROGRAM_ID,
1022        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
1023        system_program: SYSTEM_PROGRAM_ID,
1024    }
1025    .instruction()
1026}
1027
1028/// `close_round`: tear down a Finished round, sweeping a no-winner round's
1029/// orphaned pot (USD and BTC) into the treasury. Signed by the config's admin
1030/// authority, which receives the round account's rent.
1031pub fn get_close_round_instruction(
1032    authority: Pubkey,
1033    usd_mint: Pubkey,
1034    btc_mint: Pubkey,
1035    round_id: u32,
1036) -> Instruction {
1037    let board = get_board_address().0;
1038    let treasury = get_treasury_address().0;
1039    CloseRound {
1040        authority,
1041        satrush_config: get_satrush_config_address().0,
1042        board,
1043        round: get_round_address(round_id).0,
1044        treasury,
1045        usd_mint,
1046        btc_mint,
1047        board_usd_ata: get_associated_token_address(&board, &usd_mint),
1048        board_btc_ata: get_associated_token_address(&board, &btc_mint),
1049        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1050        treasury_btc_ata: get_associated_token_address(&treasury, &btc_mint),
1051        token_program: TOKEN_PROGRAM_ID,
1052        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
1053        system_program: SYSTEM_PROGRAM_ID,
1054        event_authority: get_event_authority_address().0,
1055        program: crate::SATRUSH_ID,
1056    }
1057    .instruction()
1058}
1059
1060/// `set_miner_tag`: claim `tag` for `authority`, creating the wallet's
1061/// affiliate identity automatically. The affiliate PDA is seeded by the
1062/// authority, so a wallet can only ever register once; the tag registry PDA
1063/// is seeded by the tag, so a taken tag fails at creation.
1064pub fn get_set_miner_tag_instruction(authority: Pubkey, tag: &str) -> Instruction {
1065    SetMinerTag {
1066        authority,
1067        affiliate: get_affiliate_address(authority).0,
1068        affiliate_tag: get_affiliate_tag_address(tag).0,
1069        system_program: SYSTEM_PROGRAM_ID,
1070    }
1071    .instruction(SetMinerTagInstructionArgs { tag: tag.to_string() })
1072}
1073
1074/// `exchange_affiliate_points`: convert `points_amount` of the affiliate's
1075/// spendable points into a grubstake credit on the affiliate's own miner.
1076/// Points are USD base units valued at accrual, so the exchange is 1:1.
1077pub fn get_exchange_affiliate_points_instruction(
1078    authority: Pubkey,
1079    usd_mint: Pubkey,
1080    points_amount: u64,
1081) -> Instruction {
1082    let miner = get_miner_address(authority).0;
1083    let treasury = get_treasury_address().0;
1084    ExchangeAffiliatePoints {
1085        authority,
1086        satrush_config: get_satrush_config_address().0,
1087        affiliate: get_affiliate_address(authority).0,
1088        miner,
1089        treasury,
1090        usd_mint,
1091        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1092        miner_usd_ata: get_associated_token_address(&miner, &usd_mint),
1093        token_program: TOKEN_PROGRAM_ID,
1094        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
1095        system_program: SYSTEM_PROGRAM_ID,
1096    }
1097    .instruction(ExchangeAffiliatePointsInstructionArgs { points_amount })
1098}
1099
1100/// `set_affiliate_rate`: set the affiliate's share of the protocol fee leg
1101/// (bps, admin only). Shapes future accruals; earned points keep their value.
1102pub fn get_set_affiliate_rate_instruction(
1103    authority: Pubkey,
1104    affiliate_authority: Pubkey,
1105    rate_bps: u16,
1106) -> Instruction {
1107    SetAffiliateRate {
1108        authority,
1109        satrush_config: get_satrush_config_address().0,
1110        affiliate: get_affiliate_address(affiliate_authority).0,
1111    }
1112    .instruction(SetAffiliateRateInstructionArgs { rate_bps })
1113}
1114
1115/// `deposit_grubstake`: fund the treasury's grubstake pool with `usd_amount`
1116/// from `authority`'s own USD account. Admin authority or fee recipient.
1117pub fn get_deposit_grubstake_instruction(authority: Pubkey, usd_mint: Pubkey, usd_amount: u64) -> Instruction {
1118    let treasury = get_treasury_address().0;
1119    DepositGrubstake {
1120        authority,
1121        satrush_config: get_satrush_config_address().0,
1122        treasury,
1123        usd_mint,
1124        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
1125        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1126        token_program: TOKEN_PROGRAM_ID,
1127    }
1128    .instruction(DepositGrubstakeInstructionArgs { usd_amount })
1129}
1130
1131/// `withdraw_grubstake`: pull `usd_amount` of unreserved grubstake back out to
1132/// `fee_recipient`. Only the portion not promised to issued airdrops may leave.
1133pub fn get_withdraw_grubstake_instruction(
1134    authority: Pubkey,
1135    fee_recipient: Pubkey,
1136    usd_mint: Pubkey,
1137    usd_amount: u64,
1138) -> Instruction {
1139    let treasury = get_treasury_address().0;
1140    WithdrawGrubstake {
1141        authority,
1142        satrush_config: get_satrush_config_address().0,
1143        treasury,
1144        usd_mint,
1145        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1146        fee_recipient,
1147        fee_recipient_usd_ata: get_associated_token_address(&fee_recipient, &usd_mint),
1148        token_program: TOKEN_PROGRAM_ID,
1149        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
1150        system_program: SYSTEM_PROGRAM_ID,
1151    }
1152    .instruction(WithdrawGrubstakeInstructionArgs { usd_amount })
1153}
1154
1155/// `create_grubstake_airdrop`: reserve `usd_amount` of the pool for
1156/// `airdrop_authority` to claim before `expiration_timestamp`. `airdrop` is a
1157/// fresh keypair's pubkey and must also sign the transaction. Fee recipient
1158/// only.
1159pub fn get_create_grubstake_airdrop_instruction(
1160    authority: Pubkey,
1161    airdrop: Pubkey,
1162    airdrop_authority: Pubkey,
1163    usd_amount: u64,
1164    expiration_timestamp: i64,
1165) -> Instruction {
1166    CreateGrubstakeAirdrop {
1167        authority,
1168        satrush_config: get_satrush_config_address().0,
1169        grubstake_airdrop: airdrop,
1170        treasury: get_treasury_address().0,
1171        system_program: SYSTEM_PROGRAM_ID,
1172    }
1173    .instruction(CreateGrubstakeAirdropInstructionArgs {
1174        airdrop_authority,
1175        usd_amount,
1176        expiration_timestamp,
1177    })
1178}
1179
1180/// `claim_grubstake_airdrop`: claim `airdrop` as `authority`. The payout lands
1181/// in the miner PDA's USD account and opens the 14-day spend window.
1182pub fn get_claim_grubstake_airdrop_instruction(
1183    authority: Pubkey,
1184    airdrop: Pubkey,
1185    fee_recipient: Pubkey,
1186    usd_mint: Pubkey,
1187) -> Instruction {
1188    let treasury = get_treasury_address().0;
1189    let miner = get_miner_address(authority).0;
1190    ClaimGrubstakeAirdrop {
1191        authority,
1192        satrush_config: get_satrush_config_address().0,
1193        fee_recipient,
1194        grubstake_airdrop: airdrop,
1195        miner,
1196        treasury,
1197        usd_mint,
1198        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1199        miner_usd_ata: get_associated_token_address(&miner, &usd_mint),
1200        token_program: TOKEN_PROGRAM_ID,
1201        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
1202        system_program: SYSTEM_PROGRAM_ID,
1203    }
1204    .instruction()
1205}
1206
1207/// `reclaim_grubstake_airdrop`: release an expired, unclaimed airdrop's
1208/// reservation back to the pool and close the account. Fee recipient only.
1209pub fn get_reclaim_grubstake_airdrop_instruction(authority: Pubkey, airdrop: Pubkey) -> Instruction {
1210    ReclaimGrubstakeAirdrop {
1211        authority,
1212        satrush_config: get_satrush_config_address().0,
1213        grubstake_airdrop: airdrop,
1214        treasury: get_treasury_address().0,
1215    }
1216    .instruction()
1217}
1218
1219/// `reclaim_miner_grubstake`: sweep `miner_authority`'s expired grubstake into
1220/// the treasury's withdrawable protocol fees. Sized from the miner's grubstake
1221/// counter, so anything on the ATA above it is deliberately left behind. Fee
1222/// recipient only.
1223pub fn get_reclaim_miner_grubstake_instruction(
1224    authority: Pubkey,
1225    miner_authority: Pubkey,
1226    usd_mint: Pubkey,
1227) -> Instruction {
1228    let treasury = get_treasury_address().0;
1229    let miner = get_miner_address(miner_authority).0;
1230    ReclaimMinerGrubstake {
1231        authority,
1232        satrush_config: get_satrush_config_address().0,
1233        miner_authority,
1234        miner,
1235        treasury,
1236        usd_mint,
1237        miner_usd_ata: get_associated_token_address(&miner, &usd_mint),
1238        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1239        token_program: TOKEN_PROGRAM_ID,
1240    }
1241    .instruction()
1242}
1243
1244/// `reclaim_grubstake_automation`: sweep a grubstake automation's escrow back
1245/// to the treasury once its owner's spend window has closed, and close the
1246/// automation. Fee recipient only.
1247pub fn get_reclaim_grubstake_automation_instruction(
1248    authority: Pubkey,
1249    automation_owner: Pubkey,
1250    usd_mint: Pubkey,
1251) -> Instruction {
1252    let treasury = get_treasury_address().0;
1253    let automation = get_public_automation_address(automation_owner).0;
1254    ReclaimGrubstakeAutomation {
1255        authority,
1256        satrush_config: get_satrush_config_address().0,
1257        public_automation: automation,
1258        miner: get_miner_address(automation_owner).0,
1259        automation_owner,
1260        treasury,
1261        usd_mint,
1262        automation_usd_ata: get_associated_token_address(&automation, &usd_mint),
1263        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
1264        token_program: TOKEN_PROGRAM_ID,
1265    }
1266    .instruction()
1267}