pump-dump 0.1.0

A CLI tool for inspecting Pump.fun pools, events, and transactions on Solana
use clap::{Parser, Subcommand};
use color_eyre::{Result, eyre::eyre};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_client::rpc_config::RpcTransactionConfig;
use solana_commitment_config::CommitmentConfig;
use solana_sdk::pubkey::Pubkey;
use solana_transaction_status::UiTransactionEncoding;
use solana_transaction_status::option_serializer::OptionSerializer;
use std::str::FromStr;

mod pump_accounts;
mod pump_events;
mod utils;

use crate::pump_accounts::decode_account;
use crate::pump_events::decode_event;

const BANNER: &str = r#"

            🚀 Pump.fun Pool Inspector & Event Decoder 🚀

 ██████╗ ██╗   ██╗███╗   ███╗██████╗       ██████╗ ██╗   ██╗███╗   ███╗██████╗ 
 ██╔══██╗██║   ██║████╗ ████║██╔══██╗      ██╔══██╗██║   ██║████╗ ████║██╔══██╗
 ██████╔╝██║   ██║██╔████╔██║██████╔╝█████╗██║  ██║██║   ██║██╔████╔██║██████╔╝
 ██╔═══╝ ██║   ██║██║╚██╔╝██║██╔═══╝ ╚════╝██║  ██║██║   ██║██║╚██╔╝██║██╔═══╝ 
 ██║     ╚██████╔╝██║ ╚═╝ ██║██║           ██████╔╝╚██████╔╝██║ ╚═╝ ██║██║     
 ╚═╝      ╚═════╝ ╚═╝     ╚═╝╚═╝           ╚═════╝  ╚═════╝ ╚═╝     ╚═╝╚═╝    "#;

#[derive(Parser)]
#[command(author, version, about = "Pump.fun pool inspector and event decoder", long_about = None)]
#[command(before_help = BANNER)]
struct Cli {
    /// RPC URL for Solana network
    #[arg(
        short = 'u',
        long,
        default_value = "https://api.mainnet-beta.solana.com"
    )]
    rpc_url: String,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Decode and display a pump.fun event from base64 data
    Event {
        /// Base64 encoded event data
        data: String,
    },
    /// Fetch and decode account data from Solana address
    Account {
        /// Solana account address (base58)
        address: String,
    },
    /// Fetch transaction and decode pump.fun events from logs
    Tx {
        /// Transaction signature/hash
        signature: String,
    },
}

#[tokio::main]
async fn main() -> Result<()> {
    color_eyre::install()?;

    let cli = Cli::parse();

    let rpc = RpcClient::new_with_commitment(cli.rpc_url.clone(), CommitmentConfig::confirmed());

    match cli.command {
        Commands::Event { data } => {
            println!("🔍 Decoding pump.fun event...\n");

            let event = decode_event(&data).map_err(|e| eyre!("Failed to decode event: {}", e))?;

            println!("{}", event);
        }

        Commands::Account { address } => {
            println!("📡 Fetching account data from {}\n", cli.rpc_url);

            let pubkey = Pubkey::from_str(&address)
                .map_err(|e| eyre!("Invalid account address '{}': {}", address, e))?;

            let account_data = rpc
                .get_account_data(&pubkey)
                .await
                .map_err(|e| eyre!("Failed to fetch account data: {}", e))?;

            let account = decode_account(&account_data)
                .map_err(|e| eyre!("Failed to decode account data: {}", e))?;

            println!("{}", account);
        }

        Commands::Tx { signature } => {
            println!(
                "🔗 Fetching transaction {} from {}\n",
                signature, cli.rpc_url
            );

            let sig = signature
                .parse()
                .map_err(|e| eyre!("Invalid transaction signature '{}': {}", signature, e))?;

            let transaction = rpc
                .get_transaction_with_config(
                    &sig,
                    RpcTransactionConfig {
                        encoding: Some(UiTransactionEncoding::Json),
                        commitment: Some(CommitmentConfig::confirmed()),
                        max_supported_transaction_version: Some(0),
                    },
                )
                .await
                .map_err(|e| eyre!("Failed to fetch transaction: {}", e))?;

            // Extract and process logs
            if let Some(meta) = transaction.transaction.meta {
                if let OptionSerializer::Some(logs) = meta.log_messages {
                    let mut found_events = false;

                    for log in &logs {
                        // Look for "Program data: " prefix in logs
                        if let Some(data_start) = log.find("Program data: ") {
                            let base64_data = &log[data_start + 14..];

                            // Try to decode as pump.fun event
                            if let Ok(event) = decode_event(base64_data) {
                                if !found_events {
                                    println!("📊 Found pump.fun events:");
                                    found_events = true;
                                }
                                println!("{}", event);
                            }
                        }
                    }

                    if !found_events {
                        println!("â„šī¸  No pump.fun events found in transaction logs");

                        // Show raw logs for debugging
                        println!("\n📜 Transaction logs:");
                        for (i, log) in logs.iter().enumerate() {
                            println!("{:2}: {}", i + 1, log);
                        }
                    }
                } else {
                    println!("âš ī¸  No logs found in transaction");
                }
            } else {
                println!("âš ī¸  No transaction metadata available");
            }
        }
    }

    Ok(())
}