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
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sol-trade-sdk"
version = "0.3.4"
version = "0.3.5"
edition = "2021"
authors = ["William <byteblock6@gmail.com>", "sgxiang <sgxiang@gmail.com>", "wei <1415121722@qq.com>"]
repository = "https://github.com/0xfnzero/sol-trade-sdk"
+15 -2
View File
@@ -31,14 +31,14 @@ Add the dependency to your `Cargo.toml`:
```toml
# Add to your Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.3.4" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.3.5" }
```
### Use crates.io
```toml
# Add to your Cargo.toml
sol-trade-sdk = "0.3.4"
sol-trade-sdk = "0.3.5"
```
## Usage Examples
@@ -737,6 +737,10 @@ let trade_config = TradeConfig {
- **Protocol Abstraction**: Supports trading operations across multiple protocols
- **Concurrent Execution**: Supports sending transactions to multiple MEV services simultaneously
## Price Calculation Utilities
The SDK includes price calculation utilities for all supported protocols in `src/utils/price/`.
## Project Structure
```
@@ -753,6 +757,15 @@ src/
│ ├── pumpswap/ # PumpSwap trading implementation
│ ├── raydium_cpmm/ # Raydium CPMM trading implementation
│ └── factory.rs # Trading factory
├── utils/ # Utility functions
│ └── price/ # Price calculation utilities
│ ├── common.rs # Common price functions
│ ├── bonk.rs # Bonk price calculations
│ ├── pumpfun.rs # PumpFun price calculations
│ ├── pumpswap.rs # PumpSwap price calculations
│ ├── raydium_cpmm.rs # Raydium CPMM price calculations
│ ├── raydium_clmm.rs # Raydium CLMM price calculations
│ └── raydium_amm_v4.rs # Raydium AMM V4 price calculations
├── lib.rs # Main library file
└── main.rs # Example program
```
+15 -2
View File
@@ -31,14 +31,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.3.4" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.3.5" }
```
### 使用 crates.io
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = "0.3.4"
sol-trade-sdk = "0.3.5"
```
## 使用示例
@@ -735,6 +735,10 @@ let trade_config = TradeConfig {
- **协议抽象**: 支持多个协议的交易操作
- **并发执行**: 支持同时向多个 MEV 服务发送交易
## 价格计算工具
SDK 包含所有支持协议的价格计算工具,位于 `src/utils/price/` 目录。
## 项目结构
```
@@ -751,6 +755,15 @@ src/
│ ├── pumpswap/ # PumpSwap交易实现
│ ├── raydium_cpmm/ # Raydium CPMM交易实现
│ └── factory.rs # 交易工厂
├── utils/ # 工具函数
│ └── price/ # 价格计算工具
│ ├── common.rs # 通用价格函数
│ ├── bonk.rs # Bonk 价格计算
│ ├── pumpfun.rs # PumpFun 价格计算
│ ├── pumpswap.rs # PumpSwap 价格计算
│ ├── raydium_cpmm.rs # Raydium CPMM 价格计算
│ ├── raydium_clmm.rs # Raydium CLMM 价格计算
│ └── raydium_amm_v4.rs # Raydium AMM V4 价格计算
├── lib.rs # 主库文件
└── main.rs # 示例程序
```
+2
View File
@@ -0,0 +1,2 @@
pub const SOL_DECIMALS: u8 = 9;
pub const DEFAULT_TOKEN_DECIMALS: u8 = 6;
+1
View File
@@ -4,6 +4,7 @@ pub mod pumpswap;
pub mod swqos;
pub mod trade;
pub mod raydium_cpmm;
pub mod decimals;
pub mod trade_platform {
pub const PUMPFUN: &'static str = "pumpfun";
+11 -10
View File
@@ -5,16 +5,17 @@ use spl_associated_token_account::instruction::create_associated_token_account_i
use spl_token::instruction::close_account;
use crate::{
constants::bonk::{accounts, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR},
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::bonk::{
common::{get_amount_out, get_pool_pda, get_vault_pda},
pool::Pool,
constants::{
bonk::{accounts, BUY_EXECT_IN_DISCRIMINATOR, SELL_EXECT_IN_DISCRIMINATOR},
trade::trade::DEFAULT_SLIPPAGE,
},
trading::common::utils::get_token_balance,
trading::core::{
params::{BonkParams, BuyParams, SellParams},
traits::InstructionBuilder,
trading::{
bonk::common::{fetch_pool_state, get_amount_out, get_pool_pda, get_vault_pda},
common::utils::get_token_balance,
core::{
params::{BonkParams, BuyParams, SellParams},
traits::InstructionBuilder,
},
},
};
@@ -70,7 +71,7 @@ impl BonkInstructionBuilder {
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 = Pool::fetch(params.rpc.as_ref().unwrap(), &pool_state).await?;
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;
+3 -5
View File
@@ -20,9 +20,7 @@ use crate::{
pumpswap::{
self,
common::{
coin_creator_vault_ata, coin_creator_vault_authority, fee_recipient_ata, find_pool,
get_global_volume_accumulator_pda, get_token_amount,
get_user_volume_accumulator_pda, get_wsol_amount,
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
},
},
},
@@ -138,7 +136,7 @@ impl PumpSwapInstructionBuilder {
let rpc = params.rpc.as_ref().unwrap().clone();
// Find pool
let pool = find_pool(rpc.as_ref(), &params.mint).await?;
let pool_data = pumpswap::pool::Pool::fetch(rpc.as_ref(), &pool).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 =
@@ -169,7 +167,7 @@ impl PumpSwapInstructionBuilder {
let rpc = params.rpc.as_ref().unwrap().clone();
// Find pool
let pool = find_pool(rpc.as_ref(), &params.mint).await?;
let pool_data = pumpswap::pool::Pool::fetch(rpc.as_ref(), &pool).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 =
+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)
}
}
+16 -47
View File
@@ -1,3 +1,5 @@
pub mod price;
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::trading;
use crate::SolanaTrade;
@@ -57,15 +59,6 @@ impl SolanaTrade {
// -------------------------------- PumpFun --------------------------------
#[inline]
pub fn get_pumpfun_token_price(
&self,
virtual_sol_reserves: u64,
virtual_token_reserves: u64,
) -> f64 {
trading::pumpfun::common::get_token_price(virtual_sol_reserves, virtual_token_reserves)
}
#[inline]
pub fn get_pumpfun_token_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
trading::pumpfun::common::get_buy_price(amount, trade_info)
@@ -77,15 +70,12 @@ impl SolanaTrade {
mint: &Pubkey,
) -> Result<f64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
trading::pumpfun::common::fetch_bonding_curve_account(&self.rpc, mint).await?;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
Ok(trading::pumpfun::common::get_token_price(
virtual_sol_reserves,
virtual_token_reserves,
))
Ok(price::pumpfun::price_token_in_sol(virtual_sol_reserves, virtual_token_reserves))
}
#[inline]
@@ -94,7 +84,7 @@ impl SolanaTrade {
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
trading::pumpfun::common::fetch_bonding_curve_account(&self.rpc, mint).await?;
let actual_sol_reserves = bonding_curve.real_sol_reserves;
@@ -104,7 +94,7 @@ impl SolanaTrade {
#[inline]
pub async fn get_pumpfun_token_creator(&self, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (bonding_curve, _) =
trading::pumpfun::common::get_bonding_curve_account_v2(&self.rpc, mint).await?;
trading::pumpfun::common::fetch_bonding_curve_account(&self.rpc, mint).await?;
let creator = bonding_curve.creator;
@@ -118,16 +108,15 @@ impl SolanaTrade {
&self,
pool_address: &Pubkey,
) -> Result<f64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let pool = trading::pumpswap::common::fetch_pool(&self.rpc, pool_address).await?;
let (base_amount, quote_amount) = pool.get_token_balances(&self.rpc).await?;
let (base_amount, quote_amount) =
trading::pumpswap::common::get_token_balances(&pool, &self.rpc).await?;
// Calculate price using constant product formula (x * y = k)
// Price = quote_amount / base_amount
if base_amount == 0 {
return Err(anyhow::anyhow!(
"Base amount is zero, cannot calculate price"
));
return Err(anyhow::anyhow!("Base amount is zero, cannot calculate price"));
}
let price = quote_amount as f64 / base_amount as f64;
@@ -140,9 +129,10 @@ impl SolanaTrade {
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let pool = trading::pumpswap::common::fetch_pool(&self.rpc, pool_address).await?;
let (_, quote_amount) = pool.get_token_balances(&self.rpc).await?;
let (_, quote_amount) =
trading::pumpswap::common::get_token_balances(&pool, &self.rpc).await?;
Ok(quote_amount)
}
@@ -152,32 +142,11 @@ impl SolanaTrade {
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = trading::pumpswap::pool::Pool::fetch(&self.rpc, pool_address).await?;
let pool = trading::pumpswap::common::fetch_pool(&self.rpc, pool_address).await?;
let (base_amount, _) = pool.get_token_balances(&self.rpc).await?;
let (base_amount, _) =
trading::pumpswap::common::get_token_balances(&pool, &self.rpc).await?;
Ok(base_amount)
}
// -------------------------------- Bonk --------------------------------
#[inline]
pub fn get_bonk_token_price(
&self,
virtual_base: u128,
virtual_quote: u128,
real_base: u128,
real_quote: u128,
decimal_base: u64,
decimal_quote: u64,
) -> f64 {
trading::bonk::common::get_token_price(
virtual_base,
virtual_quote,
real_base,
real_quote,
decimal_base,
decimal_quote,
)
}
}
+117
View File
@@ -0,0 +1,117 @@
use solana_streamer_sdk::streaming::event_parser::protocols::bonk::types::PoolState;
use crate::constants::{
bonk::accounts::WSOL_TOKEN_ACCOUNT,
decimals::{DEFAULT_TOKEN_DECIMALS, SOL_DECIMALS},
};
/// Calculate the token price in WSOL based on pool state
///
/// # Arguments
/// * `pool_state` - Pool state
///
/// # Returns
/// Token price in WSOL as f64
pub fn price_token_in_wsol_with_pool_state(pool_state: &PoolState) -> f64 {
if pool_state.quote_mint != WSOL_TOKEN_ACCOUNT {
log::error!("Quote mint is not WSOL: {:?}", pool_state.quote_mint);
return 0.0;
}
price_base_in_quote(
pool_state.virtual_base,
pool_state.virtual_quote,
pool_state.real_base,
pool_state.real_quote,
pool_state.base_decimals,
pool_state.quote_decimals,
)
}
/// Calculate the price of base in quote based on pool state
///
/// # Arguments
/// * `pool_state` - Pool state
///
/// # Returns
/// The price of base in quote
pub fn price_base_in_quote_with_pool_state(pool_state: &PoolState) -> f64 {
price_base_in_quote(
pool_state.virtual_base,
pool_state.virtual_quote,
pool_state.real_base,
pool_state.real_quote,
pool_state.base_decimals,
pool_state.quote_decimals,
)
}
/// Calculate the price of token in WSOL
///
/// # Arguments
/// * `virtual_base` - Virtual base reserves
/// * `virtual_quote` - Virtual quote reserves
/// * `real_base` - Real base reserves
/// * `real_quote` - Real quote reserves
///
/// # Returns
/// The price of token in WSOL
pub fn price_token_in_wsol(
virtual_base: u64,
virtual_quote: u64,
real_base: u64,
real_quote: u64,
) -> f64 {
price_base_in_quote(
virtual_base,
virtual_quote,
real_base,
real_quote,
DEFAULT_TOKEN_DECIMALS,
SOL_DECIMALS,
)
}
/// Calculate the price of base in quote
///
/// # Arguments
/// * `virtual_base` - Virtual base reserves
/// * `virtual_quote` - Virtual quote reserves
/// * `real_base` - Real base reserves
/// * `real_quote` - Real quote reserves
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// The price of base in quote
pub fn price_base_in_quote(
virtual_base: u64,
virtual_quote: u64,
real_base: u64,
real_quote: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
// Calculate decimal places difference
let decimal_diff = quote_decimals as i32 - base_decimals as i32;
let decimal_factor = if decimal_diff >= 0 {
10_f64.powi(decimal_diff)
} else {
1.0 / 10_f64.powi(-decimal_diff)
};
// Calculate reserves state before price calculation
let quote_reserves = virtual_quote.checked_add(real_quote).unwrap_or(0);
let base_reserves = virtual_base.checked_sub(real_base).unwrap_or(0);
if base_reserves == 0 {
return 0.0;
}
if decimal_factor == 0.0 {
return 0.0;
}
// Use floating point calculation to avoid precision loss from integer division
let price = (quote_reserves as f64) / (base_reserves as f64) / decimal_factor;
price
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
let base = base_reserve as f64 / 10f64.powi(base_decimals as i32);
let quote = quote_reserve as f64 / 10f64.powi(quote_decimals as i32);
if base == 0.0 {
return 0.0;
}
quote / base
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
let base = base_reserve as f64 / 10f64.powi(base_decimals as i32);
let quote = quote_reserve as f64 / 10f64.powi(quote_decimals as i32);
if quote == 0.0 {
return 0.0;
}
base / quote
}
+7
View File
@@ -0,0 +1,7 @@
pub mod bonk;
pub mod pumpfun;
pub mod pumpswap;
pub mod raydium_amm_v4;
pub mod raydium_clmm;
pub mod raydium_cpmm;
pub mod common;
+31
View File
@@ -0,0 +1,31 @@
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::types::BondingCurve;
use crate::constants::pumpfun::global_constants::{LAMPORTS_PER_SOL, SCALE};
/// Calculate the token price in SOL based on virtual reserves
///
/// # Arguments
/// * `bonding_curve` - Bonding curve account
///
/// # Returns
/// Token price in SOL as f64
pub fn price_token_in_sol_with_bonding_curve(bonding_curve: &BondingCurve) -> f64 {
price_token_in_sol(bonding_curve.virtual_sol_reserves, bonding_curve.virtual_token_reserves)
}
/// Calculate the token price in SOL based on virtual reserves
///
/// # Arguments
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
///
/// # Returns
/// Token price in SOL as f64
pub fn price_token_in_sol(virtual_sol_reserves: u64, virtual_token_reserves: u64) -> f64 {
let v_sol = virtual_sol_reserves as f64 / LAMPORTS_PER_SOL as f64;
let v_tokens = virtual_token_reserves as f64 / SCALE as f64;
if v_tokens == 0.0 {
return 0.0;
}
v_sol / v_tokens
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_base_in_quote(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_quote_in_base(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_base_in_quote(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_quote_in_base(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
+68
View File
@@ -0,0 +1,68 @@
use solana_streamer_sdk::streaming::event_parser::protocols::raydium_clmm::types::PoolState;
/// Calculate the price of token0 in token1
///
/// # Arguments
/// * `sqrt_price_x64` - The sqrt price of the pool
/// * `decimals_token0` - The decimals of token0
/// * `decimals_token1` - The decimals of token1
///
/// # Returns
/// The price of token0 in token1
pub fn price_token0_in_token1(
sqrt_price_x64: u128,
decimals_token0: u8,
decimals_token1: u8,
) -> f64 {
let sqrt_price = sqrt_price_x64 as f64 / (1u128 << 64) as f64; // Q64.64 转浮点
let price_raw = sqrt_price * sqrt_price; // 未调整小数位的价格
let scale = 10f64.powi((decimals_token0 as i32) - (decimals_token1 as i32));
price_raw * scale
}
/// Calculate the price of token1 in token0
///
/// # Arguments
/// * `sqrt_price_x64` - The sqrt price of the pool
/// * `decimals_token0` - The decimals of token0
/// * `decimals_token1` - The decimals of token1
///
/// # Returns
/// The price of token1 in token0
pub fn price_token1_in_token0(
sqrt_price_x64: u128,
decimals_token0: u8,
decimals_token1: u8,
) -> f64 {
1.0 / price_token0_in_token1(sqrt_price_x64, decimals_token0, decimals_token1)
}
/// Calculate the price of token0 in token1 based on pool state
///
/// # Arguments
/// * `pool_state` - The pool state
///
/// # Returns
/// The price of token0 in token1
pub fn price_token0_in_token1_with_pool_state(pool_state: &PoolState) -> f64 {
price_token0_in_token1(
pool_state.sqrt_price_x64,
pool_state.mint_decimals0,
pool_state.mint_decimals1,
)
}
/// Calculate the price of token1 in token0 based on pool state
///
/// # Arguments
/// * `pool_state` - The pool state
///
/// # Returns
/// The price of token1 in token0
pub fn price_token1_in_token0_with_pool_state(pool_state: &PoolState) -> f64 {
price_token1_in_token0(
pool_state.sqrt_price_x64,
pool_state.mint_decimals0,
pool_state.mint_decimals1,
)
}
+47
View File
@@ -0,0 +1,47 @@
/// Calculate the token price in quote based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in quote as f64
pub fn price_base_in_quote(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_base_in_quote(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}
/// Calculate the token price in base based on base and quote reserves
///
/// # Arguments
/// * `base_reserve` - Base reserve in the pool
/// * `quote_reserve` - Quote reserve in the pool
/// * `base_decimals` - Base decimals
/// * `quote_decimals` - Quote decimals
///
/// # Returns
/// Token price in base as f64
pub fn price_quote_in_base(
base_reserve: u64,
quote_reserve: u64,
base_decimals: u8,
quote_decimals: u8,
) -> f64 {
crate::utils::price::common::price_quote_in_base(
base_reserve,
quote_reserve,
base_decimals,
quote_decimals,
)
}