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:
+15
-30
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/// 获取池子中两个代币的余额
|
||||
///
|
||||
/// # 返回值
|
||||
|
||||
Reference in New Issue
Block a user