refactor: simplify instruction builders and optimize transaction construction

- Remove redundant helper methods in instruction builders (bonk, pumpfun, pumpswap, raydium)
- Inline buy/sell instruction construction for better performance
- Remove unnecessary Buy/Sell structs and helper functions in pumpfun
- Consolidate instruction building logic to reduce code duplication
- Fix pointer dereference issue in parallel execution
- Improve code maintainability and reduce complexity
This commit is contained in:
ysq
2025-09-07 19:50:27 +08:00
parent 21d6714c25
commit 51253c7aaf
6 changed files with 101 additions and 247 deletions
+1 -19
View File
@@ -31,20 +31,6 @@ impl InstructionBuilder for BonkInstructionBuilder {
if params.sol_amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
self.build_buy_instructions_with_accounts(params).await
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
self.build_sell_instructions_with_accounts(params).await
}
}
impl BonkInstructionBuilder {
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
@@ -179,11 +165,7 @@ impl BonkInstructionBuilder {
Ok(instructions)
}
/// Build sell instructions with provided account information
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
+69 -107
View File
@@ -1,5 +1,5 @@
use anyhow::{anyhow, Result};
use solana_sdk::instruction::Instruction;
use solana_sdk::{instruction::Instruction, signer::Signer};
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account,
};
@@ -17,7 +17,7 @@ use crate::{
},
};
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer};
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey};
use crate::{
constants::trade::trade::DEFAULT_SLIPPAGE,
@@ -85,14 +85,43 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
&accounts::TOKEN_PROGRAM,
));
// Create buy instruction data
let mut buy_data = Vec::with_capacity(8 + 8 + 8);
buy_data.extend_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // discriminator
buy_data.extend_from_slice(&buy_token_amount.to_le_bytes());
buy_data.extend_from_slice(&max_sol_cost.to_le_bytes());
// Create buy instruction
instructions.push(buy(
params.payer.as_ref(),
&params.mint,
&bonding_curve.account,
&creator_vault_pda,
&FEE_RECIPIENT,
Buy { _amount: buy_token_amount, _max_sol_cost: max_sol_cost },
instructions.push(Instruction::new_with_bytes(
accounts::PUMPFUN,
&buy_data,
vec![
AccountMeta::new_readonly(global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new(FEE_RECIPIENT, false),
AccountMeta::new_readonly(params.mint, false),
AccountMeta::new(bonding_curve.account, false),
AccountMeta::new(
get_associated_token_address(&bonding_curve.account, &params.mint),
false,
),
AccountMeta::new(
get_associated_token_address(&params.payer.pubkey(), &params.mint),
false,
),
AccountMeta::new(params.payer.pubkey(), true),
AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false),
AccountMeta::new(creator_vault_pda, false),
AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(accounts::PUMPFUN, false),
AccountMeta::new(get_global_volume_accumulator_pda().unwrap(), false),
AccountMeta::new(
get_user_volume_accumulator_pda(&params.payer.pubkey()).unwrap(),
false,
),
AccountMeta::new_readonly(get_fee_config_pda().unwrap(), false),
AccountMeta::new_readonly(accounts::FEE_PROGRAM, false),
],
));
Ok(instructions)
@@ -137,12 +166,37 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let mut instructions = vec![sell(
params.payer.as_ref(),
&params.mint,
&creator_vault_pda,
&FEE_RECIPIENT,
Sell { _amount: token_amount, _min_sol_output: min_sol_output },
// Create sell instruction data
let mut sell_data = Vec::with_capacity(8 + 8 + 8);
sell_data.extend_from_slice(&[51, 230, 133, 164, 1, 127, 131, 173]); // discriminator
sell_data.extend_from_slice(&token_amount.to_le_bytes());
sell_data.extend_from_slice(&min_sol_output.to_le_bytes());
let bonding_curve = get_bonding_curve_pda(&params.mint).unwrap();
// Create sell instruction
let mut instructions = vec![Instruction::new_with_bytes(
accounts::PUMPFUN,
&sell_data,
vec![
AccountMeta::new_readonly(global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new(FEE_RECIPIENT, false),
AccountMeta::new_readonly(params.mint, false),
AccountMeta::new(bonding_curve, false),
AccountMeta::new(get_associated_token_address(&bonding_curve, &params.mint), false),
AccountMeta::new(
get_associated_token_address(&params.payer.pubkey(), &params.mint),
false,
),
AccountMeta::new(params.payer.pubkey(), true),
AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false),
AccountMeta::new(creator_vault_pda, false),
AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(accounts::PUMPFUN, false),
AccountMeta::new_readonly(get_fee_config_pda().unwrap(), false),
AccountMeta::new_readonly(accounts::FEE_PROGRAM, false),
],
)];
// If selling all tokens, close the account
@@ -159,95 +213,3 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
Ok(instructions)
}
}
pub struct Buy {
pub _amount: u64,
pub _max_sol_cost: u64,
}
impl Buy {
pub fn data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[102, 6, 61, 18, 1, 218, 235, 234]); // discriminator
data.extend_from_slice(&self._amount.to_le_bytes());
data.extend_from_slice(&self._max_sol_cost.to_le_bytes());
data
}
}
pub struct Sell {
pub _amount: u64,
pub _min_sol_output: u64,
}
impl Sell {
pub fn data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(8 + 8 + 8);
data.extend_from_slice(&[51, 230, 133, 164, 1, 127, 131, 173]); // discriminator
data.extend_from_slice(&self._amount.to_le_bytes());
data.extend_from_slice(&self._min_sol_output.to_le_bytes());
data
}
}
pub fn buy(
payer: &Keypair,
mint: &Pubkey,
bonding_curve_pda: &Pubkey,
creator_vault_pda: &Pubkey,
fee_recipient: &Pubkey,
args: Buy,
) -> Instruction {
Instruction::new_with_bytes(
accounts::PUMPFUN,
&args.data(),
vec![
AccountMeta::new_readonly(global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new(*fee_recipient, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new(*bonding_curve_pda, false),
AccountMeta::new(get_associated_token_address(bonding_curve_pda, mint), false),
AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false),
AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false),
AccountMeta::new(*creator_vault_pda, false),
AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(accounts::PUMPFUN, false),
AccountMeta::new(get_global_volume_accumulator_pda().unwrap(), false),
AccountMeta::new(get_user_volume_accumulator_pda(&payer.pubkey()).unwrap(), false),
AccountMeta::new_readonly(get_fee_config_pda().unwrap(), false),
AccountMeta::new_readonly(accounts::FEE_PROGRAM, false),
],
)
}
pub fn sell(
payer: &Keypair,
mint: &Pubkey,
creator_vault_pda: &Pubkey,
fee_recipient: &Pubkey,
args: Sell,
) -> Instruction {
let bonding_curve: Pubkey = get_bonding_curve_pda(mint).unwrap();
Instruction::new_with_bytes(
accounts::PUMPFUN,
&args.data(),
vec![
AccountMeta::new_readonly(global_constants::GLOBAL_ACCOUNT, false),
AccountMeta::new(*fee_recipient, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new(bonding_curve, false),
AccountMeta::new(get_associated_token_address(&bonding_curve, mint), false),
AccountMeta::new(get_associated_token_address(&payer.pubkey(), mint), false),
AccountMeta::new(payer.pubkey(), true),
AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false),
AccountMeta::new(*creator_vault_pda, false),
AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false),
AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false),
AccountMeta::new_readonly(accounts::PUMPFUN, false),
AccountMeta::new_readonly(get_fee_config_pda().unwrap(), false),
AccountMeta::new_readonly(accounts::FEE_PROGRAM, false),
],
)
}
+28 -82
View File
@@ -35,78 +35,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
return Err(anyhow!("Amount cannot be zero"));
}
// Build instructions based on whether account information is provided
// Build instructions based on account information
let pool = protocol_params.pool;
let base_mint = protocol_params.base_mint;
let quote_mint = protocol_params.quote_mint;
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
let coin_creator_vault_ata = protocol_params.coin_creator_vault_ata;
let coin_creator_vault_authority = protocol_params.coin_creator_vault_authority;
let params_coin_creator_vault_ata = protocol_params.coin_creator_vault_ata;
let params_coin_creator_vault_authority = protocol_params.coin_creator_vault_authority;
let auto_handle_wsol = protocol_params.auto_handle_wsol;
let base_token_program = protocol_params.base_token_program;
let quote_token_program = protocol_params.quote_token_program;
self.build_buy_instructions_with_accounts(
params,
protocol_params.pool,
base_mint,
quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
coin_creator_vault_ata,
coin_creator_vault_authority,
protocol_params.auto_handle_wsol,
protocol_params.base_token_program,
protocol_params.quote_token_program,
)
.await
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// Get PumpSwap specific parameters
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
// Build instructions based on whether account information is provided
let base_mint = protocol_params.base_mint;
let quote_mint = protocol_params.quote_mint;
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
let coin_creator_vault_ata = protocol_params.coin_creator_vault_ata;
let coin_creator_vault_authority = protocol_params.coin_creator_vault_authority;
self.build_sell_instructions_with_accounts(
params,
protocol_params.pool,
base_mint,
quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
coin_creator_vault_ata,
coin_creator_vault_authority,
protocol_params.auto_handle_wsol,
protocol_params.base_token_program,
protocol_params.quote_token_program,
)
.await
}
}
impl PumpSwapInstructionBuilder {
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
pool: Pubkey,
base_mint: Pubkey,
quote_mint: Pubkey,
pool_base_token_reserves: u64,
pool_quote_token_reserves: u64,
params_coin_creator_vault_ata: Pubkey,
params_coin_creator_vault_authority: Pubkey,
auto_handle_wsol: bool,
base_token_program: Pubkey,
quote_token_program: Pubkey,
) -> Result<Vec<Instruction>> {
if base_mint != accounts::WSOL_TOKEN_ACCOUNT && quote_mint != accounts::WSOL_TOKEN_ACCOUNT {
return Err(anyhow!("Invalid base mint and quote mint"));
}
@@ -310,21 +250,26 @@ impl PumpSwapInstructionBuilder {
Ok(instructions)
}
/// Build sell instructions with provided account information
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
pool: Pubkey,
base_mint: Pubkey,
quote_mint: Pubkey,
pool_base_token_reserves: u64,
pool_quote_token_reserves: u64,
params_coin_creator_vault_ata: Pubkey,
params_coin_creator_vault_authority: Pubkey,
auto_handle_wsol: bool,
base_token_program: Pubkey,
quote_token_program: Pubkey,
) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// Get PumpSwap specific parameters
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
// Build instructions based on account information
let pool = protocol_params.pool;
let base_mint = protocol_params.base_mint;
let quote_mint = protocol_params.quote_mint;
let pool_base_token_reserves = protocol_params.pool_base_token_reserves;
let pool_quote_token_reserves = protocol_params.pool_quote_token_reserves;
let params_coin_creator_vault_ata = protocol_params.coin_creator_vault_ata;
let params_coin_creator_vault_authority = protocol_params.coin_creator_vault_authority;
let auto_handle_wsol = protocol_params.auto_handle_wsol;
let base_token_program = protocol_params.base_token_program;
let quote_token_program = protocol_params.quote_token_program;
if base_mint != accounts::WSOL_TOKEN_ACCOUNT && quote_mint != accounts::WSOL_TOKEN_ACCOUNT {
return Err(anyhow!("Invalid base mint and quote mint"));
}
@@ -503,3 +448,4 @@ impl PumpSwapInstructionBuilder {
Ok(instructions)
}
}
+1 -19
View File
@@ -23,20 +23,6 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder {
if params.sol_amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
self.build_buy_instructions_with_accounts(params).await
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
self.build_sell_instructions_with_accounts(params).await
}
}
impl RaydiumAmmV4InstructionBuilder {
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
@@ -147,11 +133,7 @@ impl RaydiumAmmV4InstructionBuilder {
Ok(instructions)
}
/// Build sell instructions with provided account information
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
+1 -19
View File
@@ -26,20 +26,6 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
if params.sol_amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
self.build_buy_instructions_with_accounts(params).await
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
self.build_sell_instructions_with_accounts(params).await
}
}
impl RaydiumCpmmInstructionBuilder {
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
@@ -160,11 +146,7 @@ impl RaydiumCpmmInstructionBuilder {
Ok(instructions)
}
/// Build sell instructions with provided account information
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
+1 -1
View File
@@ -69,7 +69,7 @@ pub async fn parallel_execute_with_tips(
let transaction = build_transaction(
payer,
&priority_fee,
(*instructions).clone(),
instructions.as_ref().clone(),
lookup_table_key,
recent_blockhash,
data_size_limit,