refactor: restructure trading modules and add price calculation utilities

- Remove redundant pool.rs files from trading protocols
- Consolidate protocol logic into common.rs files
- Add comprehensive price calculation utilities for all protocols
- Add decimals constants module
- Restructure utils module for better organization
- Update documentation with price utilities information

Breaking changes:
- Removed bonding_curve.rs from pumpfun module
- Consolidated trading logic across protocols
- Refactored utils module structure
This commit is contained in:
ysq
2025-08-14 22:45:33 +08:00
parent 29179519a6
commit 63244f4073
28 changed files with 688 additions and 693 deletions
+26 -40
View File
@@ -1,5 +1,25 @@
use crate::constants;
use crate::{
common::SolanaRpcClient,
constants::{self, bonk::accounts},
};
use anyhow::anyhow;
use solana_sdk::pubkey::Pubkey;
use solana_streamer_sdk::streaming::event_parser::protocols::bonk::{
pool_state_decode, types::PoolState,
};
pub async fn fetch_pool_state(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<PoolState, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::BONK {
return Err(anyhow!("Account is not owned by Bonk program"));
}
let pool_state = pool_state_decode(&account.data[8..])
.ok_or_else(|| anyhow!("Failed to decode pool state"))?;
Ok(pool_state)
}
pub fn get_amount_in_net(
amount_in: u64,
@@ -41,9 +61,7 @@ pub fn get_amount_in(
// 根据 AMM 公式反推: amount_in_net = (amount_out * input_reserve) / (output_reserve - amount_out)
let numerator = amount_out_with_slippage.checked_mul(input_reserve).unwrap();
let denominator = output_reserve
.checked_sub(amount_out_with_slippage)
.unwrap();
let denominator = output_reserve.checked_sub(amount_out_with_slippage).unwrap();
let amount_in_net = numerator.checked_div(denominator).unwrap();
// 计算总费用率
@@ -87,53 +105,21 @@ pub fn get_amount_out(
}
pub fn get_pool_pda(base_mint: &Pubkey, quote_mint: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 3] = &[
constants::bonk::seeds::POOL_SEED,
base_mint.as_ref(),
quote_mint.as_ref(),
];
let seeds: &[&[u8]; 3] =
&[constants::bonk::seeds::POOL_SEED, base_mint.as_ref(), quote_mint.as_ref()];
let program_id: &Pubkey = &constants::bonk::accounts::BONK;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 3] = &[
constants::bonk::seeds::POOL_VAULT_SEED,
pool_state.as_ref(),
mint.as_ref(),
];
let seeds: &[&[u8]; 3] =
&[constants::bonk::seeds::POOL_VAULT_SEED, pool_state.as_ref(), mint.as_ref()];
let program_id: &Pubkey = &constants::bonk::accounts::BONK;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
pub fn get_token_price(
virtual_base: u128,
virtual_quote: u128,
real_base: u128,
real_quote: u128,
decimal_base: u64,
decimal_quote: u64,
) -> f64 {
// 计算小数位数差异
let decimal_diff = decimal_quote as i32 - decimal_base as i32;
let decimal_factor = if decimal_diff >= 0 {
10_f64.powi(decimal_diff)
} else {
1.0 / 10_f64.powi(-decimal_diff)
};
// 计算价格前的状态
let quote_reserves = virtual_quote.checked_add(real_quote).unwrap();
let base_reserves = virtual_base.checked_sub(real_base).unwrap();
// 使用浮点数计算价格,避免整数除法的精度丢失
let price = (quote_reserves as f64) / (base_reserves as f64) / decimal_factor;
price
}
#[cfg(test)]
mod tests {
use crate::constants::bonk::accounts::{PLATFORM_FEE_RATE, PROTOCOL_FEE_RATE, SHARE_FEE_RATE};
-1
View File
@@ -1,2 +1 @@
pub mod common;
pub mod pool;
-62
View File
@@ -1,62 +0,0 @@
use crate::{common::SolanaRpcClient, constants::bonk::accounts};
use anyhow::anyhow;
use borsh::BorshDeserialize;
use solana_sdk::pubkey::Pubkey;
#[derive(Debug, Clone, BorshDeserialize)]
pub struct VestingSchedule {
pub total_locked_amount: u64,
pub cliff_period: u64,
pub unlock_period: u64,
pub start_time: u64,
pub allocated_share_amount: u64,
}
#[derive(Debug, Clone, BorshDeserialize)]
pub struct Pool {
pub epoch: u64,
pub auth_bump: u8,
pub status: u8,
pub base_decimals: u8,
pub quote_decimals: u8,
pub migrate_type: u8,
pub supply: u64,
pub total_base_sell: u64,
pub virtual_base: u64,
pub virtual_quote: u64,
pub real_base: u64,
pub real_quote: u64,
pub total_quote_fund_raising: u64,
pub quote_protocol_fee: u64,
pub platform_fee: u64,
pub migrate_fee: u64,
pub vesting_schedule: VestingSchedule,
pub global_config: Pubkey,
pub platform_config: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub base_vault: Pubkey,
pub quote_vault: Pubkey,
pub creator: Pubkey,
pub padding: [u64; 8],
}
impl Pool {
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
let pool = Pool::try_from_slice(&data[8..])?;
Ok(pool)
}
pub async fn fetch(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::BONK {
return Err(anyhow!("Account is not owned by Bonk program"));
}
Self::from_bytes(&account.data)
}
}
-174
View File
@@ -1,174 +0,0 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_sdk::pubkey::Pubkey;
/// Represents a bonding curve for token pricing and liquidity management
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct PumpfunBondingCurveAccount {
/// Unique identifier for the bonding curve
pub discriminator: u64,
/// Virtual token reserves used for price calculations
pub virtual_token_reserves: u64,
/// Virtual SOL reserves used for price calculations
pub virtual_sol_reserves: u64,
/// Actual token reserves available for trading
pub real_token_reserves: u64,
/// Actual SOL reserves available for trading
pub real_sol_reserves: u64,
/// Total supply of tokens
pub token_total_supply: u64,
/// Whether the bonding curve is complete/finalized
pub complete: bool,
/// Token creator's address
pub creator: Pubkey,
}
impl PumpfunBondingCurveAccount {
/// Creates a new bonding curve instance
///
/// # Arguments
/// * `discriminator` - Unique identifier for the curve
/// * `virtual_token_reserves` - Virtual token reserves for price calculations
/// * `virtual_sol_reserves` - Virtual SOL reserves for price calculations
/// * `real_token_reserves` - Actual token reserves available
/// * `real_sol_reserves` - Actual SOL reserves available
/// * `token_total_supply` - Total supply of tokens
/// * `complete` - Whether the curve is complete
#[allow(clippy::too_many_arguments)]
pub fn new(
discriminator: u64,
virtual_token_reserves: u64,
virtual_sol_reserves: u64,
real_token_reserves: u64,
real_sol_reserves: u64,
token_total_supply: u64,
complete: bool,
creator: Pubkey,
) -> Self {
Self {
discriminator,
virtual_token_reserves,
virtual_sol_reserves,
real_token_reserves,
real_sol_reserves,
token_total_supply,
complete,
creator,
}
}
/// Calculates the amount of tokens received for a given SOL amount
///
/// # Arguments
/// * `amount` - Amount of SOL to spend
///
/// # Returns
/// * `Ok(u64)` - Amount of tokens that would be received
/// * `Err(&str)` - Error message if curve is complete
pub fn get_buy_price(&self, amount: u64) -> Result<u64, &'static str> {
if self.complete {
return Err("Curve is complete");
}
if amount == 0 {
return Ok(0);
}
// Calculate the product of virtual reserves using u128 to avoid overflow
let n: u128 = (self.virtual_sol_reserves as u128) * (self.virtual_token_reserves as u128);
// Calculate the new virtual sol reserves after the purchase
let i: u128 = (self.virtual_sol_reserves as u128) + (amount as u128);
// Calculate the new virtual token reserves after the purchase
let r: u128 = n / i + 1;
// Calculate the amount of tokens to be purchased
let s: u128 = (self.virtual_token_reserves as u128) - r;
// Convert back to u64 and return the minimum of calculated tokens and real reserves
let s_u64 = s as u64;
Ok(if s_u64 < self.real_token_reserves { s_u64 } else { self.real_token_reserves })
}
/// Calculates the amount of SOL received for selling tokens
///
/// # Arguments
/// * `amount` - Amount of tokens to sell
/// * `fee_basis_points` - Fee in basis points (1/100th of a percent)
///
/// # Returns
/// * `Ok(u64)` - Amount of SOL that would be received after fees
/// * `Err(&str)` - Error message if curve is complete
pub fn get_sell_price(&self, amount: u64, fee_basis_points: u64) -> Result<u64, &'static str> {
if self.complete {
return Err("Curve is complete");
}
if amount == 0 {
return Ok(0);
}
// Calculate the proportional amount of virtual sol reserves to be received using u128
let n: u128 = ((amount as u128) * (self.virtual_sol_reserves as u128))
/ ((self.virtual_token_reserves as u128) + (amount as u128));
// Calculate the fee amount in the same units
let a: u128 = (n * (fee_basis_points as u128)) / 10000;
// Return the net amount after deducting the fee, converting back to u64
Ok((n - a) as u64)
}
/// Calculates the current market cap in SOL
pub fn get_market_cap_sol(&self) -> u64 {
if self.virtual_token_reserves == 0 {
return 0;
}
((self.token_total_supply as u128) * (self.virtual_sol_reserves as u128)
/ (self.virtual_token_reserves as u128)) as u64
}
/// Calculates the final market cap in SOL after all tokens are sold
///
/// # Arguments
/// * `fee_basis_points` - Fee in basis points (1/100th of a percent)
pub fn get_final_market_cap_sol(&self, fee_basis_points: u64) -> u64 {
let total_sell_value: u128 =
self.get_buy_out_price(self.real_token_reserves, fee_basis_points) as u128;
let total_virtual_value: u128 = (self.virtual_sol_reserves as u128) + total_sell_value;
let total_virtual_tokens: u128 =
(self.virtual_token_reserves as u128) - (self.real_token_reserves as u128);
if total_virtual_tokens == 0 {
return 0;
}
((self.token_total_supply as u128) * total_virtual_value / total_virtual_tokens) as u64
}
/// Calculates the price to buy out all remaining tokens
///
/// # Arguments
/// * `amount` - Amount of tokens to buy
/// * `fee_basis_points` - Fee in basis points (1/100th of a percent)
pub fn get_buy_out_price(&self, amount: u64, fee_basis_points: u64) -> u64 {
// Get the effective amount of sol tokens
let sol_tokens: u128 = if amount < self.real_sol_reserves {
self.real_sol_reserves as u128
} else {
amount as u128
};
// Calculate total sell value
let total_sell_value: u128 = (sol_tokens * (self.virtual_sol_reserves as u128))
/ ((self.virtual_token_reserves as u128) - sol_tokens)
+ 1;
// Calculate fee
let fee: u128 = (total_sell_value * (fee_basis_points as u128)) / 10000;
// Return total including fee, converting back to u64
(total_sell_value + fee) as u64
}
}
+4 -12
View File
@@ -4,7 +4,6 @@ use std::{collections::HashMap, sync::Arc};
use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, pubkey::Pubkey
};
use crate::trading::pumpfun::bonding_curve::PumpfunBondingCurveAccount;
use crate::{
common::{
bonding_curve::BondingCurveAccount, global::GlobalAccount, PriorityFee, SolanaRpcClient
@@ -122,11 +121,11 @@ pub async fn get_bonding_curve_account(
}
#[inline]
pub async fn get_bonding_curve_account_v2(
pub async fn fetch_bonding_curve_account(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Arc<PumpfunBondingCurveAccount>, Pubkey), anyhow::Error> {
let bonding_curve_pda = get_bonding_curve_pda(mint)
) -> 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?;
@@ -134,7 +133,7 @@ pub async fn get_bonding_curve_account_v2(
return Err(anyhow!("Bonding curve not found"));
}
let bonding_curve = solana_sdk::borsh1::try_from_slice_unchecked::<PumpfunBondingCurveAccount>(&account.data)
let bonding_curve = solana_sdk::borsh1::try_from_slice_unchecked::<crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve>(&account.data[8..])
.map_err(|e| anyhow::anyhow!("Failed to deserialize bonding curve account: {}", e))?;
Ok((Arc::new(bonding_curve), bonding_curve_pda))
@@ -215,13 +214,6 @@ pub fn get_buy_amount_with_slippage(amount_sol: u64, slippage_basis_points: Opti
amount_sol + (amount_sol * slippage / 10000)
}
#[inline]
pub fn get_token_price(virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
let v_sol = virtual_sol_reserves as f64 / 100_000_000.0;
let v_tokens = virtual_token_reserves as f64 / 100_000.0;
v_sol / v_tokens
}
#[inline]
pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
if amount == 0 {
-1
View File
@@ -1,2 +1 @@
pub mod common;
pub mod bonding_curve;
+151 -7
View File
@@ -1,10 +1,13 @@
use crate::common::SolanaRpcClient;
use crate::trading::pumpswap;
use crate::constants::pumpswap::accounts;
use anyhow::anyhow;
use solana_account_decoder::UiAccountEncoding;
use solana_sdk::pubkey::Pubkey;
use solana_streamer_sdk::streaming::event_parser::protocols::pumpswap::types::{pool_decode, Pool};
// Find a pool for a specific mint
pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (pool_address, _) = pumpswap::pool::Pool::find_by_mint(rpc, mint).await?;
let (pool_address, _) = find_by_mint(rpc, mint).await?;
Ok(pool_address)
}
@@ -120,7 +123,6 @@ pub async fn get_wsol_amount(
}
}
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()],
@@ -151,10 +153,8 @@ pub(crate) fn fee_recipient_ata(fee_recipient: Pubkey, quote_mint: Pubkey) -> Pu
}
pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[
&crate::constants::pumpswap::seeds::USER_VOLUME_ACCUMULATOR_SEED,
user.as_ref(),
];
let seeds: &[&[u8]; 2] =
&[&crate::constants::pumpswap::seeds::USER_VOLUME_ACCUMULATOR_SEED, user.as_ref()];
let program_id: &Pubkey = &&crate::constants::pumpswap::accounts::AMM_PROGRAM;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
@@ -166,3 +166,147 @@ pub fn get_global_volume_accumulator_pda() -> Option<Pubkey> {
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
pub async fn fetch_pool(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Pool, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::AMM_PROGRAM {
return Err(anyhow!("Account is not owned by PumpSwap program"));
}
let pool = pool_decode(&account.data[8..]).ok_or_else(|| anyhow!("Failed to decode pool"))?;
Ok(pool)
}
pub async fn find_by_base_mint(
rpc: &SolanaRpcClient,
base_mint: &Pubkey,
) -> Result<(Pubkey, Pool), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &base_mint.to_bytes()),
),
];
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
filters: Some(filters),
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: None,
min_context_slot: None,
},
with_context: None,
sort_results: None,
};
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", base_mint));
}
let mut pools: Vec<_> = accounts
.into_iter()
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
.collect();
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
}
pub async fn find_by_quote_mint(
rpc: &SolanaRpcClient,
quote_mint: &Pubkey,
) -> Result<(Pubkey, Pool), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(75, &quote_mint.to_bytes()),
),
];
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
filters: Some(filters),
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: None,
min_context_slot: None,
},
with_context: None,
sort_results: None,
};
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", quote_mint));
}
let mut pools: Vec<_> = accounts
.into_iter()
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
.collect();
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
}
pub async fn find_by_mint(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Pubkey, Pool), anyhow::Error> {
if let Ok((address, pool)) = find_by_base_mint(rpc, mint).await {
return Ok((address, pool));
}
if let Ok((address, pool)) = find_by_quote_mint(rpc, mint).await {
return Ok((address, pool));
}
Err(anyhow!("No pool found for mint {}", mint))
}
pub async fn get_token_balances(
pool: &Pool,
rpc: &SolanaRpcClient,
) -> Result<(u64, u64), anyhow::Error> {
let base_balance = rpc.get_token_account_balance(&pool.pool_base_token_account).await?;
let quote_balance = rpc.get_token_account_balance(&pool.pool_quote_token_account).await?;
let base_amount = base_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
let quote_amount = quote_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
Ok((base_amount, quote_amount))
}
pub async fn calculate_buy_amount(
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
View File
@@ -1,2 +1 @@
pub mod common;
pub mod pool;
-245
View File
@@ -1,245 +0,0 @@
use crate::{common::SolanaRpcClient, constants::pumpswap::accounts};
use anyhow::anyhow;
use solana_account_decoder::UiAccountEncoding;
use solana_sdk::pubkey::Pubkey;
#[derive(Debug, Clone)]
pub struct Pool {
pub pool_bump: u8,
pub index: u16,
pub creator: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub lp_mint: Pubkey,
pub pool_base_token_account: Pubkey,
pub pool_quote_token_account: Pubkey,
pub lp_supply: u64,
pub coin_creator: Pubkey,
}
impl Pool {
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
if data.len() < 211 {
return Err(anyhow!("Data too short for Pool account"));
}
// 跳过discriminator (8字节)
let data = &data[8..];
let pool_bump = data[0];
let index = u16::from_le_bytes([data[1], data[2]]);
let creator = Pubkey::new_from_array(
data[3..35]
.try_into()
.map_err(|e| anyhow!("Failed to convert creator: {:?}", e))?,
);
let base_mint = Pubkey::new_from_array(
data[35..67]
.try_into()
.map_err(|e| anyhow!("Failed to convert base_mint: {:?}", e))?,
);
let quote_mint = Pubkey::new_from_array(
data[67..99]
.try_into()
.map_err(|e| anyhow!("Failed to convert quote_mint: {:?}", e))?,
);
let lp_mint = Pubkey::new_from_array(
data[99..131]
.try_into()
.map_err(|e| anyhow!("Failed to convert lp_mint: {:?}", e))?,
);
let pool_base_token_account = Pubkey::new_from_array(
data[131..163]
.try_into()
.map_err(|e| anyhow!("Failed to convert pool_base_token_account: {:?}", e))?,
);
let pool_quote_token_account = Pubkey::new_from_array(
data[163..195]
.try_into()
.map_err(|e| anyhow!("Failed to convert pool_quote_token_account: {:?}", e))?,
);
let lp_supply = u64::from_le_bytes([
data[195], data[196], data[197], data[198], data[199], data[200], data[201], data[202],
]);
let mut coin_creator = Pubkey::default();
if data.len() >= 203 + 32 {
coin_creator = Pubkey::new_from_array(
data[203..203 + 32]
.try_into()
.map_err(|e| anyhow!("Failed to convert coin_creator: {:?}", e))?,
);
}
Ok(Self {
pool_bump,
index,
creator,
base_mint,
quote_mint,
lp_mint,
pool_base_token_account,
pool_quote_token_account,
lp_supply,
coin_creator,
})
}
pub async fn fetch(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::AMM_PROGRAM {
return Err(anyhow!("Account is not owned by PumpSwap program"));
}
Self::from_bytes(&account.data)
}
pub async fn find_by_base_mint(
rpc: &SolanaRpcClient,
base_mint: &Pubkey,
) -> Result<(Pubkey, Self), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &base_mint.to_bytes()),
),
];
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
filters: Some(filters),
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: None,
min_context_slot: None,
},
with_context: None,
sort_results: None,
};
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let accounts = rpc
.get_program_accounts_with_config(&program_id, config)
.await?;
if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", base_mint));
}
let mut pools: Vec<_> = accounts
.into_iter()
.filter_map(|(addr, acc)| Self::from_bytes(&acc.data).map(|pool| (addr, pool)).ok())
.collect();
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
}
pub async fn find_by_quote_mint(
rpc: &SolanaRpcClient,
quote_mint: &Pubkey,
) -> Result<(Pubkey, Self), anyhow::Error> {
// 使用getProgramAccounts查找给定mint的池子
let filters = vec![
// solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
solana_client::rpc_filter::Memcmp::new_base58_encoded(75, &quote_mint.to_bytes()),
),
];
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
filters: Some(filters),
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
data_slice: None,
commitment: None,
min_context_slot: None,
},
with_context: None,
sort_results: None,
};
let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM;
let accounts = rpc
.get_program_accounts_with_config(&program_id, config)
.await?;
if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", quote_mint));
}
let mut pools: Vec<_> = accounts
.into_iter()
.filter_map(|(addr, acc)| Self::from_bytes(&acc.data).map(|pool| (addr, pool)).ok())
.collect();
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
}
pub async fn find_by_mint(
rpc: &SolanaRpcClient,
mint: &Pubkey,
) -> Result<(Pubkey, Self), anyhow::Error> {
if let Ok((address, pool)) = Self::find_by_base_mint(rpc, mint).await {
return Ok((address, pool));
}
if let Ok((address, pool)) = Self::find_by_quote_mint(rpc, mint).await {
return Ok((address, pool));
}
Err(anyhow!("No pool found for mint {}", mint))
}
pub async fn get_token_balances(
&self,
rpc: &SolanaRpcClient,
) -> Result<(u64, u64), anyhow::Error> {
let base_balance = rpc
.get_token_account_balance(&self.pool_base_token_account)
.await?;
let quote_balance = rpc
.get_token_account_balance(&self.pool_quote_token_account)
.await?;
let base_amount = base_balance.amount.parse::<u64>().map_err(|e| anyhow!(e))?;
let quote_amount = quote_balance
.amount
.parse::<u64>()
.map_err(|e| anyhow!(e))?;
Ok((base_amount, quote_amount))
}
pub async fn calculate_buy_amount(
&self,
rpc: &SolanaRpcClient,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = self.get_token_balances(rpc).await?;
// 使用常数乘积公式 (x * y = k) 计算
let product = base_amount as u128 * quote_amount as u128;
let new_quote_amount = quote_amount as u128 + sol_amount as u128;
let new_base_amount = product / new_quote_amount;
let token_amount = base_amount as u128 - new_base_amount;
Ok(token_amount as u64)
}
pub async fn calculate_sell_amount(
&self,
rpc: &SolanaRpcClient,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let (base_amount, quote_amount) = self.get_token_balances(rpc).await?;
// 使用常数乘积公式 (x * y = k) 计算
let product = base_amount as u128 * quote_amount as u128;
let new_base_amount = base_amount as u128 + token_amount as u128;
let new_quote_amount = product / new_base_amount;
let sol_amount = quote_amount as u128 - new_quote_amount;
Ok(sol_amount as u64)
}
}
+32 -31
View File
@@ -1,10 +1,28 @@
use crate::{
common::SolanaRpcClient,
constants::{self, raydium_cpmm::accounts::WSOL_TOKEN_ACCOUNT},
trading::raydium_cpmm::pool::Pool,
constants::{
self,
raydium_cpmm::accounts::{self, WSOL_TOKEN_ACCOUNT},
},
};
use anyhow::anyhow;
use solana_sdk::pubkey::Pubkey;
use solana_streamer_sdk::streaming::event_parser::protocols::raydium_cpmm::types::{
pool_state_decode, PoolState,
};
pub async fn fetch_pool_state(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<PoolState, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::RAYDIUM_CPMM {
return Err(anyhow!("Account is not owned by Raydium Cpmm program"));
}
let pool_state = pool_state_decode(&account.data[8..])
.ok_or_else(|| anyhow!("Failed to decode pool state"))?;
Ok(pool_state)
}
pub fn get_pool_pda(amm_config: &Pubkey, mint1: &Pubkey, mint2: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 4] = &[
@@ -19,21 +37,16 @@ pub fn get_pool_pda(amm_config: &Pubkey, mint1: &Pubkey, mint2: &Pubkey) -> Opti
}
pub fn get_vault_pda(pool_state: &Pubkey, mint: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 3] = &[
constants::raydium_cpmm::seeds::POOL_VAULT_SEED,
pool_state.as_ref(),
mint.as_ref(),
];
let seeds: &[&[u8]; 3] =
&[constants::raydium_cpmm::seeds::POOL_VAULT_SEED, pool_state.as_ref(), mint.as_ref()];
let program_id: &Pubkey = &constants::raydium_cpmm::accounts::RAYDIUM_CPMM;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
}
pub fn get_observation_state_pda(pool_state: &Pubkey) -> Option<Pubkey> {
let seeds: &[&[u8]; 2] = &[
constants::raydium_cpmm::seeds::OBSERVATION_STATE_SEED,
pool_state.as_ref(),
];
let seeds: &[&[u8]; 2] =
&[constants::raydium_cpmm::seeds::OBSERVATION_STATE_SEED, pool_state.as_ref()];
let program_id: &Pubkey = &constants::raydium_cpmm::accounts::RAYDIUM_CPMM;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
@@ -44,12 +57,8 @@ pub async fn get_buy_token_amount(
pool_state: &Pubkey,
sol_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool = Pool::fetch(rpc, pool_state).await?;
let is_token0_input = if pool.token0_mint == WSOL_TOKEN_ACCOUNT {
true
} else {
false
};
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?;
@@ -93,12 +102,8 @@ pub async fn get_sell_sol_amount(
pool_state: &Pubkey,
token_amount: u64,
) -> Result<u64, anyhow::Error> {
let pool = Pool::fetch(rpc, pool_state).await?;
let is_token0_sol = if pool.token0_mint == WSOL_TOKEN_ACCOUNT {
true
} else {
false
};
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?;
@@ -151,15 +156,11 @@ pub async fn get_pool_token_balances(
let token1_balance = rpc.get_token_account_balance(&token1_vault).await?;
// 解析余额字符串为 u64
let token0_amount = token0_balance
.amount
.parse::<u64>()
.map_err(|e| anyhow!("解析 token0 余额失败: {}", e))?;
let token0_amount =
token0_balance.amount.parse::<u64>().map_err(|e| anyhow!("解析 token0 余额失败: {}", e))?;
let token1_amount = token1_balance
.amount
.parse::<u64>()
.map_err(|e| anyhow!("解析 token1 余额失败: {}", e))?;
let token1_amount =
token1_balance.amount.parse::<u64>().map_err(|e| anyhow!("解析 token1 余额失败: {}", e))?;
Ok((token0_amount, token1_amount))
}
-1
View File
@@ -1,2 +1 @@
pub mod common;
pub mod pool;
-51
View File
@@ -1,51 +0,0 @@
use crate::{common::SolanaRpcClient, constants::raydium_cpmm::accounts};
use anyhow::anyhow;
use borsh::BorshDeserialize;
use solana_sdk::pubkey::Pubkey;
#[derive(Debug, Clone, BorshDeserialize)]
pub struct Pool {
pub amm_config: Pubkey,
pub pool_creator: Pubkey,
pub token0_vault: Pubkey,
pub token1_vault: Pubkey,
pub lp_mint: Pubkey,
pub token0_mint: Pubkey,
pub token1_mint: Pubkey,
pub token0_program: Pubkey,
pub token1_program: Pubkey,
pub observation_key: Pubkey,
pub auth_bump: u8,
pub status: u8,
pub lp_mint_decimals: u8,
pub mint0_decimals: u8,
pub mint1_decimals: u8,
pub lp_supply: u64,
pub protocol_fees_token0: u64,
pub protocol_fees_token1: u64,
pub fund_fees_token0: u64,
pub fund_fees_token1: u64,
pub open_time: u64,
pub recent_epoch: u64,
pub padding: [u64; 31],
}
impl Pool {
pub fn from_bytes(data: &[u8]) -> Result<Self, anyhow::Error> {
let pool = Pool::try_from_slice(&data[8..])?;
Ok(pool)
}
pub async fn fetch(
rpc: &SolanaRpcClient,
pool_address: &Pubkey,
) -> Result<Self, anyhow::Error> {
let account = rpc.get_account(pool_address).await?;
if account.owner != accounts::RAYDIUM_CPMM {
return Err(anyhow!("Account is not owned by Raydium Cpmm program"));
}
Self::from_bytes(&account.data)
}
}