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::, 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 want | Read | Why |
|---|---|---|
| New tokens | CreateEvent | Name, symbol, metadata URI, mint, bonding curve and creator in one place. |
| Buys and sells | TradeEvent | The SOL and token amounts that executed, is_buy, the trader, fees and the curve's reserves after the trade. |
| Curves that filled | CompleteEvent | The mint and bonding curve; trading moves to an AMM after this. |
The trade instructions carry limits, not results: Buy has amount and max_, Sell has amount and min_, and newer variants such as BuyExactSolIn take spendable_ and min_. 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#
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_loggerWrite 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.
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#
cargo runThe 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.
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_amountis in lamports: divide by 10⁹ for SOL.token_is in the token's base units. Pump.fun mints have 6 decimals, so divide by 10⁶.amount feeandcreator_feeare in lamports too, with their rates infee_andbasis_ points creator_.fee_ basis_ points - The event also has
quote_mintandquote_fields. Checkamount quote_mintbefore you treatsol_amountas 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_ 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., or set max_ in ConnectionConfig for newest-first order.