use crate::dto::{AccountKeys, TokenData, Transaction }; use crate::utils::{base58_to_pubkey, get_token_socials}; use base64::prelude::*; use lazy_static::lazy_static; use log::{debug, error, info}; use solana_client::rpc_client::RpcClient; use solana_program::pubkey::Pubkey; use solana_sdk::signature::Signature; use solana_sdk::transaction:: VersionedTransaction; use solana_transaction_status::EncodedTransaction::Binary; use solana_transaction_status_client_types::option_serializer; use std::str::FromStr; use utils::sanitize_string; use solana_rpc_calls::*; use crate::telegram_publish::send_to_telegram; mod dto; mod utils; mod solana_rpc_calls; mod telegram_publish; lazy_static! { static ref RPC_CLIENT: RpcClient = RpcClient::new("https://api.mainnet-beta.solana.com".to_string()); } #[tokio::main] async fn main() { // Adresse du programme Token (SPL Token Program) --> pumpfun let token_program_address = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM"; //let buy_router_address = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"; match base58_to_pubkey(token_program_address) { Ok(token_program) => { info!( "{}", format!( "Récupération des transactions sur le programme {:?}", token_program_address ) ); let signatures = RPC_CLIENT.get_signatures_for_address(&token_program); match signatures { Ok(sigs) => { debug!("{}", format!( "Nombre total de transactions trouvées : {}", sigs.len() )); for sig in sigs.iter().take(1000) { let signature = Signature::from_str(&sig.signature).unwrap(); info!("================ Signature ========================"); match get_transactions(&RPC_CLIENT, &signature) { Ok(tx) => { if let Some(meta) = tx.transaction.meta { if let option_serializer::OptionSerializer::Some( inner_instructions, ) = meta.inner_instructions { let is_token_creation = inner_instructions.iter().any(|inner| { inner.instructions.iter().any(|ix| { match ix { solana_transaction_status::UiInstruction::Compiled(instruction) => { is_token_creation(instruction) } _ => false } }) }); if !is_token_creation { continue; } match tx.transaction.transaction { Binary(data) => { let decoded = BASE64_STANDARD.decode(data).unwrap(); if let Ok(versioned_tx) = bincode::deserialize::( &decoded, ) { let account_keys = versioned_tx.message.static_account_keys(); let transaction = Transaction { signature: sig.clone().signature, slot: sig.clone().slot, block_time: sig.clone().block_time, status: meta.err, account_keys: AccountKeys { payer: account_keys[0], mint_account: account_keys[1], rent_account: account_keys[2], mint_authority: account_keys[3], token_program: account_keys[4], }, }; dbg!(&transaction); let mint_pubkey = transaction.account_keys.mint_account; match get_token_metadata( &RPC_CLIENT, &mint_pubkey, ) { Ok(token_metadata) => { let dev_balance = get_dev_balance_for_token( &RPC_CLIENT, &transaction.account_keys.payer, &mint_pubkey, ); if let Err(e) = dev_balance { error!("{}", format!("{}", e)); continue; } match get_token_info( &RPC_CLIENT, &mint_pubkey, ) { Ok(token_info) => { let token_data = TokenData { name: sanitize_string( &token_metadata.name, ), symbol: sanitize_string( &token_metadata.symbol, ), uri: sanitize_string( &token_metadata.uri, ), supply: token_info.supply / 10u64.pow(token_info.decimals as u32), dev_balance: dev_balance .unwrap() .clone(), mint_authority: token_info .mint_authority, freeze_authority: token_info .freeze_authority, }; dbg!(token_data.clone()); if is_safe_token(token_data.clone()){ println!("Token OK"); let token = "8176178685:AAHMlXsjx4ffH9sQ1v_eg1nkc-W9XUncIhE"; let chat_id = "945119667"; // can be a group chat too let socials= get_token_socials(&token_data.uri).await.ok(); let socials = socials.unwrap_or_default(); let message = format!("🚨 #New token detected!\ \n**Name**: {} ({}) \ \n**Uri**: {} \ \nSignature: https://solscan.io/tx/{} \ \nMint address: https://solscan.io/token/{} \ \nDev address: https://solscan.io/account/{} \ \nSource: https://pump.fun/coin/{} \ \nDescription: {} \ \nTwitter: {} \ \nWebsite: {} \ \nImage: {} \ ", token_data.name, token_data.symbol, token_data.uri, transaction.signature, transaction.account_keys.mint_account, transaction.account_keys.payer, transaction.account_keys.mint_account, socials.description, socials.twitter, socials.website, socials.image); if let Err(e) = send_to_telegram(token, chat_id, message.as_str()).await { eprintln!("Error sending to Telegram: {}", e); } } else { println!("Token NOK") } } Err(e) => { error!( "Erreur lors de la récupération des informations du token : {:?}", e ); } } } Err(_) => {} }; } println!( "=============================================" ); } _ => {} } } } } Err(err) => eprintln!( "Erreur lors de la récupération de la transaction : {:?}", err ), } } } Err(err) => eprintln!( "Erreur lors de la récupération des transactions : {:?}", err ), } } Err(err) => eprintln!("Erreur lors de la conversion de l'adresse : {:?}", err), } } fn is_safe_token(data: TokenData) -> bool { let holding_percentage = data.dev_balance * 100.0 / data.supply as f64; let has_control = data.mint_authority || data.freeze_authority; let holds_too_much = holding_percentage > 20.0; !(has_control || holds_too_much) } fn find_metadata_account(mint: &Pubkey) -> (Pubkey, u8) { let metadata_program_id = Pubkey::from_str("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").unwrap(); Pubkey::find_program_address( &[b"metadata", metadata_program_id.as_ref(), mint.as_ref()], &metadata_program_id, ) } fn is_token_creation(instruction: &solana_transaction_status::UiCompiledInstruction) -> bool { match bs58::decode(&instruction.data).into_vec() { Ok(data) => { info!("It is a token creation"); matches!(data.first(), Some(0)) } Err(_) => { info!("not a token creation"); false } } } #[cfg(test)] mod tests { use super::*; #[test] fn safe_token_no_control_and_low_holding() { let data = TokenData { name: "".to_string(), symbol: "".to_string(), dev_balance: 10.0, supply: 1000, mint_authority: false, freeze_authority: false, uri: "".to_string(), }; assert!(is_safe_token(data)); } #[test] fn unsafe_token_due_to_mint_authority() { let data = TokenData { name: "".to_string(), symbol: "".to_string(), dev_balance: 10.0, supply: 1000, mint_authority: true, freeze_authority: false, uri: "".to_string(), }; assert!(!is_safe_token(data)); } #[test] fn unsafe_token_due_to_freeze_authority() { let data = TokenData { name: "".to_string(), symbol: "".to_string(), dev_balance: 10.0, supply: 1000, mint_authority: false, freeze_authority: true, uri: "".to_string(), }; assert!(!is_safe_token(data)); } #[test] fn unsafe_token_due_to_high_dev_balance() { let data = TokenData { name: "".to_string(), symbol: "".to_string(), dev_balance: 250.0, supply: 1000, mint_authority: false, freeze_authority: false, uri: "".to_string(), }; assert!(!is_safe_token(data)); } }