Alexandre RAY-BERNAT 9bd88aeb03
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Retrieves social infos + post on Telegram
2025-05-23 16:13:36 +02:00

357 lines
18 KiB
Rust

use std::error::Error;
use bs58;
use chrono;
use solana_sdk::message::Message;
use crate::dto::{AccountKeys, ParsedAccount, TokenData, TokenInfo, Transaction, TokenSocials };
use crate::utils::{base58_to_pubkey, get_token_socials};
use anyhow::Result;
use anyhow::anyhow;
use base64::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use chrono::Utc;
use hex;
use lazy_static::lazy_static;
use log::{debug, error, info};
use mpl_token_metadata::accounts::Metadata;
use serde::{Deserialize, Serialize};
use solana_account_decoder::{UiAccountData, UiAccountEncoding};
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_config::RpcAccountInfoConfig;
use solana_client::rpc_request::TokenAccountsFilter;
use solana_program::{program_pack::Pack, pubkey::Pubkey};
use solana_sdk::account::Account;
use solana_sdk::message::VersionedMessage;
use solana_sdk::signature::Signature;
use solana_sdk::transaction::{TransactionError, VersionedTransaction};
use solana_transaction_status::EncodedTransaction::Binary;
use solana_transaction_status::{EncodedConfirmedTransactionWithStatusMeta, UiTransactionEncoding};
use solana_transaction_status_client_types::option_serializer;
use spl_token::state::Mint;
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, encoding) => {
let decoded = BASE64_STANDARD.decode(data).unwrap();
if let Ok(versioned_tx) =
bincode::deserialize::<VersionedTransaction>(
&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());
let is_safe_token =
is_safe_token(
token_data.clone(),
);
if (is_safe_token) {
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));
}
}