use anyhow::Result;
use raydium_launchlab::{
accounts::PoolState,
instructions,
pda,
TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID, PROGRAM_ID,
};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
instruction::{Instruction, AccountMeta},
system_instruction,
system_program,
};
use raydium_launchlab::accounts::GlobalConfig;
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;
use borsh::BorshSerialize;
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const USD1_MINT: &str = "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB";
const PROTOCOL_FEE_RATE: u128 = 100; const PLATFORM_FEE_RATE: u128 = 0; const SHARE_FEE_RATE: u128 = 0;
pub fn get_creator_fee_vault_pda(creator: &Pubkey, quote_mint: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[creator.as_ref(), quote_mint.as_ref()],
&PROGRAM_ID,
)
}
pub fn get_platform_fee_vault_pda(platform_config: &Pubkey, quote_mint: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[platform_config.as_ref(), quote_mint.as_ref()],
&PROGRAM_ID,
)
}
fn main() -> Result<()> {
let rpc_url = "https://api.mainnet-beta.solana.com";
let client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
let private_key_base58 = "XiGbcPogqdTUuncBmwkn1uTjxbRjK2fyTUcVqWeMJccv2paMPavxngyURZ3M3RQBQ5Y9uYh8Y7ws9CXrfiy4mme";
let payer = Keypair::from_base58_string(private_key_base58);
println!("Payer pubkey: {}", payer.pubkey());
let base_mint = Pubkey::from_str("EG12wh9X1pLq9AxKjiKep9SnLgDSrrpipPQuay9vbonk")?;
let _quote_mint = Pubkey::from_str(WSOL_MINT)?;
let quote_amount = 5_000_0000;
let (instructions, pool_state) = build_buy_instruction(
&client,
&payer,
&base_mint,
quote_amount,
0,
)?;
let recent_blockhash = client.get_latest_blockhash()?;
let tx = Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[&payer],
recent_blockhash,
);
println!("\nSending Buy transaction with {} instructions...", instructions.len());
match client.send_and_confirm_transaction(&tx) {
Ok(signature) => {
println!("\n✅ Buy Transaction confirmed!");
println!("Signature: {}", signature);
println!("Explorer: https://solscan.io/tx/{}", signature);
println!("\n--- Sending Atomic Buy + Sell Transaction ---");
let estimated_tokens_received = 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, );
let estimated_sol_received = calculate_sell_amount(
estimated_tokens_received,
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, );
println!("Estimated SOL received from Sell: {} ({:.2} SOL)",
estimated_sol_received,
estimated_sol_received as f64 / 1_000_000_000.0
);
let sell_instructions = build_sell_instructions(&client, &payer, &base_mint, estimated_tokens_received, estimated_sol_received)?;
let mut combined_instructions = sell_instructions;
let fresh_blockhash = client.get_latest_blockhash()?;
let tx_combined = Transaction::new_signed_with_payer(
&combined_instructions,
Some(&payer.pubkey()),
&[&payer],
fresh_blockhash,
);
println!("\nSending Combined Transaction ({} instructions)...", combined_instructions.len());
match client.send_and_confirm_transaction(&tx_combined) {
Ok(sig) => {
println!("✅ Combined Buy+Sell Transaction confirmed!");
println!("Signature: {}", sig);
println!("Explorer: https://solscan.io/tx/{}", sig);
}
Err(e) => {
println!("❌ Combined Transaction failed: {:?}", e);
}
}
}
Err(e) => {
println!("❌ Buy Transaction failed: {:?}", e);
}
}
Ok(())
}
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);
println!("USD1 pool: {}", pool_state_usd1);
println!("WSOL pool: {}", pool_state_wsol);
match client.get_account_data(&pool_state_usd1) {
Ok(d) => println!("USD1 pool data found: {} bytes", d.len()),
Err(e) => println!("USD1 pool not found: {}", e),
}
match client.get_account_data(&pool_state_wsol) {
Ok(d) => println!("WSOL pool data found: {} bytes", d.len()),
Err(e) => println!("WSOL pool not found: {}", e),
}
let (pool_state_address, quote_mint, is_wsol_pool) =
if client.get_account_data(&pool_state_usd1).is_ok() {
println!("Found USD1 pool: {}", pool_state_usd1);
(pool_state_usd1, quote_mint_usd1, false)
} else if client.get_account_data(&pool_state_wsol).is_ok() {
println!("Found WSOL pool: {}", pool_state_wsol);
(pool_state_wsol, quote_mint_wsol, true)
} else {
anyhow::bail!("No pool found for this token (tried USD1 and WSOL)");
};
println!("is_wsol_pool = {}", is_wsol_pool);
println!(
"Selected pool type: {}",
if is_wsol_pool { "WSOL" } else { "USD1" }
);
let mut instructions = Vec::new();
let pool_state_data = client.get_account_data(&pool_state_address)?;
let pool_state = PoolState::try_from_bytes(&pool_state_data)?;
let total_fee_rate = 250_000_000; println!("Using Safety Fee Rate: {}", total_fee_rate);
let actual_minimum= if minimum_amount_out==0{
let expected=calculate_buy_amount(
quote_amount as u64,
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
);
expected
}else{
minimum_amount_out
};
println!("Actual minimum amount out: {}", actual_minimum/1000000);
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
};
println!("Pool Status: {} (0=Fund, 1=Migrate, 2=Trade)", pool_state.status);
println!("Quote Token: {}", if is_wsol_pool { "WSOL" } else { "USD1" });
println!("Quote mint (our): {}", quote_mint);
println!("Quote mint (pool_state): {}", pool_state.quote_mint);
println!("Token Program Flag: {} (base={}, quote={})",
pool_state.token_program_flag,
if base_uses_token_2022 { "Token-2022" } else { "SPL" },
if quote_uses_token_2022 { "Token-2022" } else { "SPL" }
);
println!("Buying with {} quote tokens", quote_amount);
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);
println!("User base ATA: {}", user_base_ata);
println!("User quote ATA: {}", user_quote_ata);
instructions.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
base_mint,
&base_token_program,
));
instructions.push(create_associated_token_account_idempotent(
&payer.pubkey(),
&payer.pubkey(),
"e_mint,
"e_token_program,
));
if is_wsol_pool {
println!("Adding WSOL wrap instructions...");
instructions.push(system_instruction::transfer(
&payer.pubkey(),
&user_quote_ata,
quote_amount,
));
instructions.push(token_instruction::sync_native(
&TOKEN_PROGRAM_ID,
&user_quote_ata,
)?);
} else {
println!("Using USD1 directly (no wrapping needed)");
}
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);
println!("Creator: {}", pool_state.creator);
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));
instructions.push(buy_ix);
Ok((instructions, pool_state))
}
fn build_sell_instructions(
client: &RpcClient,
payer: &Keypair,
base_mint: &Pubkey,
total_tokens_to_sell: u64,
minimum_amount_out: u64
) -> anyhow::Result<Vec<Instruction>> {
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() {
println!("Found USD1 pool for selling: {}", pool_state_usd1);
(pool_state_usd1, quote_mint_usd1, false)
} else if client.get_account_data(&pool_state_wsol).is_ok() {
println!("Found WSOL pool for selling: {}", pool_state_wsol);
(pool_state_wsol, quote_mint_wsol, true)
} else {
anyhow::bail!("No pool found for this token (tried USD1 and WSOL)");
};
let pool_state_data = client.get_account_data(&pool_state_address)?;
let pool_state = PoolState::try_from_bytes(&pool_state_data)?;
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
};
println!("Pool Status: {} (0=Fund, 1=Migrate, 2=Trade)", pool_state.status as u8);
println!("Quote Token: {}", if is_wsol_pool { "WSOL" } else { "USD1" });
println!("Selling {} base tokens", total_tokens_to_sell/1000000);
let estimated_out = calculate_sell_amount(
total_tokens_to_sell,
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, );
println!("Estimated output: {} (decimals={}, min_out={})", estimated_out, pool_state.quote_decimals, minimum_amount_out);
let user_base_token = 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_token = if quote_uses_token_2022 {
spl_associated_token_account::get_associated_token_address_with_program_id(
&payer.pubkey(),
"e_mint,
"e_token_program,
)
} else {
get_associated_token_address(&payer.pubkey(), "e_mint)
};
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 sell_ix = instructions::sell_exact_in(
&payer.pubkey(),
&pool_state.global_config,
&pool_state.platform_config,
&pool_state_address,
&user_base_token,
&user_quote_token,
&base_vault,
"e_vault,
base_mint,
"e_mint,
&base_token_program,
total_tokens_to_sell,
minimum_amount_out,
0, );
sell_ix.accounts[12] = AccountMeta::new_readonly(quote_token_program, false);
sell_ix.accounts.push(AccountMeta::new_readonly(system_program::id(), false));
sell_ix.accounts.push(AccountMeta::new(platform_fee_vault, false));
sell_ix.accounts.push(AccountMeta::new(creator_fee_vault, false));
let mut instructions = vec![sell_ix];
if is_wsol_pool {
println!("Adding WSOL unwrap instruction...");
instructions.push(token_instruction::close_account(
&TOKEN_PROGRAM_ID,
&user_quote_token, &payer.pubkey(), &payer.pubkey(), &[],
)?);
}
Ok(instructions)
}
fn calculate_buy_amount(
amount_in: u64,
virtual_base: u128,
virtual_quote: u128,
real_base: u128,
real_quote: u128,
slippage_basis_points: u128,
) -> u64 {
let amount_in_u128 = amount_in as u128;
let protocol_fee = amount_in_u128 * PROTOCOL_FEE_RATE / 10000;
let platform_fee = amount_in_u128 * PLATFORM_FEE_RATE / 10000;
let share_fee = amount_in_u128 * SHARE_FEE_RATE / 10000;
let amount_in_net = amount_in_u128
.saturating_sub(protocol_fee)
.saturating_sub(platform_fee)
.saturating_sub(share_fee);
let input_reserve = virtual_quote.saturating_add(real_quote);
let output_reserve = virtual_base.saturating_sub(real_base);
let numerator = amount_in_net.saturating_mul(output_reserve);
let denominator = input_reserve.saturating_add(amount_in_net);
let amount_out = numerator / denominator;
let amount_with_slippage = amount_out - (amount_out * slippage_basis_points) / 10000;
amount_with_slippage as u64
}