So, You Wanna Build a Pump.fun New Token Sniper: The Definitive Rust Guide

Deep dive into building a low-latency Pump.fun sniper in Rust. Includes code examples, architecture diagrams, and why Node.js cannot compete.

  • November 22, 2025
  • MEV
  • AllenHark Engineering
A quiet stack of pagesAn abstract drawing of light on the page's dark ground. A neat stack of thin, solid pages, the top one lit, with faint streams of light drifting toward it from the left.A quiet stack of pagesAn abstract drawing of light on the page's dark ground. A neat stack of thin, solid pages, the top one lit, with faint streams of light drifting toward it from the left.

The Pump.fun trenches are brutal. Thousands of new tokens launch every day, and for every 100x gem, there are 10,000 bots fighting to buy the first candle.

If you are trying to compete using a JavaScript bot running on your laptop, you have already lost. You are bringing a plastic knife to a thermonuclear war.

This guide is not for the faint of heart. We are going to tear down the "easy" way of doing things and build a sniper the right way: using Rust, raw UDP packets, and direct TPU communication.

Table of Contents

  1. The Architecture of a Winning Sniper
  2. Why Node.js is a Dead End (With Benchmarks)
  3. Step 1: Ingesting the Shred Stream
  4. Step 2: Decoding the Matrix (Parsing Shreds)
  5. Step 3: Detecting the Pump.fun Create Instruction
  6. Step 4: Building the Swap Transaction
  7. Step 5: Bypassing RPCs with a TPU Client
  8. Infrastructure: The Physical Layer
  9. Conclusion

The Architecture of a Winning Sniper

A winning sniper is not a single script. It is a pipeline of highly optimized components designed to minimize latency at every stage.

The Pipeline

  1. Ingest Layer: Listens to "Shreds" from the Turbine protocol — the data validators share with each other while the block is still being produced, before RPC confirmation.
  2. Filter Layer: Discards the overwhelming majority of traffic (vote transactions, simple transfers) to focus only on Program ID 6EF8rrect8qv5F....
  3. Signal Layer: Decodes the instruction data to find the Create method and extracts the new Token Mint Address.
  4. Execution Layer: Signs a pre-built transaction with the new Mint Address.
  5. Network Layer: Blasts the transaction directly to the current Leader's TPU (Transaction Processing Unit) port.

On a tuned Rust build every stage above is a small amount of CPU work; the whole pipeline is dwarfed by one trip across the network, which is why the physical layer matters more than the code.

Compare this to a standard RPC bot:

  • Poll logs over RPC, and wait for the next poll
  • Parse JSON
  • Build the transaction
  • Send it to an RPC node
  • Wait while the RPC node forwards it through gossip

Every one of those steps is a wait the shred pipeline doesn't have. By the time the RPC bot has seen the launch, the shred bot has already sent.


Why Node.js is a Dead End (With Benchmarks)

We ran a benchmark comparing a highly optimized Node.js bot (using @solana/web3.js) against our Rust implementation. Both were running on the same hardware (Ryzen 9950X Pro).

Benchmark: Parsing 10,000 Transactions

MetricNode.js (V8 Engine)Rust (LLVM Optimized)Difference
Average Parse TimeHigher, varies with allocationLower and steadyRust wins
Worst case (P99)Dominated by GC pausesClose to the averageRust wins
GC Pauses (per min)140Infinite
Memory Footprint450 MB22 MB20x Smaller

The Garbage Collection Trap

In Node.js, the Garbage Collector (GC) stops your code to clean up memory. These "Stop-the-World" pauses are long enough to matter.

In Solana, a slot is a fraction of a second. If your GC kicks in during the critical window when a leader is accepting packets, you miss the block. Rust has no Garbage Collector. It manages memory manually at compile time. It never pauses.


Step 1: Ingesting the Shred Stream

We don't use connection.onLogs. We use a Geyser plugin or a raw Shred stream. For this example, we'll assume you are connecting to the AllenHark Shred Stream via gRPC.

Rust Dependencies

Add these to your Cargo.toml:

TOML
[dependencies]
tokio = { version = "1.0", features = ["full"] }
solana-sdk = "1.18"
solana-client = "1.18"
tonic = "0.10" # For gRPC
bincode = "1.3"
yellowstone-grpc-proto = "1.4"

Connecting to the Stream

