satrush-client 0.1.2

Rust client to interact with SatRush's on-chain program.
Documentation
//! Environment-free instruction builders.
//!
//! Thin composition helpers over the generated instruction structs: they derive
//! every PDA and associated token account from the program's fixed seeds, so a
//! caller only supplies the signing authority and the mint addresses. Shared by
//! the LiteSVM test-suite and the `satrush-cli` binary.

use crate::instructions::{
    ClaimUsd, ClaimUsdInstructionArgs, CreateBoard, CreateEpochVault, CreateOneBtcVault, CreateSatrushConfig,
    CreateSatrushConfigInstructionArgs, CreateSatsVault, CreateTreasury, DeployPublic, DeployPublicInstructionArgs,
    RotateRound, SettleDeployPublic, SwapRoundStake, SwapRoundStakeInstructionArgs, UpdateBoardRoundDuration,
    UpdateBoardRoundDurationInstructionArgs,
};
use crate::{
    get_board_address, get_epoch_vault_address, get_epoch_vault_iteration_address, get_event_authority_address,
    get_miner_address, get_one_btc_vault_address, get_one_btc_vault_iteration_address, get_public_deployment_address,
    get_round_address, get_satrush_config_address, get_sats_vault_address, get_treasury_address,
};
use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::Pubkey;

/// SPL Token program.
pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
/// SPL Associated Token Account program.
pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
/// System program.
pub const SYSTEM_PROGRAM_ID: Pubkey = Pubkey::from_str_const("11111111111111111111111111111111");
/// SlotHashes sysvar.
pub const SLOT_HASHES_ID: Pubkey = Pubkey::from_str_const("SysvarS1otHashes111111111111111111111111111");

/// Derive the canonical associated token account for `wallet` holding `mint`.
pub fn get_associated_token_address(wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
    Pubkey::find_program_address(
        &[wallet.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()],
        &ASSOCIATED_TOKEN_PROGRAM_ID,
    )
    .0
}

