use solana_network_client::SolanaClient;
use solana_sdk::pubkey::Pubkey;
use std::{str::FromStr, sync::Arc};
pub const AMM_POOL_DATA_SIZE: usize = 301;
#[derive(Debug, Clone)]
pub struct AmmPoolInfo {
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,
pub base_reserves: u64,
pub quote_reserves: u64,
pub base_price: f64,
pub total_liquidity: f64,
pub lp_token_price: f64,
}
impl AmmPoolInfo {
pub async fn get_base_balance_f64(&self, solana_client: Arc<SolanaClient>) -> Option<f64> {
let base_balance = solana_client
.client_arc()
.clone()
.get_token_account_balance(&self.pool_base_token_account)
.await
.unwrap();
base_balance.ui_amount
}
pub async fn get_quote_balance_f64(&self, solana_client: Arc<SolanaClient>) -> Option<f64> {
let quote_balance = solana_client
.client_arc()
.clone()
.get_token_account_balance(&self.pool_quote_token_account)
.await
.unwrap();
quote_balance.ui_amount
}
pub async fn get_base_balance_string(
&self,
solana_client: Arc<SolanaClient>,
) -> Option<String> {
let base_balance = solana_client
.client_arc()
.clone()
.get_token_account_balance(&self.pool_base_token_account)
.await
.unwrap();
Some(base_balance.ui_amount_string)
}
pub async fn get_quote_balance_string(&self, solana: Arc<SolanaClient>) -> Option<String> {
let quote_balance = solana
.client_arc()
.clone()
.get_token_account_balance(&self.pool_quote_token_account)
.await
.unwrap();
Some(quote_balance.ui_amount_string)
}
}
pub struct Amm {
pub solana_client: Arc<SolanaClient>,
}
impl Amm {
pub fn new(solana_client: Arc<SolanaClient>) -> Self {
Self { solana_client }
}
pub async fn get_amm_pool_info(&self, pool_address: &str) -> Result<AmmPoolInfo, String> {
let account_data = self
.solana_client
.client_arc()
.get_account_data(
&Pubkey::from_str(pool_address)
.map_err(|e| format!("{:?}", e))
.unwrap(),
)
.await
.map_err(|e| format!("Failed to get account data: {:?}", e))?;
Self::parse_amm_data(&account_data)
}
pub fn parse_amm_data(data: &[u8]) -> Result<AmmPoolInfo, String> {
let result = Self::try_parse_with_offset(data, 0) .or_else(|| Self::try_parse_with_offset(data, 8)) .or_else(|| Self::try_parse_with_offset(data, 1)) .or_else(|| Self::brute_force_parse(data)); match result {
Some(data) => Ok(data),
None => Err("Unable to parse AMM pool data - data format mismatch".to_string()),
}
}
fn try_parse_with_offset(data: &[u8], offset: usize) -> Option<AmmPoolInfo> {
fn read_u8_safe(data: &[u8], offset: usize) -> Option<u8> {
if offset < data.len() {
Some(data[offset])
} else {
None
}
}
fn read_u16_safe(data: &[u8], offset: usize, is_be: bool) -> Option<u16> {
if offset + 1 < data.len() {
let bytes: [u8; 2] = data[offset..offset + 2].try_into().ok()?;
Some(if is_be {
u16::from_be_bytes(bytes)
} else {
u16::from_le_bytes(bytes)
})
} else {
None
}
}
fn read_u64_safe(data: &[u8], offset: usize, is_be: bool) -> Option<u64> {
if offset + 7 < data.len() {
let bytes: [u8; 8] = data[offset..offset + 8].try_into().ok()?;
Some(if is_be {
u64::from_be_bytes(bytes)
} else {
u64::from_le_bytes(bytes)
})
} else {
None
}
}
fn read_pubkey_safe(data: &[u8], offset: usize) -> Option<Pubkey> {
if offset + 31 < data.len() {
let bytes: [u8; 32] = data[offset..offset + 32].try_into().ok()?;
Some(Pubkey::new_from_array(bytes))
} else {
None
}
}
for &is_be in &[false, true] {
if let Some(pool_bump) = read_u8_safe(data, offset) {
if let Some(index) = read_u16_safe(data, offset + 1, is_be) {
if pool_bump == 255 && index == 0 {
let creator_offset = offset + 3;
if let Some(creator) = read_pubkey_safe(data, creator_offset) {
let base_mint_offset = creator_offset + 32;
if let Some(base_mint) = read_pubkey_safe(data, base_mint_offset) {
let quote_mint_offset = base_mint_offset + 32;
if let Some(quote_mint) = read_pubkey_safe(data, quote_mint_offset)
{
let lp_mint_offset = quote_mint_offset + 32;
if let Some(lp_mint) = read_pubkey_safe(data, lp_mint_offset) {
let pool_base_offset = lp_mint_offset + 32;
if let Some(pool_base_token_account) =
read_pubkey_safe(data, pool_base_offset)
{
let pool_quote_offset = pool_base_offset + 32;
if let Some(pool_quote_token_account) =
read_pubkey_safe(data, pool_quote_offset)
{
let lp_supply_offset = pool_quote_offset + 32;
if let Some(lp_supply) =
read_u64_safe(data, lp_supply_offset, is_be)
{
let coin_creator_offset = lp_supply_offset + 8;
if let Some(coin_creator) =
read_pubkey_safe(data, coin_creator_offset)
{
if lp_supply > 1_000_000_000
&& lp_supply < 10_000_000_000_000_000
{
return Some(AmmPoolInfo {
pool_bump,
index,
creator,
base_mint,
quote_mint,
lp_mint,
pool_base_token_account,
pool_quote_token_account,
lp_supply,
coin_creator,
base_reserves: 0,
quote_reserves: 0,
base_price: 0.0,
total_liquidity: 0.0,
lp_token_price: 0.0,
});
}
}
}
}
}
}
}
}
}
}
}
}
}
None
}
fn brute_force_parse(data: &[u8]) -> Option<AmmPoolInfo> {
const MIN_REQUIRED_SIZE: usize = 3 + 32 * 7 + 8; for start in 0..data.len().saturating_sub(MIN_REQUIRED_SIZE) {
if let Some(result) = Self::try_parse_with_offset(data, start) {
return Some(result);
}
}
None
}
pub fn parse_amm_data_simple(data: &[u8]) -> Result<AmmPoolInfo, String> {
if data.len() < 235 {
return Err(format!(
"Data too short: need at least 235 bytes, actual {} bytes",
data.len()
));
}
for i in 0..data.len().saturating_sub(32) {
let slice = &data[i..i + 32];
}
Err("Need to debug actual data structure".to_string())
}
}
#[cfg(test)]
mod tests {
use solana_network_client::Mode;
use super::*;
use std::sync::Arc;
#[tokio::test]
async fn test_parse_amm_data_debug() {
let solana_client = Arc::new(SolanaClient::new(Mode::MAIN).unwrap());
let amm = Amm::new(solana_client.clone());
let amm_pool_info = amm
.get_amm_pool_info("GjK3S2ZgxTVFEkxg43JE8eC1tbztWCseBYyZ8o8sg9f")
.await
.unwrap();
println!(
"balance 1: {:?}",
amm_pool_info
.get_base_balance_f64(solana_client.clone())
.await
);
println!(
"balance 2: {:?}",
amm_pool_info
.get_quote_balance_f64(solana_client.clone())
.await
);
println!("AMM Pool Info: {:?}", amm_pool_info);
}
}