1. Guides

Track Pump.fun launches and trades

Decode Pump.fun token launches, bonding-curve buys and sells, and completed curves from the program's events with the Pump.fun decoder.

On this page

Pump.fun records every token launch, every trade on a bonding curve and every curve that fills in an Anchor event, which it emits through a self-CPI. The Pump.fun decoder decodes those events as PumpfunInstruction::CpiEvent, so your processor reads what actually executed. This guide prints launches, trades and completed curves as they happen on mainnet.

You need: Rust 1.96.1 or newer. The example uses Solana's public RPC, so no account is needed.

Read events, not instruction arguments#

You wantReadWhy
New tokensCreateEventName, symbol, metadata URI, mint, bonding curve and creator in one place.
Buys and sellsTradeEventThe SOL and token amounts that executed, is_buy, the trader, fees and the curve's reserves after the trade.
Curves that filledCompleteEventThe mint and bonding curve; trading moves to an AMM after this.

The trade instructions carry limits, not results: Buy has amount and max_sol_cost, Sell has amount and min_sol_output, and newer variants such as BuyExactSolIn take spendable_sol_in and min_tokens_out. Each emits the same TradeEvent, whose ix_name field names the instruction that ran, so one match arm covers all of them.

Create a project#

Terminal
cargo new pumpfun-tracker
cd pumpfun-tracker
cargo add carbon-core@2.0.0 carbon-rpc-transaction-crawler-datasource@2.0.0 carbon-pumpfun-decoder@2.0.0 carbon-log-metrics@2.0.0
cargo add tokio --features full
cargo add env_logger

Write the tracker#

The RPC transaction crawler fetches every transaction that calls Pump.fun, newest first, then keeps polling for new ones. The processor ignores every decoded instruction except the events, and prints the three it cares about.

src/main.rs
use {
    carbon_core::{
        error::CarbonResult, instruction::InstructionProcessorInputType, pipeline::Pipeline,
        processor::Processor,
    },
    carbon_log_metrics::LogMetrics,
    carbon_pumpfun_decoder::{
        PROGRAM_ID, PumpfunDecoder,
        instructions::{CpiEvent, PumpfunInstruction},
    },
    carbon_rpc_transaction_crawler_datasource::{ConnectionConfig, Filters, RpcTransactionCrawler},
    std::sync::Arc,
};

#[tokio::main]
async fn main() -> CarbonResult<()> {
    env_logger::init();

    let rpc_url = std::env::var("RPC_URL")
        .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());

    // Every transaction that calls Pump.fun, newest first, then new ones as they land.
    let datasource = RpcTransactionCrawler::new(
        rpc_url,
        PROGRAM_ID,
        ConnectionConfig::default(),
        Filters::new(None, None, None),
        None,
    );

    Pipeline::builder()
        .datasource(datasource)
        .metrics(Arc::new(LogMetrics::new()))
        .instruction(PumpfunDecoder, LaunchTracker)
        .build()?
        .run()
        .await
}

/// SOL has 9 decimals; Pump.fun tokens have 6.
const LAMPORTS_PER_SOL: f64 = 1e9;
const TOKEN_UNITS: f64 = 1e6;

struct LaunchTracker;

impl Processor<InstructionProcessorInputType<'_, PumpfunInstruction>> for LaunchTracker {
    async fn process(
        &mut self,
        input: &InstructionProcessorInputType<'_, PumpfunInstruction>,
    ) -> CarbonResult<()> {
        // Pump.fun reports what happened in events, emitted as a self-CPI after each action.
        let PumpfunInstruction::CpiEvent { data, .. } = input.decoded_instruction else {
            return Ok(());
        };
        match data {
            CpiEvent::CreateEvent(e) => {
                println!(
                    "launch   {} ({}) mint={} creator={}",
                    e.name, e.symbol, e.mint, e.creator
                )
            }
            CpiEvent::TradeEvent(e) => println!(
                "{} {:>12.4} SOL {:>16.2} tokens mint={}",
                if e.is_buy { "buy     " } else { "sell    " },
                e.sol_amount as f64 / LAMPORTS_PER_SOL,
                e.token_amount as f64 / TOKEN_UNITS,
                e.mint,
            ),
            CpiEvent::CompleteEvent(e) => println!("complete mint={} bonding curve filled", e.mint),
            _ => {}
        }
        Ok(())
    }
}

Run it#

Terminal
cargo run

The first build takes one to three minutes. Then trades start printing, newest first. On the public endpoint the first line can take a minute or more, because it rate-limits getTransaction.

Output
buy            0.0059 SOL        201272.73 tokens mint=7TUpdfVds47vFqwiDubJjAq51y9nMhU5mmCwEmkUpump
sell           0.2325 SOL       1385083.79 tokens mint=EaCgYCrKyGDfquYhUapfw33Xh65U4ZQshPgQnKt1pump
sell           0.0063 SOL      26635028.55 tokens mint=Fqptwy5FV7y89yawECyK9xiCNKmcLHWhLGAoJxL4pump
sell           0.0259 SOL      39136997.32 tokens mint=3sTmKFwVV6PRB5GD8M88VapRa524Mozu3LAwdB4Ypump
sell           0.0028 SOL         28738.62 tokens mint=GdMjsTtrn3AKFHD8x3u15Hg6QLCWfXbdvPsofd83pump
sell           0.0067 SOL      12869465.83 tokens mint=3DvhrxjakLL5s8aJJcHUuCtaUMVrCHAj68KBYiLfpump
…

A launch line prints when a token is created, and a complete line when its bonding curve fills. Both are much rarer than trades. Press Ctrl+C to stop.

Amounts and decimals#

  • sol_amount is in lamports: divide by 10⁹ for SOL.
  • token_amount is in the token's base units. Pump.fun mints have 6 decimals, so divide by 10⁶.
  • fee and creator_fee are in lamports too, with their rates in fee_basis_points and creator_fee_basis_points.
  • The event also has quote_mint and quote_amount fields. Check quote_mint before you treat sol_amount as the price if you index curves quoted in another token.

Every field is listed on the decoder page under TradeEvent.

After a curve completes#

Once CompleteEvent fires, the token leaves the bonding curve and trades on an AMM. Pump.fun moves completed tokens to PumpSwap, and CompletePumpAmmMigrationEvent records the move. To keep following a token after it graduates, add the PumpSwap decoder and read its BuyEvent and SellEvent: index swaps across DEXs shows how. Other launchpads work the same way; see launchpad decoders.

Stream launches in real time#

The crawler polls every few seconds and the public endpoint is slow. For launches within a second of landing, swap the datasource for a Geyser stream that follows Pump.fun's program ID: stream with Yellowstone gRPC or stream with Helius LaserStream. The processor doesn't change.

Common problems#

Nothing prints for a minute or more#

The public endpoint rate-limits getTransaction per IP, and Pump.fun has more transactions than it lets through. Run with RUST_LOG=info to see the crawler's counters and 429 retries, and set RPC_URL to your own endpoint for anything beyond a test.

Trades print out of order#

The crawler fetches transactions concurrently, so they complete in any order. Sort by input.metadata.transaction_metadata.slot, or set max_concurrent_requests: 1 in ConnectionConfig for newest-first order.

Next steps#

Your endpoints

Code samples use these instead of the placeholders, so you can copy and run them as they are. They’re stored only in this browser and never sent anywhere.