use crate::{ constants::trade::trade::DEFAULT_SLIPPAGE, instruction::utils::bonk::{ accounts, get_pool_pda, get_vault_pda, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR, }, trading::core::{ params::{BonkParams, SwapParams}, traits::InstructionBuilder, }, utils::calc::bonk::{ get_buy_token_amount_from_sol_amount, get_sell_sol_amount_from_token_amount, }, }; use anyhow::{anyhow, Result}; use solana_sdk::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, signer::Signer, }; /// Instruction builder for Bonk protocol pub struct BonkInstructionBuilder; #[async_trait::async_trait] impl InstructionBuilder for BonkInstructionBuilder { async fn build_buy_instructions(&self, params: &SwapParams) -> Result> { // ======================================== // Parameter validation and basic data preparation // ======================================== if params.input_amount.unwrap_or(0) == 0 { return Err(anyhow!("Amount cannot be zero")); } let protocol_params = params .protocol_params .as_any() .downcast_ref::() .ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?; let usd1_pool = protocol_params.global_config == accounts::USD1_GLOBAL_CONFIG; let pool_state = if protocol_params.pool_state == Pubkey::default() { if usd1_pool { get_pool_pda(¶ms.output_mint, &crate::constants::USD1_TOKEN_ACCOUNT).unwrap() } else { get_pool_pda(¶ms.output_mint, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() } } else { protocol_params.pool_state }; let global_config = if usd1_pool { accounts::USD1_GLOBAL_CONFIG_META } else { accounts::GLOBAL_CONFIG_META }; let quote_token_mint = if usd1_pool { crate::constants::USD1_TOKEN_ACCOUNT_META } else { crate::constants::WSOL_TOKEN_ACCOUNT_META }; // ======================================== // Trade calculation and account address preparation // ======================================== let amount_in: u64 = params.input_amount.unwrap_or(0); let share_fee_rate: u64 = 0; let minimum_amount_out: u64 = match params.fixed_output_amount { Some(fixed_amount) => fixed_amount, None => get_buy_token_amount_from_sol_amount( amount_in, protocol_params.virtual_base, protocol_params.virtual_quote, protocol_params.real_base, protocol_params.real_quote, params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128, ), }; let user_base_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), ¶ms.output_mint, &protocol_params.mint_token_program, params.open_seed_optimize, ); let user_quote_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), if usd1_pool { &crate::constants::USD1_TOKEN_ACCOUNT } else { &crate::constants::WSOL_TOKEN_ACCOUNT }, &crate::constants::TOKEN_PROGRAM, params.open_seed_optimize, ); let base_vault_account = if protocol_params.base_vault == Pubkey::default() { get_vault_pda(&pool_state, ¶ms.output_mint).unwrap() } else { protocol_params.base_vault }; let quote_vault_account = if protocol_params.quote_vault == Pubkey::default() { if usd1_pool { get_vault_pda(&pool_state, &crate::constants::USD1_TOKEN_ACCOUNT).unwrap() } else { get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() } } else { protocol_params.quote_vault }; // ======================================== // Build instructions // ======================================== let mut instructions = Vec::with_capacity(6); if params.create_input_mint_ata && !usd1_pool { instructions .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), amount_in)); } if params.create_output_mint_ata { instructions.extend( crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), ¶ms.output_mint, &protocol_params.mint_token_program, params.open_seed_optimize, ), ); } let mut data = [0u8; 32]; data[..8].copy_from_slice(&BUY_EXECT_IN_DISCRIMINATOR); data[8..16].copy_from_slice(&amount_in.to_le_bytes()); data[16..24].copy_from_slice(&minimum_amount_out.to_le_bytes()); data[24..32].copy_from_slice(&share_fee_rate.to_le_bytes()); let accounts: [AccountMeta; 18] = [ AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) accounts::AUTHORITY_META, // Authority (readonly) global_config, // Global Config (readonly) AccountMeta::new_readonly(protocol_params.platform_config, false), // Platform Config (readonly) AccountMeta::new(pool_state, false), // Pool State AccountMeta::new(user_base_token_account, false), // User Base Token AccountMeta::new(user_quote_token_account, false), // User Quote Token AccountMeta::new(base_vault_account, false), // Base Vault AccountMeta::new(quote_vault_account, false), // Quote Vault AccountMeta::new_readonly(params.output_mint, false), // Base Token Mint (readonly) quote_token_mint, // Quote Token Mint (readonly) AccountMeta::new_readonly(protocol_params.mint_token_program, false), // Base Token Program (readonly) crate::constants::TOKEN_PROGRAM_META, // Quote Token Program (readonly) accounts::EVENT_AUTHORITY_META, // Event Authority (readonly) accounts::BONK_META, // Program (readonly) crate::constants::SYSTEM_PROGRAM_META, // System Program (readonly) AccountMeta::new(protocol_params.platform_associated_account, false), // Platform Associated Account AccountMeta::new(protocol_params.creator_associated_account, false), // Creator Associated Account ]; instructions.push(Instruction::new_with_bytes(accounts::BONK, &data, accounts.to_vec())); if params.close_input_mint_ata { instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) } async fn build_sell_instructions(&self, params: &SwapParams) -> Result> { // ======================================== // Parameter validation and basic data preparation // ======================================== let amount = params .input_amount .filter(|&a| a > 0) .ok_or_else(|| anyhow!("Bonk sell requires input_amount (token amount to sell); fetch balance via RPC before calling build_sell"))?; let protocol_params = params .protocol_params .as_any() .downcast_ref::() .ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?; let usd1_pool = protocol_params.global_config == accounts::USD1_GLOBAL_CONFIG; let pool_state = if protocol_params.pool_state == Pubkey::default() { if usd1_pool { get_pool_pda(¶ms.input_mint, &crate::constants::USD1_TOKEN_ACCOUNT).unwrap() } else { get_pool_pda(¶ms.input_mint, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() } } else { protocol_params.pool_state }; let global_config = if usd1_pool { accounts::USD1_GLOBAL_CONFIG_META } else { accounts::GLOBAL_CONFIG_META }; let quote_token_mint = if usd1_pool { crate::constants::USD1_TOKEN_ACCOUNT_META } else { crate::constants::WSOL_TOKEN_ACCOUNT_META }; // ======================================== // Trade calculation and account address preparation // ======================================== let share_fee_rate: u64 = 0; let minimum_amount_out: u64 = match params.fixed_output_amount { Some(fixed_amount) => fixed_amount, None => get_sell_sol_amount_from_token_amount( amount, protocol_params.virtual_base, protocol_params.virtual_quote, protocol_params.real_base, protocol_params.real_quote, params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128, ), }; let user_base_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), ¶ms.input_mint, &protocol_params.mint_token_program, params.open_seed_optimize, ); let user_quote_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), if usd1_pool { &crate::constants::USD1_TOKEN_ACCOUNT } else { &crate::constants::WSOL_TOKEN_ACCOUNT }, &crate::constants::TOKEN_PROGRAM, params.open_seed_optimize, ); let base_vault_account = if protocol_params.base_vault == Pubkey::default() { get_vault_pda(&pool_state, ¶ms.input_mint).unwrap() } else { protocol_params.base_vault }; let quote_vault_account = if protocol_params.quote_vault == Pubkey::default() { if usd1_pool { get_vault_pda(&pool_state, &crate::constants::USD1_TOKEN_ACCOUNT).unwrap() } else { get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() } } else { protocol_params.quote_vault }; // ======================================== // Build instructions // ======================================== let mut instructions = Vec::with_capacity(3); if params.close_output_mint_ata && !usd1_pool { instructions.extend(crate::trading::common::create_wsol_ata(¶ms.payer.pubkey())); } let mut data = [0u8; 32]; data[..8].copy_from_slice(&SELL_EXECT_IN_DISCRIMINATOR); data[8..16].copy_from_slice(&amount.to_le_bytes()); data[16..24].copy_from_slice(&minimum_amount_out.to_le_bytes()); data[24..32].copy_from_slice(&share_fee_rate.to_le_bytes()); let accounts: [AccountMeta; 18] = [ AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) accounts::AUTHORITY_META, // Authority (readonly) global_config, // Global Config (readonly) AccountMeta::new_readonly(protocol_params.platform_config, false), // Platform Config (readonly) AccountMeta::new(pool_state, false), // Pool State AccountMeta::new(user_base_token_account, false), // User Base Token AccountMeta::new(user_quote_token_account, false), // User Quote Token AccountMeta::new(base_vault_account, false), // Base Vault AccountMeta::new(quote_vault_account, false), // Quote Vault AccountMeta::new_readonly(params.input_mint, false), // Base Token Mint (readonly) quote_token_mint, // Quote Token Mint (readonly) AccountMeta::new_readonly(protocol_params.mint_token_program, false), // Base Token Program (readonly) crate::constants::TOKEN_PROGRAM_META, // Quote Token Program (readonly) accounts::EVENT_AUTHORITY_META, // Event Authority (readonly) accounts::BONK_META, // Program (readonly) crate::constants::SYSTEM_PROGRAM_META, // System Program (readonly) AccountMeta::new(protocol_params.platform_associated_account, false), // Platform Associated Account AccountMeta::new(protocol_params.creator_associated_account, false), // Creator Associated Account ]; instructions.push(Instruction::new_with_bytes(accounts::BONK, &data, accounts.to_vec())); if params.close_output_mint_ata { instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } if params.close_input_mint_ata { instructions.push(crate::common::spl_token::close_account( &protocol_params.mint_token_program, &user_base_token_account, ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), &[¶ms.payer.pubkey()], )?); } Ok(instructions) } }