rebuild sdk dir

This commit is contained in:
wood
2025-07-10 02:38:06 +08:00
parent 1540fb6469
commit 8f79c3bfd9
24 changed files with 348 additions and 195 deletions
+107
View File
@@ -0,0 +1,107 @@
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::swqos::SwqosClient;
use crate::trading::{
core::params::{PumpSwapParams, BonkParams},
factory::Protocol,
BuyParams, TradeFactory,
};
use crate::{common::PriorityFee, SolanaRpcClient};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
// Buy tokens from a Pumpswap pool
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建协议特定参数
let protocol_params = Box::new(BonkParams {
auto_handle_wsol: auto_handle_wsol,
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: Pubkey::default(),
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
// 执行买入
executor.buy(buy_params).await?;
Ok(())
}
// Buy tokens using a MEV service
pub async fn buy_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建协议特定参数
let protocol_params = Box::new(BonkParams {
auto_handle_wsol: auto_handle_wsol,
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: Pubkey::default(),
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
// 执行买入
executor.buy_with_tip(buy_with_tip_params).await?;
Ok(())
}
+75
View File
@@ -0,0 +1,75 @@
use anyhow::anyhow;
use solana_sdk::pubkey::Pubkey;
use spl_associated_token_account::get_associated_token_address;
use crate::{common::SolanaRpcClient, constants};
pub fn get_amount_out(
amount_in: u64,
protocol_fee_rate: u128,
platform_fee_rate: u128,
share_fee_rate: u128,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
slippage_basis_points: u128,
) -> u64 {
let amount_in_u128 = amount_in as u128;
let protocol_fee = (amount_in_u128 * protocol_fee_rate / 10000) as u128;
let platform_fee = (amount_in_u128 * platform_fee_rate / 10000) as u128;
let share_fee = (amount_in_u128 * share_fee_rate / 10000) as u128;
let amount_in_net = amount_in_u128
.checked_sub(protocol_fee)
.unwrap()
.checked_sub(platform_fee)
.unwrap()
.checked_sub(share_fee)
.unwrap();
let input_reserve = virtual_quote.checked_add(real_quote_before).unwrap();
let output_reserve = virtual_base.checked_sub(real_base_before).unwrap();
let numerator = amount_in_net.checked_mul(output_reserve).unwrap();
let denominator = input_reserve.checked_add(amount_in_net).unwrap();
let mut amount_out = numerator.checked_div(denominator).unwrap();
amount_out = amount_out - (amount_out * slippage_basis_points) / 10000;
amount_out as u64
}
pub fn get_pool_pda(base_mint: &Pubkey, quote_mint: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 3] = &[
constants::bonk::seeds::POOL_SEED,
base_mint.as_ref(),
quote_mint.as_ref(),
];
let program_id: &Pubkey = &constants::bonk::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<Pubkey> {
let seeds: &[&[u8]; 3] = &[
constants::bonk::seeds::POOL_VAULT_SEED,
pool_state.as_ref(),
mint.as_ref(),
];
let program_id: &Pubkey = &constants::bonk::accounts::BONK;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
pub async fn get_token_balance(
rpc: &SolanaRpcClient,
payer: &Pubkey,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
println!("payer: {:?}", payer);
println!("mint: {:?}", mint);
let ata = get_associated_token_address(payer, mint);
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance
.amount
.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
Ok(balance_u64)
}
+4
View File
@@ -0,0 +1,4 @@
pub mod buy;
pub mod sell;
pub mod common;
pub mod pool;
+62
View File
@@ -0,0 +1,62 @@
use crate::{common::SolanaRpcClient, constants::bonk::accounts};
use anyhow::anyhow;
use borsh::BorshDeserialize;
use solana_sdk::pubkey::Pubkey;
#[derive(Debug, Clone, BorshDeserialize)]
pub struct VestingSchedule {
pub total_locked_amount: u64,
pub cliff_period: u64,
pub unlock_period: u64,
pub start_time: u64,
pub allocated_share_amount: u64,
}
#[derive(Debug, Clone, BorshDeserialize)]
pub struct Pool {
pub epoch: u64,
pub auth_bump: u8,
pub status: u8,
pub base_decimals: u8,
pub quote_decimals: u8,
pub migrate_type: u8,
pub supply: u64,
pub total_base_sell: u64,
pub virtual_base: u64,
pub virtual_quote: u64,
pub real_base: u64,
pub real_quote: u64,
pub total_quote_fund_raising: u64,
pub quote_protocol_fee: u64,
pub platform_fee: u64,
pub migrate_fee: u64,
pub vesting_schedule: VestingSchedule,
pub global_config: Pubkey,
pub platform_config: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub base_vault: Pubkey,
pub quote_vault: Pubkey,
pub creator: Pubkey,
pub padding: [u64; 8],
}
impl Pool {
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
let pool = Pool::try_from_slice(&data[8..])?;
Ok(pool)
}
pub async fn fetch(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::BONK {
return Err(anyhow!("Account is not owned by Bonk program"));
}
Self::from_bytes(&account.data)
}
}
+240
View File
@@ -0,0 +1,240 @@
use anyhow::anyhow;
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::pumpswap::common::get_token_balance;
use crate::swqos::SwqosClient;
use crate::trading::{
core::params::BonkParams, factory::Protocol, SellParams, TradeFactory,
};
// Sell tokens to a Pumpswap pool
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建PumpFun协议参数
let protocol_params = Box::new(BonkParams {
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator: Pubkey::default(),
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
// 执行卖出交易
executor.sell(sell_params).await?;
Ok(())
}
// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell(
rpc,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell(
rpc,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
// Sell tokens using a MEV service
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::Bonk);
// 创建PumpFun协议参数
let protocol_params = Box::new(BonkParams {
virtual_base: Some(virtual_base),
virtual_quote: Some(virtual_quote),
real_base_before: Some(real_base_before),
real_quote_before: Some(real_quote_before),
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator: Pubkey::default(),
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
let sell_with_tip_params = sell_params.with_tip(swqos_clients);
// 执行卖出交易
executor.sell_with_tip(sell_with_tip_params).await?;
Ok(())
}
// Sell tokens by percentage using a MEV service
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
// Sell tokens by amount using a MEV service
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
virtual_base: u128,
virtual_quote: u128,
real_base_before: u128,
real_quote_before: u128,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
+1 -2
View File
@@ -1,11 +1,10 @@
use anyhow::{anyhow, Result};
use std::sync::Arc;
use crate::trading::protocols::bonk::BonkInstructionBuilder;
use crate::instruction::{bonk::BonkInstructionBuilder, pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder};
use super::{
core::{executor::GenericTradeExecutor, traits::TradeExecutor},
protocols::{pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder},
};
/// 支持的交易协议
+3 -1
View File
@@ -1,7 +1,9 @@
pub mod common;
pub mod core;
pub mod factory;
pub mod protocols;
pub mod bonk;
pub mod pumpfun;
pub mod pumpswap;
pub use core::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams};
pub use core::traits::{InstructionBuilder, TradeExecutor};
-284
View File
@@ -1,284 +0,0 @@
use anyhow::{anyhow, Result};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use crate::{
constants::bonk::{
accounts, trade::DEFAULT_SLIPPAGE, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR,
},
bonk::{
common::{get_amount_out, get_pool_pda, get_token_balance, get_vault_pda},
pool::Pool,
},
trading::core::{
params::{BuyParams, BonkParams, SellParams},
traits::InstructionBuilder,
},
};
/// Bonk协议的指令构建器
pub struct BonkInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for BonkInstructionBuilder {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
if params.amount_sol == 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 {
/// 使用提供的账户信息构建买入指令
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
) -> Result<Vec<Instruction>> {
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
let pool_state = get_pool_pda(&params.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
// 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let base_vault_account = get_vault_pda(&pool_state, &params.mint).unwrap();
let quote_vault_account =
get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
let mut virtual_base = protocol_params.virtual_base.unwrap_or(0);
let mut virtual_quote = protocol_params.virtual_quote.unwrap_or(0);
let mut real_base_before = protocol_params.real_base_before.unwrap_or(0);
let mut real_quote_before = protocol_params.real_quote_before.unwrap_or(0);
if virtual_base == 0
|| virtual_quote == 0
|| real_base_before == 0
|| real_quote_before == 0
{
let pool = Pool::fetch(params.rpc.as_ref().unwrap(), &pool_state).await?;
virtual_base = pool.virtual_base as u128;
virtual_quote = pool.virtual_quote as u128;
real_base_before = pool.real_base as u128;
real_quote_before = pool.real_quote as u128;
}
let amount_in: u64 = params.amount_sol;
let share_fee_rate: u64 = 0;
let minimum_amount_out: u64 = get_amount_out(
amount_in,
accounts::PROTOCOL_FEE_RATE,
accounts::PLATFORM_FEE_RATE,
accounts::SHARE_FEE_RATE,
virtual_base,
virtual_quote,
real_base_before,
real_quote_before,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
);
let mut instructions = vec![];
if protocol_params.auto_handle_wsol {
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
instructions.push(
// 将SOL转入wSOL ATA账户
solana_sdk::system_instruction::transfer(
&params.payer.pubkey(),
&user_quote_token_account,
amount_in,
),
);
// 同步wSOL余额
instructions.push(
spl_token::instruction::sync_native(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
)
.unwrap(),
);
}
// 创建用户的基础代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
// 创建买入指令
let accounts = vec![
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_CONFIG, false), // Global Config (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::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(params.mint, false), // Base Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Quote Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Base Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Quote Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // Event Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::BONK, false), // Program (readonly)
];
// 创建指令数据
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());
instructions.push(Instruction {
program_id: accounts::BONK,
accounts,
data,
});
if protocol_params.auto_handle_wsol {
// 关闭wSOL ATA账户,回收租金
instructions.push(
spl_token::instruction::close_account(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[],
)
.unwrap(),
);
}
Ok(instructions)
}
/// 使用提供的账户信息构建卖出指令
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 获取代币余额
let mut amount = params.amount_token;
if params.amount_token.is_none() || params.amount_token.unwrap_or(0) == 0 {
let balance_u64 =
get_token_balance(rpc.as_ref(), &params.payer.pubkey(), &params.mint).await?;
amount = Some(balance_u64);
}
let amount = amount.unwrap_or(0);
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// 计算预期的SOL数量
let minimum_amount_out: u64 = 1;
let pool_state = get_pool_pda(&params.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
// 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let base_vault_account = get_vault_pda(&pool_state, &params.mint).unwrap();
let quote_vault_account =
get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
let share_fee_rate: u64 = 0;
let mut instructions = vec![];
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
// 创建用户的代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
// 创建卖出指令
let accounts = vec![
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // Payer (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AUTHORITY, false), // Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_CONFIG, false), // Global Config (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::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(params.mint, false), // Base Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Quote Token Mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Base Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Quote Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // Event Authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::BONK, false), // Program (readonly)
];
// 创建指令数据
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());
instructions.push(Instruction {
program_id: accounts::BONK,
accounts,
data,
});
Ok(instructions)
}
}
-3
View File
@@ -1,3 +0,0 @@
pub mod pumpfun;
pub mod pumpswap;
pub mod bonk;
-144
View File
@@ -1,144 +0,0 @@
use anyhow::{anyhow, Result};
use solana_sdk::{
instruction::Instruction, native_token::sol_to_lamports, pubkey::Pubkey, signer::Signer,
};
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account,
};
use spl_token::instruction::close_account;
use std::sync::Arc;
use crate::{
accounts::BondingCurveAccount,
constants::{self, pumpfun::{global_constants::FEE_RECIPIENT, trade::DEFAULT_SLIPPAGE}, trade_type::SNIPER_BUY},
instruction,
pumpfun::common::{
calculate_with_slippage_buy, get_bonding_curve_account_v2, get_bonding_curve_pda,
get_buy_token_amount_from_sol_amount, get_creator_vault_pda, init_bonding_curve_account,
},
trading::core::{
params::{BuyParams, PumpFunParams, SellParams},
traits::InstructionBuilder,
},
};
/// PumpFun协议的指令构建器
pub struct PumpFunInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for PumpFunInstructionBuilder {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// 获取PumpFun特定参数
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpFunParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
if params.amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
let bonding_curve = if protocol_params.bonding_curve.is_some() {
protocol_params.bonding_curve.clone().unwrap()
} else {
return Err(anyhow!("Bonding curve not found"));
};
let max_sol_cost = calculate_with_slippage_buy(
params.amount_sol,
params
.slippage_basis_points
.unwrap_or(DEFAULT_SLIPPAGE),
);
let creator_vault_pda = bonding_curve.get_creator_vault_pda();
let mut buy_token_amount =
get_buy_token_amount_from_sol_amount(&bonding_curve, params.amount_sol);
if buy_token_amount <= 100 * 1_000_000_u64 {
buy_token_amount = if max_sol_cost > sol_to_lamports(0.01) {
25547619 * 1_000_000_u64
} else {
255476 * 1_000_000_u64
};
}
let mut instructions = vec![];
// 创建关联代币账户
instructions.push(create_associated_token_account(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&constants::pumpfun::accounts::TOKEN_PROGRAM,
));
// 创建买入指令
instructions.push(instruction::buy(
params.payer.as_ref(),
&params.mint,
&bonding_curve.account,
&creator_vault_pda,
&FEE_RECIPIENT,
instruction::Buy {
_amount: buy_token_amount,
_max_sol_cost: max_sol_cost,
},
));
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
let amount_token = if let Some(amount) = params.amount_token {
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
amount
} else {
return Err(anyhow!("Amount token is required"));
};
let creator_vault_pda = get_creator_vault_pda(&params.creator).unwrap();
let ata = get_associated_token_address(&params.payer.pubkey(), &params.mint);
// 获取代币余额
let balance_u64 = if let Some(rpc) = &params.rpc {
let balance = rpc.get_token_account_balance(&ata).await?;
balance
.amount
.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?
} else {
return Err(anyhow!("RPC client is required to get token balance"));
};
let mut amount_token = amount_token;
if amount_token > balance_u64 {
amount_token = balance_u64;
}
let mut instructions = vec![instruction::sell(
params.payer.as_ref(),
&params.mint,
&creator_vault_pda,
&FEE_RECIPIENT,
instruction::Sell {
_amount: amount_token,
_min_sol_output: 1,
},
)];
// 如果卖出全部代币,关闭账户
if amount_token >= balance_u64 {
instructions.push(close_account(
&spl_token::ID,
&ata,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)?);
}
Ok(instructions)
}
}
-428
View File
@@ -1,428 +0,0 @@
use anyhow::{anyhow, Result};
use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer};
use spl_associated_token_account::instruction::create_associated_token_account_idempotent;
use std::sync::Arc;
use crate::{
constants::pumpswap::{
accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR,
},
pumpswap::common::{
calculate_with_slippage_buy, calculate_with_slippage_sell, coin_creator_vault_ata,
coin_creator_vault_authority, find_pool, get_buy_token_amount, get_sell_sol_amount,
get_token_balance,
},
trading::core::{
params::{BuyParams, PumpSwapParams, SellParams},
traits::InstructionBuilder,
},
};
/// PumpSwap协议的指令构建器
pub struct PumpSwapInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for PumpSwapInstructionBuilder {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// 获取PumpSwap特定参数
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
if params.amount_sol == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// 根据是否提供了账户信息来构建指令
match (
&protocol_params.pool,
&protocol_params.pool_base_token_account,
&protocol_params.pool_quote_token_account,
&protocol_params.user_base_token_account,
&protocol_params.user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
self.build_buy_instructions_with_accounts(
params,
*pool,
*pool_base_token_account,
*pool_quote_token_account,
*user_base_token_account,
*user_quote_token_account,
protocol_params.auto_handle_wsol,
)
.await
}
_ => self.build_buy_instructions_auto_discover(params).await,
}
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// 获取PumpSwap特定参数
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpSwapParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
// 根据是否提供了账户信息来构建指令
match (
&protocol_params.pool,
&protocol_params.pool_base_token_account,
&protocol_params.pool_quote_token_account,
&protocol_params.user_base_token_account,
&protocol_params.user_quote_token_account,
) {
(
Some(pool),
Some(pool_base_token_account),
Some(pool_quote_token_account),
Some(user_base_token_account),
Some(user_quote_token_account),
) => {
self.build_sell_instructions_with_accounts(
params,
*pool,
*pool_base_token_account,
*pool_quote_token_account,
*user_base_token_account,
*user_quote_token_account,
)
.await
}
_ => self.build_sell_instructions_auto_discover(params).await,
}
}
}
impl PumpSwapInstructionBuilder {
/// 自动发现池和账户信息并构建买入指令
async fn build_buy_instructions_auto_discover(
&self,
params: &BuyParams,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 查找池
let pool = find_pool(rpc.as_ref(), &params.mint).await?;
// 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&params.mint,
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
self.build_buy_instructions_with_accounts(
params,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
true,
)
.await
}
/// 自动发现池和账户信息并构建卖出指令
async fn build_sell_instructions_auto_discover(
&self,
params: &SellParams,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 查找池
let pool = find_pool(rpc.as_ref(), &params.mint).await?;
// 创建用户代币账户
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
);
let user_quote_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
let pool_base_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&params.mint,
&accounts::TOKEN_PROGRAM,
);
let pool_quote_token_account =
spl_associated_token_account::get_associated_token_address_with_program_id(
&pool,
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
);
self.build_sell_instructions_with_accounts(
params,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
/// 使用提供的账户信息构建买入指令
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
pool: Pubkey,
pool_base_token_account: Pubkey,
pool_quote_token_account: Pubkey,
user_base_token_account: Pubkey,
user_quote_token_account: Pubkey,
auto_handle_wsol: bool,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 计算预期的代币数量
let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?;
// 计算滑点后的最大SOL数量
let max_sol_amount = calculate_with_slippage_buy(
params.amount_sol,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let mut instructions = vec![];
if auto_handle_wsol {
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
instructions.push(
// 将SOL转入wSOL ATA账户
solana_sdk::system_instruction::transfer(
&params.payer.pubkey(),
&user_quote_token_account,
max_sol_amount,
),
);
// 同步wSOL余额
instructions.push(
spl_token::instruction::sync_native(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
)
.unwrap(),
);
}
// 创建用户的基础代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator);
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
// 创建买入指令
let accounts = vec![
solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
];
// 创建指令数据
let mut data = vec![];
data.extend_from_slice(&BUY_DISCRIMINATOR);
data.extend_from_slice(&token_amount.to_le_bytes());
data.extend_from_slice(&max_sol_amount.to_le_bytes());
instructions.push(Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
});
if auto_handle_wsol {
// 关闭wSOL ATA账户,回收租金
instructions.push(
spl_token::instruction::close_account(
&accounts::TOKEN_PROGRAM,
&user_quote_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[],
)
.unwrap(),
);
}
Ok(instructions)
}
/// 使用提供的账户信息构建卖出指令
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
pool: Pubkey,
pool_base_token_account: Pubkey,
pool_quote_token_account: Pubkey,
user_base_token_account: Pubkey,
user_quote_token_account: Pubkey,
) -> Result<Vec<Instruction>> {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 获取代币余额
let mut amount = params.amount_token;
if params.amount_token.is_none() {
let (balance_u64, _) =
get_token_balance(rpc.as_ref(), params.payer.as_ref(), &params.mint).await?;
amount = Some(balance_u64);
}
let amount = amount.unwrap_or(0);
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
}
// 计算预期的SOL数量
let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?;
// 计算滑点后的最小SOL数量
let min_sol_amount = calculate_with_slippage_sell(
sol_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator);
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
let mut instructions = vec![];
// 插入wsol
instructions.push(
// 创建wSOL ATA账户,如果不存在
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&accounts::TOKEN_PROGRAM,
),
);
// 创建用户的代币账户
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&accounts::TOKEN_PROGRAM,
));
// 创建卖出指令
let accounts = vec![
solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly)
solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly)
solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account
solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account
solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account
solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly)
solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
accounts::ASSOCIATED_TOKEN_PROGRAM,
false,
), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly)
solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata
solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly)
];
// 创建指令数据
let mut data = vec![];
data.extend_from_slice(&SELL_DISCRIMINATOR);
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&min_sol_amount.to_le_bytes());
instructions.push(Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
data,
});
Ok(instructions)
}
}
+89
View File
@@ -0,0 +1,89 @@
use crate::accounts::BondingCurveAccount;
use crate::{
common::{PriorityFee, SolanaRpcClient},
swqos::SwqosClient,
trading::{core::params::PumpFunParams, factory::Protocol, BuyParams, TradeFactory},
};
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 250000;
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
bonding_curve: Option<Arc<BondingCurveAccount>>,
trade_type: String,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建协议特定参数
let protocol_params = Box::new(PumpFunParams {
trade_type: trade_type,
bonding_curve: bonding_curve,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc),
payer,
mint,
creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
// 执行买入
executor.buy(buy_params).await?;
Ok(())
}
pub async fn buy_with_tip(
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
bonding_curve: Option<Arc<BondingCurveAccount>>,
trade_type: String,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建协议特定参数
let protocol_params = Box::new(PumpFunParams {
trade_type: trade_type,
bonding_curve: bonding_curve,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: None,
payer,
mint,
creator,
amount_sol: buy_sol_cost,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
// 执行买入
executor.buy_with_tip(buy_with_tip_params).await?;
Ok(())
}
+352
View File
@@ -0,0 +1,352 @@
use anyhow::anyhow;
use borsh::BorshDeserialize;
use spl_token::instruction::close_account;
use tokio::sync::RwLock;
use std::{collections::HashMap, sync::Arc};
use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction
};
use spl_associated_token_account::get_associated_token_address;
use pumpfun_program::accounts::BondingCurveAccount as PumpfunBondingCurveAccount;
use crate::{accounts::{self, BondingCurveAccount}, common::{PriorityFee, SolanaRpcClient}, constants::{self, pumpfun::{self, global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::DEFAULT_SLIPPAGE}}, event_parser::protocols::pumpfun::PumpFunTradeEvent};
lazy_static::lazy_static! {
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<accounts::GlobalAccount>>> = RwLock::new(HashMap::new());
}
pub async fn transfer_sol(rpc: &SolanaRpcClient, payer: &Keypair, receive_wallet: &Pubkey, amount: u64) -> Result<(), anyhow::Error> {
if amount == 0 {
return Err(anyhow!("transfer_sol: Amount cannot be zero"));
}
let balance = get_sol_balance(rpc, &payer.pubkey()).await?;
if balance < amount {
return Err(anyhow!("Insufficient balance"));
}
let transfer_instruction = system_instruction::transfer(
&payer.pubkey(),
receive_wallet,
amount,
);
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[transfer_instruction],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
/// 关闭代币账户
///
/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。
///
/// # 参数
///
/// * `rpc` - Solana RPC客户端
/// * `payer` - 支付交易费用的账户
/// * `mint` - 代币的Mint地址
///
/// # 返回值
///
/// 返回一个Result,成功时返回(),失败时返回错误
pub async fn close_token_account(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(), anyhow::Error> {
// 获取关联代币账户地址
let ata = get_associated_token_address(&payer.pubkey(), mint);
// 检查账户是否存在
let account_exists = rpc.get_account(&ata).await.is_ok();
if !account_exists {
return Ok(()); // 如果账户不存在,直接返回成功
}
// 构建关闭账户指令
let close_account_ix = close_account(
&spl_token::ID,
&ata,
&payer.pubkey(),
&payer.pubkey(),
&[&payer.pubkey()],
)?;
// 构建交易
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[close_account_ix],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
);
// 发送交易
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
#[inline]
pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instruction> {
let mut instructions = Vec::with_capacity(2);
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit));
instructions.push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price));
instructions
}
// #[inline]
pub async fn get_token_balance(rpc: &SolanaRpcClient, payer: &Pubkey, mint: &Pubkey) -> Result<u64, anyhow::Error> {
let ata = get_associated_token_address(payer, mint);
// let account_data = rpc.get_account_data(&ata).await?;
// let token_account = Account::unpack(&account_data.as_slice())?;
// Ok(token_account.amount)
// println!("get_token_balance ata: {}", ata);
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
Ok(balance_u64)
}
#[inline]
pub async fn get_token_balance_and_ata(rpc: &SolanaRpcClient, payer: &Keypair, mint: &Pubkey) -> Result<(u64, Pubkey), anyhow::Error> {
let ata = get_associated_token_address(&payer.pubkey(), mint);
// let account_data = rpc.get_account_data(&ata).await?;
// let token_account = Account::unpack(&account_data)?;
// Ok((token_account.amount, ata))
let balance = rpc.get_token_account_balance(&ata).await?;
let balance_u64 = balance.amount.parse::<u64>()
.map_err(|_| anyhow!("Failed to parse token balance"))?;
if balance_u64 == 0 {
return Err(anyhow!("Balance is 0"));
}
Ok((balance_u64, ata))
}
#[inline]
pub async fn get_sol_balance(rpc: &SolanaRpcClient, account: &Pubkey) -> Result<u64, anyhow::Error> {
let balance = rpc.get_balance(account).await?;
Ok(balance)
}
#[inline]
pub fn get_global_pda() -> Pubkey {
static GLOBAL_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
Pubkey::find_program_address(&[constants::pumpfun::seeds::GLOBAL_SEED], &constants::pumpfun::accounts::PUMPFUN).0
});
*GLOBAL_PDA
}
#[inline]
pub fn get_mint_authority_pda() -> Pubkey {
static MINT_AUTHORITY_PDA: once_cell::sync::Lazy<Pubkey> = once_cell::sync::Lazy::new(|| {
Pubkey::find_program_address(&[constants::pumpfun::seeds::MINT_AUTHORITY_SEED], &constants::pumpfun::accounts::PUMPFUN).0
});
*MINT_AUTHORITY_PDA
}
#[inline]
pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::BONDING_CURVE_SEED, mint.as_ref()];
let program_id: &Pubkey = &constants::pumpfun::accounts::PUMPFUN;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
#[inline]
pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::CREATOR_VAULT_SEED, creator.as_ref()];
let program_id: &Pubkey = &constants::pumpfun::accounts::PUMPFUN;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
#[inline]
pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[
constants::pumpfun::seeds::METADATA_SEED,
constants::pumpfun::accounts::MPL_TOKEN_METADATA.as_ref(),
mint.as_ref(),
],
&constants::pumpfun::accounts::MPL_TOKEN_METADATA
).0
}
#[inline]
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result<Arc<accounts::GlobalAccount>, anyhow::Error> {
// let global = constants::global_constants::GLOBAL_ACCOUNT;
// if let Some(account) = ACCOUNT_CACHE.read().await.get(&global) {
// return Ok(account.clone());
// }
let global_account = accounts::GlobalAccount::new();
// let account = rpc.get_account(&global).await?;
// let global_account = bincode::deserialize::<accounts::GlobalAccount>(&account.data)?;
let global_account = Arc::new(global_account);
// ACCOUNT_CACHE.write().await.insert(global, global_account.clone());
Ok(global_account)
}
#[inline]
pub async fn get_initial_buy_price(global_account: &Arc<accounts::GlobalAccount>, amount_sol: u64) -> Result<u64, anyhow::Error> {
let buy_amount = global_account.get_initial_buy_price(amount_sol);
Ok(buy_amount)
}
#[inline]
pub async fn get_bonding_curve_account(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Arc<accounts::BondingCurveAccount>, Pubkey), anyhow::Error> {
let bonding_curve_pda = get_bonding_curve_pda(mint)
.ok_or(anyhow!("Bonding curve not found"))?;
let account = rpc.get_account(&bonding_curve_pda).await?;
if account.data.is_empty() {
return Err(anyhow!("Bonding curve not found"));
}
let bonding_curve = Arc::new(bincode::deserialize::<accounts::BondingCurveAccount>(&account.data)?);
Ok((bonding_curve, bonding_curve_pda))
}
#[inline]
pub async fn get_bonding_curve_account_v2(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Arc<PumpfunBondingCurveAccount>, Pubkey), anyhow::Error> {
let bonding_curve_pda = get_bonding_curve_pda(mint)
.ok_or(anyhow!("Bonding curve not found"))?;
let account = rpc.get_account(&bonding_curve_pda).await?;
if account.data.is_empty() {
return Err(anyhow!("Bonding curve not found"));
}
let bonding_curve = solana_sdk::borsh1::try_from_slice_unchecked::<PumpfunBondingCurveAccount>(&account.data)
.map_err(|e| anyhow::anyhow!("Failed to deserialize bonding curve account: {}", e))?;
Ok((Arc::new(bonding_curve), bonding_curve_pda))
}
#[inline]
pub fn get_buy_token_amount(
bonding_curve_account: &BondingCurveAccount,
buy_sol_cost: u64,
slippage_basis_points: Option<u64>,
) -> anyhow::Result<(u64, u64)> {
let buy_token = bonding_curve_account.get_buy_price(buy_sol_cost).map_err(|e| anyhow!(e))?;
let max_sol_cost = calculate_with_slippage_buy(buy_sol_cost, slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE));
Ok((buy_token, max_sol_cost))
}
pub fn get_buy_token_amount_from_sol_amount(
bonding_curve: &BondingCurveAccount,
amount: u64,
) -> u64 {
if amount == 0 {
return 0;
}
if bonding_curve.virtual_token_reserves == 0 {
return 0;
}
let total_fee_basis_points = FEE_BASIS_POINTS
+ if bonding_curve.creator != Pubkey::default() {
CREATOR_FEE
} else {
0
};
// 转为 u128 防止溢出
let amount_128 = amount as u128;
let total_fee_basis_points_128 = total_fee_basis_points as u128;
let input_amount = amount_128
.checked_mul(10_000)
.unwrap()
.checked_div(total_fee_basis_points_128 + 10_000)
.unwrap();
let virtual_token_reserves = bonding_curve.virtual_token_reserves as u128;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves as u128;
let real_token_reserves = bonding_curve.real_token_reserves as u128;
let denominator = virtual_sol_reserves + input_amount;
let tokens_received = input_amount
.checked_mul(virtual_token_reserves)
.unwrap()
.checked_div(denominator)
.unwrap();
tokens_received.min(real_token_reserves) as u64
}
#[inline]
pub async fn init_bonding_curve_account(
mint: &Pubkey,
dev_buy_token: u64,
dev_sol_cost: u64,
creator: Pubkey,
) -> Result<Arc<BondingCurveAccount>, anyhow::Error> {
let bonding_curve = BondingCurveAccount::new(mint, dev_buy_token, dev_sol_cost, creator);
let bonding_curve = Arc::new(bonding_curve);
Ok(bonding_curve)
}
#[inline]
pub fn get_buy_amount_with_slippage(amount_sol: u64, slippage_basis_points: Option<u64>) -> u64 {
let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE);
amount_sol + (amount_sol * slippage / 10000)
}
#[inline]
pub fn get_token_price(virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
let v_sol = virtual_sol_reserves as f64 / 100_000_000.0;
let v_tokens = virtual_token_reserves as f64 / 100_000.0;
v_sol / v_tokens
}
#[inline]
pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
if amount == 0 {
return 0;
}
let n: u128 = (trade_info.virtual_sol_reserves as u128) * (trade_info.virtual_token_reserves as u128);
let i: u128 = (trade_info.virtual_sol_reserves as u128) + (amount as u128);
let r: u128 = n / i + 1;
let s: u128 = (trade_info.virtual_token_reserves as u128) - r;
let s_u64 = s as u64;
s_u64.min(trade_info.real_token_reserves)
}
#[inline]
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points) / 10000
}
#[inline]
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
amount - (amount * basis_points) / 10000
}
+3
View File
@@ -0,0 +1,3 @@
pub mod buy;
pub mod sell;
pub mod common;
+184
View File
@@ -0,0 +1,184 @@
use crate::trading::{
core::params::PumpFunSellParams, factory::Protocol, SellParams, TradeFactory,
};
use crate::{
common::{PriorityFee, SolanaRpcClient},
swqos::SwqosClient,
};
use anyhow::anyhow;
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpFunSellParams {});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
// 执行卖出交易
executor.sell(sell_params).await?;
Ok(())
}
/// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let amount = amount_token * percent / 100;
sell(
rpc,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell(
rpc,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let amount = amount_token * percent / 100;
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
sell_with_tip(
rpc,
fee_clients,
payer,
mint,
creator,
amount,
priority_fee,
lookup_table_key,
recent_blockhash,
)
.await
}
/// Sell tokens using Jito
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
fee_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: u64,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpFun);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpFunSellParams {});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: Some(amount_token),
slippage_basis_points: None,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
let sell_with_tip_params = sell_params.with_tip(fee_clients);
// 执行卖出交易
executor.sell_with_tip(sell_with_tip_params).await?;
Ok(())
}
+111
View File
@@ -0,0 +1,111 @@
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::swqos::SwqosClient;
use crate::trading::{core::params::PumpSwapParams, factory::Protocol, BuyParams, TradeFactory};
use crate::{common::PriorityFee, SolanaRpcClient};
// Constants for compute budget
// Increased from 64KB to 256KB to handle larger transactions
const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024;
// Buy tokens from a Pumpswap pool
pub async fn buy(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建协议特定参数
let protocol_params = Box::new(PumpSwapParams {
pool: pool,
pool_base_token_account: pool_base_token_account,
pool_quote_token_account: pool_quote_token_account,
user_base_token_account: user_base_token_account,
user_quote_token_account: user_quote_token_account,
auto_handle_wsol: auto_handle_wsol,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: creator,
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
// 执行买入
executor.buy(buy_params).await?;
Ok(())
}
// Buy tokens using a MEV service
pub async fn buy_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_sol: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
auto_handle_wsol: bool,
) -> Result<(), anyhow::Error> {
// 创建执行器
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建协议特定参数
let protocol_params = Box::new(PumpSwapParams {
pool: pool,
pool_base_token_account: pool_base_token_account,
pool_quote_token_account: pool_quote_token_account,
user_base_token_account: user_base_token_account,
user_quote_token_account: user_quote_token_account,
auto_handle_wsol: auto_handle_wsol,
});
// 创建买入参数
let buy_params = BuyParams {
rpc: Some(rpc.clone()),
payer: payer,
mint: mint,
creator: creator,
amount_sol: amount_sol,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee,
lookup_table_key: lookup_table_key,
recent_blockhash: recent_blockhash,
data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT,
protocol_params,
};
let buy_with_tip_params = buy_params.with_tip(swqos_clients);
// 执行买入
executor.buy_with_tip(buy_with_tip_params).await?;
Ok(())
}
+86
View File
@@ -0,0 +1,86 @@
use anyhow::anyhow;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use crate::common::SolanaRpcClient;
use crate::trading::pumpswap;
// Calculate slippage for buy operations
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points / 10000)
}
// Calculate slippage for sell operations
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
if amount <= basis_points / 10000 {
1
} else {
amount - (amount * basis_points / 10000)
}
}
// Get token balance for a specific mint and owner
pub async fn get_token_balance(
rpc: &SolanaRpcClient,
owner: &Keypair,
mint: &Pubkey,
) -> Result<(u64, Pubkey), anyhow::Error> {
let ata = spl_associated_token_account::get_associated_token_address(&owner.pubkey(), mint);
match rpc.get_token_account_balance(&ata).await {
Ok(balance) => {
let amount = balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
Ok((amount, ata))
}
Err(_) => Ok((0, ata)),
}
}
// Find a pool for a specific mint
pub async fn find_pool(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<Pubkey, anyhow::Error> {
let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?;
Ok(pool_address)
}
// Calculate the amount of tokens to receive for a given SOL amount
pub async fn get_buy_token_amount(
rpc: &SolanaRpcClient,
pool: &Pubkey,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool_data = pumpswap::pool::Pool::fetch(rpc, pool).await?;
pool_data.calculate_buy_amount(rpc, sol_amount).await
}
// Calculate the amount of SOL to receive for a given token amount
pub async fn get_sell_sol_amount(
rpc: &SolanaRpcClient,
pool: &Pubkey,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool_data = pumpswap::pool::Pool::fetch(rpc, pool).await?;
pool_data.calculate_sell_amount(rpc, token_amount).await
}
pub(crate) fn coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey {
let (pump_pool_authority, _) = Pubkey::find_program_address(
&[b"creator_vault", &coin_creator.to_bytes()],
&crate::constants::pumpswap::accounts::AMM_PROGRAM,
);
pump_pool_authority
}
pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey {
let creator_vault_authority = coin_creator_vault_authority(coin_creator);
let associated_token_creator_vault_authority =
spl_associated_token_account::get_associated_token_address_with_program_id(
&creator_vault_authority,
&crate::constants::pumpswap::accounts::WSOL_TOKEN_ACCOUNT,
&crate::constants::pumpswap::accounts::TOKEN_PROGRAM,
);
associated_token_creator_vault_authority
}
+4
View File
@@ -0,0 +1,4 @@
pub mod buy;
pub mod sell;
pub mod common;
pub mod pool;
+189
View File
@@ -0,0 +1,189 @@
use crate::{common::SolanaRpcClient, constants::pumpswap::accounts};
use anyhow::anyhow;
use solana_account_decoder::UiAccountEncoding;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub struct Pool {
pub pool_bump: u8,
pub index: u16,
pub creator: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub lp_mint: Pubkey,
pub pool_base_token_account: Pubkey,
pub pool_quote_token_account: Pubkey,
pub lp_supply: u64,
}
impl Pool {
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
if data.len() < 211 {
return Err(anyhow!("Data too short for Pool account"));
}
// 跳过discriminator (8字节)
let data = &data[8..];
let pool_bump = data[0];
let index = u16::from_le_bytes([data[1], data[2]]);
let creator = Pubkey::new_from_array(
data[3..35]
.try_into()
.map_err(|e| anyhow!("Failed to convert creator: {:?}", e))?,
);
let base_mint = Pubkey::new_from_array(
data[35..67]
.try_into()
.map_err(|e| anyhow!("Failed to convert base_mint: {:?}", e))?,
);
let quote_mint = Pubkey::new_from_array(
data[67..99]
.try_into()
.map_err(|e| anyhow!("Failed to convert quote_mint: {:?}", e))?,
);
let lp_mint = Pubkey::new_from_array(
data[99..131]
.try_into()
.map_err(|e| anyhow!("Failed to convert lp_mint: {:?}", e))?,
);
let pool_base_token_account = Pubkey::new_from_array(
data[131..163]
.try_into()
.map_err(|e| anyhow!("Failed to convert pool_base_token_account: {:?}", e))?,
);
let pool_quote_token_account = Pubkey::new_from_array(
data[163..195]
.try_into()
.map_err(|e| anyhow!("Failed to convert pool_quote_token_account: {:?}", e))?,
);
let lp_supply = u64::from_le_bytes([
data[195], data[196], data[197], data[198], data[199], data[200], data[201], data[202],
]);
Ok(Self {
pool_bump,
index,
creator,
base_mint,
quote_mint,
lp_mint,
pool_base_token_account,
pool_quote_token_account,
lp_supply,
})
}
pub async fn fetch(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::AMM_PROGRAM {
return Err(anyhow!("Account is not owned by PumpSwap program"));
}
Self::from_bytes(&account.data)
}
pub async fn find_by_mint(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Pubkey, Self), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &mint.to_bytes()),
),
];
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
filters: Some(filters),
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: None,
min_context_slot: None,
},
with_context: None,
sort_results: None,
};
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let accounts = rpc
.get_program_accounts_with_config(&program_id, config)
.await?;
if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", mint));
}
let mut pools: Vec<_> = accounts
.into_iter()
.filter_map(|(addr, acc)| Self::from_bytes(&acc.data).map(|pool| (addr, pool)).ok())
.collect();
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
}
pub async fn get_token_balances(
&self,
rpc: &SolanaRpcClient,
) -> Result<(u64, u64), anyhow::Error> {
let base_balance = rpc
.get_token_account_balance(&self.pool_base_token_account)
.await?;
let quote_balance = rpc
.get_token_account_balance(&self.pool_quote_token_account)
.await?;
let base_amount = base_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
let quote_amount = quote_balance
.amount
.parse::<u64>()
.map_err(|e| anyhow!(e))?;
Ok((base_amount, quote_amount))
}
pub async fn calculate_buy_amount(
&self,
rpc: &SolanaRpcClient,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = self.get_token_balances(rpc).await?;
// 使用常数乘积公式 (x * y = k) 计算
let product = base_amount as u128 * quote_amount as u128;
let new_quote_amount = quote_amount as u128 + sol_amount as u128;
let new_base_amount = product / new_quote_amount;
let token_amount = base_amount as u128 - new_base_amount;
Ok(token_amount as u64)
}
pub async fn calculate_sell_amount(
&self,
rpc: &SolanaRpcClient,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = self.get_token_balances(rpc).await?;
// 使用常数乘积公式 (x * y = k) 计算
let product = base_amount as u128 * quote_amount as u128;
let new_base_amount = base_amount as u128 + token_amount as u128;
let new_quote_amount = product / new_base_amount;
let sol_amount = quote_amount as u128 - new_quote_amount;
Ok(sol_amount as u64)
}
}
+266
View File
@@ -0,0 +1,266 @@
use anyhow::anyhow;
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use std::sync::Arc;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::swqos::SwqosClient;
use crate::trading::pumpswap::common::get_token_balance;
use crate::trading::{core::params::PumpSwapParams, factory::Protocol, SellParams, TradeFactory};
// Sell tokens to a Pumpswap pool
pub async fn sell(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpSwapParams {
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
// 执行卖出交易
executor.sell(sell_params).await?;
Ok(())
}
// Sell tokens by percentage
pub async fn sell_by_percent(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
/// Sell tokens by amount
pub async fn sell_by_amount(
rpc: Arc<SolanaRpcClient>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
sell(
rpc,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens using a MEV service
pub async fn sell_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount_token: Option<u64>,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(Protocol::PumpSwap);
// 创建PumpFun协议参数
let protocol_params = Box::new(PumpSwapParams {
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
auto_handle_wsol: true,
});
// 创建卖出参数
let sell_params = SellParams {
rpc: Some(rpc.clone()),
payer: payer.clone(),
mint,
creator,
amount_token: amount_token,
slippage_basis_points: slippage_basis_points,
priority_fee: priority_fee.clone(),
lookup_table_key,
recent_blockhash,
protocol_params,
};
let sell_with_tip_params = sell_params.with_tip(swqos_clients);
// 执行卖出交易
executor.sell_with_tip(sell_with_tip_params).await?;
Ok(())
}
// Sell tokens by percentage using a MEV service
pub async fn sell_by_percent_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
percent: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow!("Percentage must be between 1 and 100"));
}
let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?;
let amount = balance_u64 * percent / 100;
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}
// Sell tokens by amount using a MEV service
pub async fn sell_by_amount_with_tip(
rpc: Arc<SolanaRpcClient>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
mint: Pubkey,
creator: Pubkey,
amount: u64,
slippage_basis_points: Option<u64>,
priority_fee: PriorityFee,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
// 可选(必须全部传)
pool: Option<Pubkey>,
pool_base_token_account: Option<Pubkey>,
pool_quote_token_account: Option<Pubkey>,
user_base_token_account: Option<Pubkey>,
user_quote_token_account: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
sell_with_tip(
rpc,
swqos_clients,
payer,
mint,
creator,
Some(amount),
slippage_basis_points,
priority_fee,
lookup_table_key,
recent_blockhash,
pool,
pool_base_token_account,
pool_quote_token_account,
user_base_token_account,
user_quote_token_account,
)
.await
}