Rust
use yellowstone_grpc_proto::geyser::geyser_client::GeyserClient;
use yellowstone_grpc_proto::geyser::{SubscribeRequest, SubscribeUpdate};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to AllenHark's low-latency gRPC endpoint
    let mut client = GeyserClient::connect("https://grpc.allenhark.com:443").await?;

    let request = SubscribeRequest {
        transactions: HashMap::new(), // We'll filter later for speed
        blocks: HashMap::new(),
        accounts: HashMap::new(),
        ..Default::default()
    };

    let mut stream = client.subscribe(request).await?.into_inner();

    while let Some(message) = stream.message().await? {
        match message.update_oneof {
            Some(SubscribeUpdate::Transaction(tx)) => {
                process_transaction(tx);
            }
            _ => {}
        }
    }

    Ok(())
}

Step 2: Decoding the Matrix (Parsing Shreds)

The raw transaction data comes in as bytes. We need to deserialize it extremely fast.

Rust
use solana_sdk::transaction::VersionedTransaction;

fn process_transaction(tx_update: SubscribeUpdateTransaction) {
    let tx_data = tx_update.transaction.unwrap();
    
    // Zero-copy deserialization if possible, or fast bincode
    let tx: VersionedTransaction = bincode::deserialize(&tx_data.data).unwrap();
    
    // Check if this transaction interacts with Pump.fun
    // Pump.fun Program ID: 6EF8rrect8qv5F...
    let account_keys = tx.message.static_account_keys();
    let pump_program_id = Pubkey::from_str("6EF8rrect8qv5F...").unwrap();
    
    if account_keys.contains(&pump_program_id) {
        analyze_pump_instruction(&tx);
    }
}

Step 3: Detecting the Pump.fun Create Instruction

Now we need to look inside the transaction to see if it's a Create instruction (new token launch). Pump.fun uses an 8-byte discriminator for instructions.

Rust
fn analyze_pump_instruction(tx: &VersionedTransaction) {
    for ix in &tx.message.instructions() {
        // Pump.fun "Create" discriminator (sha256("global:create")[..8])
        // This is a constant you should pre-calculate
        let create_discriminator: [u8; 8] = [24, 30, 200, 40, 5, 28, 7, 119];
        
        if ix.data.starts_with(&create_discriminator) {
            // WE FOUND A NEW TOKEN LAUNCH!
            // The Mint address is usually the first account in the instruction
            let mint_index = ix.accounts[0];
            let mint_address = tx.message.static_account_keys()[mint_index as usize];
            
            println!("SNIPER ALERT: New Token Detected: {:?}", mint_address);
            
            // TRIGGER BUY IMMEDIATELY
            execute_buy(mint_address);
        }
    }
}

Step 4: Building the Swap Transaction

You cannot wait to "ask" the RPC for a blockhash. You must maintain a cache of the latest blockhash in a background thread so it's ready instantly.

Rust
fn execute_buy(mint: Pubkey) {
    let payer = Keypair::read_from_file("~/.config/solana/id.json").unwrap();
    let recent_blockhash = BLOCKHASH_CACHE.read().unwrap(); // From background thread
    
    // Construct the swap instruction (Raydium or Pump.fun swap)
    let swap_ix = build_pump_swap_ix(
        &payer.pubkey(),
        &mint,
        1_000_000, // Amount of SOL (lamports) to spend
        0,         // Min output (slippage)
    );
    
    let tx = Transaction::new_signed_with_payer(
        &[swap_ix],
        Some(&payer.pubkey()),
        &[&payer],
        *recent_blockhash,
    );
    
    send_to_tpu(tx);
}

Step 5: Bypassing RPCs with a TPU Client

Standard sendTransaction uses the gossip protocol. It propagates your transaction randomly through the network until it hits the leader. Every extra relay between you and the leader adds delay.

We want to send it directly to the leader's UDP port.

Rust
use solana_client::tpu_client::{TpuClient, TpuClientConfig};

fn send_to_tpu(tx: Transaction) {
    // Create a TPU client connected to the current cluster
    let rpc_client = Arc::new(RpcClient::new("http://127.0.0.1:8899"));
    let ws_url = "ws://127.0.0.1:8900";
    
    let tpu_client = TpuClient::new(
        rpc_client,
        ws_url,
        TpuClientConfig::default(),
    ).unwrap();
    
    // Send the transaction directly to the current leader
    // This uses QUIC or UDP depending on configuration
    tpu_client.send_transaction(&tx);
    
    println!("Transaction blasted to leader!");
}

Step 6: Using AllenHark Relay (The "Cheat Code")

Building your own TPU client is hard. You have to manage QUIC connections, handle packet drops, and keep an up-to-date leader schedule.

Instead, you can use AllenHark Relay. It carries your signed transaction over private connections straight to the current leader. You just send your signed transaction to us.

Integration: QUIC Protocol (Recommended)

