2025-07-10 02:38:06 +08:00
|
|
|
use anyhow::{anyhow, Result};
|
2025-08-18 18:00:14 +08:00
|
|
|
use solana_sdk::instruction::Instruction;
|
2025-07-10 02:38:06 +08:00
|
|
|
use spl_associated_token_account::{
|
|
|
|
|
get_associated_token_address, instruction::create_associated_token_account,
|
|
|
|
|
};
|
|
|
|
|
use spl_token::instruction::close_account;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2025-07-10 18:14:21 +08:00
|
|
|
constants,
|
|
|
|
|
trading::pumpfun::common::{
|
2025-08-31 22:54:08 +08:00
|
|
|
get_bonding_curve_pda, get_fee_config_pda, get_global_volume_accumulator_pda, get_user_volume_accumulator_pda
|
2025-07-10 18:14:21 +08:00
|
|
|
},
|
2025-08-18 18:00:14 +08:00
|
|
|
utils::calc::{
|
|
|
|
|
common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
|
|
|
|
|
pumpfun::{get_buy_token_amount_from_sol_amount, get_sell_sol_amount_from_token_amount},
|
|
|
|
|
},
|
2025-07-10 02:38:06 +08:00
|
|
|
};
|
|
|
|
|
|
2025-07-10 18:14:21 +08:00
|
|
|
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer};
|
2025-07-10 02:38:06 +08:00
|
|
|
|
|
|
|
|
use crate::{
|
2025-07-10 18:14:21 +08:00
|
|
|
constants::pumpfun::global_constants::FEE_RECIPIENT,
|
|
|
|
|
constants::trade::trade::DEFAULT_SLIPPAGE,
|
2025-07-10 02:38:06 +08:00
|
|
|
trading::core::{
|
|
|
|
|
params::{BuyParams, PumpFunParams, SellParams},
|
|
|
|
|
traits::InstructionBuilder,
|
|
|
|
|
},
|
2025-08-18 18:00:14 +08:00
|
|
|
trading::pumpfun::common::get_creator_vault_pda,
|
2025-07-10 02:38:06 +08:00
|
|
|
};
|
|
|
|
|
|
2025-08-18 18:00:14 +08:00
|
|
|
/// Instruction builder for PumpFun protocol
|
2025-07-10 02:38:06 +08:00
|
|
|
pub struct PumpFunInstructionBuilder;
|
|
|
|
|
|
|
|
|
|
#[async_trait::async_trait]
|
|
|
|
|
impl InstructionBuilder for PumpFunInstructionBuilder {
|
|
|
|
|
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
|
2025-08-18 18:00:14 +08:00
|
|
|
// Get PumpFun specific parameters
|
2025-07-10 02:38:06 +08:00
|
|
|
let protocol_params = params
|
|
|
|
|
.protocol_params
|
|
|
|
|
.as_any()
|
|
|
|
|
.downcast_ref::<PumpFunParams>()
|
|
|
|
|
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
|
|
|
|
|
|
2025-07-10 23:54:49 +08:00
|
|
|
if params.sol_amount == 0 {
|
2025-07-10 02:38:06 +08:00
|
|
|
return Err(anyhow!("Amount cannot be zero"));
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-07 18:07:45 +08:00
|
|
|
let bonding_curve = &protocol_params.bonding_curve;
|
2025-07-10 02:38:06 +08:00
|
|
|
|
|
|
|
|
let max_sol_cost = calculate_with_slippage_buy(
|
2025-07-10 23:54:49 +08:00
|
|
|
params.sol_amount,
|
2025-07-10 18:14:21 +08:00
|
|
|
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
2025-07-10 02:38:06 +08:00
|
|
|
);
|
2025-08-23 20:37:03 +08:00
|
|
|
let creator_vault_pda = protocol_params.creator_vault;
|
|
|
|
|
|
2025-09-07 18:07:45 +08:00
|
|
|
// Optimize creator lookup - avoid PDA calculation if not default
|
|
|
|
|
let creator = if creator_vault_pda == Pubkey::default() {
|
|
|
|
|
Pubkey::default()
|
|
|
|
|
} else {
|
|
|
|
|
// Fast check against cached default creator vault
|
|
|
|
|
static DEFAULT_CREATOR_VAULT: std::sync::LazyLock<Option<Pubkey>> =
|
|
|
|
|
std::sync::LazyLock::new(|| get_creator_vault_pda(&Pubkey::default()));
|
|
|
|
|
|
|
|
|
|
if Some(creator_vault_pda) == *DEFAULT_CREATOR_VAULT {
|
|
|
|
|
Pubkey::default()
|
|
|
|
|
} else {
|
|
|
|
|
creator_vault_pda
|
2025-08-23 20:37:03 +08:00
|
|
|
}
|
2025-09-07 18:07:45 +08:00
|
|
|
};
|
2025-07-10 02:38:06 +08:00
|
|
|
|
2025-08-18 18:00:14 +08:00
|
|
|
let buy_token_amount = get_buy_token_amount_from_sol_amount(
|
|
|
|
|
bonding_curve.virtual_token_reserves as u128,
|
|
|
|
|
bonding_curve.virtual_sol_reserves as u128,
|
|
|
|
|
bonding_curve.real_token_reserves as u128,
|
2025-08-23 20:37:03 +08:00
|
|
|
creator,
|
2025-08-18 18:00:14 +08:00
|
|
|
params.sol_amount,
|
|
|
|
|
);
|
2025-07-10 02:38:06 +08:00
|
|
|
|
2025-09-07 18:07:45 +08:00
|
|
|
let mut instructions = Vec::with_capacity(2);
|
2025-07-10 02:38:06 +08:00
|
|
|
|
2025-08-18 18:00:14 +08:00
|
|
|
// Create associated token account
|
2025-07-10 02:38:06 +08:00
|
|
|
instructions.push(create_associated_token_account(
|
|
|
|
|
¶ms.payer.pubkey(),
|
|
|
|
|
¶ms.payer.pubkey(),
|
|
|
|
|
¶ms.mint,
|
|
|
|
|
&constants::pumpfun::accounts::TOKEN_PROGRAM,
|
|
|
|
|
));
|
|
|
|
|
|
2025-08-18 18:00:14 +08:00
|
|
|
// Create buy instruction
|
2025-07-10 02:38:06 +08:00
|
|
|
instructions.push(buy(
|
|
|
|
|
params.payer.as_ref(),
|
|
|
|
|
¶ms.mint,
|
|
|
|
|
&bonding_curve.account,
|
|
|
|
|
&creator_vault_pda,
|
|
|
|
|
&FEE_RECIPIENT,
|
2025-08-18 18:00:14 +08:00
|
|
|
Buy { _amount: buy_token_amount, _max_sol_cost: max_sol_cost },
|
2025-07-10 02:38:06 +08:00
|
|
|
));
|
|
|
|
|
|
|
|
|
|
Ok(instructions)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
|
2025-08-18 18:00:14 +08:00
|
|
|
// Get PumpFun specific parameters
|
|
|
|
|
let protocol_params = params
|
|
|
|
|
.protocol_params
|
|
|
|
|
.as_any()
|
|
|
|
|
.downcast_ref::<PumpFunParams>()
|
|
|
|
|
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
|
|
|
|
|
|
2025-09-07 18:07:45 +08:00
|
|
|
let bonding_curve = &protocol_params.bonding_curve;
|
2025-08-18 18:00:14 +08:00
|
|
|
|
2025-07-10 23:54:49 +08:00
|
|
|
let token_amount = if let Some(amount) = params.token_amount {
|
2025-07-10 02:38:06 +08:00
|
|
|
if amount == 0 {
|
|
|
|
|
return Err(anyhow!("Amount cannot be zero"));
|
|
|
|
|
}
|
|
|
|
|
amount
|
|
|
|
|
} else {
|
|
|
|
|
return Err(anyhow!("Amount token is required"));
|
|
|
|
|
};
|
2025-08-23 20:37:03 +08:00
|
|
|
let creator_vault_pda = protocol_params.creator_vault;
|
2025-07-10 02:38:06 +08:00
|
|
|
let ata = get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint);
|
|
|
|
|
|
2025-08-23 20:37:03 +08:00
|
|
|
let mut creator = Pubkey::default();
|
|
|
|
|
if let Some(default_creator_ata) = get_creator_vault_pda(&creator) {
|
|
|
|
|
if default_creator_ata != creator_vault_pda {
|
|
|
|
|
creator = creator_vault_pda;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-18 18:00:14 +08:00
|
|
|
let sol_amount = get_sell_sol_amount_from_token_amount(
|
|
|
|
|
bonding_curve.virtual_token_reserves as u128,
|
|
|
|
|
bonding_curve.virtual_sol_reserves as u128,
|
2025-08-23 20:37:03 +08:00
|
|
|
creator,
|
2025-08-18 18:00:14 +08:00
|
|
|
token_amount,
|
|
|
|
|
);
|
|
|
|
|
let min_sol_output = calculate_with_slippage_sell(
|
|
|
|
|
sol_amount,
|
|
|
|
|
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
|
|
|
|
);
|
2025-07-10 02:38:06 +08:00
|
|
|
|
|
|
|
|
let mut instructions = vec![sell(
|
|
|
|
|
params.payer.as_ref(),
|
|
|
|
|
¶ms.mint,
|
|
|
|
|
&creator_vault_pda,
|
|
|
|
|
&FEE_RECIPIENT,
|
2025-08-18 18:00:14 +08:00
|
|
|
Sell { _amount: token_amount, _min_sol_output: min_sol_output },
|
2025-07-10 02:38:06 +08:00
|
|
|
)];
|
|
|
|
|
|
2025-08-18 18:00:14 +08:00
|
|
|
// If selling all tokens, close the account
|
|
|
|
|
if protocol_params.close_token_account_when_sell.unwrap_or(false) {
|
2025-07-10 02:38:06 +08:00
|
|
|
instructions.push(close_account(
|
|
|
|
|
&spl_token::ID,
|
|
|
|
|
&ata,
|
|
|
|
|
¶ms.payer.pubkey(),
|
|
|
|
|
¶ms.payer.pubkey(),
|
|
|
|
|
&[¶ms.payer.pubkey()],
|
|
|
|
|
)?);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
constants::pumpfun::accounts::PUMPFUN,
|
|
|
|
|
&args.data(),
|
|
|
|
|
vec![
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::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(constants::pumpfun::accounts::SYSTEM_PROGRAM, false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false),
|
|
|
|
|
AccountMeta::new(*creator_vault_pda, false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false),
|
2025-08-01 22:59:39 +08:00
|
|
|
AccountMeta::new(get_global_volume_accumulator_pda().unwrap(), false),
|
|
|
|
|
AccountMeta::new(get_user_volume_accumulator_pda(&payer.pubkey()).unwrap(), false),
|
2025-08-31 22:54:08 +08:00
|
|
|
AccountMeta::new_readonly(get_fee_config_pda().unwrap(), false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::FEE_PROGRAM, false),
|
2025-07-10 02:38:06 +08:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
constants::pumpfun::accounts::PUMPFUN,
|
|
|
|
|
&args.data(),
|
|
|
|
|
vec![
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::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(constants::pumpfun::accounts::SYSTEM_PROGRAM, false),
|
|
|
|
|
AccountMeta::new(*creator_vault_pda, false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::TOKEN_PROGRAM, false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::EVENT_AUTHORITY, false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::PUMPFUN, false),
|
2025-08-31 22:54:08 +08:00
|
|
|
AccountMeta::new_readonly(get_fee_config_pda().unwrap(), false),
|
|
|
|
|
AccountMeta::new_readonly(constants::pumpfun::accounts::FEE_PROGRAM, false),
|
2025-07-10 02:38:06 +08:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
}
|