feat: major refactor with calculation utilities and instruction optimization

- Add comprehensive calculation utilities for all protocols (bonk, pumpfun, pumpswap, raydium)
- Refactor instruction modules to reduce code complexity and improve maintainability
- Optimize trading parameters structure and enhance common utilities
- Separate calculation logic from trading modules for better code organization
- Update dependencies and module exports
This commit is contained in:
ysq
2025-08-18 18:00:14 +08:00
parent 350e34e0a0
commit 57474bfa6e
21 changed files with 1513 additions and 1107 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ readme = "README.md"
crate-type = ["cdylib", "rlib"]
[dependencies]
solana-streamer-sdk = "0.3.1"
solana-streamer-sdk = "0.3.3"
solana-sdk = "2.3.0"
solana-client = "2.3.6"
solana-program = "2.3.0"
+6
View File
@@ -25,6 +25,12 @@ pub mod accounts {
pub const TOKEN_PROGRAM: Pubkey = spl_token::ID;
pub const WSOL_TOKEN_ACCOUNT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
pub const RAYDIUM_CPMM: Pubkey = pubkey!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C");
pub const FEE_RATE_DENOMINATOR_VALUE: u128 = 1_000_000;
pub const TRADE_FEE_RATE: u64 = 2500;
pub const CREATOR_FEE_RATE: u64 = 0;
pub const PROTOCOL_FEE_RATE: u64 = 120000;
pub const FUND_FEE_RATE: u64 = 40000;
}
pub const SWAP_BASE_IN_DISCRIMINATOR: &[u8] = &[143, 190, 90, 218, 196, 30, 51, 222];
+59 -48
View File
@@ -10,16 +10,19 @@ use crate::{
trade::trade::DEFAULT_SLIPPAGE,
},
trading::{
bonk::common::{fetch_pool_state, get_amount_out, get_pool_pda, get_vault_pda},
bonk::common::{get_pool_pda, get_vault_pda},
common::utils::get_token_balance,
core::{
params::{BonkParams, BuyParams, SellParams},
traits::InstructionBuilder,
},
},
utils::calc::bonk::{
get_buy_token_amount_from_sol_amount, get_sell_sol_amount_from_token_amount,
},
};
/// Bonk协议的指令构建器
/// Instruction builder for Bonk protocol
pub struct BonkInstructionBuilder;
#[async_trait::async_trait]
@@ -37,7 +40,7 @@ impl InstructionBuilder for BonkInstructionBuilder {
}
impl BonkInstructionBuilder {
/// 使用提供的账户信息构建买入指令
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
@@ -50,7 +53,7 @@ impl BonkInstructionBuilder {
let pool_state = get_pool_pda(&params.mint, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
// 创建用户代币账户
// Create user token accounts
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
@@ -60,31 +63,20 @@ impl BonkInstructionBuilder {
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
// Get pool token accounts
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 = protocol_params.real_base.unwrap_or(0);
let mut real_quote = protocol_params.real_quote.unwrap_or(0);
if virtual_base == 0 || virtual_quote == 0 || real_base == 0 || real_quote == 0 {
let pool = fetch_pool_state(params.rpc.as_ref().unwrap(), &pool_state).await?;
virtual_base = pool.virtual_base as u128;
virtual_quote = pool.virtual_quote as u128;
real_base = pool.real_base as u128;
real_quote = pool.real_quote as u128;
}
let virtual_base = protocol_params.virtual_base;
let virtual_quote = protocol_params.virtual_quote;
let real_base = protocol_params.real_base;
let real_quote = protocol_params.real_quote;
let amount_in: u64 = params.sol_amount;
let share_fee_rate: u64 = 0;
let minimum_amount_out: u64 = get_amount_out(
let minimum_amount_out: u64 = get_buy_token_amount_from_sol_amount(
amount_in,
accounts::PROTOCOL_FEE_RATE,
accounts::PLATFORM_FEE_RATE,
accounts::SHARE_FEE_RATE,
virtual_base,
virtual_quote,
real_base,
@@ -95,9 +87,9 @@ impl BonkInstructionBuilder {
let mut instructions = vec![];
if protocol_params.auto_handle_wsol {
// 插入wsol
// Handle wSOL
instructions.push(
// 创建wSOL ATA账户,如果不存在
// Create wSOL ATA account if it doesn't exist
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
@@ -106,11 +98,11 @@ impl BonkInstructionBuilder {
),
);
instructions.push(
// 将SOL转入wSOL ATA账户
// Transfer SOL to wSOL ATA account
transfer(&params.payer.pubkey(), &user_quote_token_account, amount_in),
);
// 同步wSOL余额
// Sync wSOL balance
instructions.push(
spl_token::instruction::sync_native(
&accounts::TOKEN_PROGRAM,
@@ -120,7 +112,7 @@ impl BonkInstructionBuilder {
);
}
// 创建用户的基础代币账户
// Create user's base token account
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
@@ -128,7 +120,7 @@ impl BonkInstructionBuilder {
&accounts::TOKEN_PROGRAM,
));
// 创建买入指令
// Create buy instruction
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)
@@ -141,12 +133,15 @@ impl BonkInstructionBuilder {
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
solana_sdk::instruction::AccountMeta::new_readonly(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(
protocol_params.mint_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)
];
// 创建指令数据
// Create instruction data
let mut data = vec![];
data.extend_from_slice(&BUY_EXECT_IN_DISCRIMINATOR);
data.extend_from_slice(&amount_in.to_le_bytes());
@@ -156,7 +151,7 @@ impl BonkInstructionBuilder {
instructions.push(Instruction { program_id: accounts::BONK, accounts, data });
if protocol_params.auto_handle_wsol {
// 关闭wSOL ATA账户,回收租金
// Close wSOL ATA account, reclaim rent
instructions.push(
spl_token::instruction::close_account(
&accounts::TOKEN_PROGRAM,
@@ -172,7 +167,7 @@ impl BonkInstructionBuilder {
Ok(instructions)
}
/// 使用提供的账户信息构建卖出指令
/// Build sell instructions with provided account information
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
@@ -180,9 +175,16 @@ impl BonkInstructionBuilder {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
let rpc = params.rpc.as_ref().unwrap().clone();
// 获取代币余额
// Get token balance
let mut amount = params.token_amount;
if params.token_amount.is_none() || params.token_amount.unwrap_or(0) == 0 {
let balance_u64 =
@@ -195,12 +197,24 @@ impl BonkInstructionBuilder {
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 virtual_base = protocol_params.virtual_base;
let virtual_quote = protocol_params.virtual_quote;
let real_base = protocol_params.real_base;
let real_quote = protocol_params.real_quote;
// Calculate expected SOL amount
let minimum_amount_out: u64 = get_sell_sol_amount_from_token_amount(
amount,
virtual_base,
virtual_quote,
real_base,
real_quote,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE) as u128,
);
// Create user token accounts
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
&params.mint,
@@ -210,7 +224,7 @@ impl BonkInstructionBuilder {
&accounts::WSOL_TOKEN_ACCOUNT,
);
// 获取池的代币账户
// Get pool token accounts
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();
@@ -219,9 +233,9 @@ impl BonkInstructionBuilder {
let mut instructions = vec![];
// 插入wsol
// Handle wSOL
instructions.push(
// 创建wSOL ATA账户,如果不存在
// Create wSOL ATA account if it doesn't exist
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
@@ -230,7 +244,7 @@ impl BonkInstructionBuilder {
),
);
// 创建卖出指令
// Create sell instruction
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)
@@ -243,13 +257,16 @@ impl BonkInstructionBuilder {
solana_sdk::instruction::AccountMeta::new(quote_vault_account, false), // Quote Vault
solana_sdk::instruction::AccountMeta::new_readonly(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(
protocol_params.mint_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)
];
// 创建指令数据
// Create instruction data
let mut data = vec![];
data.extend_from_slice(&SELL_EXECT_IN_DISCRIMINATOR);
data.extend_from_slice(&amount.to_le_bytes());
@@ -258,12 +275,6 @@ impl BonkInstructionBuilder {
instructions.push(Instruction { program_id: accounts::BONK, accounts, data });
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<BonkParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for Bonk"))?;
if protocol_params.auto_handle_wsol {
instructions.push(
close_account(
+41 -46
View File
@@ -1,5 +1,5 @@
use anyhow::{anyhow, Result};
use solana_sdk::{instruction::Instruction, native_token::sol_str_to_lamports};
use solana_sdk::instruction::Instruction;
use spl_associated_token_account::{
get_associated_token_address, instruction::create_associated_token_account,
};
@@ -10,6 +10,10 @@ use crate::{
trading::pumpfun::common::{
get_bonding_curve_pda, get_global_volume_accumulator_pda, get_user_volume_accumulator_pda,
},
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},
},
};
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, signer::Signer};
@@ -17,21 +21,20 @@ use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signature::Keypair, s
use crate::{
constants::pumpfun::global_constants::FEE_RECIPIENT,
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::common::utils::calculate_with_slippage_buy,
trading::core::{
params::{BuyParams, PumpFunParams, SellParams},
traits::InstructionBuilder,
},
trading::pumpfun::common::{get_buy_token_amount_from_sol_amount, get_creator_vault_pda},
trading::pumpfun::common::get_creator_vault_pda,
};
/// PumpFun协议的指令构建器
/// Instruction builder for PumpFun protocol
pub struct PumpFunInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for PumpFunInstructionBuilder {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// 获取PumpFun特定参数
// Get PumpFun specific parameters
let protocol_params = params
.protocol_params
.as_any()
@@ -42,11 +45,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
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 bonding_curve = protocol_params.bonding_curve.clone();
let max_sol_cost = calculate_with_slippage_buy(
params.sol_amount,
@@ -54,19 +53,17 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
);
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.sol_amount);
if buy_token_amount <= 100 * 1_000_000_u64 {
buy_token_amount = if max_sol_cost > sol_str_to_lamports("0.01").unwrap_or(0) {
25547619 * 1_000_000_u64
} else {
255476 * 1_000_000_u64
};
}
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,
bonding_curve.creator,
params.sol_amount,
);
let mut instructions = vec![];
// 创建关联代币账户
// Create associated token account
instructions.push(create_associated_token_account(
&params.payer.pubkey(),
&params.payer.pubkey(),
@@ -74,23 +71,29 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
&constants::pumpfun::accounts::TOKEN_PROGRAM,
));
// 创建买入指令
// 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,
},
Buy { _amount: buy_token_amount, _max_sol_cost: max_sol_cost },
));
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// Get PumpFun specific parameters
let protocol_params = params
.protocol_params
.as_any()
.downcast_ref::<PumpFunParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
let bonding_curve = protocol_params.bonding_curve.clone();
let token_amount = if let Some(amount) = params.token_amount {
if amount == 0 {
return Err(anyhow!("Amount cannot be zero"));
@@ -102,35 +105,27 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
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 token_amount = token_amount;
if token_amount > balance_u64 {
token_amount = balance_u64;
}
let sol_amount = get_sell_sol_amount_from_token_amount(
bonding_curve.virtual_token_reserves as u128,
bonding_curve.virtual_sol_reserves as u128,
bonding_curve.creator,
token_amount,
);
let min_sol_output = calculate_with_slippage_sell(
sol_amount,
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: 1,
},
Sell { _amount: token_amount, _min_sol_output: min_sol_output },
)];
// 如果卖出全部代币,关闭账户
if token_amount >= balance_u64 {
// If selling all tokens, close the account
if protocol_params.close_token_account_when_sell.unwrap_or(false) {
instructions.push(close_account(
&spl_token::ID,
&ata,
+100 -181
View File
@@ -10,20 +10,16 @@ use crate::{
trade::trade::DEFAULT_SLIPPAGE,
},
trading::{
common::utils::{
calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance,
},
core::{
params::{BuyParams, PumpSwapParams, SellParams},
traits::InstructionBuilder,
},
pumpswap::{
self,
common::{
coin_creator_vault_ata, coin_creator_vault_authority, fee_recipient_ata, fetch_pool, find_pool, get_global_volume_accumulator_pda, get_token_amount, get_user_volume_accumulator_pda, get_wsol_amount
},
pumpswap::common::{
coin_creator_vault_ata, coin_creator_vault_authority, fee_recipient_ata,
get_global_volume_accumulator_pda, get_user_volume_accumulator_pda,
},
},
utils::calc::pumpswap::{buy_quote_input_internal, sell_base_input_internal},
};
/// Instruction builder for PumpSwap protocol
@@ -44,40 +40,21 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
}
// Build instructions based on whether account information is provided
match (&protocol_params.pool,) {
(Some(pool),) => {
let mut base_mint = params.mint;
let mut quote_mint = accounts::WSOL_TOKEN_ACCOUNT;
let mut pool_base_token_reserves = 0;
let mut pool_quote_token_reserves = 0;
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;
if let Some(p_base_mint) = protocol_params.base_mint {
base_mint = p_base_mint;
}
if let Some(p_quote_mint) = protocol_params.quote_mint {
quote_mint = p_quote_mint;
}
if let Some(p_pool_base_token_reserves) = protocol_params.pool_base_token_reserves {
pool_base_token_reserves = p_pool_base_token_reserves;
}
if let Some(p_pool_quote_token_reserves) = protocol_params.pool_quote_token_reserves
{
pool_quote_token_reserves = p_pool_quote_token_reserves;
}
self.build_buy_instructions_with_accounts(
params,
*pool,
base_mint,
quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
protocol_params.auto_handle_wsol,
)
.await
}
_ => self.build_buy_instructions_auto_discover(params).await,
}
self.build_buy_instructions_with_accounts(
params,
protocol_params.pool,
base_mint,
quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
protocol_params.auto_handle_wsol,
)
.await
}
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
@@ -88,104 +65,25 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
.downcast_ref::<PumpSwapParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?;
// Build instructions based on whether account information is provided
match (&protocol_params.pool,) {
(Some(pool),) => {
let mut base_mint = params.mint;
let mut quote_mint = accounts::WSOL_TOKEN_ACCOUNT;
let mut pool_base_token_reserves = 0;
let mut pool_quote_token_reserves = 0;
if let Some(p_base_mint) = protocol_params.base_mint {
base_mint = p_base_mint;
}
if let Some(p_quote_mint) = protocol_params.quote_mint {
quote_mint = p_quote_mint;
}
if let Some(p_pool_base_token_reserves) = protocol_params.pool_base_token_reserves {
pool_base_token_reserves = p_pool_base_token_reserves;
}
if let Some(p_pool_quote_token_reserves) = protocol_params.pool_quote_token_reserves
{
pool_quote_token_reserves = p_pool_quote_token_reserves;
}
self.build_sell_instructions_with_accounts(
params,
*pool,
base_mint,
quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
protocol_params.auto_handle_wsol,
)
.await
}
_ => self.build_sell_instructions_auto_discover(params).await,
}
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;
self.build_sell_instructions_with_accounts(
params,
protocol_params.pool,
base_mint,
quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
protocol_params.auto_handle_wsol,
)
.await
}
}
impl PumpSwapInstructionBuilder {
/// Auto-discover pool and account information and build buy instructions
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"));
}
println!("❗️Going through RPC request, increasing instruction building time");
let rpc = params.rpc.as_ref().unwrap().clone();
// Find pool
let pool = find_pool(rpc.as_ref(), &params.mint).await?;
let pool_data = fetch_pool(rpc.as_ref(), &pool).await?;
let pool_base_token_reserves =
get_token_balance(rpc.as_ref(), &pool, &pool_data.base_mint).await?;
let pool_quote_token_reserves =
get_token_balance(rpc.as_ref(), &pool, &pool_data.quote_mint).await?;
let mut params = params.clone();
params.creator = pool_data.coin_creator;
self.build_buy_instructions_with_accounts(
&params,
pool,
pool_data.base_mint,
pool_data.quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
true,
)
.await
}
/// Auto-discover pool and account information and build sell instructions
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"));
}
println!("❗️Going through RPC request, increasing instruction building time");
let rpc = params.rpc.as_ref().unwrap().clone();
// Find pool
let pool = find_pool(rpc.as_ref(), &params.mint).await?;
let pool_data = fetch_pool(rpc.as_ref(), &pool).await?;
let pool_base_token_reserves =
get_token_balance(rpc.as_ref(), &pool, &pool_data.base_mint).await?;
let pool_quote_token_reserves =
get_token_balance(rpc.as_ref(), &pool, &pool_data.quote_mint).await?;
let mut params = params.clone();
params.creator = pool_data.coin_creator;
self.build_sell_instructions_with_accounts(
&params,
pool,
pool_data.base_mint,
pool_data.quote_mint,
pool_base_token_reserves,
pool_quote_token_reserves,
true,
)
.await
}
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
@@ -201,38 +99,36 @@ impl PumpSwapInstructionBuilder {
return Err(anyhow!("RPC is not set"));
}
let quote_mint_is_wsol = quote_mint == accounts::WSOL_TOKEN_ACCOUNT;
// Calculate token amount
let mut token_amount = get_token_amount(
quote_mint_is_wsol,
pool_base_token_reserves,
pool_quote_token_reserves,
params.sol_amount,
accounts::LP_FEE_BASIS_POINTS,
accounts::PROTOCOL_FEE_BASIS_POINTS,
if params.creator == Pubkey::default() {
0
} else {
accounts::COIN_CREATOR_FEE_BASIS_POINTS
},
)
.await?;
if !quote_mint_is_wsol {
// min_quote_amount_out
token_amount = calculate_with_slippage_sell(
token_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
}
let sol_amount = if quote_mint_is_wsol {
// max_quote_amount_in
calculate_with_slippage_buy(
let mut token_amount = 0;
let mut sol_amount = 0;
if quote_mint_is_wsol {
let result = buy_quote_input_internal(
params.sol_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
pool_base_token_reserves,
pool_quote_token_reserves,
&params.creator,
)
.unwrap();
// base_amount_out
token_amount = result.base;
// max_quote_amount_in
sol_amount = result.max_quote;
} else {
let result = sell_base_input_internal(
params.sol_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
pool_base_token_reserves,
pool_quote_token_reserves,
&params.creator,
)
.unwrap();
// min_quote_amount_out
token_amount = result.min_quote;
// base_amount_in
params.sol_amount
};
sol_amount = params.sol_amount;
}
// Create user token accounts
let user_base_token_account = spl_associated_token_account::get_associated_token_address(
@@ -351,11 +247,15 @@ impl PumpSwapInstructionBuilder {
let mut data = vec![];
if quote_mint_is_wsol {
data.extend_from_slice(&BUY_DISCRIMINATOR);
// base_amount_out
data.extend_from_slice(&token_amount.to_le_bytes());
// max_quote_amount_in
data.extend_from_slice(&sol_amount.to_le_bytes());
} else {
data.extend_from_slice(&SELL_DISCRIMINATOR);
// base_amount_in
data.extend_from_slice(&sol_amount.to_le_bytes());
// min_quote_amount_out
data.extend_from_slice(&token_amount.to_le_bytes());
}
@@ -394,27 +294,42 @@ impl PumpSwapInstructionBuilder {
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
if params.token_amount.is_none() {
return Err(anyhow!("Token amount is not set"));
}
let quote_mint_is_wsol = quote_mint == accounts::WSOL_TOKEN_ACCOUNT;
let mut sol_amount = get_wsol_amount(
quote_mint_is_wsol,
pool_base_token_reserves,
pool_quote_token_reserves,
params.token_amount.unwrap_or(0),
accounts::LP_FEE_BASIS_POINTS,
accounts::PROTOCOL_FEE_BASIS_POINTS,
if params.creator == Pubkey::default() {
0
} else {
accounts::COIN_CREATOR_FEE_BASIS_POINTS
},
)
.await?;
sol_amount = calculate_with_slippage_sell(
sol_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let token_amount = params.token_amount.unwrap_or(0);
let mut token_amount = 0;
let mut sol_amount = 0;
if quote_mint_is_wsol {
let result = sell_base_input_internal(
params.token_amount.unwrap(),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
pool_base_token_reserves,
pool_quote_token_reserves,
&params.creator,
)
.unwrap();
// base_amount_in
token_amount = params.token_amount.unwrap();
// min_quote_amount_out
sol_amount = result.min_quote;
} else {
let result = buy_quote_input_internal(
params.token_amount.unwrap(),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
pool_base_token_reserves,
pool_quote_token_reserves,
&params.creator,
)
.unwrap();
// max_quote_amount_in
token_amount = result.max_quote;
// base_amount_out
sol_amount = result.base;
}
let coin_creator_vault_ata = coin_creator_vault_ata(params.creator, quote_mint);
let coin_creator_vault_authority = coin_creator_vault_authority(params.creator);
@@ -502,11 +417,15 @@ impl PumpSwapInstructionBuilder {
let mut data = vec![];
if quote_mint_is_wsol {
data.extend_from_slice(&SELL_DISCRIMINATOR);
// base_amount_in
data.extend_from_slice(&token_amount.to_le_bytes());
// min_quote_amount_out
data.extend_from_slice(&sol_amount.to_le_bytes());
} else {
data.extend_from_slice(&BUY_DISCRIMINATOR);
// base_amount_out
data.extend_from_slice(&sol_amount.to_le_bytes());
// max_quote_amount_in
data.extend_from_slice(&token_amount.to_le_bytes());
}
+75 -124
View File
@@ -5,20 +5,21 @@ use spl_associated_token_account::instruction::create_associated_token_account_i
use spl_token::instruction::close_account;
use crate::{
constants::raydium_cpmm::{accounts, SWAP_BASE_IN_DISCRIMINATOR},
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::common::utils::get_token_balance,
trading::core::{
params::{BuyParams, RaydiumCpmmParams, SellParams},
traits::InstructionBuilder,
constants::{
raydium_cpmm::{accounts, SWAP_BASE_IN_DISCRIMINATOR},
trade::trade::DEFAULT_SLIPPAGE,
},
trading::raydium_cpmm::{
common::{get_observation_state_pda, get_pool_pda, get_vault_pda},
// pool::Pool,
trading::{
core::{
params::{BuyParams, RaydiumCpmmParams, SellParams},
traits::InstructionBuilder,
},
raydium_cpmm::common::{get_observation_state_pda, get_pool_pda, get_vault_pda},
},
utils::calc::raydium_cpmm::compute_swap_amount,
};
/// RaydiumCpmm协议的指令构建器
/// Instruction builder for RaydiumCpmm protocol
pub struct RaydiumCpmmInstructionBuilder;
#[async_trait::async_trait]
@@ -36,7 +37,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
}
impl RaydiumCpmmInstructionBuilder {
/// 使用提供的账户信息构建买入指令
/// Build buy instructions with provided account information
async fn build_buy_instructions_with_accounts(
&self,
params: &BuyParams,
@@ -47,26 +48,12 @@ impl RaydiumCpmmInstructionBuilder {
.downcast_ref::<RaydiumCpmmParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
let pool_state = if protocol_params.pool_state.is_some() {
protocol_params.pool_state.unwrap()
} else {
let mint_token_in_pool_state_index =
protocol_params.mint_token_in_pool_state_index.unwrap_or(1);
get_pool_pda(
&accounts::AMM_CONFIG,
if mint_token_in_pool_state_index == 1 {
&accounts::WSOL_TOKEN_ACCOUNT
} else {
&params.mint
},
if mint_token_in_pool_state_index == 1 {
&params.mint
} else {
&accounts::WSOL_TOKEN_ACCOUNT
},
)
.unwrap()
};
let pool_state = get_pool_pda(
&accounts::AMM_CONFIG,
&protocol_params.base_mint,
&protocol_params.quote_mint,
)
.unwrap();
let wsol_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
@@ -77,36 +64,29 @@ impl RaydiumCpmmInstructionBuilder {
&params.mint,
);
// 获取池的代币账户
// Get pool token accounts
let wsol_vault_account = get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
let mint_vault_account = get_vault_pda(&pool_state, &params.mint).unwrap();
let observation_state_account = get_observation_state_pda(&pool_state).unwrap();
let is_base_in = protocol_params.base_mint == accounts::WSOL_TOKEN_ACCOUNT;
let amount_in: u64 = params.sol_amount;
let mut minimum_amount_out: u64 = if protocol_params.minimum_amount_out.is_some() {
protocol_params.minimum_amount_out.unwrap()
} else {
println!("未提供minimum_amount_out,使用默认值0");
0
};
if minimum_amount_out != 0 {
let slippage_basis_points: u64 = if params.slippage_basis_points.is_some() {
params.slippage_basis_points.unwrap()
} else {
DEFAULT_SLIPPAGE
} as u64;
minimum_amount_out = minimum_amount_out * (10000 - slippage_basis_points) / 10000;
println!("slippage_basis_points: {}", slippage_basis_points);
}
println!("minimum_amount_out: {}", minimum_amount_out);
let result = compute_swap_amount(
protocol_params.base_reserve,
protocol_params.quote_reserve,
is_base_in,
amount_in,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
let minimum_amount_out = result.min_amount_out;
let mut instructions = vec![];
if protocol_params.auto_handle_wsol {
// 插入wsol
// Handle wSOL
instructions.push(
// 创建wSOL ATA账户,如果不存在
// Create wSOL ATA account if it doesn't exist
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
@@ -115,26 +95,31 @@ impl RaydiumCpmmInstructionBuilder {
),
);
instructions.push(
// 将SOL转入wSOL ATA账户
// Transfer SOL to wSOL ATA account
transfer(&params.payer.pubkey(), &wsol_token_account, amount_in),
);
// 同步wSOL余额
// Sync wSOL balance
instructions.push(
spl_token::instruction::sync_native(&accounts::TOKEN_PROGRAM, &wsol_token_account)
.unwrap(),
);
}
// 创建用户的基础代币账户
let mint_token_program = if is_base_in {
protocol_params.quote_token_program
} else {
protocol_params.base_token_program
};
instructions.push(create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.mint,
&protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
&mint_token_program,
));
// 创建买入指令
// Create buy instruction
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)
@@ -145,15 +130,12 @@ impl RaydiumCpmmInstructionBuilder {
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Input Vault Account
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Output Vault Account
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Input Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(
protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
false,
), // Output Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(mint_token_program, false), // Output Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Input token mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Output token mint (readonly)
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
];
// 创建指令数据
// Create instruction data
let mut data = vec![];
data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR);
data.extend_from_slice(&amount_in.to_le_bytes());
@@ -162,7 +144,7 @@ impl RaydiumCpmmInstructionBuilder {
instructions.push(Instruction { program_id: accounts::RAYDIUM_CPMM, accounts, data });
if protocol_params.auto_handle_wsol {
// 关闭wSOL ATA账户,回收租金
// Close wSOL ATA account, reclaim rent
instructions.push(
spl_token::instruction::close_account(
&accounts::TOKEN_PROGRAM,
@@ -178,7 +160,7 @@ impl RaydiumCpmmInstructionBuilder {
Ok(instructions)
}
/// 使用提供的账户信息构建卖出指令
/// Build sell instructions with provided account information
async fn build_sell_instructions_with_accounts(
&self,
params: &SellParams,
@@ -189,61 +171,27 @@ impl RaydiumCpmmInstructionBuilder {
.downcast_ref::<RaydiumCpmmParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for RaydiumCpmm"))?;
if params.rpc.is_none() {
return Err(anyhow!("RPC is not set"));
}
let rpc = params.rpc.as_ref().unwrap().clone();
// 获取代币余额
let mut amount = params.token_amount;
if params.token_amount.is_none() || params.token_amount.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"));
return Err(anyhow!("Token amount is not set"));
}
let mut minimum_amount_out: u64 = if protocol_params.minimum_amount_out.is_some() {
protocol_params.minimum_amount_out.unwrap()
} else {
println!("未提供minimum_amount_out,使用默认值0");
0
};
if minimum_amount_out != 0 {
let slippage_basis_points: u64 = if params.slippage_basis_points.is_some() {
params.slippage_basis_points.unwrap()
} else {
DEFAULT_SLIPPAGE
} as u64;
minimum_amount_out = minimum_amount_out * (10000 - slippage_basis_points) / 10000;
println!("slippage_basis_points: {}", slippage_basis_points);
}
println!("minimum_amount_out: {}", minimum_amount_out);
let is_base_in = protocol_params.base_mint == params.mint;
let pool_state = if protocol_params.pool_state.is_some() {
protocol_params.pool_state.unwrap()
} else {
let mint_token_in_pool_state_index =
protocol_params.mint_token_in_pool_state_index.unwrap_or(1);
get_pool_pda(
&accounts::AMM_CONFIG,
if mint_token_in_pool_state_index == 1 {
&accounts::WSOL_TOKEN_ACCOUNT
} else {
&params.mint
},
if mint_token_in_pool_state_index == 1 {
&params.mint
} else {
&accounts::WSOL_TOKEN_ACCOUNT
},
)
.unwrap()
};
let minimum_amount_out: u64 = compute_swap_amount(
protocol_params.base_reserve,
protocol_params.quote_reserve,
is_base_in,
params.token_amount.unwrap_or(0),
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
)
.min_amount_out;
let pool_state = get_pool_pda(
&accounts::AMM_CONFIG,
&protocol_params.base_mint,
&protocol_params.quote_mint,
)
.unwrap();
let wsol_token_account = spl_associated_token_account::get_associated_token_address(
&params.payer.pubkey(),
@@ -254,7 +202,7 @@ impl RaydiumCpmmInstructionBuilder {
&params.mint,
);
// 获取池的代币账户
// Get pool token accounts
let wsol_vault_account = get_vault_pda(&pool_state, &accounts::WSOL_TOKEN_ACCOUNT).unwrap();
let mint_vault_account = get_vault_pda(&pool_state, &params.mint).unwrap();
@@ -262,18 +210,24 @@ impl RaydiumCpmmInstructionBuilder {
let mut instructions = vec![];
// 插入wsol
// Handle wSOL
instructions.push(
// 创建wSOL ATA账户,如果不存在
// Create wSOL ATA account if it doesn't exist
create_associated_token_account_idempotent(
&params.payer.pubkey(),
&params.payer.pubkey(),
&accounts::WSOL_TOKEN_ACCOUNT,
&protocol_params.mint_token_program.unwrap(),
&spl_token::ID,
),
);
// 创建卖出指令
let mint_token_program = if is_base_in {
protocol_params.base_token_program
} else {
protocol_params.quote_token_program
};
// Create sell instruction
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)
@@ -283,19 +237,16 @@ impl RaydiumCpmmInstructionBuilder {
solana_sdk::instruction::AccountMeta::new(wsol_token_account, false), // Output Token Account
solana_sdk::instruction::AccountMeta::new(mint_vault_account, false), // Input Vault Account
solana_sdk::instruction::AccountMeta::new(wsol_vault_account, false), // Output Vault Account
solana_sdk::instruction::AccountMeta::new_readonly(
protocol_params.mint_token_program.unwrap_or(accounts::TOKEN_PROGRAM),
false,
), // Input Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(mint_token_program, false), // Input Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // Output Token Program (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // Input token mint (readonly)
solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // Output token mint (readonly)
solana_sdk::instruction::AccountMeta::new(observation_state_account, false), // Observation State Account
];
// 创建指令数据
// Create instruction data
let mut data = vec![];
data.extend_from_slice(&SWAP_BASE_IN_DISCRIMINATOR);
data.extend_from_slice(&amount.to_le_bytes());
data.extend_from_slice(&params.token_amount.unwrap_or(0).to_le_bytes());
data.extend_from_slice(&minimum_amount_out.to_le_bytes());
instructions.push(Instruction { program_id: accounts::RAYDIUM_CPMM, accounts, data });
+5 -27
View File
@@ -175,22 +175,11 @@ impl SolanaTrade {
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_buy_tip_fee: Option<f64>,
extension_params: Option<Box<dyn ProtocolParams>>,
extension_params: Box<dyn ProtocolParams>,
lookup_table_key: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(dex_type.clone());
let protocol_params = if let Some(params) = extension_params {
params
} else {
match dex_type {
DexType::PumpFun => Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>,
DexType::PumpSwap => Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>,
DexType::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
DexType::RaydiumCpmm => {
Box::new(RaydiumCpmmParams::default()) as Box<dyn ProtocolParams>
}
}
};
let protocol_params = extension_params;
let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key);
@@ -308,22 +297,11 @@ impl SolanaTrade {
recent_blockhash: Hash,
custom_buy_tip_fee: Option<f64>,
with_tip: bool,
extension_params: Option<Box<dyn ProtocolParams>>,
extension_params: Box<dyn ProtocolParams>,
lookup_table_key: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
let executor = TradeFactory::create_executor(dex_type.clone());
let protocol_params = if let Some(params) = extension_params {
params
} else {
match dex_type {
DexType::PumpFun => Box::new(PumpFunParams::default()) as Box<dyn ProtocolParams>,
DexType::PumpSwap => Box::new(PumpSwapParams::default()) as Box<dyn ProtocolParams>,
DexType::Bonk => Box::new(BonkParams::default()) as Box<dyn ProtocolParams>,
DexType::RaydiumCpmm => {
Box::new(RaydiumCpmmParams::default()) as Box<dyn ProtocolParams>
}
}
};
let protocol_params = extension_params;
let final_lookup_table_key = lookup_table_key.or(self.trade_config.lookup_table_key);
@@ -454,7 +432,7 @@ impl SolanaTrade {
recent_blockhash: Hash,
custom_buy_tip_fee: Option<f64>,
with_tip: bool,
extension_params: Option<Box<dyn ProtocolParams>>,
extension_params: Box<dyn ProtocolParams>,
lookup_table_key: Option<Pubkey>,
) -> Result<(), anyhow::Error> {
if percent == 0 || percent > 100 {
+255 -238
View File
@@ -1,11 +1,5 @@
use std::{str::FromStr, sync::Arc};
use sol_trade_sdk::{
common::{bonding_curve::BondingCurveAccount, AnyResult, PriorityFee, TradeConfig},
swqos::{SwqosConfig, SwqosRegion},
trading::{core::params::{BonkParams, PumpFunParams, PumpSwapParams, RaydiumCpmmParams}, factory::DexType, raydium_cpmm::common::{get_buy_token_amount, get_sell_sol_amount}},
SolanaTrade,
};
use sol_trade_sdk::solana_streamer_sdk::{
match_event,
streaming::{
@@ -16,15 +10,33 @@ use sol_trade_sdk::solana_streamer_sdk::{
pumpswap::{
PumpSwapBuyEvent, PumpSwapCreatePoolEvent, PumpSwapDepositEvent,
PumpSwapSellEvent, PumpSwapWithdrawEvent,
}, raydium_cpmm::RaydiumCpmmSwapEvent,
},
raydium_cpmm::RaydiumCpmmSwapEvent,
},
Protocol, UnifiedEvent,
},
ShredStreamGrpc, YellowstoneGrpc,
},
};
use sol_trade_sdk::{
common::{AnyResult, PriorityFee, TradeConfig},
swqos::{SwqosConfig, SwqosRegion},
trading::{
core::params::{BonkParams, PumpFunParams, PumpSwapParams, RaydiumCpmmParams},
factory::DexType,
},
SolanaTrade,
};
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair};
use solana_streamer_sdk::streaming::{event_parser::protocols::{bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID, pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID, raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID, raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID}, yellowstone_grpc::{AccountFilter, TransactionFilter}};
use solana_streamer_sdk::streaming::{
event_parser::protocols::{
bonk::parser::BONK_PROGRAM_ID, pumpfun::parser::PUMPFUN_PROGRAM_ID,
pumpswap::parser::PUMPSWAP_PROGRAM_ID, raydium_amm_v4::parser::RAYDIUM_AMM_V4_PROGRAM_ID,
raydium_clmm::parser::RAYDIUM_CLMM_PROGRAM_ID,
raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID,
},
yellowstone_grpc::{AccountFilter, TransactionFilter},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -37,7 +49,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
/// 创建 SolanaTrade 客户端
/// Create SolanaTrade client
/// Initializes a new SolanaTrade client with configuration
async fn test_create_solana_trade_client() -> AnyResult<SolanaTrade> {
println!("Creating SolanaTrade client...");
@@ -57,7 +70,7 @@ fn create_swqos_configs(rpc_url: &str) -> Vec<SwqosConfig> {
vec![
SwqosConfig::Jito("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::NextBlock("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt),
SwqosConfig::Default(rpc_url.to_string()),
@@ -83,39 +96,40 @@ async fn test_pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> Any
let buy_sol_cost = 100_000;
let slippage_basis_points = Some(100);
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
let bonding_curve = BondingCurveAccount::from_trade(&trade_info);
// Buy tokens
println!("Buying tokens from PumpFun...");
client.buy(
DexType::PumpFun,
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Some(Box::new(PumpFunParams {
bonding_curve: Some(Arc::new(bonding_curve.clone())),
})),
None,
).await?;
client
.buy(
DexType::PumpFun,
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Box::new(PumpFunParams::from_trade(&trade_info, None)),
None,
)
.await?;
// Sell tokens
// Sell tokens
println!("Selling tokens from PumpFun...");
let amount_token = 0;
client.sell(
DexType::PumpFun,
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
None,
None,
).await?;
client
.sell(
DexType::PumpFun,
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
Box::new(PumpFunParams::from_trade(&trade_info, None)),
None,
)
.await?;
Ok(())
}
@@ -133,45 +147,52 @@ async fn test_pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) ->
let slippage_basis_points = Some(100);
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
let bonding_curve = BondingCurveAccount::from_dev_trade(
&mint_pubkey,
trade_info.token_amount,
trade_info.max_sol_cost,
creator,
);
// Buy tokens
println!("Buying tokens from PumpFun...");
let buy_sol_amount = 100_000;
client.buy(
DexType::PumpFun,
mint_pubkey,
Some(creator),
buy_sol_amount,
slippage_basis_points,
recent_blockhash,
None,
Some(Box::new(PumpFunParams {
bonding_curve: Some(Arc::new(bonding_curve.clone())),
})),
None,
).await?;
client
.buy(
DexType::PumpFun,
mint_pubkey,
Some(creator),
buy_sol_amount,
slippage_basis_points,
recent_blockhash,
None,
Box::new(PumpFunParams::from_dev_trade(
&mint_pubkey,
trade_info.token_amount,
trade_info.max_sol_cost,
creator,
None,
)),
None,
)
.await?;
// Sell tokens
println!("Selling tokens from PumpFun...");
let amount_token = 0;
client.sell(
DexType::PumpFun,
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
None,
None,
).await?;
client
.sell(
DexType::PumpFun,
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
Box::new(PumpFunParams::from_dev_trade(
&mint_pubkey,
trade_info.token_amount,
trade_info.max_sol_cost,
creator,
None,
)),
None,
)
.await?;
Ok(())
}
@@ -186,60 +207,46 @@ async fn test_pumpswap() -> AnyResult<()> {
let slippage_basis_points = Some(100);
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
let pool_address = Pubkey::from_str("xxxxxxx")?;
let base_mint = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?;
let quote_mint = Pubkey::from_str("So11111111111111111111111111111111111111112")?;
let pool_base_token_reserves = 0; // Input the correct value
let pool_quote_token_reserves = 0; // Input the correct value
// Buy tokens
println!("Buying tokens from PumpSwap...");
client.buy(
DexType::PumpSwap,
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Some(Box::new(PumpSwapParams {
pool: Some(pool_address),
base_mint: Some(base_mint),
quote_mint: Some(quote_mint),
pool_base_token_reserves: Some(pool_base_token_reserves),
pool_quote_token_reserves: Some(pool_quote_token_reserves),
auto_handle_wsol: true,
})),
None,
).await?;
client
.buy(
DexType::PumpSwap,
mint_pubkey,
Some(creator),
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
// 经过 rpc,增加耗时,可以通过from_buy_trade或者自行初始化PumpSwapParams参数来优化耗时
Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?),
None,
)
.await?;
// Sell tokens
println!("Selling tokens from PumpSwap...");
let amount_token = 0;
client.sell(
DexType::PumpSwap,
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
Some(Box::new(PumpSwapParams {
pool: Some(pool_address),
base_mint: Some(base_mint),
quote_mint: Some(quote_mint),
pool_base_token_reserves: Some(pool_base_token_reserves),
pool_quote_token_reserves: Some(pool_quote_token_reserves),
auto_handle_wsol: true,
})),
None,
).await?;
client
.sell(
DexType::PumpSwap,
mint_pubkey,
Some(creator),
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
// 经过 rpc,增加耗时,可以通过from_sell_trade或者自行初始化PumpSwapParams参数来优化耗时
Box::new(PumpSwapParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?),
None,
)
.await?;
Ok(())
}
async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> {
println!("Testing Bonk trading...");
@@ -251,33 +258,37 @@ async fn test_bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult
// Buy tokens
println!("Buying tokens from letsbonk.fun...");
client.buy(
DexType::Bonk,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Some(Box::new(BonkParams::from_trade(trade_info))),
None,
).await?;
client
.buy(
DexType::Bonk,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Box::new(BonkParams::from_trade(trade_info.clone())),
None,
)
.await?;
// Sell tokens
println!("Selling tokens from letsbonk.fun...");
let amount_token = 0;
client.sell(
DexType::Bonk,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
None,
None,
).await?;
client
.sell(
DexType::Bonk,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
Box::new(BonkParams::from_trade(trade_info)),
None,
)
.await?;
Ok(())
}
@@ -297,38 +308,41 @@ async fn test_bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyRe
// Buy tokens
println!("Buying tokens from letsbonk.fun...");
client.buy(
DexType::Bonk,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Some(Box::new(BonkParams::from_dev_trade(trade_info))),
None,
).await?;
client
.buy(
DexType::Bonk,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Box::new(BonkParams::from_dev_trade(trade_info.clone())),
None,
)
.await?;
// Sell tokens
println!("Selling tokens from letsbonk.fun...");
let amount_token = 0;
client.sell(
DexType::Bonk,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
None,
None,
).await?;
client
.sell(
DexType::Bonk,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
Box::new(BonkParams::from_dev_trade(trade_info)),
None,
)
.await?;
Ok(())
}
async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
println!("Testing Bonk trading...");
@@ -340,38 +354,43 @@ async fn test_bonk() -> Result<(), Box<dyn std::error::Error>> {
// Buy tokens
println!("Buying tokens from letsbonk.fun...");
client.buy(
DexType::Bonk,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
None,
None,
).await?;
client
.buy(
DexType::Bonk,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
// 经过 rpc,增加耗时,可以通过from_trade或者自行初始化BonkParams参数来优化耗时
Box::new(BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?),
None,
)
.await?;
// Sell tokens
println!("Selling tokens from letsbonk.fun...");
let amount_token = 0;
client.sell(
DexType::Bonk,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
None,
None,
).await?;
client
.sell(
DexType::Bonk,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
// 经过 rpc,增加耗时,可以通过from_trade或者自行初始化BonkParams参数来优化耗时
Box::new(BonkParams::from_mint_by_rpc(&client.rpc, &mint_pubkey).await?),
None,
)
.await?;
Ok(())
}
async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
println!("Testing Raydium Cpmm trading...");
@@ -380,56 +399,52 @@ async fn test_raydium_cpmm() -> Result<(), Box<dyn std::error::Error>> {
let buy_sol_cost = 100_000;
let slippage_basis_points = Some(100);
let recent_blockhash = client.rpc.get_latest_blockhash().await?;
let pool_state = Pubkey::from_str("xxxxxxx")?;
let buy_amount_out = get_buy_token_amount(&client.rpc, &pool_state, buy_sol_cost).await?;
let pool_address = Pubkey::from_str("xxxxxxx")?;
// Buy tokens
println!("Buying tokens from Raydium Cpmm...");
client.buy(
DexType::RaydiumCpmm,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // 如果不传,会自动计算
mint_token_program: Some(spl_token::ID), // spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
minimum_amount_out: Some(buy_amount_out), // 如果不传、默认为0
auto_handle_wsol: true,
})),
None,
).await?;
client
.buy(
DexType::RaydiumCpmm,
mint_pubkey,
None,
buy_sol_cost,
slippage_basis_points,
recent_blockhash,
None,
// 经过 rpc,增加耗时,或者自行初始化RaydiumCpmmParams参数
Box::new(
RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?,
),
None,
)
.await?;
// Sell tokens
println!("Selling tokens from Raydium Cpmm...");
let amount_token = 0;
let sell_sol_amount = get_sell_sol_amount(&client.rpc, &pool_state, amount_token).await?;
client.sell(
DexType::RaydiumCpmm,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
Some(Box::new(RaydiumCpmmParams {
pool_state: Some(pool_state), // 如果不传,会自动计算
mint_token_program: Some(spl_token::ID), // spl_token_2022::ID
mint_token_in_pool_state_index: Some(1), // mint_token 在 pool_state 中的索引,默认在索引1
minimum_amount_out: Some(sell_sol_amount), // 如果不传、默认为0
auto_handle_wsol: true,
})),
None,
).await?;
client
.sell(
DexType::RaydiumCpmm,
mint_pubkey,
None,
amount_token,
slippage_basis_points,
recent_blockhash,
None,
false,
// 经过 rpc,增加耗时,或者自行初始化RaydiumCpmmParams参数
Box::new(
RaydiumCpmmParams::from_pool_address_by_rpc(&client.rpc, &pool_address).await?,
),
None,
)
.await?;
Ok(())
}
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
println!("正在订阅 GRPC 事件...");
println!("Subscribing to GRPC events...");
let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
@@ -437,37 +452,39 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
)?;
let callback = create_event_callback();
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
let protocols =
vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk, Protocol::RaydiumCpmm];
// Filter accounts
let account_include = vec![
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
RAYDIUM_AMM_V4_PROGRAM_ID.to_string(), // Listen to raydium_amm_v4 program ID
"xxxxxxxx".to_string(), // Listen to xxxxx account
"xxxxxxxx".to_string(), // Listen to xxxxx account
];
let account_exclude = vec![];
let account_required = vec![];
// 监听交易数据
let transaction_filter = TransactionFilter {
// Listen to transaction data
let transaction_filter = TransactionFilter {
account_include: account_include.clone(),
account_exclude,
account_required,
};
// 监听属于owner程序的账号数据 -> 账号事件监听
// Listen to account data belonging to owner programs -> account event monitoring
let account_filter = AccountFilter { account: vec![], owner: account_include.clone() };
println!("开始监听事件,按 Ctrl+C 停止...");
println!("Starting to listen for events, press Ctrl+C to stop...");
grpc.subscribe_events_immediate(
protocols,
None,
transaction_filter,
account_filter,
None,
None,
callback,
)
.await?;
@@ -476,14 +493,14 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
}
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
println!("正在订阅 ShredStream 事件...");
println!("Subscribing to ShredStream events...");
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
let callback = create_event_callback();
let protocols = vec![Protocol::PumpFun, Protocol::PumpSwap, Protocol::Bonk];
println!("开始监听事件,按 Ctrl+C 停止...");
shred_stream.shredstream_subscribe(protocols, None, callback).await?;
println!("Starting to listen for events, press Ctrl+C to stop...");
shred_stream.shredstream_subscribe(protocols, None, None, callback).await?;
Ok(())
}
@@ -521,8 +538,8 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
println!("RaydiumCpmmSwapEvent: {:?}", e);
},
// .....
// 更多的事件和说明请参考 https://github.com/0xfnzero/solana-streamer
// .....
// For more events and documentation, please refer to https://github.com/0xfnzero/solana-streamer
});
}
}
+15 -30
View File
@@ -28,22 +28,6 @@ pub async fn get_sol_balance(
Ok(balance)
}
// Calculate slippage for buy operations
#[inline]
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points / 10000)
}
// Calculate slippage for sell operations
#[inline]
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
if amount <= basis_points / 10000 {
1
} else {
amount - (amount * basis_points / 10000)
}
}
pub async fn transfer_sol(
rpc: &SolanaRpcClient,
payer: &Keypair,
@@ -75,38 +59,39 @@ pub async fn transfer_sol(
Ok(())
}
/// 关闭代币账户
/// Close token account
///
/// 此函数用于关闭指定代币的关联代币账户,将账户中的代币余额转移给账户所有者。
/// This function is used to close the associated token account for a specified token,
/// transferring the token balance in the account to the account owner.
///
/// # 参数
/// # Parameters
///
/// * `rpc` - Solana RPC客户端
/// * `payer` - 支付交易费用的账户
/// * `mint` - 代币的Mint地址
/// * `rpc` - Solana RPC client
/// * `payer` - Account that pays transaction fees
/// * `mint` - Token mint address
///
/// # 返回值
/// # Returns
///
/// 返回一个Result,成功时返回(),失败时返回错误
/// Returns a Result, success returns (), failure returns error
pub async fn close_token_account(
rpc: &SolanaRpcClient,
payer: &Keypair,
mint: &Pubkey,
) -> Result<(), anyhow::Error> {
// 获取关联代币账户地址
// Get associated token account address
let ata = get_associated_token_address(&payer.pubkey(), mint);
// 检查账户是否存在
// Check if account exists
let account_exists = rpc.get_account(&ata).await.is_ok();
if !account_exists {
return Ok(()); // 如果账户不存在,直接返回成功
return Ok(()); // If account doesn't exist, return success directly
}
// 构建关闭账户指令
// Build close account instruction
let close_account_ix =
close_account(&spl_token::ID, &ata, &payer.pubkey(), &payer.pubkey(), &[&payer.pubkey()])?;
// 构建交易
// Build transaction
let recent_blockhash = rpc.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[close_account_ix],
@@ -115,7 +100,7 @@ pub async fn close_token_account(
recent_blockhash,
);
// 发送交易
// Send transaction
rpc.send_and_confirm_transaction(&transaction).await?;
Ok(())
+160 -75
View File
@@ -1,17 +1,26 @@
use solana_hash::Hash;
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::{
PumpSwapBuyEvent, PumpSwapSellEvent,
};
use std::sync::Arc;
use super::traits::ProtocolParams;
use crate::common::bonding_curve::BondingCurveAccount;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::constants::bonk::accounts::{PLATFORM_FEE_RATE, PROTOCOL_FEE_RATE, SHARE_FEE_RATE};
use crate::constants::bonk::accounts::{
self, PLATFORM_FEE_RATE, PROTOCOL_FEE_RATE, SHARE_FEE_RATE,
};
use crate::solana_streamer_sdk::streaming::event_parser::common::EventType;
use crate::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent;
use crate::swqos::SwqosClient;
use crate::trading::bonk::common::{get_amount_in, get_amount_in_net, get_amount_out};
use crate::trading::pumpswap::common::get_token_balances;
use crate::trading::raydium_cpmm::common::get_pool_token_balances;
/// 通用买入参数
/// Common buy parameters
/// Contains all necessary information for executing buy transactions
#[derive(Clone)]
pub struct BuyParams {
pub rpc: Option<Arc<SolanaRpcClient>>,
@@ -27,7 +36,8 @@ pub struct BuyParams {
pub protocol_params: Box<dyn ProtocolParams>,
}
/// 带MEV服务的买入参数
/// Buy parameters with MEV service support
/// Extends BuyParams with MEV client configurations for transaction acceleration
#[derive(Clone)]
pub struct BuyWithTipParams {
pub rpc: Option<Arc<SolanaRpcClient>>,
@@ -44,7 +54,8 @@ pub struct BuyWithTipParams {
pub protocol_params: Box<dyn ProtocolParams>,
}
/// 通用卖出参数
/// Common sell parameters
/// Contains all necessary information for executing sell transactions
#[derive(Clone)]
pub struct SellParams {
pub rpc: Option<Arc<SolanaRpcClient>>,
@@ -59,7 +70,8 @@ pub struct SellParams {
pub protocol_params: Box<dyn ProtocolParams>,
}
/// 带MEV服务的卖出参数
/// Sell parameters with MEV service support
/// Extends SellParams with MEV client configurations for transaction acceleration
#[derive(Clone)]
pub struct SellWithTipParams {
pub rpc: Option<Arc<SolanaRpcClient>>,
@@ -75,16 +87,39 @@ pub struct SellWithTipParams {
pub protocol_params: Box<dyn ProtocolParams>,
}
/// PumpFun协议特定参数
/// PumpFun protocol specific parameters
/// Configuration parameters specific to PumpFun trading protocol
#[derive(Clone)]
pub struct PumpFunParams {
pub bonding_curve: Option<Arc<BondingCurveAccount>>,
pub bonding_curve: Arc<BondingCurveAccount>,
/// Whether to close token account when selling, only effective during sell operations
pub close_token_account_when_sell: Option<bool>,
}
impl PumpFunParams {
pub fn default() -> Self {
pub fn from_dev_trade(
mint: &Pubkey,
dev_token_amount: u64,
dev_sol_amount: u64,
creator: Pubkey,
close_token_account_when_sell: Option<bool>,
) -> Self {
let bonding_curve =
BondingCurveAccount::from_dev_trade(mint, dev_token_amount, dev_sol_amount, creator);
Self {
bonding_curve: None,
bonding_curve: Arc::new(bonding_curve),
close_token_account_when_sell: close_token_account_when_sell,
}
}
pub fn from_trade(
event: &PumpFunTradeEvent,
close_token_account_when_sell: Option<bool>,
) -> Self {
let bonding_curve = BondingCurveAccount::from_trade(event);
Self {
bonding_curve: Arc::new(bonding_curve),
close_token_account_when_sell: close_token_account_when_sell,
}
}
}
@@ -110,41 +145,61 @@ impl ProtocolParams for PumpFunParams {
#[derive(Clone)]
pub struct PumpSwapParams {
/// Liquidity pool address
/// If None, it will be queried via RPC, which adds latency
pub pool: Option<Pubkey>,
pub pool: Pubkey,
/// Base token mint address
/// The mint account address of the base token in the trading pair
/// If None, it will be queried via RPC, which adds latency
pub base_mint: Option<Pubkey>,
pub base_mint: Pubkey,
/// Quote token mint address
/// The mint account address of the quote token in the trading pair, usually SOL or USDC
/// If None, it will be queried via RPC, which adds latency
pub quote_mint: Option<Pubkey>,
pub quote_mint: Pubkey,
/// Base token reserves in the pool
pub pool_base_token_reserves: Option<u64>,
pub pool_base_token_reserves: u64,
/// Quote token reserves in the pool
pub pool_quote_token_reserves: Option<u64>,
pub pool_quote_token_reserves: u64,
/// Automatically handle WSOL wrapping
/// When true, automatically handles wrapping and unwrapping operations between SOL and WSOL
pub auto_handle_wsol: bool,
}
impl PumpSwapParams {
pub fn default() -> Self {
pub fn from_buy_trade(event: &PumpSwapBuyEvent) -> Self {
Self {
pool: None,
base_mint: None,
quote_mint: None,
pool_base_token_reserves: None,
pool_quote_token_reserves: None,
pool: event.pool,
base_mint: event.base_mint,
quote_mint: event.quote_mint,
pool_base_token_reserves: event.pool_base_token_reserves,
pool_quote_token_reserves: event.pool_quote_token_reserves,
auto_handle_wsol: true,
}
}
pub fn from_sell_trade(event: &PumpSwapSellEvent) -> Self {
Self {
pool: event.pool,
base_mint: event.base_mint,
quote_mint: event.quote_mint,
pool_base_token_reserves: event.pool_base_token_reserves,
pool_quote_token_reserves: event.pool_quote_token_reserves,
auto_handle_wsol: true,
}
}
pub async fn from_pool_address_by_rpc(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let pool_data = crate::trading::pumpswap::common::fetch_pool(rpc, pool_address).await?;
let (pool_base_token_reserves, pool_quote_token_reserves) =
get_token_balances(&pool_data, rpc).await?;
Ok(Self {
pool: pool_address.clone(),
base_mint: pool_data.base_mint,
quote_mint: pool_data.quote_mint,
pool_base_token_reserves: pool_base_token_reserves,
pool_quote_token_reserves: pool_quote_token_reserves,
auto_handle_wsol: true,
})
}
}
impl ProtocolParams for PumpSwapParams {
@@ -157,32 +212,28 @@ impl ProtocolParams for PumpSwapParams {
}
}
/// Bonk协议特定参数
/// Bonk protocol specific parameters
/// Configuration parameters specific to Bonk trading protocol
#[derive(Clone)]
pub struct BonkParams {
pub virtual_base: Option<u128>,
pub virtual_quote: Option<u128>,
pub real_base: Option<u128>,
pub real_quote: Option<u128>,
pub virtual_base: u128,
pub virtual_quote: u128,
pub real_base: u128,
pub real_quote: u128,
/// Token program ID
/// Specifies the program used by the token, usually spl_token::ID or spl_token_2022::ID
pub mint_token_program: Pubkey,
pub auto_handle_wsol: bool,
}
impl BonkParams {
pub fn default() -> Self {
Self {
virtual_base: None,
virtual_quote: None,
real_base: None,
real_quote: None,
auto_handle_wsol: true,
}
}
pub fn from_trade(trade_info: BonkTradeEvent) -> Self {
Self {
virtual_base: Some(trade_info.virtual_base as u128),
virtual_quote: Some(trade_info.virtual_quote as u128),
real_base: Some(trade_info.real_base_after as u128),
real_quote: Some(trade_info.real_quote_after as u128),
virtual_base: trade_info.virtual_base as u128,
virtual_quote: trade_info.virtual_quote as u128,
real_base: trade_info.real_base_after as u128,
real_quote: trade_info.real_quote_after as u128,
mint_token_program: trade_info.base_token_program,
auto_handle_wsol: true,
}
}
@@ -205,12 +256,9 @@ impl BonkParams {
0,
)
};
let real_quote = get_amount_in_net(
amount_in,
PROTOCOL_FEE_RATE,
PLATFORM_FEE_RATE,
SHARE_FEE_RATE,
) as u128;
let real_quote =
get_amount_in_net(amount_in, PROTOCOL_FEE_RATE, PLATFORM_FEE_RATE, SHARE_FEE_RATE)
as u128;
let amount_out = if trade_info.metadata.event_type == EventType::BonkBuyExactIn {
get_amount_out(
trade_info.amount_in,
@@ -228,13 +276,33 @@ impl BonkParams {
};
let real_base = amount_out;
Self {
virtual_base: Some(DEFAULT_VIRTUAL_BASE),
virtual_quote: Some(DEFAULT_VIRTUAL_QUOTE),
real_base: Some(real_base),
real_quote: Some(real_quote),
virtual_base: DEFAULT_VIRTUAL_BASE,
virtual_quote: DEFAULT_VIRTUAL_QUOTE,
real_base: real_base,
real_quote: real_quote,
mint_token_program: trade_info.base_token_program,
auto_handle_wsol: true,
}
}
pub async fn from_mint_by_rpc(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<Self, anyhow::Error> {
let pool_address =
crate::trading::bonk::common::get_pool_pda(mint, &accounts::WSOL_TOKEN_ACCOUNT)
.unwrap();
let pool_data = crate::trading::bonk::common::fetch_pool_state(rpc, &pool_address).await?;
let token_account = rpc.get_account(&pool_data.base_mint).await?;
Ok(Self {
virtual_base: pool_data.virtual_base as u128,
virtual_quote: pool_data.virtual_quote as u128,
real_base: pool_data.real_base as u128,
real_quote: pool_data.real_quote as u128,
mint_token_program: token_account.owner,
auto_handle_wsol: true,
})
}
}
impl ProtocolParams for BonkParams {
@@ -247,30 +315,45 @@ impl ProtocolParams for BonkParams {
}
}
/// RaydiumCpmm协议特定参数
/// RaydiumCpmm protocol specific parameters
/// Configuration parameters specific to Raydium CPMM trading protocol
#[derive(Clone)]
pub struct RaydiumCpmmParams {
/// 池子状态账户地址
pub pool_state: Option<Pubkey>,
/// 代币程序ID
/// 指定代币使用的程序,通常为 spl_token::ID 或 spl_token_2022::ID
pub mint_token_program: Option<Pubkey>,
/// 指定 mint_token 在 pool_state 账户数据中的索引位置
/// 默认值为1,表示在索引1的位置
pub mint_token_in_pool_state_index: Option<usize>,
pub minimum_amount_out: Option<u64>,
/// Base token mint address
pub base_mint: Pubkey,
/// Quote token mint address
pub quote_mint: Pubkey,
/// Base token reserve amount in the pool
pub base_reserve: u64,
/// Quote token reserve amount in the pool
pub quote_reserve: u64,
/// Base token program ID (usually spl_token::ID or spl_token_2022::ID)
pub base_token_program: Pubkey,
/// Quote token program ID (usually spl_token::ID or spl_token_2022::ID)
pub quote_token_program: Pubkey,
/// Whether to automatically handle wSOL wrapping and unwrapping
pub auto_handle_wsol: bool,
}
impl RaydiumCpmmParams {
pub fn default() -> Self {
Self {
pool_state: None,
mint_token_program: Some(spl_token::ID),
mint_token_in_pool_state_index: Some(1),
minimum_amount_out: None,
pub async fn from_pool_address_by_rpc(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let pool =
crate::trading::raydium_cpmm::common::fetch_pool_state(rpc, pool_address).await?;
let (token0_balance, token1_balance) =
get_pool_token_balances(rpc, pool_address, &pool.token0_mint, &pool.token1_mint)
.await?;
Ok(Self {
base_mint: pool.token0_mint,
quote_mint: pool.token1_mint,
base_reserve: token0_balance,
quote_reserve: token1_balance,
base_token_program: pool.token0_program,
quote_token_program: pool.token1_program,
auto_handle_wsol: true,
}
})
}
}
@@ -285,7 +368,8 @@ impl ProtocolParams for RaydiumCpmmParams {
}
impl BuyParams {
/// 转换为BuyWithTipParams
/// Convert to BuyWithTipParams
/// Transforms basic buy parameters into MEV-enabled parameters
pub fn with_tip(self, swqos_clients: Vec<Arc<SwqosClient>>) -> BuyWithTipParams {
BuyWithTipParams {
rpc: self.rpc,
@@ -305,7 +389,8 @@ impl BuyParams {
}
impl SellParams {
/// 转换为SellWithTipParams
/// Convert to SellWithTipParams
/// Transforms basic sell parameters into MEV-enabled parameters
pub fn with_tip(self, swqos_clients: Vec<Arc<SwqosClient>>) -> SellWithTipParams {
SellWithTipParams {
rpc: self.rpc,
+39 -102
View File
@@ -1,19 +1,16 @@
use anyhow::anyhow;
use tokio::sync::RwLock;
use std::{collections::HashMap, sync::Arc};
use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey
};
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::{
common::{
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient,
},
constants::{
self, pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS}, trade::trade::DEFAULT_SLIPPAGE
},
trading::common::calculate_with_slippage_buy
constants::{self, trade::trade::DEFAULT_SLIPPAGE},
};
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use anyhow::anyhow;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey,
};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
lazy_static::lazy_static! {
static ref ACCOUNT_CACHE: RwLock<HashMap<Pubkey, Arc<GlobalAccount>>> = RwLock::new(HashMap::new());
@@ -24,15 +21,18 @@ pub fn create_priority_fee_instructions(priority_fee: PriorityFee) -> Vec<Instru
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 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
Pubkey::find_program_address(
&[constants::pumpfun::seeds::GLOBAL_SEED],
&constants::pumpfun::accounts::PUMPFUN,
)
.0
});
*GLOBAL_PDA
}
@@ -40,7 +40,11 @@ pub fn get_global_pda() -> Pubkey {
#[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
Pubkey::find_program_address(
&[constants::pumpfun::seeds::MINT_AUTHORITY_SEED],
&constants::pumpfun::accounts::PUMPFUN,
)
.0
});
*MINT_AUTHORITY_PDA
}
@@ -63,7 +67,8 @@ pub fn get_creator_vault_pda(creator: &Pubkey) -> Option<Pubkey> {
#[inline]
pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[constants::pumpfun::seeds::USER_VOLUME_ACCUMULATOR_SEED, user.as_ref()];
let seeds: &[&[u8]; 2] =
&[constants::pumpfun::seeds::USER_VOLUME_ACCUMULATOR_SEED, user.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)
@@ -85,48 +90,35 @@ pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey {
constants::pumpfun::accounts::MPL_TOKEN_METADATA.as_ref(),
mint.as_ref(),
],
&constants::pumpfun::accounts::MPL_TOKEN_METADATA
).0
&constants::pumpfun::accounts::MPL_TOKEN_METADATA,
)
.0
}
#[inline]
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/) -> Result<Arc<GlobalAccount>, anyhow::Error> {
pub async fn get_global_account(/*rpc: &SolanaRpcClient*/
) -> Result<Arc<GlobalAccount>, anyhow::Error> {
let global_account = GlobalAccount::new();
let global_account = Arc::new(global_account);
Ok(global_account)
}
#[inline]
pub async fn get_initial_buy_price(global_account: &Arc<GlobalAccount>, amount_sol: u64) -> Result<u64, anyhow::Error> {
pub async fn get_initial_buy_price(
global_account: &Arc<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<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::<BondingCurveAccount>(&account.data)?);
Ok((bonding_curve, bonding_curve_pda))
}
#[inline]
pub async fn fetch_bonding_curve_account(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Arc<crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve>, Pubkey), anyhow::Error> {
let bonding_curve_pda: Pubkey = get_bonding_curve_pda(mint)
.ok_or(anyhow!("Bonding curve not found"))?;
) -> Result<(Arc<crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve>, Pubkey), anyhow::Error>{
let bonding_curve_pda: Pubkey =
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() {
@@ -139,63 +131,6 @@ pub async fn fetch_bonding_curve_account(
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,
@@ -203,7 +138,8 @@ pub async fn init_bonding_curve_account(
dev_sol_cost: u64,
creator: Pubkey,
) -> Result<Arc<BondingCurveAccount>, anyhow::Error> {
let bonding_curve = BondingCurveAccount::from_dev_trade(mint, dev_buy_token, dev_sol_cost, creator);
let bonding_curve =
BondingCurveAccount::from_dev_trade(mint, dev_buy_token, dev_sol_cost, creator);
let bonding_curve = Arc::new(bonding_curve);
Ok(bonding_curve)
}
@@ -220,11 +156,12 @@ pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
return 0;
}
let n: u128 = (trade_info.virtual_sol_reserves as u128) * (trade_info.virtual_token_reserves as u128);
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)
}
-146
View File
@@ -11,118 +11,6 @@ pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, a
Ok(pool_address)
}
pub async fn get_token_amount(
quote_mint_is_wsol: bool,
pool_base_token_reserves: u64,
pool_quote_token_reserves: u64,
sol_amount: u64,
lp_fee_basis_points: u64,
protocol_fee_basis_points: u64,
coin_creator_fee_basis_points: u64,
) -> Result<u64, anyhow::Error> {
let product = pool_base_token_reserves as u128 * pool_quote_token_reserves as u128;
if quote_mint_is_wsol {
// base_amount_out
let mut sol_amount = sol_amount as u128;
sol_amount = sol_amount
.checked_mul(10000)
.unwrap()
.checked_div(
(10000
+ lp_fee_basis_points
+ protocol_fee_basis_points
+ coin_creator_fee_basis_points) as u128,
)
.unwrap()
.checked_sub(1)
.unwrap();
let new_quote_amount = pool_quote_token_reserves as u128 + sol_amount as u128;
let new_base_amount = product / new_quote_amount;
let token_amount = pool_base_token_reserves as u128 - new_base_amount;
return Ok(token_amount as u64);
} else {
// min_quote_amount_out
let new_base_amount = pool_base_token_reserves as u128 + sol_amount as u128;
let new_quote_amount = product / new_base_amount;
let token_amount = pool_quote_token_reserves as u128 - new_quote_amount;
let lp_fee = token_amount
.checked_mul(lp_fee_basis_points as u128)
.unwrap()
.checked_div(10000)
.unwrap();
let protocol_fee = token_amount
.checked_mul(protocol_fee_basis_points as u128)
.unwrap()
.checked_div(10000)
.unwrap();
let coin_creator_fee = token_amount
.checked_mul(coin_creator_fee_basis_points as u128)
.unwrap()
.checked_div(10000)
.unwrap();
let token_amount = token_amount.checked_sub(lp_fee).unwrap();
let token_amount = token_amount.checked_sub(protocol_fee).unwrap();
let token_amount = token_amount.checked_sub(coin_creator_fee).unwrap();
return Ok(token_amount as u64);
}
}
pub async fn get_wsol_amount(
quote_mint_is_wsol: bool,
pool_base_token_reserves: u64,
pool_quote_token_reserves: u64,
token_amount: u64,
lp_fee_basis_points: u64,
protocol_fee_basis_points: u64,
coin_creator_fee_basis_points: u64,
) -> Result<u64, anyhow::Error> {
let product = pool_base_token_reserves as u128 * pool_quote_token_reserves as u128;
if !quote_mint_is_wsol {
// base_amount_out
let mut token_amount = token_amount as u128;
token_amount = token_amount
.checked_mul(10000)
.unwrap()
.checked_div(
(10000
+ lp_fee_basis_points
+ protocol_fee_basis_points
+ coin_creator_fee_basis_points) as u128,
)
.unwrap()
.checked_sub(1)
.unwrap();
let new_quote_amount = pool_quote_token_reserves as u128 + token_amount as u128;
let new_base_amount = product / new_quote_amount;
let wsol_amount = pool_base_token_reserves as u128 - new_base_amount;
Ok(wsol_amount as u64)
} else {
// min_quote_amount_out
let new_base_amount = pool_base_token_reserves as u128 + token_amount as u128;
let new_quote_amount = product / new_base_amount;
let token_amount = pool_quote_token_reserves as u128 - new_quote_amount;
let lp_fee = token_amount
.checked_mul(lp_fee_basis_points as u128)
.unwrap()
.checked_div(10000)
.unwrap();
let protocol_fee = token_amount
.checked_mul(protocol_fee_basis_points as u128)
.unwrap()
.checked_div(10000)
.unwrap();
let coin_creator_fee = token_amount
.checked_mul(coin_creator_fee_basis_points as u128)
.unwrap()
.checked_div(10000)
.unwrap();
let wsol_amount = token_amount.checked_sub(lp_fee).unwrap();
let wsol_amount = wsol_amount.checked_sub(protocol_fee).unwrap();
let wsol_amount = wsol_amount.checked_sub(coin_creator_fee).unwrap();
Ok(wsol_amount as u64)
}
}
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()],
@@ -276,37 +164,3 @@ pub async fn get_token_balances(
Ok((base_amount, quote_amount))
}
pub async fn calculate_buy_amount(
pool: &Pool,
rpc: &SolanaRpcClient,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = get_token_balances(pool, 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(
pool: &Pool,
rpc: &SolanaRpcClient,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = get_token_balances(pool, 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)
}
+1 -89
View File
@@ -2,7 +2,7 @@ use crate::{
common::SolanaRpcClient,
constants::{
self,
raydium_cpmm::accounts::{self, WSOL_TOKEN_ACCOUNT},
raydium_cpmm::accounts::{self},
},
};
use anyhow::anyhow;
@@ -52,94 +52,6 @@ pub fn get_observation_state_pda(pool_state: &Pubkey) -> Option<Pubkey> {
pda.map(|pubkey| pubkey.0)
}
pub async fn get_buy_token_amount(
rpc: &SolanaRpcClient,
pool_state: &Pubkey,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool = fetch_pool_state(rpc, pool_state).await?;
let is_token0_input = if pool.token0_mint == WSOL_TOKEN_ACCOUNT { true } else { false };
let (token0_balance, token1_balance) =
get_pool_token_balances(rpc, pool_state, &pool.token0_mint, &pool.token1_mint).await?;
// 使用恒定乘积公式计算
let (reserve_in, reserve_out) = if is_token0_input {
(token0_balance, token1_balance)
} else {
(token1_balance, token0_balance)
};
if reserve_in == 0 || reserve_out == 0 {
return Err(anyhow!("池子储备金为零,无法进行交换"));
}
// 使用 u128 防止溢出
let amount_in_128 = sol_amount as u128;
let reserve_in_128 = reserve_in as u128;
let reserve_out_128 = reserve_out as u128;
// 恒定乘积公式: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in)
let numerator = amount_in_128 * reserve_out_128;
let denominator = reserve_in_128 + amount_in_128;
if denominator == 0 {
return Err(anyhow!("分母为零,计算错误"));
}
let amount_out = numerator / denominator;
// 检查是否超出储备金
if amount_out >= reserve_out_128 {
return Err(anyhow!("输出数量超过池子储备金"));
}
Ok(amount_out as u64)
}
pub async fn get_sell_sol_amount(
rpc: &SolanaRpcClient,
pool_state: &Pubkey,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool = fetch_pool_state(rpc, pool_state).await?;
let is_token0_sol = if pool.token0_mint == WSOL_TOKEN_ACCOUNT { true } else { false };
let (token0_balance, token1_balance) =
get_pool_token_balances(rpc, pool_state, &pool.token0_mint, &pool.token1_mint).await?;
let (reserve_in, reserve_out) = if is_token0_sol {
(token1_balance, token0_balance)
} else {
(token0_balance, token1_balance)
};
if reserve_in == 0 || reserve_out == 0 {
return Err(anyhow!("池子储备金为零,无法进行交换"));
}
// 使用 u128 防止溢出
let amount_in_128 = token_amount as u128;
let reserve_in_128 = reserve_in as u128;
let reserve_out_128 = reserve_out as u128;
// 恒定乘积公式: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in)
let numerator = amount_in_128 * reserve_out_128;
let denominator = reserve_in_128 + amount_in_128;
if denominator == 0 {
return Err(anyhow!("分母为零,计算错误"));
}
let amount_out = numerator / denominator;
// 检查是否超出储备金
if amount_out >= reserve_out_128 {
return Err(anyhow!("输出数量超过池子储备金"));
}
Ok(amount_out as u64)
}
/// 获取池子中两个代币的余额
///
/// # 返回值
+112
View File
@@ -0,0 +1,112 @@
use crate::constants::bonk::accounts;
/// Calculates the amount of tokens to receive when buying with SOL
///
/// This function implements the constant product formula (x * y = k) for token swaps,
/// taking into account various fees and slippage protection.
///
/// # Arguments
///
/// * `amount_in` - The amount of SOL to spend (in lamports)
/// * `virtual_base` - Virtual base token reserves
/// * `virtual_quote` - Virtual quote token (SOL) reserves
/// * `real_base` - Real base token reserves
/// * `real_quote` - Real quote token (SOL) reserves
/// * `slippage_basis_points` - Maximum slippage tolerance in basis points (e.g., 100 = 1%)
///
/// # Returns
///
/// The minimum amount of tokens that will be received after fees and slippage
pub fn get_buy_token_amount_from_sol_amount(
amount_in: u64,
virtual_base: u128,
virtual_quote: u128,
real_base: u128,
real_quote: u128,
slippage_basis_points: u128,
) -> u64 {
let amount_in_u128 = amount_in as u128;
// Calculate various fees deducted from input amount
let protocol_fee = (amount_in_u128 * accounts::PROTOCOL_FEE_RATE / 10000) as u128;
let platform_fee = (amount_in_u128 * accounts::PLATFORM_FEE_RATE / 10000) as u128;
let share_fee = (amount_in_u128 * accounts::SHARE_FEE_RATE / 10000) as u128;
// Calculate net input amount after deducting all fees
let amount_in_net = amount_in_u128
.checked_sub(protocol_fee)
.unwrap()
.checked_sub(platform_fee)
.unwrap()
.checked_sub(share_fee)
.unwrap();
// Calculate total reserves (virtual + real)
let input_reserve = virtual_quote.checked_add(real_quote).unwrap();
let output_reserve = virtual_base.checked_sub(real_base).unwrap();
// Apply constant product formula: amount_out = (amount_in * output_reserve) / (input_reserve + amount_in)
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();
// Apply slippage protection
amount_out = amount_out - (amount_out * slippage_basis_points) / 10000;
amount_out as u64
}
/// Calculates the amount of SOL to receive when selling tokens
///
/// This function implements the constant product formula (x * y = k) for token swaps,
/// calculating the SOL output for a given token input amount, accounting for fees and slippage.
///
/// # Arguments
///
/// * `amount_in` - The amount of tokens to sell
/// * `virtual_base` - Virtual base token reserves
/// * `virtual_quote` - Virtual quote token (SOL) reserves
/// * `real_base` - Real base token reserves
/// * `real_quote` - Real quote token (SOL) reserves
/// * `slippage_basis_points` - Maximum slippage tolerance in basis points (e.g., 100 = 1%)
///
/// # Returns
///
/// The minimum amount of SOL that will be received after fees and slippage
pub fn get_sell_sol_amount_from_token_amount(
amount_in: u64,
virtual_base: u128,
virtual_quote: u128,
real_base: u128,
real_quote: u128,
slippage_basis_points: u128,
) -> u64 {
let amount_in_u128 = amount_in as u128;
// For sell operation, input_reserve is token reserves, output_reserve is SOL reserves
let input_reserve = virtual_base.checked_add(real_base).unwrap();
let output_reserve = virtual_quote.checked_add(real_quote).unwrap();
// Use constant product formula to calculate SOL amount received from selling tokens
let numerator = amount_in_u128.checked_mul(output_reserve).unwrap();
let denominator = input_reserve.checked_add(amount_in_u128).unwrap();
let sol_amount_out = numerator.checked_div(denominator).unwrap();
// Calculate various fees
let protocol_fee = (sol_amount_out * accounts::PROTOCOL_FEE_RATE / 10000) as u128;
let platform_fee = (sol_amount_out * accounts::PLATFORM_FEE_RATE / 10000) as u128;
let share_fee = (sol_amount_out * accounts::SHARE_FEE_RATE / 10000) as u128;
// Net SOL amount after deducting fees
let sol_amount_net = sol_amount_out
.checked_sub(protocol_fee)
.unwrap()
.checked_sub(platform_fee)
.unwrap()
.checked_sub(share_fee)
.unwrap();
// Apply slippage protection
let final_amount = sol_amount_net - (sol_amount_net * slippage_basis_points) / 10000;
final_amount as u64
}
+63
View File
@@ -0,0 +1,63 @@
/// Calculate transaction fee based on amount and fee basis points
///
/// # Parameters
/// * `amount` - Transaction amount
/// * `fee_basis_points` - Fee basis points, 1 basis point = 0.01%
///
/// # Examples
/// * fee_basis_points = 1 -> 0.01% fee
/// * fee_basis_points = 10 -> 0.1% fee
/// * fee_basis_points = 25 -> 0.25% fee (common exchange rate)
/// * fee_basis_points = 100 -> 1% fee
pub fn compute_fee(amount: u128, fee_basis_points: u128) -> u128 {
ceil_div(amount * fee_basis_points, 10_000)
}
/// Ceiling division implementation
/// Ceiling division that ensures results are not lost due to integer division precision
///
/// # Parameters
/// * `a` - Dividend
/// * `b` - Divisor
///
/// # Returns
/// Returns the ceiling result of a/b
pub fn ceil_div(a: u128, b: u128) -> u128 {
(a + b - 1) / b
}
/// Calculate buy amount with slippage protection
/// Add slippage percentage to the amount to ensure successful purchase
///
/// # Parameters
/// * `amount` - Original transaction amount
/// * `basis_points` - Slippage basis points, 1 basis point = 0.01%
///
/// # Examples
/// * basis_points = 1 -> 0.01% slippage
/// * basis_points = 10 -> 0.1% slippage
/// * basis_points = 100 -> 1% slippage
/// * basis_points = 500 -> 5% slippage
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
amount + (amount * basis_points / 10000)
}
/// Calculate sell amount with slippage protection
/// Subtract slippage percentage from the amount to ensure successful sale
///
/// # Parameters
/// * `amount` - Original transaction amount
/// * `basis_points` - Slippage basis points, 1 basis point = 0.01%
///
/// # Examples
/// * basis_points = 1 -> 0.01% slippage
/// * basis_points = 10 -> 0.1% slippage
/// * basis_points = 100 -> 1% slippage
/// * basis_points = 500 -> 5% slippage
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
if amount <= basis_points / 10000 {
1
} else {
amount - (amount * basis_points / 10000)
}
}
+6
View File
@@ -0,0 +1,6 @@
pub mod pumpfun;
pub mod common;
pub mod pumpswap;
pub mod bonk;
pub mod raydium_amm_v4;
pub mod raydium_cpmm;
+108
View File
@@ -0,0 +1,108 @@
use solana_sdk::{native_token::sol_str_to_lamports, pubkey::Pubkey};
use crate::{
constants::pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS},
utils::calc::common::compute_fee,
};
/// Calculates the amount of tokens that can be purchased with a given SOL amount
/// using the bonding curve formula.
///
/// # Arguments
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
/// * `real_token_reserves` - Actual token reserves available for purchase
/// * `creator` - Creator's public key (affects fee calculation)
/// * `amount` - SOL amount to spend (in lamports)
///
/// # Returns
/// The amount of tokens that will be received (in token's smallest unit)
pub fn get_buy_token_amount_from_sol_amount(
virtual_token_reserves: u128,
virtual_sol_reserves: u128,
real_token_reserves: u128,
creator: Pubkey,
amount: u64,
) -> u64 {
if amount == 0 {
return 0;
}
if virtual_token_reserves == 0 {
return 0;
}
let total_fee_basis_points =
FEE_BASIS_POINTS + if creator != Pubkey::default() { CREATOR_FEE } else { 0 };
// Convert to u128 to prevent overflow
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 denominator = virtual_sol_reserves + input_amount;
let mut tokens_received =
input_amount.checked_mul(virtual_token_reserves).unwrap().checked_div(denominator).unwrap();
tokens_received = tokens_received.min(real_token_reserves);
if tokens_received <= 100 * 1_000_000_u128 {
tokens_received = if amount > sol_str_to_lamports("0.01").unwrap_or(0) {
25547619 * 1_000_000_u128
} else {
255476 * 1_000_000_u128
};
}
tokens_received as u64
}
/// Calculates the amount of SOL that will be received when selling a given token amount
/// using the bonding curve formula with transaction fees deducted.
///
/// # Arguments
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
/// * `creator` - Creator's public key (affects fee calculation)
/// * `amount` - Token amount to sell (in token's smallest unit)
///
/// # Returns
/// The amount of SOL that will be received after fees (in lamports)
pub fn get_sell_sol_amount_from_token_amount(
virtual_token_reserves: u128,
virtual_sol_reserves: u128,
creator: Pubkey,
amount: u64,
) -> u64 {
if amount == 0 {
return 0;
}
// migrated bonding curve
if virtual_token_reserves == 0 {
return 0;
}
let amount_128 = amount as u128;
// Calculate SOL amount received from selling tokens using constant product formula
let numerator = amount_128.checked_mul(virtual_sol_reserves).unwrap_or(0);
let denominator = virtual_token_reserves.checked_add(amount_128).unwrap_or(1);
let sol_cost = numerator.checked_div(denominator).unwrap_or(0);
let total_fee_basis_points =
FEE_BASIS_POINTS + if creator != Pubkey::default() { CREATOR_FEE } else { 0 };
let total_fee_basis_points_128 = total_fee_basis_points as u128;
// Calculate transaction fee
let fee = compute_fee(sol_cost, total_fee_basis_points_128);
sol_cost.saturating_sub(fee) as u64
}
+275
View File
@@ -0,0 +1,275 @@
use super::common::{
calculate_with_slippage_buy, calculate_with_slippage_sell, ceil_div, compute_fee,
};
use crate::constants::pumpswap::accounts::{
COIN_CREATOR_FEE_BASIS_POINTS, LP_FEE_BASIS_POINTS, PROTOCOL_FEE_BASIS_POINTS,
};
use solana_sdk::pubkey::Pubkey;
/// Result for buying base tokens with base amount input
#[derive(Clone, Debug)]
pub struct BuyBaseInputResult {
/// Raw quote amount needed before fees
pub internal_quote_amount: u64,
/// Total quote amount including all fees
pub ui_quote: u64,
/// Maximum quote amount with slippage protection
pub max_quote: u64,
}
/// Result for buying base tokens with quote amount input
#[derive(Clone, Debug)]
pub struct BuyQuoteInputResult {
/// Amount of base tokens received
pub base: u64,
/// Effective quote amount after fee deduction
pub internal_quote_without_fees: u64,
/// Maximum quote amount with slippage protection
pub max_quote: u64,
}
/// Result for selling base tokens with base amount input
#[derive(Clone, Debug)]
pub struct SellBaseInputResult {
/// Final quote amount received after fees
pub ui_quote: u64,
/// Minimum quote amount with slippage protection
pub min_quote: u64,
/// Raw quote amount before fee deduction
pub internal_quote_amount_out: u64,
}
/// Result for selling base tokens with quote amount input
#[derive(Clone, Debug)]
pub struct SellQuoteInputResult {
/// Raw quote amount including fees
pub internal_raw_quote: u64,
/// Amount of base tokens needed to sell
pub base: u64,
/// Minimum quote amount with slippage protection
pub min_quote: u64,
}
/// Calculate quote amount needed to buy a specific amount of base tokens
///
/// # Arguments
/// * `base` - Amount of base tokens to buy
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `BuyBaseInputResult` containing quote amounts and slippage calculations
pub fn buy_base_input_internal(
base: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<BuyBaseInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
if base > base_reserve {
return Err("Cannot buy more base tokens than the pool reserves.".to_string());
}
// Calculate required quote amount using constant product formula
let numerator = (quote_reserve as u128) * (base as u128);
let denominator = base_reserve - base;
if denominator == 0 {
return Err("Pool would be depleted; denominator is zero.".to_string());
}
let quote_amount_in = ceil_div(numerator, denominator as u128) as u64;
// Calculate fees
let lp_fee = compute_fee(quote_amount_in as u128, LP_FEE_BASIS_POINTS as u128) as u64;
let protocol_fee =
compute_fee(quote_amount_in as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
let coin_creator_fee = if *coin_creator == Pubkey::default() {
0
} else {
compute_fee(quote_amount_in as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
};
let total_quote = quote_amount_in + lp_fee + protocol_fee + coin_creator_fee;
// Calculate max quote with slippage
let max_quote = calculate_with_slippage_buy(total_quote, slippage_basis_points);
Ok(BuyBaseInputResult {
internal_quote_amount: quote_amount_in,
ui_quote: total_quote,
max_quote,
})
}
/// Calculate base tokens received for a specific quote amount
///
/// # Arguments
/// * `quote` - Amount of quote tokens to spend
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `BuyQuoteInputResult` containing base amount and slippage calculations
pub fn buy_quote_input_internal(
quote: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<BuyQuoteInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
// Calculate total fee basis points
let total_fee_bps = LP_FEE_BASIS_POINTS
+ PROTOCOL_FEE_BASIS_POINTS
+ if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS };
let denominator = 10_000 + total_fee_bps;
// Calculate effective quote amount after fees
let effective_quote = (quote as u128 * 10_000) / denominator as u128;
// Calculate base amount out using constant product formula
let numerator = (base_reserve as u128) * effective_quote;
let denominator_effective = (quote_reserve as u128) + effective_quote;
if denominator_effective == 0 {
return Err("Pool would be depleted; denominator is zero.".to_string());
}
let base_amount_out = (numerator / denominator_effective) as u64;
// Calculate max quote with slippage
let max_quote = calculate_with_slippage_buy(quote, slippage_basis_points);
Ok(BuyQuoteInputResult {
base: base_amount_out,
internal_quote_without_fees: effective_quote as u64,
max_quote,
})
}
/// Calculate quote tokens received for selling a specific amount of base tokens
///
/// # Arguments
/// * `base` - Amount of base tokens to sell
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `SellBaseInputResult` containing quote amounts and slippage calculations
pub fn sell_base_input_internal(
base: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<SellBaseInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
// Calculate quote amount out using constant product formula
let quote_amount_out = ((quote_reserve as u128) * (base as u128)
/ ((base_reserve as u128) + (base as u128))) as u64;
// Calculate fees
let lp_fee = compute_fee(quote_amount_out as u128, LP_FEE_BASIS_POINTS as u128) as u64;
let protocol_fee =
compute_fee(quote_amount_out as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
let coin_creator_fee = if *coin_creator == Pubkey::default() {
0
} else {
compute_fee(quote_amount_out as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
};
// Calculate final quote after fees
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
if total_fees > quote_amount_out {
return Err("Fees exceed total output; final quote is negative.".to_string());
}
let final_quote = quote_amount_out - total_fees;
// Calculate min quote with slippage
let min_quote = calculate_with_slippage_sell(final_quote, slippage_basis_points);
Ok(SellBaseInputResult {
ui_quote: final_quote,
min_quote,
internal_quote_amount_out: quote_amount_out,
})
}
const MAX_FEE_BASIS_POINTS: u64 = 10_000;
/// Calculate quote amount out including fees
fn calculate_quote_amount_out(
user_quote_amount_out: u64,
lp_fee_basis_points: u64,
protocol_fee_basis_points: u64,
coin_creator_fee_basis_points: u64,
) -> u64 {
let total_fee_basis_points =
lp_fee_basis_points + protocol_fee_basis_points + coin_creator_fee_basis_points;
let denominator = MAX_FEE_BASIS_POINTS - total_fee_basis_points;
ceil_div((user_quote_amount_out as u128) * (MAX_FEE_BASIS_POINTS as u128), denominator as u128)
as u64
}
/// Calculate base tokens needed to receive a specific amount of quote tokens
///
/// # Arguments
/// * `quote` - Desired amount of quote tokens to receive
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `SellQuoteInputResult` containing base amount and slippage calculations
pub fn sell_quote_input_internal(
quote: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<SellQuoteInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
if quote > quote_reserve {
return Err("Cannot receive more quote tokens than the pool quote reserves.".to_string());
}
// Calculate raw quote amount including fees
let raw_quote = calculate_quote_amount_out(
quote,
LP_FEE_BASIS_POINTS,
PROTOCOL_FEE_BASIS_POINTS,
if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS },
);
// Calculate base amount needed using inverse constant product formula
if raw_quote >= quote_reserve {
return Err("Invalid input: Desired quote amount exceeds available reserve.".to_string());
}
let base_amount_in =
ceil_div((base_reserve as u128) * (raw_quote as u128), (quote_reserve - raw_quote) as u128)
as u64;
// Calculate min quote with slippage
let min_quote = calculate_with_slippage_sell(quote, slippage_basis_points);
Ok(SellQuoteInputResult { internal_raw_quote: raw_quote, base: base_amount_in, min_quote })
}
+1
View File
@@ -0,0 +1 @@
// TODO
+190
View File
@@ -0,0 +1,190 @@
use crate::constants::raydium_cpmm::accounts::{
CREATOR_FEE_RATE, FEE_RATE_DENOMINATOR_VALUE, FUND_FEE_RATE, PROTOCOL_FEE_RATE, TRADE_FEE_RATE,
};
/// Computes trading fee using ceiling division.
///
/// # Arguments
/// * `amount` - The amount to calculate fee for
/// * `fee_rate` - The fee rate to apply
///
/// # Returns
/// The calculated trading fee
fn compute_trading_fee(amount: u64, fee_rate: u64) -> u64 {
let numerator = (amount as u128) * (fee_rate as u128);
((numerator + FEE_RATE_DENOMINATOR_VALUE - 1) / FEE_RATE_DENOMINATOR_VALUE) as u64
}
/// Computes protocol or fund fee using floor division.
///
/// # Arguments
/// * `amount` - The amount to calculate fee for
/// * `fee_rate` - The fee rate to apply
///
/// # Returns
/// The calculated protocol or fund fee
fn compute_protocol_fund_fee(amount: u64, fee_rate: u64) -> u64 {
let numerator = (amount as u128) * (fee_rate as u128);
(numerator / FEE_RATE_DENOMINATOR_VALUE) as u64
}
/// Computes creator fee using ceiling division.
///
/// # Arguments
/// * `amount` - The amount to calculate fee for
/// * `fee_rate` - The fee rate to apply
///
/// # Returns
/// The calculated creator fee
fn compute_creator_fee_new(amount: u64, fee_rate: u64) -> u64 {
let numerator = (amount as u128) * (fee_rate as u128);
((numerator + FEE_RATE_DENOMINATOR_VALUE - 1) / FEE_RATE_DENOMINATOR_VALUE) as u64
}
/// Parameters for computing swap amounts and fees.
#[derive(Debug, Clone)]
pub struct ComputeSwapParams {
/// Whether the entire input amount is traded
pub all_trade: bool,
/// The input amount for the swap
pub amount_in: u64,
/// The expected output amount from the swap
pub amount_out: u64,
/// The minimum acceptable output amount (considering slippage_basis_points)
pub min_amount_out: u64,
/// The trading fee amount
pub fee: u64,
}
/// Result of a swap calculation containing all relevant amounts and fees.
#[derive(Debug, Clone)]
pub struct SwapResult {
/// The new amount in the input vault after the swap
pub new_input_vault_amount: u64,
/// The new amount in the output vault after the swap
pub new_output_vault_amount: u64,
/// The actual input amount used in the swap
pub input_amount: u64,
/// The actual output amount received from the swap
pub output_amount: u64,
/// The trading fee charged
pub trade_fee: u64,
/// The protocol fee charged
pub protocol_fee: u64,
/// The fund fee charged
pub fund_fee: u64,
/// The creator fee charged
pub creator_fee: u64,
}
/// Performs a swap calculation based on input amount.
///
/// Calculates the output amount and all associated fees when swapping a specific input amount.
///
/// # Arguments
/// * `input_amount` - The amount of input tokens to swap
/// * `input_vault_amount` - Current amount in the input token vault
/// * `output_vault_amount` - Current amount in the output token vault
/// * `trade_fee_rate` - The trading fee rate
/// * `creator_fee_rate` - The creator fee rate
/// * `protocol_fee_rate` - The protocol fee rate
/// * `fund_fee_rate` - The fund fee rate
/// * `is_creator_fee_on_input` - Whether creator fee is charged on input tokens
///
/// # Returns
/// A `SwapResult` containing all swap calculations and fees
fn swap_base_input(
input_amount: u64,
input_vault_amount: u64,
output_vault_amount: u64,
trade_fee_rate: u64,
creator_fee_rate: u64,
protocol_fee_rate: u64,
fund_fee_rate: u64,
is_creator_fee_on_input: bool,
) -> SwapResult {
let mut creator_fee = 0u64;
let trade_fee = compute_trading_fee(input_amount, trade_fee_rate);
let input_amount_less_fees = if is_creator_fee_on_input {
creator_fee = compute_creator_fee_new(input_amount, creator_fee_rate);
input_amount.saturating_sub(trade_fee).saturating_sub(creator_fee)
} else {
input_amount.saturating_sub(trade_fee)
};
let protocol_fee = compute_protocol_fund_fee(trade_fee, protocol_fee_rate);
let fund_fee = compute_protocol_fund_fee(trade_fee, fund_fee_rate);
let output_amount_swapped = ((output_vault_amount as u128)
.saturating_mul(input_amount_less_fees as u128)
/ (input_vault_amount as u128).saturating_add(input_amount_less_fees as u128))
as u64;
let output_amount = if is_creator_fee_on_input {
output_amount_swapped
} else {
creator_fee = compute_creator_fee_new(output_amount_swapped, creator_fee_rate);
output_amount_swapped.saturating_sub(creator_fee)
};
SwapResult {
new_input_vault_amount: input_vault_amount.saturating_add(input_amount_less_fees),
new_output_vault_amount: output_vault_amount.saturating_sub(output_amount_swapped),
input_amount,
output_amount,
trade_fee,
protocol_fee,
fund_fee,
creator_fee,
}
}
/// Computes swap parameters including amounts, fees, and slippage protection.
///
/// This function calculates the expected output amount, minimum output amount (with slippage),
/// and trading fees for a given input amount in a CPMM (Constant Product Market Maker) pool.
///
/// # Arguments
/// * `base_reserve` - The current reserve amount of the base token in the pool
/// * `quote_reserve` - The current reserve amount of the quote token in the pool
/// * `is_base_in` - Whether the input token is the base token (true) or quote token (false)
/// * `amount_in` - The amount of input tokens to swap
/// * `slippage_basis_points` - The acceptable slippage in basis points (e.g., 100 for 1%)
///
/// # Returns
/// A `ComputeSwapParams` struct containing all computed swap parameters
pub fn compute_swap_amount(
base_reserve: u64,
quote_reserve: u64,
is_base_in: bool,
amount_in: u64,
slippage_basis_points: u64,
) -> ComputeSwapParams {
let (input_reserve, output_reserve) =
if is_base_in { (base_reserve, quote_reserve) } else { (quote_reserve, base_reserve) };
let swap_result = swap_base_input(
amount_in,
input_reserve,
output_reserve,
TRADE_FEE_RATE,
CREATOR_FEE_RATE,
PROTOCOL_FEE_RATE,
FUND_FEE_RATE,
true,
);
let min_amount_out = ((swap_result.output_amount as f64) * (1.0 - (slippage_basis_points as f64) / 10000.0)) as u64;
let all_trade = swap_result.input_amount == amount_in;
ComputeSwapParams {
all_trade,
amount_in,
amount_out: swap_result.output_amount,
min_amount_out,
fee: swap_result.trade_fee,
}
}
+1
View File
@@ -1,4 +1,5 @@
pub mod price;
pub mod calc;
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::trading;