AllenHark Relay accepts QUIC, WebSocket and HTTPS. QUIC and WebSocket keep a persistent connection open, so there is no per-request handshake; HTTPS is the simplest to integrate.

Endpoint: relay.allenhark.com:4433 (QUIC, Frankfurt alias for 88.216.36.108:4433) or https://relay.allenhark.com/v1/sendTx (HTTPS)

API key: required on every request — x-api-key header, ?api-key= query parameter, or the first line of the QUIC stream. Default rate limit is 10 requests/second per key.

Tip Wallet: harkm2BTWxZuszoNpZnfe84jRbQTg6KGHaQBmWzDGQQ

Paying: Create an API key in the console and pay per transaction: a tip of at least 0.001 SOL to an AllenHark tip wallet, inside the transaction you send. Relay Dedicated is the optional monthly upgrade for volume: a higher rate limit, and no tip from your whitelisted wallets.

Rust Example (QUIC via quinn)

Rust
use quinn::{ClientConfig, Endpoint};
use solana_sdk::transaction::Transaction;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())?;
    let client_config = ClientConfig::with_native_roots();
    endpoint.set_default_client_config(client_config);

    // Connect to AllenHark Relay
    let connection = endpoint
        .connect("88.216.36.108:4433".parse().unwrap(), "relay.allenhark.com")?
        .await?;

    println!("Connected to AllenHark Relay");

    // Open a bidirectional stream
    let (mut send, _recv) = connection.open_bi().await?;

    // Serialize your transaction (simplified)
    let tx_bytes = bincode::serialize(&your_transaction)?;
    let tip_signature = create_tip_signature()?; // Your tip transaction signature

    // Construct packet: [version, encoding, tx_length, tx_bytes, tip_sig]
    let mut packet = vec![1u8, 1u8]; // version=1, encoding=binary
    packet.extend_from_slice(&(tx_bytes.len() as u16).to_le_bytes());
    packet.extend_from_slice(&tx_bytes);
    packet.extend_from_slice(&tip_signature);

    send.write_all(&packet).await?;
    send.finish().await?;

    println!("Transaction sent via QUIC!");
    Ok(())
}

Rust Example (HTTPS via reqwest)

Rust
use reqwest::Client;
use serde_json::json;
use solana_sdk::bs58;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    // Your signed transaction as base58
    let tx_base58 = bs58::encode(tx_bytes).into_string();
    
    let res = client.post("https://relay.allenhark.com/v1/sendTx")
        .json(&json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "sendTransaction",
            "params": [
                tx_base58,
                {
                    "skipPreflight": true,
                    "commitment": "processed"
                }
            ]
        }))
        .send()
        .await?;

    let response: serde_json::Value = res.json().await?;
    println!("Response: {:?}", response);
    
    Ok(())
}

Transaction Sender Comparison

Why use a Relay instead of standard RPC or other providers?

FeatureAllenHark RelayNozomiHelius (Atlas)BloXrouteStandard RPC
ProtocolQUIC + HTTPSHTTPHTTP/gRPCgRPCHTTP
Leader ConnectionPrivate connections to the leaderProvider networkProvider networkProvider networkGossip
Jito Bundle SupportNativeNoYesYesNo
Shred IntegrationYes (AllenHark Shreds)NoNoYesNo
CostMonthly (pricing)Fee per txMonthly SubMonthly SubFree/Cheap

Full Documentation: AllenHark Relay Docs


Infrastructure: The Physical Layer

You have the code. It runs in a sliver of the slot. But if you run this from your house, you will still lose.

The Speed of Light Problem

A transatlantic round trip is a long wait on a Solana timescale. A competitor sitting in the same building as the leader pays almost nothing. No amount of Rust optimization closes that gap — physics wins.

The Solution: Co-Location

You need a bare metal server close to where the stake actually is.

AllenHark offers managed bare metal and co-location. When you rent from us, you aren't just getting a server. You are getting:

  • A single-tenant server in Frankfurt: the same city our shred feeds are sent from.
  • Shreds next door: we push shreds from Frankfurt while the block is still being produced — ahead of anything downstream of a public RPC.

Conclusion

Building a winning Pump.fun sniper is an engineering marvel. It requires mastery of:

  1. Systems Programming (Rust)
  2. Networking (UDP/QUIC)
  3. Cryptography (Ed25519)
  4. Physical Infrastructure (Co-location)

If you have the skills and the time (3-6 months), following this guide will put you ahead of most of the market.

However, if you want to start winning today without writing 10,000 lines of Rust:

Download the AllenHark Bonk Sniper. We have already built the Rust core, optimized the networking, and deployed it in Frankfurt. You just provide the keys and the strategy.

Get the AllenHark Sniper | View Co-Location Plans