diff --git a/src/common/fast_fn.rs b/src/common/fast_fn.rs index 1b1b94c..d87db04 100644 --- a/src/common/fast_fn.rs +++ b/src/common/fast_fn.rs @@ -26,6 +26,8 @@ pub enum InstructionCacheKey { mint: Pubkey, token_program: Pubkey, }, + /// Close wSOL Account + CloseWsolAccount { payer: Pubkey, wsol_token_account: Pubkey }, } /// 全局指令缓存,用于存储常用指令 @@ -106,6 +108,8 @@ pub enum PdaCacheKey { PumpFunUserVolume(Pubkey), PumpFunBondingCurve(Pubkey), PumpFunCreatorVault(Pubkey), + BonkPool(Pubkey, Pubkey), + BonkVault(Pubkey, Pubkey), } /// 全局 PDA 缓存,用于存储计算结果 @@ -190,5 +194,29 @@ pub fn get_associated_token_address_with_program_id_fast( // --------------------- 初始化账号 --------------------- pub fn fast_init(payer: &Pubkey) { + // 获取 PumpFun 用户量累加器 PDA crate::instruction::utils::pumpfun::get_user_volume_accumulator_pda(payer); + // 获取 wSOL ATA 地址 + let wsol_token_account = get_associated_token_address_with_program_id_fast( + payer, + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); + // 获取 Close wSOL Account 指令 + get_cached_instruction( + crate::common::fast_fn::InstructionCacheKey::CloseWsolAccount { + payer: *payer, + wsol_token_account, + }, + || { + spl_token::instruction::close_account( + &crate::constants::TOKEN_PROGRAM, + &wsol_token_account, + &payer, + &payer, + &[], + ) + .unwrap() + }, + ); } diff --git a/src/instruction/bonk.rs b/src/instruction/bonk.rs index 93c7195..b3d26aa 100755 --- a/src/instruction/bonk.rs +++ b/src/instruction/bonk.rs @@ -1,9 +1,3 @@ -use anyhow::{anyhow, Result}; -use solana_sdk::{instruction::Instruction, signer::Signer}; -use solana_system_interface::instruction::transfer; -use spl_associated_token_account::instruction::create_associated_token_account_idempotent; -use spl_token::instruction::close_account; - use crate::{ constants::trade::trade::DEFAULT_SLIPPAGE, instruction::utils::bonk::{ @@ -21,6 +15,12 @@ use crate::{ 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; @@ -37,7 +37,11 @@ impl InstructionBuilder for BonkInstructionBuilder { .downcast_ref::() .ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?; - let pool_state = get_pool_pda(¶ms.mint, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap(); + let pool_state = if protocol_params.pool_state == Pubkey::default() { + get_pool_pda(¶ms.mint, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() + } else { + protocol_params.pool_state + }; // Create user token accounts let user_base_token_account = @@ -54,9 +58,16 @@ impl InstructionBuilder for BonkInstructionBuilder { ); // Get pool token accounts - let base_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap(); - let quote_vault_account = - get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap(); + let base_vault_account = if protocol_params.base_vault == Pubkey::default() { + get_vault_pda(&pool_state, ¶ms.mint).unwrap() + } else { + protocol_params.base_vault + }; + let quote_vault_account = if protocol_params.quote_vault == Pubkey::default() { + get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() + } else { + protocol_params.quote_vault + }; let virtual_base = protocol_params.virtual_base; let virtual_quote = protocol_params.virtual_quote; @@ -74,36 +85,15 @@ impl InstructionBuilder for BonkInstructionBuilder { params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128, ); - let mut instructions = vec![]; + let mut instructions = Vec::with_capacity(6); if protocol_params.auto_handle_wsol { - // Handle wSOL - instructions.push( - // Create wSOL ATA account if it doesn't exist - create_associated_token_account_idempotent( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - ), - ); - instructions.push( - // Transfer SOL to wSOL ATA account - transfer(¶ms.payer.pubkey(), &user_quote_token_account, amount_in), - ); - - // Sync wSOL balance - instructions.push( - spl_token::instruction::sync_native( - &crate::constants::TOKEN_PROGRAM, - &user_quote_token_account, - ) - .unwrap(), - ); + instructions + .extend(crate::trading::common::handle_wsol(¶ms.payer.pubkey(), amount_in)); } // Create user's base token account - instructions.push(create_associated_token_account_idempotent( + instructions.push(crate::common::fast_fn::create_associated_token_account_idempotent_fast( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), ¶ms.mint, @@ -111,59 +101,38 @@ impl InstructionBuilder for BonkInstructionBuilder { )); // Create buy instruction - let accounts = vec![ - solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) - accounts::AUTHORITY_META, // Authority (readonly) - accounts::GLOBAL_CONFIG_META, // Global Config (readonly) - solana_sdk::instruction::AccountMeta::new_readonly( - protocol_params.platform_config, - false, - ), // Platform Config (readonly) - solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State - solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // User Base Token - solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // User Quote Token - solana_sdk::instruction::AccountMeta::new(base_vault_account, false), // Base Vault - solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault - solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Base Token Mint (readonly) - crate::constants::WSOL_TOKEN_ACCOUNT_META, // Quote Token Mint (readonly) - solana_sdk::instruction::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) - solana_sdk::instruction::AccountMeta::new( - protocol_params.platform_associated_account, - false, - ), // Platform Associated Account - solana_sdk::instruction::AccountMeta::new( - protocol_params.creator_associated_account, - false, - ), // Creator Associated Account + let accounts: [AccountMeta; 18] = [ + AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) + accounts::AUTHORITY_META, // Authority (readonly) + accounts::GLOBAL_CONFIG_META, // 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.mint, false), // Base Token Mint (readonly) + crate::constants::WSOL_TOKEN_ACCOUNT_META, // 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 ]; // Create instruction data - let mut data = vec![]; - data.extend_from_slice(&BUY_EXECT_IN_DISCRIMINATOR); - data.extend_from_slice(&amount_in.to_le_bytes()); - data.extend_from_slice(&minimum_amount_out.to_le_bytes()); - data.extend_from_slice(&share_fee_rate.to_le_bytes()); + 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()); - instructions.push(Instruction { program_id: accounts::BONK, accounts, data }); + instructions.push(Instruction::new_with_bytes(accounts::BONK, &data, accounts.to_vec())); if protocol_params.auto_handle_wsol { // Close wSOL ATA account, reclaim rent - instructions.push( - spl_token::instruction::close_account( - &crate::constants::TOKEN_PROGRAM, - &user_quote_token_account, - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &[], - ) - .unwrap(), - ); + instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) @@ -195,7 +164,11 @@ impl InstructionBuilder for BonkInstructionBuilder { return Err(anyhow!("Amount cannot be zero")); } - let pool_state = get_pool_pda(¶ms.mint, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap(); + let pool_state = if protocol_params.pool_state == Pubkey::default() { + get_pool_pda(¶ms.mint, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() + } else { + protocol_params.pool_state + }; let virtual_base = protocol_params.virtual_base; let virtual_quote = protocol_params.virtual_quote; @@ -214,90 +187,73 @@ impl InstructionBuilder for BonkInstructionBuilder { // Create user token accounts let user_base_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( ¶ms.payer.pubkey(), ¶ms.mint, &protocol_params.mint_token_program, ); - let user_quote_token_account = spl_associated_token_account::get_associated_token_address( - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - ); + let user_quote_token_account = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + ¶ms.payer.pubkey(), + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); // Get pool token accounts - let base_vault_account = get_vault_pda(&pool_state, ¶ms.mint).unwrap(); - let quote_vault_account = - get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap(); + let base_vault_account = if protocol_params.base_vault == Pubkey::default() { + get_vault_pda(&pool_state, ¶ms.mint).unwrap() + } else { + protocol_params.base_vault + }; + let quote_vault_account = if protocol_params.quote_vault == Pubkey::default() { + get_vault_pda(&pool_state, &crate::constants::WSOL_TOKEN_ACCOUNT).unwrap() + } else { + protocol_params.quote_vault + }; let share_fee_rate: u64 = 0; - let mut instructions = vec![]; + let mut instructions = Vec::with_capacity(3); // Handle wSOL instructions.push( // Create wSOL ATA account if it doesn't exist - create_associated_token_account_idempotent( - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &crate::constants::WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - ), + crate::trading::common::create_wsol_ata(¶ms.payer.pubkey()), ); // Create sell instruction - let accounts = vec![ - solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) - accounts::AUTHORITY_META, // Authority (readonly) - accounts::GLOBAL_CONFIG_META, // Global Config (readonly) - solana_sdk::instruction::AccountMeta::new_readonly( - protocol_params.platform_config, - false, - ), // Platform Config (readonly) - solana_sdk::instruction::AccountMeta::new(pool_state, false), // Pool State - solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // User Base Token - solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // User Quote Token - solana_sdk::instruction::AccountMeta::new(base_vault_account, false), // Base Vault - solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault - solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Base Token Mint (readonly) - crate::constants::WSOL_TOKEN_ACCOUNT_META, // Quote Token Mint (readonly) - solana_sdk::instruction::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) - solana_sdk::instruction::AccountMeta::new( - protocol_params.platform_associated_account, - false, - ), // Platform Associated Account - solana_sdk::instruction::AccountMeta::new( - protocol_params.creator_associated_account, - false, - ), // Creator Associated Account + let accounts: [AccountMeta; 18] = [ + AccountMeta::new(params.payer.pubkey(), true), // Payer (signer) + accounts::AUTHORITY_META, // Authority (readonly) + accounts::GLOBAL_CONFIG_META, // 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.mint, false), // Base Token Mint (readonly) + crate::constants::WSOL_TOKEN_ACCOUNT_META, // 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 ]; // Create instruction data - let mut data = vec![]; - data.extend_from_slice(&SELL_EXECT_IN_DISCRIMINATOR); - data.extend_from_slice(&amount.to_le_bytes()); - data.extend_from_slice(&minimum_amount_out.to_le_bytes()); - data.extend_from_slice(&share_fee_rate.to_le_bytes()); + 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()); - instructions.push(Instruction { program_id: accounts::BONK, accounts, data }); + instructions.push(Instruction::new_with_bytes(accounts::BONK, &data, accounts.to_vec())); if protocol_params.auto_handle_wsol { - instructions.push( - close_account( - &crate::constants::TOKEN_PROGRAM, - &user_quote_token_account, - ¶ms.payer.pubkey(), - ¶ms.payer.pubkey(), - &[¶ms.payer.pubkey()], - ) - .unwrap(), - ); + instructions.push(crate::trading::common::close_wsol(¶ms.payer.pubkey())); } Ok(instructions) diff --git a/src/instruction/utils/bonk.rs b/src/instruction/utils/bonk.rs index cfedd15..b72c482 100644 --- a/src/instruction/utils/bonk.rs +++ b/src/instruction/utils/bonk.rs @@ -147,17 +147,27 @@ pub fn get_amount_out( } pub fn get_pool_pda(base_mint: &Pubkey, quote_mint: &Pubkey) -> Option { - let seeds: &[&[u8]; 3] = &[seeds::POOL_SEED, base_mint.as_ref(), quote_mint.as_ref()]; - let program_id: &Pubkey = &accounts::BONK; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) + crate::common::fast_fn::get_cached_pda( + crate::common::fast_fn::PdaCacheKey::BonkPool(*base_mint, *quote_mint), + || { + let seeds: &[&[u8]; 3] = &[seeds::POOL_SEED, base_mint.as_ref(), quote_mint.as_ref()]; + let program_id: &Pubkey = &accounts::BONK; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) + }, + ) } pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option { - let seeds: &[&[u8]; 3] = &[seeds::POOL_VAULT_SEED, pool_state.as_ref(), mint.as_ref()]; - let program_id: &Pubkey = &accounts::BONK; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) + crate::common::fast_fn::get_cached_pda( + crate::common::fast_fn::PdaCacheKey::BonkVault(*pool_state, *mint), + || { + let seeds: &[&[u8]; 3] = &[seeds::POOL_VAULT_SEED, pool_state.as_ref(), mint.as_ref()]; + let program_id: &Pubkey = &accounts::BONK; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) + }, + ) } pub fn get_platform_associated_account(platform_config: &Pubkey) -> Option { diff --git a/src/trading/common/mod.rs b/src/trading/common/mod.rs index b1c8777..c73d130 100755 --- a/src/trading/common/mod.rs +++ b/src/trading/common/mod.rs @@ -3,10 +3,12 @@ pub mod transaction_builder; pub mod compute_budget_manager; pub mod address_lookup_manager; pub mod utils; +pub mod wsol_manager; // Re-export commonly used functions pub use nonce_manager::*; pub use transaction_builder::*; pub use compute_budget_manager::*; pub use address_lookup_manager::*; -pub use utils::*; \ No newline at end of file +pub use utils::*; +pub use wsol_manager::*; \ No newline at end of file diff --git a/src/trading/common/wsol_manager.rs b/src/trading/common/wsol_manager.rs new file mode 100644 index 0000000..7e1adea --- /dev/null +++ b/src/trading/common/wsol_manager.rs @@ -0,0 +1,65 @@ +use crate::common::fast_fn::create_associated_token_account_idempotent_fast; +use smallvec::SmallVec; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; +use solana_system_interface::instruction::transfer; +use spl_token::instruction::close_account; + +#[inline] +pub fn handle_wsol(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 3]> { + let wsol_token_account = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &payer, + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); + + let mut insts = SmallVec::<[Instruction; 3]>::new(); + insts.extend([ + create_associated_token_account_idempotent_fast( + &payer, + &payer, + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ), + transfer(&payer, &wsol_token_account, amount_in), + spl_token::instruction::sync_native(&crate::constants::TOKEN_PROGRAM, &wsol_token_account) + .unwrap(), + ]); + + insts +} + +pub fn close_wsol(payer: &Pubkey) -> Instruction { + let wsol_token_account = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &payer, + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); + crate::common::fast_fn::get_cached_instruction( + crate::common::fast_fn::InstructionCacheKey::CloseWsolAccount { + payer: *payer, + wsol_token_account, + }, + || { + close_account( + &crate::constants::TOKEN_PROGRAM, + &wsol_token_account, + &payer, + &payer, + &[], + ) + .unwrap() + }, + ) +} + +#[inline] +pub fn create_wsol_ata(payer: &Pubkey) -> Instruction { + create_associated_token_account_idempotent_fast( + &payer, + &payer, + &crate::constants::WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ) +} diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 0508739..676796c 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -243,6 +243,9 @@ pub struct BonkParams { pub virtual_quote: u128, pub real_base: u128, pub real_quote: u128, + pub pool_state: Pubkey, + pub base_vault: Pubkey, + pub quote_vault: Pubkey, /// Token program ID /// Specifies the program used by the token, usually spl_token::ID or spl_token_2022::ID pub mint_token_program: Pubkey, @@ -274,6 +277,9 @@ impl BonkParams { virtual_quote: trade_info.virtual_quote as u128, real_base: trade_info.real_base_after as u128, real_quote: trade_info.real_quote_after as u128, + pool_state: trade_info.pool_state, + base_vault: trade_info.base_vault, + quote_vault: trade_info.quote_vault, mint_token_program: trade_info.base_token_program, platform_config: trade_info.platform_config, platform_associated_account: trade_info.platform_associated_account, @@ -327,6 +333,9 @@ impl BonkParams { virtual_quote: DEFAULT_VIRTUAL_QUOTE, real_base: real_base, real_quote: real_quote, + pool_state: trade_info.pool_state, + base_vault: trade_info.base_vault, + quote_vault: trade_info.quote_vault, mint_token_program: trade_info.base_token_program, platform_config: trade_info.platform_config, platform_associated_account: trade_info.platform_associated_account, @@ -360,6 +369,9 @@ impl BonkParams { virtual_quote: pool_data.virtual_quote as u128, real_base: pool_data.real_base as u128, real_quote: pool_data.real_quote as u128, + pool_state: pool_address, + base_vault: pool_data.base_vault, + quote_vault: pool_data.quote_vault, mint_token_program: token_account.owner, platform_config: pool_data.platform_config, platform_associated_account,