The PumpFun Sniper represents a significant advancement in Solana trading bot architecture. This technical deep-dive explores the engineering decisions, performance optimizations, and infrastructure integrations that let it act on a launch while the block is still being produced, before RPC confirmation.
Table of Contents
- Architecture Overview
- ShredStream Integration
- Detection Pipeline
- Transaction Execution
- AllenHark Relay Integration
- Performance Benchmarks
- Configuration Deep-Dive
- Advanced Features
Architecture Overview
The PumpFun Sniper is built on a lock-free, event-driven architecture designed to minimize latency at every stage of the detection-to-execution pipeline.
Core Components
graph LR
A[ShredStream] -->|UDP Packets| B[Shred Processor]
B -->|Parsed Transactions| C[Filter Layer]
C -->|PumpFun Creates| D[Signal Generator]
D -->|Token Mint| E[Transaction Builder]
E -->|Signed TX| F[AllenHark Relay]
F -->|QUIC/UDP| G[Validator TPU]
Latency Budget:
- ShredStream ingestion: 20-50 μs
- Transaction parsing: 10-15 μs
- Filter evaluation: 5-10 μs
- Signal generation: 30-50 μs
- Transaction building: 80-100 μs
- Relay submission: 100-200 μs
Total internal latency: < 500 μs (0.5ms)
Why Rust?
The bot is implemented in Rust for several critical reasons:
- Zero-cost abstractions: No runtime overhead for high-level constructs
- No garbage collection: Eliminates unpredictable GC pauses (10-50ms in Node.js)
- Memory safety: Prevents crashes without runtime checks
- Lock-free concurrency: Uses atomic operations and channels for thread communication
- LLVM optimization: Produces highly optimized machine code
Benchmark: Transaction Parsing (10,000 iterations)
| Language | Average | P99 | Memory |
|---|---|---|---|
| Rust | 12 μs | 18 μs | 22 MB |
| Node.js | 450 μs | 2,100 μs | 450 MB |
| Python | 1,200 μs | 3,500 μs | 680 MB |
ShredStream Integration
ShredStream provides access to raw Turbine protocol data - the UDP packets validators exchange before block finalization. This enables 0-slot detection: seeing transactions in the same slot they're created.
How ShredStream Works
Solana validators use the Turbine protocol to propagate blocks as "shreds" (fragments). By tapping into this stream, we can:
- Detect transactions before RPC confirmation (200-400ms advantage)
- Process transactions in parallel with validators
- Avoid RPC polling overhead (eliminates 500ms+ latency)
Integration Code
The bot connects to a local ShredStream proxy via gRPC:
use tonic::transport::Channel;
use shredstream_proto::shred_stream_client::ShredStreamClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to local ShredStream proxy
let channel = Channel::from_static("http://127.0.0.1:9999")
.connect()
.await?;
let mut client = ShredStreamClient::new(channel);
// Subscribe to transaction stream
let request = SubscribeRequest {
filter_accounts: vec![PUMPFUN_PROGRAM_ID.to_string()],
..Default::default()
};
let mut stream = client.subscribe(request).await?.into_inner();
// Process shreds in real-time
while let Some(shred) = stream.message().await? {
process_shred(shred).await;
}
Ok(())
}
Performance Characteristics
- Latency: 20-50 μs from validator broadcast to bot receipt
- Throughput: 50,000+ shreds/second
- Reliability: Direct UDP multicast, no intermediary hops
Detection Pipeline
The detection pipeline uses a multi-stage filter to minimize CPU overhead while maintaining 100% detection accuracy.
Stage 1: Program ID Filter
const PUMPFUN_PROGRAM: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
fn quick_filter(tx: &VersionedTransaction) -> bool {
// Fast path: check if PumpFun program is in account keys
tx.message.static_account_keys().contains(&PUMPFUN_PROGRAM)
}
This eliminates 99.9% of transactions (votes, transfers, etc.) in < 5 μs.
Stage 2: Instruction Discriminator
const CREATE_DISCRIMINATOR: [u8; 8] = [24, 30, 200, 40, 5, 28, 7, 119];
fn is_create_instruction(ix: &CompiledInstruction) -> bool {
ix.data.len() >= 8 && ix.data[..8] == CREATE_DISCRIMINATOR
}
The discriminator is computed as sha256("global:create")[..8] and identifies token creation instructions.
Stage 3: Spam Filter
The spam filter evaluates multiple heuristics to avoid rug pulls:
pub struct SpamFilter {
min_buy_sol: f64,
max_buy_sol: f64,
min_tokens: u64,
min_accounts: usize,
}
impl SpamFilter {
fn evaluate(&self, tx: &ParsedCreate) -> bool {
// Check dev buy amount
if tx.dev_buy_sol < self.min_buy_sol || tx.dev_buy_sol > self.max_buy_sol {
return false;
}
// Check token amount
if tx.token_amount < self.min_tokens {
return false;
}
// Check account count (malformed txs have fewer accounts)
if tx.accounts.len() < self.min_accounts {
return false;
}
true
}
}
Stage 4: Blacklist Check
The blacklist is stored in a lock-free concurrent hash set for O(1) lookups:
use dashmap::DashSet;
static BLACKLIST: Lazy<DashSet<Pubkey>> = Lazy::new(DashSet::new);
fn is_blacklisted(creator: &Pubkey) -> bool {
BLACKLIST.contains(creator)
}
The blacklist is reloaded every 10 seconds from blacklist.jsonl without blocking the hot path.
Transaction Execution
Once a token passes all filters, the bot must build and submit a transaction in < 100 μs.
Pre-computed Transaction Template
To minimize latency, we pre-build a transaction template with placeholder accounts:
struct TransactionTemplate {
instructions: Vec<Instruction>,
recent_blockhash: Hash,
payer: Pubkey,
}
impl TransactionTemplate {
fn instantiate(&self, mint: Pubkey) -> Transaction {
// Clone template and replace mint address
let mut tx = self.clone();
tx.instructions[0].accounts[MINT_INDEX] = AccountMeta::new(mint, false);
// Sign transaction
tx.sign(&[&PAYER_KEYPAIR], self.recent_blockhash);
tx
}
}
Blockhash Management
A background thread maintains a cache of the latest blockhash:
static BLOCKHASH_CACHE: Lazy<RwLock<Hash>> = Lazy::new(|| RwLock::new(Hash::default()));
async fn blockhash_updater(rpc_client: Arc<RpcClient>) {
loop {
if let Ok(hash) = rpc_client.get_latest_blockhash().await {
*BLOCKHASH_CACHE.write().unwrap() = hash;
}
tokio::time::sleep(Duration::from_millis(400)).await; // Update every slot
}
}
This ensures we always have a fresh blockhash without blocking the critical path.
AllenHark Relay Integration
The AllenHark Relay provides optimized transaction submission straight to the leader's TPU.
Why Use a Relay?
Standard RPC submission has several bottlenecks:
- HTTP overhead: 20-50ms for connection setup and headers
- RPC processing: 10-30ms for validation and forwarding
- Gossip propagation: 100-300ms to reach the leader
- No priority routing: Transactions may take multiple hops
AllenHark Relay eliminates these bottlenecks:
- QUIC protocol: 0-RTT connection resumption — no handshake when the transaction is ready
- Direct TPU connection: Physical cables to validator racks
- Priority routing: Jito bundle integration
- Persistent connections: No connection setup overhead
QUIC Integration
use quinn::{ClientConfig, Endpoint};
async fn send_via_relay(tx: &Transaction) -> Result<(), Box<dyn std::error::Error>> {
// Create QUIC endpoint
let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?;
endpoint.set_default_client_config(ClientConfig::with_native_roots());
// Connect to AllenHark Relay
let connection = endpoint
.connect("88.216.36.108:4433".parse()?, "relay.allenhark.com")?
.await?;
// Open bidirectional stream
let (mut send, mut recv) = connection.open_bi().await?;
// Serialize transaction
let tx_bytes = bincode::serialize(tx)?;
// Send transaction with tip
let packet = build_relay_packet(&tx_bytes, TIP_LAMPORTS)?;
send.write_all(&packet).await?;
send.finish().await?;
// Read response
let response = recv.read_to_end(1024).await?;
Ok(())
}
Performance Comparison
| Method | Dispatch cost | Cost |
|---|---|---|
| AllenHark Relay (QUIC) | No per-request handshake — the connection is already open | 0.001 SOL tip, in-transaction |
| AllenHark Relay (HTTPS) | Single-digit ms near a region, tens of ms cross-region | 0.001 SOL tip, in-transaction |
| Standard RPC | Validation, then gossip propagation | Free |
| Jito (Public) | Auction round before submission | Variable |
What to Measure
We don't publish latency or win-rate figures for this bot, because the numbers that matter are yours: they depend on where your box sits relative to the relay and the stake, how aggressive your filters are, and what the rest of the field is doing on the day. Anyone quoting a P99 without telling you the vantage point is selling you something.
Instrument these yourself and you'll know whether your setup is actually competitive:
- Detection: time from the entry arriving on the shred feed to your filter firing.
- End-to-end: detection → signed transaction handed to the relay.
- Landing: what fraction of attempts land in the intended slot, not just eventually.
- Filter yield: how many launches survive your filters, and how many of those you'd actually have wanted.
Configuration Deep-Dive
Trading Parameters
purchase_amount_sol = 0.001
slippage_basis_points = 100
live = true
purchase_amount_sol: Position size per snipe. Start with 0.001 SOL for testing.slippage_basis_points: 100 = 1%. Increase for volatile tokens.live: Set tofalsefor dry-run mode (no real transactions).
Dev Buy Filter
min_dev_buy_sol = 0.5
max_dev_buy_sol = 4.5
Filters tokens based on the developer's initial buy:
- Too low (< 0.5 SOL): Likely spam or low-effort launch
- Too high (> 4.5 SOL): Risk of immediate dump
User Volume Accumulator
The user_volume_accumulator is a PDA required by PumpFun to track trading volume:
fn derive_accumulator(user: &Pubkey) -> Pubkey {
let pumpfun = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
let fee_program = pubkey!("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
Pubkey::find_program_address(
&[
b"user-volume-accumulator",
user.as_ref(),
pumpfun.as_ref(),
],
&fee_program,
).0
}
Advanced Features
Automatic Blacklisting
The bot tracks creator performance and automatically blacklists unprofitable wallets:
fn update_blacklist(creator: Pubkey, profit: f64) {
if profit < -0.001 { // Lost more than 0.001 SOL
BLACKLIST.insert(creator);
append_to_file("blacklist.jsonl", &creator);
}
}
Profit Tracking
After each sell, the bot calculates profit including rent recovery:
fn calculate_profit(initial_balance: u64, final_balance: u64, rent: u64) -> f64 {
let profit_lamports = (final_balance + rent) as i64 - initial_balance as i64;
profit_lamports as f64 / 1e9 // Convert to SOL
}
RPC Pool Rotation
The bot rotates through multiple RPC endpoints every 20ms for load balancing and redundancy:
struct RpcPool {
endpoints: Vec<String>,
current: AtomicUsize,
}
impl RpcPool {
fn next(&self) -> &str {
let idx = self.current.fetch_add(1, Ordering::Relaxed) % self.endpoints.len();
&self.endpoints[idx]
}
}
Conclusion
The PumpFun Sniper demonstrates that competitive Solana trading requires:
- Low-level infrastructure access (ShredStream)
- Optimized execution (Rust, lock-free architecture)
- Direct network connectivity (AllenHark Relay, QUIC)
- Intelligent filtering (spam detection, blacklisting)
By combining these elements, the bot detects a launch while the block is still being produced — before RPC confirmation — and goes for first-block execution, which an RPC-polling bot structurally cannot do.
Download the PumpFun Sniper: Latest Release
Full Documentation: PumpFun Sniper Docs
AllenHark Services: ShredStream | Relay | Co-Location