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:
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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 # 示例程序
|
||||
```
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub const SOL_DECIMALS: u8 = 9;
|
||||
pub const DEFAULT_TOKEN_DECIMALS: u8 = 6;
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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(), ¶ms.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(), ¶ms.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
@@ -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,2 +1 @@
|
||||
pub mod common;
|
||||
pub mod pool;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,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,2 +1 @@
|
||||
pub mod common;
|
||||
pub mod bonding_curve;
|
||||
@@ -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, "e_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,2 +1 @@
|
||||
pub mod common;
|
||||
pub mod pool;
|
||||
|
||||
@@ -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, "e_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)
|
||||
}
|
||||
}
|
||||
@@ -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,2 +1 @@
|
||||
pub mod common;
|
||||
pub mod pool;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user