Skip to content

Commit

Permalink
check rug token
Browse files Browse the repository at this point in the history
  • Loading branch information
codewithmecoder committed Feb 8, 2025
1 parent 282f78d commit 4c0ef26
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 9 deletions.
35 changes: 35 additions & 0 deletions rug_check_res_sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"tokenProgram": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"tokenType": "",
"risks": [
{
"name": "Large Amount of LP Unlocked",
"value": "100.00%",
"description": "A large amount of LP tokens are unlocked, allowing the owner to remove liquidity at any point.",
"score": 10999,
"level": "danger"
},
{
"name": "High holder correlation",
"value": "(19)",
"description": "The top users hold similar supply amounts",
"score": 2900,
"level": "warn"
},
{
"name": "Low amount of LP Providers",
"value": "",
"description": "Only a few users are providing liquidity",
"score": 400,
"level": "warn"
},
{
"name": "Mutable metadata",
"value": "",
"description": "Token metadata can be changed by the owner",
"score": 100,
"level": "warn"
}
],
"score": 14399
}
19 changes: 19 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ pub struct Config {
pub program_id: String,
pub private_key: String,
pub liquidility_pool_wsol_pc_mint: String,
pub rug_checker_url: String,
pub rug_check_config: RugCheckConfig,
}

#[derive(Debug, Clone, PartialEq)]
pub struct RugCheckConfig {
pub signal_holder_ownership: f64,
pub not_allowed_risk: Vec<String>,
}