/// `create_satrush_config`: the config PDA holding authorities, mints and fee
/// parameters. `authority` pays and must equal the program's upgrade authority.
pub fn get_create_satrush_config_instruction(
    authority: Pubkey,
    args: CreateSatrushConfigInstructionArgs,
) -> Instruction {
    CreateSatrushConfig {
        authority,
        satrush_config: get_satrush_config_address().0,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction(args)
}

/// `create_board`: the board singleton, its USD/BTC pools and the initial round
/// (id 1). Signed by the config's admin authority.
pub fn get_create_board_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
    let board = get_board_address().0;
    CreateBoard {
        authority,
        satrush_config: get_satrush_config_address().0,
        board,
        initial_round: get_round_address(1).0,
        usd_mint,
        btc_mint,
        board_usd_ata: get_associated_token_address(&board, &usd_mint),
        board_btc_ata: get_associated_token_address(&board, &btc_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction()
}

/// `update_board_round_duration`: overwrite the board's `round_duration` (slots).
/// Applies to future round activations only; the active round keeps its window.
/// Signed by the config's admin authority.
pub fn get_update_board_round_duration_instruction(authority: Pubkey, new_round_duration: u32) -> Instruction {
    UpdateBoardRoundDuration {
        authority,
        satrush_config: get_satrush_config_address().0,
        board: get_board_address().0,
    }
    .instruction(UpdateBoardRoundDurationInstructionArgs { new_round_duration })
}

/// `create_epoch_vault`: the epoch vault singleton, its USD/BTC pools and the
/// first iteration (id 1).
pub fn get_create_epoch_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
    let epoch_vault = get_epoch_vault_address().0;
    CreateEpochVault {
        authority,
        satrush_config: get_satrush_config_address().0,
        epoch_vault,
        epoch_vault_iteration: get_epoch_vault_iteration_address(1).0,
        usd_mint,
        btc_mint,
        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
        epoch_vault_btc_ata: get_associated_token_address(&epoch_vault, &btc_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction()
}

/// `create_one_btc_vault`: the 1 BTC vault singleton, its USD/BTC pools and the
/// first iteration (id 1).
pub fn get_create_one_btc_vault_instruction(authority: Pubkey, usd_mint: Pubkey, btc_mint: Pubkey) -> Instruction {
    let one_btc_vault = get_one_btc_vault_address().0;
    CreateOneBtcVault {
        authority,
        satrush_config: get_satrush_config_address().0,
        one_btc_vault,
        one_btc_vault_iteration: get_one_btc_vault_iteration_address(1).0,
        usd_mint,
        btc_mint,
        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
        one_btc_vault_btc_ata: get_associated_token_address(&one_btc_vault, &btc_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction()
}

/// `create_treasury`: the treasury singleton and its USD fee pool.
pub fn get_create_treasury_instruction(authority: Pubkey, usd_mint: Pubkey) -> Instruction {
    let treasury = get_treasury_address().0;
    CreateTreasury {
        authority,
        satrush_config: get_satrush_config_address().0,
        treasury,
        usd_mint,
        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction()
}

/// `deploy_public`: stake `amount` USD (base units, gross of fees) on the tiles
/// in `selection_mask` for round `round_id`, as the miner `authority`. The round
/// must be the board's current one and open for deploys; the miner profile and
/// deployment record PDAs are created by the instruction.
pub fn get_deploy_public_instruction(
    authority: Pubkey,
    usd_mint: Pubkey,
    round_id: u32,
    selection_mask: u32,
    amount: u64,
) -> Instruction {
    let board = get_board_address().0;
    let epoch_vault = get_epoch_vault_address().0;
    let one_btc_vault = get_one_btc_vault_address().0;
    let treasury = get_treasury_address().0;

    DeployPublic {
        authority,
        satrush_config: get_satrush_config_address().0,
        board,
        usd_mint,
        board_usd_ata: get_associated_token_address(&board, &usd_mint),
        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
        round: get_round_address(round_id).0,
        public_deployment: get_public_deployment_address(authority, round_id).0,
        miner: get_miner_address(authority).0,
        epoch_vault,
        epoch_vault_usd_ata: get_associated_token_address(&epoch_vault, &usd_mint),
        one_btc_vault,
        one_btc_vault_usd_ata: get_associated_token_address(&one_btc_vault, &usd_mint),
        treasury,
        treasury_usd_ata: get_associated_token_address(&treasury, &usd_mint),
        token_program: TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction(DeployPublicInstructionArgs { selection_mask, amount })
}

/// `rotate_round`: reveal `current_round_id`'s winning tile and deploy round
/// `current_round_id + 1` as the board's new active round. Signed by the
/// config's round authority; valid once the current round's window elapsed.
pub fn get_rotate_round_instruction(authority: Pubkey, current_round_id: u32) -> Instruction {
    RotateRound {
        authority,
        satrush_config: get_satrush_config_address().0,
        board: get_board_address().0,
        current_round: get_round_address(current_round_id).0,
        next_round: get_round_address(current_round_id + 1).0,
        slot_hashes: SLOT_HASHES_ID,
        system_program: SYSTEM_PROGRAM_ID,
        event_authority: get_event_authority_address().0,
        program: crate::SATRUSH_ID,
    }
    .instruction()
}

/// `swap_round_stake`: relay a pre-built aggregator route that converts part of
/// the revealed round's USD into BTC. `swap_data` and `route_accounts` are the
/// route's opaque instruction data and account list; the on-chain handler
/// enforces the economics against observed balance deltas. Signed by the
/// config's round authority.
#[allow(clippy::too_many_arguments)]
pub fn get_swap_round_stake_instruction(
    authority: Pubkey,
    usd_mint: Pubkey,
    btc_mint: Pubkey,
    round_id: u32,
    min_btc_out: u64,
    swap_program: Pubkey,
    swap_data: Vec<u8>,
    route_accounts: &[AccountMeta],
) -> Instruction {
    let board = get_board_address().0;
    SwapRoundStake {
        authority,
        satrush_config: get_satrush_config_address().0,
        board,
        round: get_round_address(round_id).0,
        usd_mint,
        btc_mint,
        board_usd_ata: get_associated_token_address(&board, &usd_mint),
        board_btc_ata: get_associated_token_address(&board, &btc_mint),
        swap_program,
        token_program: TOKEN_PROGRAM_ID,
        event_authority: get_event_authority_address().0,
        program: crate::SATRUSH_ID,
    }
    .instruction_with_remaining_accounts(SwapRoundStakeInstructionArgs { min_btc_out, swap_data }, route_accounts)
}

/// `settle_deploy_public`: settle the miner `authority`'s deployment in a
/// Settled round — credit winnings (USD + sats vault shares) and hashrate
/// points to the miner profile, then close the deployment account, returning
/// its rent. Valid for losing deployments too (they still earn hashrate points).
pub fn get_settle_deploy_public_instruction(
    authority: Pubkey,
    usd_mint: Pubkey,
    btc_mint: Pubkey,
    round_id: u32,
) -> Instruction {
    let board = get_board_address().0;
    let sats_vault = get_sats_vault_address().0;
    SettleDeployPublic {
        authority,
        satrush_config: get_satrush_config_address().0,
        round: get_round_address(round_id).0,
        board,
        public_deployment: get_public_deployment_address(authority, round_id).0,
        miner: get_miner_address(authority).0,
        sats_vault,
        btc_mint,
        usd_mint,
        board_btc_ata: get_associated_token_address(&board, &btc_mint),
        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
        event_authority: get_event_authority_address().0,
        program: crate::SATRUSH_ID,
    }
    .instruction()
}

/// `claim_usd`: withdraw `amount` of the miner `authority`'s unclaimed USD
/// winnings from the board's USD pool to the authority's USD account. No exit
/// fee — the full amount transfers.
pub fn get_claim_usd_instruction(authority: Pubkey, usd_mint: Pubkey, amount: u64) -> Instruction {
    let board = get_board_address().0;
    ClaimUsd {
        authority,
        satrush_config: get_satrush_config_address().0,
        board,
        miner: get_miner_address(authority).0,
        usd_mint,
        board_usd_ata: get_associated_token_address(&board, &usd_mint),
        authority_usd_ata: get_associated_token_address(&authority, &usd_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction(ClaimUsdInstructionArgs { amount })
}

/// `create_sats_vault`: the sats vault singleton and its BTC reserve pool.
pub fn get_create_sats_vault_instruction(authority: Pubkey, btc_mint: Pubkey) -> Instruction {
    let sats_vault = get_sats_vault_address().0;
    CreateSatsVault {
        authority,
        satrush_config: get_satrush_config_address().0,
        sats_vault,
        btc_mint,
        sats_vault_btc_ata: get_associated_token_address(&sats_vault, &btc_mint),
        token_program: TOKEN_PROGRAM_ID,
        associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .instruction()
}