use anyhow::Result;
use crate::{
accounts::PoolState,
pda,
TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID,
calculate_buy_amount,
};
use crate::generated::instructions;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
instruction::{Instruction, AccountMeta},
system_instruction,
};
use spl_associated_token_account::{
get_associated_token_address,
instruction::create_associated_token_account_idempotent,
};
use spl_token::instruction as token_instruction;
use std::str::FromStr;
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const USD1_MINT: &str = "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB";
pub fn build_buy_instruction(
client: &RpcClient,
payer: &Keypair,
base_mint: &Pubkey,
quote_amount: u64,
minimum_amount_out: u64,
) -> Result<(Vec<Instruction>, PoolState)> {
let quote_mint_usd1 = Pubkey::from_str(USD1_MINT)?;
let quote_mint_wsol = Pubkey::from_str(WSOL_MINT)?;
let (pool_state_usd1, _) = pda::get_pool_state_pda(base_mint, "e_mint_usd1);
let (pool_state_wsol, _) = pda::get_pool_state_pda(base_mint, "e_mint_wsol);
let (pool_state_address, quote_mint, is_wsol_pool) =
if client.get_account_data(&pool_state_usd1).is_ok() {
(pool_state_usd1, quote_mint_usd1, false)
} else if client.get_account_data(&pool_state_wsol).is_ok() {
(pool_state_wsol, quote_mint_wsol, true)
} else {
anyhow::bail!("No pool found for this token (tried USD1 and WSOL)");
};
let mut instr = Vec::new();
let pool_state_data = client.get_account_data(&pool_state_address)?;
let pool_state = PoolState::try_from_bytes(&pool_state_data)?;
let actual_minimum = if minimum_amount_out == 0 {
calculate_buy_amount(
quote_amount,
pool_state.virtual_base as u128,
pool_state.virtual_quote as u128,
pool_state.real_base as u128,
pool_state.real_quote as u128,
100,
)
} else {
minimum_amount_out
};
let base_uses_token_2022 = (pool_state.token_program_flag & 0x01) != 0;
let quote_uses_token_2022 = (pool_state.token_program_flag & 0x02) != 0;
let base_token_program = if base_uses_token_2022 {
Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")?
} else {
TOKEN_PROGRAM_ID
};
let quote_token_program = if quote_uses_token_2022 {
Pubkey::from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")?
} else {
TOKEN_PROGRAM_ID
};
let user_base_ata = if base_uses_token_2022 {
spl_associated_token_account::get_associated_token_address_with_program_id(
&payer.pubkey(), base_mint, &base_token_program
)
} else {
get_associated_token_address(&payer.pubkey(), base_mint)
};
let user_quote_ata = get_associated_token_address(&payer.pubkey(), "e_mint);
instr.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
base_mint,
&base_token_program,
));
instr.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
"e_mint,
"e_token_program,
));
if is_wsol_pool {
instr.push(system_instruction::transfer(
&payer.pubkey(),
&user_quote_ata,
quote_amount,
));
instr.push(token_instruction::sync_native(
&TOKEN_PROGRAM_ID,
&user_quote_ata,
)?);
}
let (base_vault, _) = pda::get_pool_vault_pda(&pool_state_address, base_mint);
let (quote_vault, _) = pda::get_pool_vault_pda(&pool_state_address, "e_mint);
let (creator_fee_vault, _) = pda::get_creator_fee_vault_pda(&pool_state.creator, "e_mint);
let (platform_fee_vault, _) = pda::get_platform_fee_vault_pda(&pool_state.platform_config, "e_mint);
let mut buy_ix = instructions::buy_exact_in(
&payer.pubkey(),
&pool_state.global_config,
&pool_state.platform_config,
&pool_state_address,
&user_base_ata,
&user_quote_ata,
&base_vault,
"e_vault,
base_mint,
"e_mint,
&base_token_program,
quote_amount,
actual_minimum,
0,
);
buy_ix.accounts[12] = AccountMeta::new_readonly(quote_token_program, false);
buy_ix.accounts.push(AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false));
buy_ix.accounts.push(AccountMeta::new(platform_fee_vault, false));
buy_ix.accounts.push(AccountMeta::new(creator_fee_vault, false));
instr.push(buy_ix);
Ok((instr, pool_state))
}