impl Config {
pub fn init() -> Config {
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
Expand All @@ -24,6 +33,7 @@ impl Config {
let private_key = std::env::var("PRIVATE_KEY").expect("PRIVATE_KEY must be set");
let liquidility_pool_wsol_pc_mint = std::env::var("LIQUIDILITY_POOL_WSOL_PC_MINT")
.expect("LIQUIDILITY_POOL_WSOL_PC_MINT must be set");
let rug_checker_url = std::env::var("RUG_CHECKER_URL").expect("RUG_CHECKER_URL must be set");

Config {
database_url,
Expand All @@ -35,6 +45,15 @@ impl Config {
program_id,
telegram_chat_id: telegram_chat_id.parse::<i64>().unwrap(),
liquidility_pool_wsol_pc_mint,
rug_checker_url,
rug_check_config: RugCheckConfig {
signal_holder_ownership: 0 as f64,
not_allowed_risk: vec![
"Freeze Authority still enabled".to_string(),
"Large Amount of LP Unlocked".to_string(),
"Copycat token".to_string(),
],
},
}
}
}
9 changes: 3 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ mod config;
mod database;
mod model;
mod price_monitor;
mod rug_checker;
mod telegram;
mod transaction_processor;
mod websocket;

use std::clone;

use crate::{
database::Database, price_monitor::PriceMonitor, telegram::TelegramNotifier,
websocket::SolanaWebsocket,
Expand Down Expand Up @@ -43,14 +42,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
while let Some(message) = stream.next().await {
// Process transaction
let signer = Keypair::from_base58_string(&config.private_key);
let processor = transaction_processor::TransactionProcessor::new(config.clone(), &websocket);
let processor =
transaction_processor::TransactionProcessor::new(config.clone(), &websocket, &notifier);

// let notifier = notifier.clone();
if let Err(e) = processor.process_transaction(&message, &signer).await {
eprintln!("Error processing transaction: {}", e);
notifier
.send_message(&format!("Error processing transaction: {}", e))
.await?;
}
}

Expand Down
27 changes: 26 additions & 1 deletion src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,33 @@ pub struct InstructionErrorClass {
pub custom: i64,
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DisplayDataItem {
pub token_mint: String,
pub sol_mint: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RugCheckRes {
pub token_program: String,

pub token_type: String,

pub risks: Vec<RugCheckRisk>,

pub score: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RugCheckRisk {
pub name: String,

pub value: String,

pub description: String,

pub score: i64,

pub level: String,
}
76 changes: 76 additions & 0 deletions src/rug_checker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::{config::Config, model::RugCheckRes};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum RugCheckerError {
#[error("Connection error: {0}")]
RugCheckError(String),
}

pub struct RugChecker<'a> {
pub config: &'a Config,
}

impl<'a> RugChecker<'a> {
pub fn new(config: &'a Config) -> Self {
Self { config }
}

pub async fn isvalid_rug_check(&self, token_mint: &str) -> Result<bool, RugCheckerError> {
let client = reqwest::Client::new();
let url = format!(
"{}/tokens/{}/report/summary",
self.config.rug_checker_url, token_mint
);
let res = client
.get(url)
.header("Content-Type", "application/json")
.send()
.await;

match res {
Ok(data) => {
let status = data.status();
let response_txt = data.text().await;
println!("Status: {}", status);

match response_txt {
Ok(txt) => {
println!("Response: {}", txt);
let res_data = serde_json::from_str::<RugCheckRes>(&txt).unwrap();
let mut is_valid = true;
res_data.risks.iter().for_each(|risk| {
println!("Rug: {:?}", risk);
if risk.name.to_lowercase() == "single holder ownership" {
let value = risk.value.replace("%", "");
let numberic_value = value.parse::<f64>().unwrap();
if numberic_value > self.config.rug_check_config.signal_holder_ownership {
is_valid = false;
}
}
});
if !is_valid {
return Ok(false);
}

let res_data = res_data.clone();
let valid = !res_data.risks.iter().any(|r| {
self
.config
.rug_check_config
.not_allowed_risk
.contains(&r.name)
});
return Ok(valid);
}
Err(e) => {
return Err(RugCheckerError::RugCheckError(e.to_string()));
}
}
}
Err(e) => {
return Err(RugCheckerError::RugCheckError(e.to_string()));
}
};
}
}
43 changes: 41 additions & 2 deletions src/transaction_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use thiserror::Error;
use crate::{
config::Config,
model::{DisplayDataItem, StreamMessage, TrxDetailRes},
rug_checker::RugChecker,
telegram::TelegramNotifier,
websocket::SolanaWebsocket,
};

Expand All @@ -22,11 +24,16 @@ pub enum TransactionError {
pub struct TransactionProcessor<'a> {
config: Config,
ws: &'a SolanaWebsocket,
notifier: &'a TelegramNotifier,
}

impl<'a> TransactionProcessor<'a> {
pub fn new(config: Config, ws: &'a SolanaWebsocket) -> Self {
Self { config, ws }
pub fn new(config: Config, ws: &'a SolanaWebsocket, notifier: &'a TelegramNotifier) -> Self {
Self {
config,
ws,
notifier,
}
}

pub async fn process_transaction(
Expand Down Expand Up @@ -86,6 +93,38 @@ impl<'a> TransactionProcessor<'a> {
match trx_details {
Ok(trx_details) => {
print!("πŸ”Ž Transaction details: {:?}", trx_details);
let rug_checker = RugChecker::new(&self.config);
let is_valid_rug_check =
rug_checker.isvalid_rug_check(&trx_details.token_mint).await;

match is_valid_rug_check {
Ok(is_valid) => {
// let trx_details = trx_details.clone();
if is_valid {
println!("πŸš€ Liquidity Pool is valid.");
_ = self
.notifier
.send_message(&format!(
"πŸš€ Liquidity Pool is valid. \nTokenMint: {}\n ViewToken: https://gmgn.ai/sol/token/{}",
&trx_details.token_mint, &trx_details.token_mint,
))
.await;
} else {
println!("🚨 Liquidity Pool is not valid rug check.");
_ = self
.notifier
.send_message(&format!(
"🚨 Liquidity Pool is not valid rug check. \nTokenMint: {}\n ViewToken: https://gmgn.ai/sol/token/{}",
&trx_details.token_mint,
&trx_details.token_mint,
))
.await;
}
}
Err(e) => {
println!("πŸ”Ž Error checking rug pull: {}", e);
}
}
}
Err(e) => {
println!("πŸ”Ž Error fetching transaction details: {}", e);
Expand Down

0 comments on commit 4c0ef26

Please sign in to comment.