Retrieve token data and make some checks
This commit is contained in:
parent
73fe72f829
commit
b4a6da9a81
3196
Cargo.lock
generated
3196
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
17
Cargo.toml
@ -4,16 +4,27 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-client = "1.17"
|
solana-client = "2.2.6"
|
||||||
solana-sdk = "1.17"
|
solana-sdk = "2.2.2"
|
||||||
bs58 = "0.5.0"
|
bs58 = "0.5.0"
|
||||||
solana-commitment-config = "2.2.1"
|
solana-commitment-config = "2.2.1"
|
||||||
solana-transaction-status-client-types = "2.2.4"
|
solana-transaction-status-client-types = "2.2.4"
|
||||||
solana-transaction-status = "1.18.26"
|
solana-transaction-status = "2.2.6"
|
||||||
spl-token = "8.0.0"
|
spl-token = "8.0.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
borsh = "1.5.7"
|
||||||
|
borsh-derive = "1.5.7"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
solana-account-decoder = "2.2.6"
|
||||||
|
chrono = "0.4.40"
|
||||||
|
anyhow = "1.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
mpl-token-metadata = "5.1.0"
|
||||||
|
solana-program = "2.2.1"
|
||||||
|
serde_json = "1.0.140"
|
||||||
|
log = "0.4.27"
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|||||||
72
src/dto.rs
Normal file
72
src/dto.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use borsh_derive::BorshDeserialize;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use solana_program::clock::Slot;
|
||||||
|
use solana_program::program_option::COption;
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
use solana_sdk::transaction::TransactionError;
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, Debug, Deserialize)]
|
||||||
|
pub struct TokenInfo {
|
||||||
|
pub(crate) mint_address: String,
|
||||||
|
pub(crate) decimals: u8,
|
||||||
|
pub(crate) mint_authority: bool,
|
||||||
|
pub(crate) freeze_authority: bool,
|
||||||
|
pub(crate) supply: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, Debug, Deserialize)]
|
||||||
|
pub struct TokenAmount {
|
||||||
|
amount: String,
|
||||||
|
decimals: u8,
|
||||||
|
pub(crate) uiAmount: Option<f64>,
|
||||||
|
uiAmountString: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, Debug, Deserialize)]
|
||||||
|
pub struct ParsedAccount {
|
||||||
|
pub(crate) info: TokenInfoWithAmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, Debug, Deserialize)]
|
||||||
|
pub struct TokenInfoWithAmount {
|
||||||
|
isNative: bool,
|
||||||
|
mint: String,
|
||||||
|
owner: String,
|
||||||
|
state: String,
|
||||||
|
pub(crate) tokenAmount: TokenAmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, Debug, Deserialize)]
|
||||||
|
pub struct UiParsedTokenAccount {
|
||||||
|
program: String,
|
||||||
|
parsed: ParsedAccount,
|
||||||
|
space: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Transaction {
|
||||||
|
pub(crate) signature: String,
|
||||||
|
pub(crate) slot: Slot,
|
||||||
|
pub(crate) block_time: Option<i64>,
|
||||||
|
pub(crate) status: Option<TransactionError>,
|
||||||
|
pub(crate) account_keys: AccountKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AccountKeys {
|
||||||
|
pub(crate) payer: Pubkey,
|
||||||
|
pub(crate) mint_account: Pubkey,
|
||||||
|
pub(crate) rent_account: Pubkey,
|
||||||
|
pub(crate) mint_authority: Pubkey,
|
||||||
|
pub(crate) token_program: Pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TokenData {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) symbol: String,
|
||||||
|
pub(crate) uri: String,
|
||||||
|
pub(crate) supply: u64,
|
||||||
|
pub(crate) dev_balance: f64,
|
||||||
|
pub(crate) mint_authority: bool,
|
||||||
|
pub(crate) freeze_authority: bool,
|
||||||
|
|
||||||
|
}
|
||||||
597
src/main.rs
597
src/main.rs
@ -1,250 +1,461 @@
|
|||||||
use solana_client::rpc_client::RpcClient;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use bs58;
|
use bs58;
|
||||||
use solana_sdk::instruction::Instruction;
|
use chrono;
|
||||||
use solana_sdk::message::Message;
|
use solana_sdk::message::Message;
|
||||||
use solana_client::rpc_response::RpcConfirmedTransactionStatusWithSignature;
|
|
||||||
use solana_sdk::transaction::Transaction;
|
use anyhow::anyhow;
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
use anyhow::Result;
|
||||||
use solana_sdk::signature::Signature;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use spl_token::state::Mint;
|
|
||||||
use solana_transaction_status::EncodedTransaction::{Json, Binary};
|
|
||||||
use solana_transaction_status::UiMessage::{Parsed, Raw};
|
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
use solana_sdk::message::VersionedMessage;
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use solana_sdk::transaction::VersionedTransaction;
|
use chrono::Utc;
|
||||||
use hex;
|
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::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 solana_transaction_status_client_types::option_serializer;
|
||||||
|
use spl_token::state::Mint;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use solana_program::clock::Slot;
|
||||||
|
use solana_sdk::account::Account;
|
||||||
|
use utils::sanitize_string;
|
||||||
|
use crate::dto::{AccountKeys, ParsedAccount, TokenData, TokenInfo, Transaction};
|
||||||
|
use crate::utils::base58_to_pubkey;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
mod dto;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref RPC_CLIENT: RpcClient =
|
||||||
|
RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let rpc_url = "https://api.mainnet-beta.solana.com";
|
// Adresse du programme Token (SPL Token Program) --> pumpfun
|
||||||
let client = RpcClient::new(rpc_url.to_string());
|
|
||||||
|
|
||||||
// Adresse du programme Token (SPL Token Program)
|
|
||||||
let token_program_address = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM";
|
let token_program_address = "TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM";
|
||||||
|
|
||||||
let buy_router_address = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
let buy_router_address = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
||||||
|
|
||||||
match base58_to_pubkey(token_program_address) {
|
match base58_to_pubkey(token_program_address) {
|
||||||
Ok(token_program) => {
|
Ok(token_program) => {
|
||||||
println!("Récupération des transactions pour le programme Token...");
|
info!(
|
||||||
|
"{}",
|
||||||
// Récupérer les transactions associées au programme Token
|
format!(
|
||||||
let transactions = client.get_signatures_for_address(&token_program);
|
"Récupération des transactions sur le programme {:?}",
|
||||||
|
token_program_address
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let signatures = RPC_CLIENT.get_signatures_for_address(&token_program);
|
||||||
|
|
||||||
match transactions {
|
match signatures {
|
||||||
Ok(sigs) => {
|
Ok(sigs) => {
|
||||||
println!("Nombre total de transactions trouvées : {}", sigs.len());
|
debug!(
|
||||||
for sig in sigs.iter().take(1000) { // Limiter à 10 transactions pour commencer
|
"{}",
|
||||||
// Convertir la signature en Signature
|
format!("Nombre total de transactions trouvées : {}", sigs.len())
|
||||||
|
);
|
||||||
|
|
||||||
|
for sig in sigs.iter().take(1000) {
|
||||||
let signature = Signature::from_str(&sig.signature).unwrap();
|
let signature = Signature::from_str(&sig.signature).unwrap();
|
||||||
|
|
||||||
|
|
||||||
let config = solana_client::rpc_config::RpcTransactionConfig {
|
info!("================ Signature ========================");
|
||||||
encoding: Some(UiTransactionEncoding::Base64),
|
|
||||||
commitment: Some(solana_sdk::commitment_config::CommitmentConfig::confirmed()),
|
|
||||||
max_supported_transaction_version: Some(0),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Récupérer les détails de la transaction
|
match get_transactions(&RPC_CLIENT, &signature) {
|
||||||
match client.get_transaction_with_config(&signature, config) {
|
|
||||||
Ok(tx) => {
|
Ok(tx) => {
|
||||||
if let Some(meta) = tx.transaction.meta {
|
if let Some(meta) = tx.transaction.meta {
|
||||||
if let solana_transaction_status::option_serializer::OptionSerializer::Some(inner_instructions) = meta.inner_instructions {
|
if let option_serializer::OptionSerializer::Some(
|
||||||
//Vérifier si c'est une création de token
|
inner_instructions,
|
||||||
|
) = meta.inner_instructions
|
||||||
|
{
|
||||||
let is_token_creation = inner_instructions.iter().any(|inner| {
|
let is_token_creation = inner_instructions.iter().any(|inner| {
|
||||||
inner.instructions.iter().any(|ix| {
|
inner.instructions.iter().any(|ix| {
|
||||||
//println!("ix: {:?}", ix);
|
|
||||||
match ix {
|
match ix {
|
||||||
solana_transaction_status::UiInstruction::Compiled(instruction) => {
|
solana_transaction_status::UiInstruction::Compiled(instruction) => {
|
||||||
is_token_creation(instruction)
|
is_token_creation(instruction)
|
||||||
},
|
}
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// if is_token_creation {
|
if !is_token_creation {
|
||||||
// println!("Création de token trouvée :");
|
continue
|
||||||
// println!(" Signature: {}", sig.signature);
|
|
||||||
// println!(" Slot: {}", sig.slot);
|
|
||||||
// println!(" Block time: {:?}", sig.block_time);
|
|
||||||
// println!(" Status: {:?}", meta.err);
|
|
||||||
// println!("---");
|
|
||||||
// }
|
|
||||||
|
|
||||||
//println!("tx.transaction.transaction: {:?}", tx.transaction.transaction);
|
|
||||||
|
|
||||||
match tx.transaction.transaction {
|
|
||||||
Binary(data, encoding) => {
|
|
||||||
let decoded = BASE64_STANDARD.decode(data).unwrap();
|
|
||||||
|
|
||||||
// Try to deserialize as a VersionedTransaction
|
|
||||||
if let Ok(versioned_tx) = bincode::deserialize::<VersionedTransaction>(&decoded) {
|
|
||||||
|
|
||||||
// dbg!(&versioned_tx);
|
|
||||||
|
|
||||||
// Try to deserialize the message
|
|
||||||
if let Ok(message) = bincode::deserialize::<VersionedMessage>(&versioned_tx.message.serialize()) {
|
|
||||||
|
|
||||||
let account_keys = versioned_tx.message.static_account_keys();
|
|
||||||
|
|
||||||
println!("\nAccount Keys in Transaction:");
|
|
||||||
println!(" Payer (account_keys[0]): {}", account_keys[0]);
|
|
||||||
println!(" Mint Account (account_keys[1]): {}", account_keys[1]);
|
|
||||||
println!(" Rent Account (account_keys[2]): {}", account_keys[2]);
|
|
||||||
println!(" Mint Authority (account_keys[3]): {}", account_keys[3]);
|
|
||||||
println!(" Token Program (account_keys[4]): {}", account_keys[4]);
|
|
||||||
// println!("Instructions: {:?}", message.compiled_instructions);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
match get_token_info(&client, account_keys[1].to_string().as_str()) {
|
|
||||||
Ok(token_info) => {
|
|
||||||
display_token_info(&token_info);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Erreur lors de la récupération des informations du token : {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a versioned transaction, try as a legacy transaction
|
|
||||||
if let Ok(legacy_tx) = bincode::deserialize::<Transaction>(&decoded) {
|
|
||||||
// dbg!(&legacy_tx);
|
|
||||||
|
|
||||||
}
|
|
||||||
println!("--------------------------------");
|
|
||||||
// match json_transaction.message {
|
|
||||||
// Parsed(msg) => {
|
|
||||||
// println!("binary msg: {:?}", msg);
|
|
||||||
|
|
||||||
// // println!("Token trouvé : {}", &msg);
|
|
||||||
// // println!("Supply total : {}", &msg.supply);
|
|
||||||
// // println!("Decimals : {}", &msg.decimals);
|
|
||||||
|
|
||||||
|
|
||||||
// },
|
|
||||||
// Raw(msg) => {
|
|
||||||
// println!("Raw msg: {:?}", msg);
|
|
||||||
// // println!("Token trouvé : {}", &msg);
|
|
||||||
// // println!("Supply total : {}", &msg.supply);
|
|
||||||
// // println!("Decimals : {}", &msg.decimals);
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mint_pubkey =transaction.account_keys.mint_account;
|
||||||
|
|
||||||
|
let dev_pubkey = transaction.account_keys.payer;
|
||||||
|
|
||||||
|
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,
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
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 de la transaction : {:?}",
|
||||||
|
err
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => eprintln!("Erreur lors de la récupération des transactions : {:?}", 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),
|
Err(err) => eprintln!("Erreur lors de la conversion de l'adresse : {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base58_to_pubkey(address: &str) -> Result<Pubkey, Box<dyn std::error::Error>> {
|
fn is_safe_token(data: TokenData) -> bool {
|
||||||
let decoded = bs58::decode(address).into_vec()?;
|
let holding_percentage = data.dev_balance * 100.0 / data.supply as f64;
|
||||||
if decoded.len() != 32 {
|
|
||||||
return Err("Address must be 32 bytes long".into());
|
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 get_token_metadata(client: &RpcClient, mint: &Pubkey) -> Result<Metadata> {
|
||||||
|
let (metadata_pubkey, _) = find_metadata_account(mint);
|
||||||
|
let account = client.get_account(&metadata_pubkey)?;
|
||||||
|
let metadata = Metadata::safe_deserialize(&mut account.data.as_ref())?;
|
||||||
|
Ok(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TOKEN_METADATA_PROGRAM_ID: Pubkey = Pubkey::new_from_array([
|
||||||
|
0x6D, 0x65, 0x74, 0x61, 0x70, 0x6C, 0x65, 0x78, 0x2D, 0x6D, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||||
|
0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]);
|
||||||
|
|
||||||
|
fn get_creator_of_mint(rpc_client: &RpcClient, mint_address: &Pubkey) -> anyhow::Result<Pubkey> {
|
||||||
|
// 1. Récupère les signatures associées au compte mint
|
||||||
|
let signatures = rpc_client
|
||||||
|
.get_signatures_for_address(mint_address)
|
||||||
|
.into_iter()
|
||||||
|
.rev() // on inverse pour avoir les plus anciennes d'abord
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let first_signature = signatures.first();
|
||||||
|
|
||||||
|
match first_signature {
|
||||||
|
None => Err(anyhow!("Pas de transactions trouvées pour ce mint")),
|
||||||
|
Some(sig) => {
|
||||||
|
let sig2 = sig.first().expect("Pzs de signature trouvée");
|
||||||
|
|
||||||
|
let sig = sig2
|
||||||
|
.signature
|
||||||
|
.parse::<Signature>()
|
||||||
|
.expect("Signature invalide");
|
||||||
|
|
||||||
|
// 2. Récupère les détails de la première transaction
|
||||||
|
let tx = get_transactions(rpc_client, &sig)?.transaction.transaction;
|
||||||
|
|
||||||
|
let tx: anyhow::Result<VersionedMessage> = match tx {
|
||||||
|
Binary(data, encoding) => {
|
||||||
|
let decoded = BASE64_STANDARD.decode(data)?;
|
||||||
|
|
||||||
|
if let Ok(versioned_tx) = bincode::deserialize::<VersionedTransaction>(&decoded)
|
||||||
|
{
|
||||||
|
if let Ok(message) = bincode::deserialize::<VersionedMessage>(
|
||||||
|
&versioned_tx.message.serialize(),
|
||||||
|
) {
|
||||||
|
Ok(message)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(""))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("Encoding of transaction is not supported")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let payer: Result<Message> = match tx {
|
||||||
|
Ok(msg) => {
|
||||||
|
match msg {
|
||||||
|
VersionedMessage::Legacy(legacy_msg) => {
|
||||||
|
println!("{:?}", legacy_msg);
|
||||||
|
Ok(legacy_msg)
|
||||||
|
}
|
||||||
|
//VersionedMessage::V0(v0_msg) => { println!("{:?}", v0_msg); v0_msg}
|
||||||
|
_ => Err(anyhow!("Encoding of transaction is not supported")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err(anyhow!("Encoding of transaction is not supported")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let payer = payer;
|
||||||
|
|
||||||
|
Ok(*payer?.account_keys.first().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let mut bytes = [0u8; 32];
|
|
||||||
bytes.copy_from_slice(&decoded);
|
|
||||||
Ok(Pubkey::new_from_array(bytes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_token_creation(instruction: &solana_transaction_status::UiCompiledInstruction) -> bool {
|
fn is_token_creation(instruction: &solana_transaction_status::UiCompiledInstruction) -> bool {
|
||||||
// L'instruction de création de token a un discriminant de 0
|
match bs58::decode(&instruction.data).into_vec() {
|
||||||
if instruction.data.len() < 1 {
|
Ok(data) => {
|
||||||
return false;
|
info!("It is a token creation");
|
||||||
}
|
|
||||||
|
|
||||||
// instruction.data == "0".to_string()
|
matches!(data.first(), Some(0))
|
||||||
println!("instruction: {:?}", instruction);
|
}
|
||||||
true
|
Err(_) => {
|
||||||
//instruction.data[0] == 0
|
info!("not a token creation");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn get_dev_balance_for_token(rpc: &RpcClient, dev_pubkey: &Pubkey, mint: &Pubkey) -> Result<f64> {
|
||||||
struct TokenInfo {
|
let token_accounts =
|
||||||
mint_address: String,
|
rpc.get_token_accounts_by_owner(dev_pubkey, TokenAccountsFilter::Mint(*mint))?;
|
||||||
transaction_signature: String,
|
|
||||||
slot: u64,
|
let mut total_balance: f64 = 0.0;
|
||||||
block_time: Option<i64>,
|
|
||||||
decimals: u8,
|
for account_info in token_accounts {
|
||||||
mint_authority: Option<String>,
|
let amount_option = match account_info.account.data.clone() {
|
||||||
freeze_authority: Option<String>,
|
UiAccountData::Json(test) => {
|
||||||
supply: u64,
|
let json = serde_json::to_value(&test.parsed)?;
|
||||||
|
let token: ParsedAccount = serde_json::from_value(json)?;
|
||||||
|
|
||||||
|
token.info.tokenAmount.uiAmount
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Cannot parse account_data");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
total_balance += amount_option.unwrap_or_else(|| 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(total_balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_token_info(client: &RpcClient, mint_address: &str) -> Result<TokenInfo, Box<dyn std::error::Error>> {
|
fn fetch_account(rpc_client: &RpcClient, mint_pubkey: &Pubkey) -> Result<Account> {
|
||||||
let mint_pubkey = Pubkey::from_str(mint_address)?;
|
let config = RpcAccountInfoConfig {
|
||||||
|
encoding: Some(UiAccountEncoding::Base64),
|
||||||
// Get the account data
|
..RpcAccountInfoConfig::default()
|
||||||
let account = client.get_account(&mint_pubkey)?;
|
};
|
||||||
|
|
||||||
// Afficher les données brutes en hexadécimal pour le débogage
|
match rpc_client.get_account_with_config(&mint_pubkey, config) {
|
||||||
println!("Données brutes en hex: {}", hex::encode(&account.data));
|
Ok(response) => {
|
||||||
|
match response.value {
|
||||||
// Décoder les données en utilisant Mint::unpack
|
Some(account) => {
|
||||||
let mint = Mint::unpack(&account.data)?;
|
Ok(account)
|
||||||
|
}
|
||||||
// Afficher les informations décodées
|
None => {
|
||||||
println!("\nInformations du token décodées:");
|
Err(anyhow::Error::msg(format!("Account not found")))
|
||||||
println!("Decimals: {}", mint.decimals);
|
|
||||||
println!("Supply: {}", mint.supply);
|
}
|
||||||
println!("Mint Authority: {:?}", mint.mint_authority);
|
}
|
||||||
println!("Freeze Authority: {:?}", mint.freeze_authority);
|
}
|
||||||
|
Err(e) => {
|
||||||
Ok(TokenInfo {
|
Err(anyhow::Error::msg(format!("RPC error {}", e)))
|
||||||
mint_address: mint_address.to_string(),
|
}
|
||||||
transaction_signature: "".to_string(),
|
}
|
||||||
slot: 0,
|
|
||||||
block_time: None,
|
|
||||||
decimals: mint.decimals,
|
|
||||||
mint_authority: mint.mint_authority.map(|pk| pk.to_string()),
|
|
||||||
freeze_authority: mint.freeze_authority.map(|pk| pk.to_string()),
|
|
||||||
supply: mint.supply,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_token_info(token: &TokenInfo) {
|
fn get_token_info(client: &RpcClient, mint_pubkey: &Pubkey) -> Result<TokenInfo> {
|
||||||
println!("\nToken Information:");
|
|
||||||
println!(" Mint Address: {}", token.mint_address);
|
let account = fetch_account(client, mint_pubkey);
|
||||||
println!(" Decimals: {}", token.decimals);
|
|
||||||
println!(" Supply: {}", token.supply);
|
let mint = Mint::unpack(&*account?.data);
|
||||||
match &token.mint_authority {
|
|
||||||
Some(auth) => println!(" Mint Authority: {}", auth),
|
match mint {
|
||||||
None => println!(" Mint Authority: None (immutable)"),
|
Ok(mint) => {
|
||||||
|
Ok(TokenInfo {
|
||||||
|
mint_address: mint_pubkey.to_string(),
|
||||||
|
decimals: mint.decimals,
|
||||||
|
mint_authority: mint.mint_authority.is_some(),
|
||||||
|
freeze_authority: mint.freeze_authority.is_some(),
|
||||||
|
supply: mint.supply,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => Err(anyhow::Error::msg(format!("ProgramError: {:?}", e))),
|
||||||
}
|
}
|
||||||
match &token.freeze_authority {
|
|
||||||
Some(auth) => println!(" Freeze Authority: {}", auth),
|
|
||||||
None => println!(" Freeze Authority: None (unfrozen)"),
|
|
||||||
}
|
|
||||||
println!(" Transaction: {}", token.transaction_signature);
|
|
||||||
println!(" Slot: {}", token.slot);
|
|
||||||
if let Some(time) = token.block_time {
|
|
||||||
println!(" Block Time: {}", time);
|
|
||||||
}
|
|
||||||
println!("---");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_transactions(
|
||||||
|
client: &RpcClient,
|
||||||
|
signature: &Signature,
|
||||||
|
) -> solana_client::client_error::Result<EncodedConfirmedTransactionWithStatusMeta> {
|
||||||
|
let config = solana_client::rpc_config::RpcTransactionConfig {
|
||||||
|
encoding: Some(UiTransactionEncoding::Base64),
|
||||||
|
commitment: Some(solana_sdk::commitment_config::CommitmentConfig::confirmed()),
|
||||||
|
max_supported_transaction_version: Some(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
client.get_transaction_with_config(&signature, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
15
src/utils.rs
Normal file
15
src/utils.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
pub fn sanitize_string(s: &str) -> String {
|
||||||
|
s.trim_end_matches('\0').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base58_to_pubkey(address: &str) -> anyhow::Result<Pubkey, Box<dyn std::error::Error>> {
|
||||||
|
let decoded = bs58::decode(address).into_vec()?;
|
||||||
|
if decoded.len() != 32 {
|
||||||
|
return Err("Address must be 32 bytes long".into());
|
||||||
|
}
|
||||||
|
let mut bytes = [0u8; 32];
|
||||||
|
bytes.copy_from_slice(&decoded);
|
||||||
|
Ok(Pubkey::new_from_array(bytes))
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user