USER
use {
anyhow::{anyhow, Result},
base64::{engine::general_purpose::STANDARD as BASE64, Engine},
chrono::Utc,
dashmap::DashMap,
futures_util::StreamExt,
log::{error, info, warn},
reqwest::Client,
serde_json::{json, Value},
solana_client::{
nonblocking::rpc_client::RpcClient,
rpc_request::TokenAccountsFilter,
},
solana_account_decoder::UiAccountData,
solana_sdk::{
compute_budget::ComputeBudgetInstruction,
instruction::Instruction,
message::Message as SolanaMessage,
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
},
std::{
convert::TryInto,
str::FromStr,
sync::Arc,
time::{Duration, Instant},
},
tokio::{
net::TcpStream,
sync::RwLock,
time::sleep,
},
tokio_tungstenite::{tungstenite::Message, WebSocketStream, MaybeTlsStream},
crate::constants::*,
};
pub mod constants {
// API Endpoints and Network IDs
pub const JUPITER_API: &str = "https://quote-api.jup.ag/v6";
pub const RAYDIUM_AMM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
pub const SOL_MINT: &str = "So11111111111111111111111111111111111111112";
pub const STOP_LOSS_THRESHOLD: f64 = 5.0; // 5% loss
pub const TAKE_PROFIT_THRESHOLD: f64 = 10.0; // 10% gain
pub const MIN_LIQUIDITY_THRESHOLD: f64 = 1000.0; // Minimum pool liquidity
pub const MAX_VOLATILITY_THRESHOLD: f64 = 20.0; // Maximum acceptable volatility
// Trading Parameters
pub const TAKE_PROFIT: f64 = 20.0;
pub const STOP_LOSS: f64 = -5.0;
pub const MIN_LIQUIDITY: f64 = 50.0;
pub const MAX_LIQUIDITY: f64 = 20000.0;
// Pool Requirements
pub const MIN_POOL_AGE_MS: u64 = 200;
pub const MAX_POOL_AGE_MS: u64 = 5000;
pub const POOL_SIZE_REQUIREMENT: u64 = 2208;
pub const MAX_CONCURRENT_TRADES: usize = 5;
// Transaction Settings
pub const PRIORITY_FEE: u64 = 1_000_000;
pub const TRADE_AMOUNT: u64 = 10_000_000;
pub const MAX_SLIPPAGE_BPS: u16 = 50;
// Validation Timing
pub const INITIAL_VALIDATION_DELAY: u64 = 100;
pub const VALIDATION_INTERVAL: u64 = 50;
pub const MAX_VALIDATION_ATTEMPTS: u8 = 10;
pub const EXECUTION_TIMEOUT: u64 = 1500;
pub const LIQUIDITY_CHECK_INTERVAL: u64 = 100;
}
// Struct Definitions
#[derive(Clone, Debug)]
pub struct TradeMetrics {
pub timestamp: i64,
pub detection_time: Duration,
pub validation_time: Duration,
pub execution_time: Duration,
pub total_time: Duration,
pub attempts: u8,
pub success: bool,
pub error: Option<String>,
}
#[derive(Clone)]
pub struct Trade {
pub entry_price: f64,
pub current_price: f64,
pub amount: u64,
pub profit_loss: f64,
pub timestamp: i64,
pub take_profit: f64,
pub stop_loss: f64,
pub metrics: Option<TradeMetrics>,
}
#[derive(Clone)]
pub struct Config {
pub rpc_url: String,
pub ws_url: String,
pub fallback_ws: String,
pub helius_api_key: String,
}
#[derive(Clone)]
pub struct PoolStatus {
pub token: String,
pub creation_time: Instant,
pub last_validation: Instant,
pub validation_attempts: u8,
pub liquidity: f64,
pub is_tradeable: bool,
pub has_failed: bool,
pub error_reason: Option<String>,
}
pub struct Bot {
pub config: Config,
pub keypair: Keypair,
pub http_client: Client,
pub active_trades: DashMap<String, Trade>,
pub rpc_client: RpcClient,
pub jupiter_program_id: Pubkey,
pub pool_status: Arc<DashMap<String, PoolStatus>>,
pub trade_metrics: Arc<RwLock<Vec<TradeMetrics>>>,
}
#[derive(Clone)] // Add Clone trait
pub struct BotMonitor {
pub bot: Arc<Bot>,
}
#[derive(Debug)]
pub struct PoolData {
pub token: String,
pub liquidity: f64,
pub timestamp: i64,
}
impl BotMonitor {
pub fn new(bot: Arc<Bot>) -> Self {
Self { bot }
}
pub async fn start_monitoring(&self) {
info!("🔄 Starting system monitoring...");
}
pub fn display_system_health(&self) {
info!("💻 System health: Optimal");
}
}
// Trade Implementation
impl Trade {
pub fn new(entry_price: f64, amount: u64) -> Self {
Self {
entry_price,
current_price: entry_price,
amount,
profit_loss: 0.0,
timestamp: Utc::now().timestamp(),
take_profit: TAKE_PROFIT,
stop_loss: STOP_LOSS,
metrics: None,
}
}
pub fn calculate_pl(&self) -> f64 {
((self.current_price - self.entry_price) / self.entry_price) * 100.0
}
pub fn should_close(&self) -> bool {
let current_pl = self.calculate_pl();
current_pl >= self.take_profit || current_pl <= self.stop_loss
}
}
impl Bot {
pub async fn new(config: Config, keypair: Keypair) -> Result<Self> {
Ok(Self {
rpc_client: RpcClient::new(config.rpc_url.clone()),
config: config.clone(), // Clone config before moving
keypair,
http_client: Client::new(),
active_trades: DashMap::new(),
jupiter_program_id: Pubkey::from_str("JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB")?,
pool_status: Arc::new(DashMap::new()),
trade_metrics: Arc::new(RwLock::new(Vec::new())),
})
}
pub async fn handle_market_feed(
mut ws_stream: WebSocketStream<MaybeTlsStream<TcpStream>>,
bot: Arc<Bot>,
) -> Result<()> {
info!("🔍 Scanning for new pools...");
while let Some(message) = ws_stream.next().await {
match message {
Ok(msg) => {
if let Some(pool_data) = bot.parse_pool_creation(&msg) {
info!("🌟 New Pool Detected!");
info!("📍 Token: {}", pool_data.token);
info!("💧 Initial Liquidity: {}", pool_data.liquidity);
info!("⏱️ Creation Time: {}", pool_data.timestamp);
// Track pool for potential trades
bot.track_pool_creation(&pool_data.token);
}
}
Err(e) => error!("❌ Feed error: {}", e),
}
}
Ok(())
}
pub fn parse_pool_creation(&self, msg: &Message) -> Option<PoolData> {
if let Message::Text(text) = msg {
if let Ok(json) = serde_json::from_str::<Value>(&text) {
// Extract pool creation data
if let Some(pool_info) = json.get("data") {
return Some(PoolData {
token: pool_info["mint"].as_str()?.to_string(),
liquidity: pool_info["liquidity"].as_f64()?,
timestamp: Utc::now().timestamp(),
});
}
}
}
None
}
async fn get_token_price(&self, token: &str) -> Result<f64> {
let request = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "getTokenPrice",
"params": [
token,
{
"source": "raydium",
"priority": "high"
}
]
});
let response = self
.http_client
.post(&self.config.rpc_url)
.header(
"Authorization",
format!("Bearer {}", self.config.helius_api_key),
)
.json(&request)
.send()
.await?
.json::<Value>()
.await?;
response["result"]["price"]
.as_f64()
.ok_or_else(|| anyhow!("Invalid price data"))
}
pub fn load_keypair(path: &str) -> Result<Keypair> {
let keypair_bytes = std::fs::read(path)?;
let keypair: Vec<u8> = serde_json::from_slice(&keypair_bytes)?;
Ok(Keypair::from_bytes(&keypair)?)
}
pub async fn monitor_positions(&self) -> Result<()> {
for item in self.active_trades.iter() {
let token = item.key();
let mut trade = item.value().clone();
// Update current price
match self.get_token_price(token).await {
Ok(current_price) => {
trade.current_price = current_price;
trade.profit_loss = ((current_price - trade.entry_price) / trade.entry_price) * 100.0;
// Check stop loss
if trade.profit_loss <= trade.stop_loss {
info!("🛑 Stop loss triggered for {}: {}%", token, trade.profit_loss);
self.execute_rapid_trade(token, false).await?;
continue;
}
// Check take profit
if trade.profit_loss >= trade.take_profit {
info!("💰 Take profit triggered for {}: {}%", token, trade.profit_loss);
self.execute_rapid_trade(token, false).await?;
continue;
}
// Update metrics
if let Some(metrics) = &trade.metrics {
let mut trade_metrics = self.trade_metrics.write().await;
trade_metrics.push(metrics.clone());
}
}
Err(e) => error!("💢 Price update failed for {}: {}", token, e),
}
}
Ok(())
}
pub fn display_market_stats(&self) {
info!("📈 Market stats updated");
}
fn is_pool_age_valid(&self, token: &str) -> bool {
if let Some(status) = self.pool_status.get(token) {
let age = status.creation_time.elapsed().as_millis() as u64;
(MIN_POOL_AGE_MS..=MAX_POOL_AGE_MS).contains(&age)
} else {
false
}
}
pub async fn execute_trade(&self, token: &str, is_buy: bool) -> Result<String> {
// Validate pool age
if !self.is_pool_age_valid(token) {
return Err(anyhow!("Pool age invalid"));
}
// Track pool creation
self.track_pool_creation(token);
// Check token supply and readiness
let token_pubkey = Pubkey::from_str(token)?;
let supply = self.get_token_supply(&token_pubkey).await?;
// Verify minimum supply requirement
if supply < POOL_SIZE_REQUIREMENT {
return Err(anyhow!("Insufficient token supply"));
}
let is_ready = self.validate_pool_readiness(token).await?;
// Verify mint authority
let mint_locked = !self.check_mint_authority(&token_pubkey).await?;
if !mint_locked {
return Err(anyhow!("Mint authority not locked"));
}
// Check balance for sell orders
let balance = self.check_token_balance(&token_pubkey).await?;
if !is_buy && balance < TRADE_AMOUNT {
return Err(anyhow!("Insufficient balance for sell"));
}
// Update pool status
let liquidity = self.get_pool_liquidity(&token_pubkey).await?;
self.update_pool_status(token, is_ready, liquidity);
// Execute the trade
self.execute_rapid_trade(token, is_buy).await
}
fn track_pool_creation(&self, token: &str) {
self.pool_status.insert(
token.to_string(),
PoolStatus {
token: token.to_string(),
creation_time: Instant::now(),
last_validation: Instant::now(),
validation_attempts: 0,
liquidity: 0.0,
is_tradeable: false,
has_failed: false,
error_reason: None,
},
);
}
fn update_pool_status(&self, token: &str, is_tradeable: bool, liquidity: f64) {
if let Some(mut status) = self.pool_status.get_mut(token) {
status.last_validation = Instant::now();
status.validation_attempts += 1;
status.liquidity = liquidity;
status.is_tradeable = is_tradeable;
}
}
pub async fn execute_rapid_trade(&self, token: &str, is_buy: bool) -> Result<String> {
let amount = if is_buy { TRADE_AMOUNT } else { 0 };
let blockhash = self.rpc_client.get_latest_blockhash().await?;
// Pre-execution checks
if is_buy {
let token_pubkey = Pubkey::from_str(token)?;
let (liquidity, tradeable) = tokio::join!(
self.get_pool_liquidity(&token_pubkey),
self.check_tradeable_status(token)
);
if !tradeable? {
return Err(anyhow!("Token not tradeable at execution time"));
}
let current_liquidity = liquidity?;
if !(MIN_LIQUIDITY..=MAX_LIQUIDITY).contains(¤t_liquidity) {
return Err(anyhow!("Liquidity outside range at execution time"));
}
}
// Get quote and prepare transaction
let quote = self.get_jupiter_quote(SOL_MINT, token, amount).await?;
let priority_ix = ComputeBudgetInstruction::set_compute_unit_price(PRIORITY_FEE);
let swap_data = self.prepare_swap_data("e).await?;
let tx_data = BASE64.decode(&swap_data)?;
let swap_ix = Instruction {
program_id: self.jupiter_program_id,
accounts: vec![],
data: tx_data,
};
let message = SolanaMessage::new(&[priority_ix, swap_ix], Some(&self.keypair.pubkey()));
let mut transaction = Transaction::new_unsigned(message);
transaction.sign(&[&self.keypair], blockhash);
let signature = self.rpc_client.send_transaction(&transaction).await?;
Ok(signature.to_string())
}
async fn verify_transaction(&self, signature: &str) -> Result<bool> {
let sig = signature.parse()?;
let mut attempts = 0;
const MAX_ATTEMPTS: u8 = 5;
while attempts < MAX_ATTEMPTS {
if let Ok(confirmed) = self.rpc_client.confirm_transaction(&sig).await {
return Ok(confirmed);
}
attempts += 1;
sleep(Duration::from_millis(200)).await;
}
Ok(false)
}
async fn check_emergency_conditions(&self, token: &str, trade: &Trade) -> Result<bool> {
if trade.calculate_pl() < -10.0 {
warn!("🚨 Emergency exit triggered: Rapid price decline");
return Ok(true);
}
let token_pubkey = Pubkey::from_str(token)?;
if let Ok(current_liquidity) = self.get_pool_liquidity(&token_pubkey).await {
if current_liquidity < MIN_LIQUIDITY {
warn!("🚨 Emergency exit triggered: Liquidity below minimum");
return Ok(true);
}
}
Ok(false)
}
async fn get_token_supply(&self, token_pubkey: &Pubkey) -> Result<u64> {
match self.rpc_client.get_token_supply(token_pubkey).await {
Ok(supply) => {
let amount = supply
.amount
.parse::<u64>()
.unwrap_or(0);
info!("📈 Supply amount: {}", amount);
Ok(amount)
}
Err(e) => {
error!("❌ Supply check failed: {}", e);
Err(anyhow!("Supply check failed: {}", e))
}
}
}
async fn get_pool_liquidity(&self, token_pubkey: &Pubkey) -> Result<f64> {
match self.rpc_client.get_account_data(token_pubkey).await {
Ok(pool_data) => {
if pool_data.len() >= 16 {
let liquidity_bytes = &pool_data[8..16];
let liquidity_value = u64::from_le_bytes(
liquidity_bytes.try_into().unwrap_or([0u8; 8]),
);
Ok((liquidity_value as f64) / 1_000_000.0)
} else {
Ok(0.0)
}
}
Err(e) => Err(anyhow!("Liquidity check failed: {}", e)),
}
}
async fn get_jupiter_quote(
&self,
input_mint: &str,
output_mint: &str,
amount: u64,
) -> Result<Value> {
let url = format!(
"{}/quote?inputMint={}&outputMint={}&amount={}&slippageBps={}&onlyDirectRoutes=true",
JUPITER_API, input_mint, output_mint, amount, MAX_SLIPPAGE_BPS
);
let response = self
.http_client
.get(&url)
.send()
.await?
.json::<Value>()
.await?;
Ok(response)
}
async fn prepare_swap_data(&self, quote: &Value) -> Result<String> {
let swap_request = json!({
"quoteResponse": quote,
"userPublicKey": self.keypair.pubkey().to_string(),
"computeUnitPriceMicroLamports": PRIORITY_FEE,
"priorityFeeLamports": PRIORITY_FEE,
"asLegacyTransaction": false,
"maxAccounts": 64
});
let response = self
.http_client
.post(format!("{}/swap", JUPITER_API))
.json(&swap_request)
.send()
.await?
.json::<Value>()
.await?;
response["swapTransaction"]
.as_str()
.ok_or_else(|| anyhow!("Invalid swap transaction data"))
.map(String::from)
}
async fn validate_pool_readiness(&self, token: &str) -> Result<bool> {
let mut total_attempts = 0;
let max_attempts = 3;
let delay = 100;
while total_attempts < max_attempts {
total_attempts += 1;
info!("🔄 Readiness check {} of {}", total_attempts, max_attempts);
let token_pubkey = Pubkey::from_str(token)?;
let (supply, liquidity, mint_status) = tokio::join!(
self.get_token_supply(&token_pubkey),
self.get_pool_liquidity(&token_pubkey),
self.check_mint_authority(&token_pubkey)
);
match (supply, liquidity, mint_status) {
(Ok(supply), Ok(liquidity), Ok(mint_status)) => {
let ready = (MIN_LIQUIDITY..=MAX_LIQUIDITY).contains(&liquidity)
&& (1_000_000..=1_000_000_000_000).contains(&supply)
&& !mint_status;
if ready {
info!("✅ Pool readiness confirmed!");
info!("💧 Liquidity: ${:.2}", liquidity);
info!("📊 Supply: {}", supply);
info!("🔒 Mint Locked: {}", !mint_status);
return Ok(true);
}
}
_ => {
warn!("⚠️ Readiness check failed on attempt {}", total_attempts);
}
}
if total_attempts < max_attempts {
sleep(Duration::from_millis(delay)).await;
}
}
warn!("❌ Readiness validation failed");
Ok(false)
}
async fn check_tradeable_status(&self, token: &str) -> Result<bool> {
let test_amounts = [1_000_000, 2_000_000, 5_000_000];
for amount in test_amounts {
match self.get_jupiter_quote(token, SOL_MINT, amount).await {
Ok(quote) => {
if let Some(routes) = quote.get("data") {
if let Some(routes_array) = routes.as_array() {
if routes_array.is_empty() {
info!("⏳ No valid routes for amount {}", amount);
return Ok(false);
}
}
} else {
return Ok(false);
}
if let Some(impact) = quote.get("priceImpactPct").and_then(|v| v.as_f64()) {
if impact.abs() > 5.0 {
warn!("❌ High price impact: {}%", impact);
return Ok(false);
}
}
}
Err(e) => {
info!("⏳ Quote fetch failed: {}", e);
return Ok(false);
}
}
}
info!("✅ Token is fully tradeable");
Ok(true)
}
async fn check_mint_authority(&self, token_pubkey: &Pubkey) -> Result<bool> {
match self.rpc_client.get_account_data(token_pubkey).await {
Ok(data) => {
// Assuming standard SPL Token mint layout
if data.len() >= 82 {
let mint_authority_option = data[0];
Ok(mint_authority_option != 0)
} else {
Ok(false)
}
}
Err(_) => Ok(false),
}
}
async fn check_token_balance(&self, token_pubkey: &Pubkey) -> Result<u64> {
let filter = TokenAccountsFilter::Mint(*token_pubkey);
let accounts = self.rpc_client
.get_token_accounts_by_owner(
&self.keypair.pubkey(),
filter
)
.await?;
if let Some(account) = accounts.first() {
match &account.account.data {
UiAccountData::Binary(data, _) => {
let amount = u64::from_str(data)?;
Ok(amount)
},
_ => Ok(0),
}
} else {
Ok(0)
}
}
pub async fn monitor_trades(&self) {
loop {
let trades = self.active_trades.clone();
for item in trades.iter() {
let token = item.key();
let mut trade = item.value().clone();
match self.get_token_price(token).await {
Ok(current_price) => {
trade.current_price = current_price;
trade.profit_loss = trade.calculate_pl();
if trade.should_close()
|| self
.check_emergency_conditions(token, &trade)
.await
.unwrap_or(false)
{
info!(
"🔄 Closing trade for {} with P/L: {:.2}%",
token, trade.profit_loss
);
match self.execute_rapid_trade(token, false).await {
Ok(signature) => {
if self
.verify_transaction(&signature)
.await
.unwrap_or(false)
{
self.active_trades.remove(token);
let mut metrics = self.trade_metrics.write().await;
if let Some(trade_metrics) = trade.metrics.clone() {
metrics.push(trade_metrics);
}
}
}
Err(e) => error!("❌ Trade closure failed: {}", e),
}
}
}
Err(e) => error!("❌ Price update failed: {}", e),
}
}
sleep(Duration::from_secs(1)).await;
}
}
pub async fn get_trading_stats(&self) -> String {
let active_count = self.active_trades.len();
let total_pl: f64 = self
.active_trades
.iter()
.map(|trade| trade.value().profit_loss)
.sum();
let metrics = if let Ok(guard) = self.trade_metrics.try_read() {
guard.clone()
} else {
Vec::new()
};
format!(
"Active Trades: {}\nTotal P/L: {:.2}%\nTotal Trades: {}",
active_count,
total_pl,
metrics.len()
)
}
pub async fn display_detailed_stats(&self) -> String {
let active_trades = self.active_trades.len();
let mut total_profit = 0.0;
let mut best_trade: f64 = f64::MIN;
let mut worst_trade: f64 = f64::MAX;
let mut total_volume: f64 = 0.0;
let trade_details: Vec<String> = self
.active_trades
.iter()
.map(|trade| {
let pl = trade.value().profit_loss;
let volume = trade.value().amount as f64 * trade.value().current_price;
total_profit += pl;
total_volume += volume; // Added semicolon here
best_trade = best_trade.max(pl);
worst_trade = worst_trade.min(pl);
format!(
"🎯 Token: {}\n💰 Entry: ${:.4}\n📊 Current: ${:.4}\n📈 P/L: {:.2}%\n💎 Volume: ${:.2}\n",
trade.key(),
trade.value().entry_price,
trade.value().current_price,
pl,
volume
)
})
.collect();
format!(
"🤖 Bot Status Report 🤖\n\
============================\n\
🔄 Active Trades: {}\n\
💫 Total P/L: {:.2}%\n\
🌟 Best Trade: {:.2}%\n\
⚠️ Worst Trade: {:.2}%\n\
💰 Total Volume: ${:.2}\n\
============================\n\
📊 Active Positions:\n\
{}\n\
============================\n\
🕒 Last Updated: {}\n",
active_trades,
total_profit,
best_trade,
worst_trade,
total_volume,
trade_details.join("\n"),
Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
)
}
}
is this script good?