use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::global::METEORA_PROGRAM_ID;
use crate::types::PoolInfo;
use crate::{MeteoraClient, MeteoraError};
use solana_sdk::program_pack::Pack;
use solana_sdk::pubkey::Pubkey;
use spl_token::state::{Account, Mint};
use tokio::time::Instant;
struct PoolCache {
pools: HashMap<Pubkey, (PoolInfo, Instant)>,
all_pools: Vec<Pubkey>,
last_update: Instant,
cache_ttl: Duration,
}
pub struct PoolManager {
client: Arc<MeteoraClient>,
cache: Arc<Mutex<PoolCache>>,
}
impl PoolManager {
pub fn new(client: Arc<MeteoraClient>) -> Self {
Self {
client,
cache: Arc::new(Mutex::new(PoolCache {
pools: HashMap::new(),
all_pools: Vec::new(),
last_update: Instant::now() - Duration::from_secs(3600),
cache_ttl: Duration::from_secs(300),
})),
}
}
pub async fn find_all_pools_cached(&self) -> Result<Vec<Pubkey>, MeteoraError> {
let mut cache = self.cache.lock().unwrap();
if cache.last_update.elapsed() < cache.cache_ttl && !cache.all_pools.is_empty() {
return Ok(cache.all_pools.clone());
}
let accounts = self
.client
.get_program_accounts(&Pubkey::from_str(METEORA_PROGRAM_ID).unwrap(), None)
.await?;
let pools: Vec<Pubkey> = accounts.into_iter().map(|(pubkey, _)| pubkey).collect();
cache.all_pools = pools.clone();
cache.last_update = Instant::now();
Ok(pools)
}
pub async fn get_pool_info_cached(
&self,
pool_address: &Pubkey,
) -> Result<PoolInfo, MeteoraError> {
let mut cache = self.cache.lock().unwrap();
if let Some((cached_info, timestamp)) = cache.pools.get(pool_address) {
if timestamp.elapsed() < cache.cache_ttl {
return Ok(cached_info.clone());
}
}
let pool_info = self.get_pool_info(pool_address).await?;
cache
.pools
.insert(*pool_address, (pool_info.clone(), Instant::now()));
Ok(pool_info)
}
pub async fn get_pool_info(&self, pool_address: &Pubkey) -> Result<PoolInfo, MeteoraError> {
let pool_data = self.client.get_account_data(pool_address).await?;
if pool_data.len() < 300 {
return Err(MeteoraError::InvalidPoolData);
}
let token_a_mint = Pubkey::new_from_array(
pool_data[8..40]
.try_into()
.map_err(|_| MeteoraError::InvalidPoolData)?,
);
let token_b_mint = Pubkey::new_from_array(
pool_data[40..72]
.try_into()
.map_err(|_| MeteoraError::InvalidPoolData)?,
);
let token_a_reserve = Pubkey::new_from_array(
pool_data[72..104]
.try_into()
.map_err(|_| MeteoraError::InvalidPoolData)?,
);
let token_b_reserve = Pubkey::new_from_array(
pool_data[104..136]
.try_into()
.map_err(|_| MeteoraError::InvalidPoolData)?,
);
let lp_mint = Pubkey::new_from_array(
pool_data[136..168]
.try_into()
.map_err(|_| MeteoraError::InvalidPoolData)?,
);
let fee_account = Pubkey::new_from_array(
pool_data[168..200]
.try_into()
.map_err(|_| MeteoraError::InvalidPoolData)?,
);
let token_a_decimals = self.get_token_decimals(&token_a_mint).await?;
let token_b_decimals = self.get_token_decimals(&token_b_mint).await?;
let token_a_reserve_amount = self.get_token_balance(&token_a_reserve).await?;
let token_b_reserve_amount = self.get_token_balance(&token_b_reserve).await?;
let lp_supply = self.get_token_supply(&lp_mint).await?;
Ok(PoolInfo {
address: *pool_address,
token_a_mint,
token_b_mint,
token_a_reserve,
token_b_reserve,
lp_mint,
fee_account,
trade_fee_bps: 30, token_a_decimals,
token_b_decimals,
token_a_reserve_amount,
token_b_reserve_amount,
lp_supply,
})
}
pub async fn find_pools_by_tokens(
&self,
token_a: &Pubkey,
token_b: &Pubkey,
) -> Result<Vec<PoolInfo>, MeteoraError> {
let all_pools = self.find_all_pools().await?;
let mut matching_pools = Vec::new();
for pool_address in all_pools {
if let Ok(pool_info) = self.get_pool_info(&pool_address).await {
if (pool_info.token_a_mint == *token_a && pool_info.token_b_mint == *token_b)
|| (pool_info.token_a_mint == *token_b && pool_info.token_b_mint == *token_a)
{
matching_pools.push(pool_info);
}
}
}
Ok(matching_pools)
}
pub async fn find_all_pools(&self) -> Result<Vec<Pubkey>, MeteoraError> {
let accounts = self
.client
.get_program_accounts(&Pubkey::from_str(METEORA_PROGRAM_ID).unwrap(), None)
.await?;
Ok(accounts.into_iter().map(|(pubkey, _)| pubkey).collect())
}
pub async fn find_token_pools(&self, token_mint: &Pubkey) -> Result<Vec<Pubkey>, MeteoraError> {
let all_pools = self.find_all_pools().await?;
let mut token_pools = Vec::new();
for pool_address in all_pools {
if let Ok(pool_info) = self.get_pool_info(&pool_address).await {
if pool_info.token_a_mint == *token_mint || pool_info.token_b_mint == *token_mint {
token_pools.push(pool_address);
}
}
}
Ok(token_pools)
}
pub async fn get_pool_liquidity(&self, pool_address: &Pubkey) -> Result<u64, MeteoraError> {
let pool_info = self.get_pool_info(pool_address).await?;
let liquidity = pool_info.token_a_reserve_amount + pool_info.token_b_reserve_amount;
Ok(liquidity)
}
async fn get_token_balance(&self, token_account: &Pubkey) -> Result<u64, MeteoraError> {
let account_data = self.client.get_account_data(token_account).await?;
let token_account = Account::unpack(&account_data)
.map_err(|e| MeteoraError::DeserializationError(e.to_string()))?;
Ok(token_account.amount)
}
async fn get_token_decimals(&self, mint: &Pubkey) -> Result<u8, MeteoraError> {
let account_data = self.client.get_account_data(mint).await?;
let token_mint = Mint::unpack(&account_data)
.map_err(|e| MeteoraError::DeserializationError(e.to_string()))?;
Ok(token_mint.decimals)
}
async fn get_token_supply(&self, mint: &Pubkey) -> Result<u64, MeteoraError> {
let account_data = self.client.get_account_data(mint).await?;
let token_mint = Mint::unpack(&account_data)
.map_err(|e| MeteoraError::DeserializationError(e.to_string()))?;
Ok(token_mint.supply)
}
}