
Pumpfun Mechanics
- 196 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
pumpfun-mechanics is a Claude Code skill that computes PumpFun bonding-curve math, graduation, and instruction/event parsing for the Solana token launchpad.
About
pumpfun-mechanics is a Claude Code skill for the PumpFun Solana token launchpad. It documents the virtual constant-product bonding-curve math, computes buy/sell amounts and price impact, tracks the ~85 SOL graduation threshold, and parses PumpFun CreateEvent, TradeEvent, and CompleteEvent plus PumpSwap migration. A developer uses it to analyze new token launches and build strategies around graduation.
- Virtual constant-product bonding-curve math with buy/sell and buy-cost functions matching on-chain rounding
- Graduation mechanics at ~85 SOL and PumpSwap/Raydium migration targets
- Program IDs and event discriminators for parsing PumpFun and PumpSwap transactions
Pumpfun Mechanics by the numbers
- 196 all-time installs (skills.sh)
- Ranked #477 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
pumpfun-mechanics capabilities & compatibility
Free skill; needs a Solana RPC endpoint to fetch live on-chain data, demo mode uses default parameters.
- Capabilities
- bonding curve math · onchain event parsing · graduation tracking · price impact analysis
- Use cases
- data analysis
- Pricing
- Bring your own API key
What pumpfun-mechanics says it does
PumpFun is the dominant Solana token launchpad. Understanding its bonding curve math, graduation process, and instruction formats is essential
Graduation occurs when `realSolReserves` reaches **~85 SOL** (~$12K-14K depending on SOL price). Only ~1.4% of PumpFun tokens ever graduate.
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill pumpfun-mechanicsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 196 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Compute PumpFun bonding curve prices, track graduation, and parse on-chain PumpFun instructions and events on Solana.
Who is it for?
Analyzing PumpFun token launches and parsing on-chain PumpFun/PumpSwap activity.
Skip if: General technical analysis or non-Solana markets.
When should I use this skill?
You need PumpFun price math, graduation status, or to decode PumpFun trade/create/complete events.
What you get
Accurate PumpFun prices, fill percentages, graduation estimates, and parsed on-chain events.
- PumpFun price and price-impact calculations
- Graduation fill percentage
- Parsed PumpFun/PumpSwap events
By the numbers
- 30 SOL initial virtual reserves
- ~85 SOL graduation threshold
- ~1.4% of tokens graduate
Files
PumpFun Mechanics — Bonding Curves, Graduation & Instruction Parsing
PumpFun is the dominant Solana token launchpad. Understanding its bonding curve math, graduation process, and instruction formats is essential for analyzing new token launches, building trading strategies around graduation events, and parsing on-chain PumpFun activity.
Bonding Curve Math
PumpFun uses a virtual constant-product (CPMM) bonding curve:
k = virtualSolReserves × virtualTokenReservesInitial Parameters
| Parameter | Value |
|---|---|
| Initial Virtual SOL | 30 SOL (30,000,000,000 lamports) |
| Initial Virtual Tokens | ~1.073B tokens (1,073,000,000,000,000 raw, 6 decimals) |
| Token Total Supply | 1B tokens (1,000,000,000,000,000 raw) |
| Real Token Reserves | ~793M tokens (793,000,000,000,000 raw) |
| Real SOL Reserves | 0 (no real SOL at launch) |
| Fee | 1% (applied externally by the program) |
Virtual vs Real: Virtual reserves define the curve shape. Real reserves track actual withdrawable funds. The difference (1.073B - 793M = 280M virtual tokens) shapes the initial price but can never be withdrawn.
Spot Price
price_sol_per_token = virtual_sol_reserves / virtual_token_reserves
# In human-readable units:
price = (virtual_sol / 1e9) / (virtual_token / 1e6)
# At genesis: 30 / 1,073,000,000 ≈ 2.796e-8 SOL/token
# At graduation: ~4.1e-7 SOL/token (~14.7x from launch)Buy Tokens (SOL → Tokens)
def buy_tokens(v_sol: int, v_tok: int, real_tok: int, sol_in: int) -> int:
"""Calculate tokens received for a given SOL input.
Args:
v_sol: Virtual SOL reserves (lamports).
v_tok: Virtual token reserves (raw).
real_tok: Real token reserves (raw).
sol_in: SOL to spend (lamports, BEFORE 1% fee).
Returns:
Tokens received (raw units).
"""
k = v_sol * v_tok
new_v_sol = v_sol + sol_in
new_v_tok = k // new_v_sol + 1 # +1 matches on-chain rounding
tokens_out = v_tok - new_v_tok
return min(tokens_out, real_tok)Sell Tokens (Tokens → SOL)
def sell_tokens(v_sol: int, v_tok: int, real_sol: int, tokens_in: int) -> int:
"""Calculate SOL received for selling tokens.
Args:
v_sol: Virtual SOL reserves (lamports).
v_tok: Virtual token reserves (raw).
real_sol: Real SOL reserves (lamports).
tokens_in: Tokens to sell (raw units).
Returns:
SOL received (lamports, BEFORE 1% fee).
"""
k = v_sol * v_tok
new_v_tok = v_tok + tokens_in
new_v_sol = k // new_v_tok
sol_out = v_sol - new_v_sol - 1 # -1 matches on-chain floor rounding
return min(sol_out, real_sol)Buy Cost (Exact token amount → SOL needed)
def buy_cost(v_sol: int, v_tok: int, tokens_wanted: int) -> int:
"""Calculate SOL needed to buy exact token amount.
Returns:
SOL cost in lamports (before fee). Returns max int if impossible.
"""
if tokens_wanted >= v_tok:
return 2**64 - 1 # impossible
k = v_sol * v_tok
new_v_tok = v_tok - tokens_wanted
new_v_sol = k // new_v_tok + 1
return new_v_sol - v_solFee Handling
The 1% fee is not part of the curve math. It's applied externally:
# Buying: fee deducted from SOL input before curve
actual_sol_to_curve = sol_input * 0.99
# Selling: fee deducted from SOL output after curve
actual_sol_received = sol_from_curve * 0.99
# Roundtrip minimum cost: ~2% from fees alone, plus price impactMarket Cap
market_cap_sol = (token_total_supply * virtual_sol_reserves) / virtual_token_reservesGraduation
Graduation occurs when realSolReserves reaches ~85 SOL (~$12K-14K depending on SOL price). Only ~1.4% of PumpFun tokens ever graduate.
What Happens
1. complete flag set to true on bonding curve account 2. CompleteEvent emitted (discriminator 5f72619cd42e9808) 3. Bonding curve stops accepting trades 4. ~$12K liquidity deposited to the destination DEX 5. Token becomes tradeable on PumpSwap (or Raydium for older tokens)
Fill Percentage
GRADUATION_THRESHOLD = 85_000_000_000 # 85 SOL in lamports
fill_pct = (real_sol_reserves / GRADUATION_THRESHOLD) * 100.0Migration Targets
- March 2025+: PumpSwap (
pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA) — native AMM, no migration fee - Before March 2025: Raydium V4 (
675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8) — 6 SOL fee
PumpSwap Post-Graduation
PumpSwap is a constant-product AMM with 1% fee (same as bonding curve). Key differences:
- Base asset is always WSOL, quote is token
- Instruction semantics are inverted: "buy" instruction sells tokens, "sell" instruction buys tokens
- Supports creator revenue sharing (0.05% of volume to original creator)
Program IDs & Addresses
| Program/Account | Address |
|---|---|
| PumpFun Program | 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P |
| PumpSwap Program | pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA |
| Fee Program | pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ |
| Global Account | 4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf |
| Fee Recipient | 62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV |
| Event Authority | Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1 |
Event Parsing
Events are Anchor-style: sha256("event:<EventName>")[0..8]
| Event | Discriminator (hex) |
|---|---|
| CreateEvent | 1b72a94ddeeb6376 |
| TradeEvent | bddb7fd34ee661ee |
| CompleteEvent | 5f72619cd42e9808 |
TradeEvent Layout (after 8-byte discriminator)
mint: pubkey 32 bytes
solAmount: u64 8 bytes
tokenAmount: u64 8 bytes
isBuy: bool 1 byte
user: pubkey 32 bytes
timestamp: i64 8 bytes
virtualSolReserves: u64 8 bytes
virtualTokenReserves: u64 8 bytes
realSolReserves: u64 8 bytes
realTokenReserves: u64 8 bytesCritical: Events are in CPI inner instructions. Search for discriminators anywhere in instruction data, not just at offset 0.
Bonding Curve Account Layout
Offset 0: discriminator 8 bytes
Offset 8: virtualTokenReserves u64
Offset 16: virtualSolReserves u64
Offset 24: realTokenReserves u64
Offset 32: realSolReserves u64
Offset 40: tokenTotalSupply u64
Offset 48: complete bool (1 byte)
Offset 49: creator pubkey (32 bytes)PDA Derivation
| PDA | Seeds |
|---|---|
| Bonding Curve | ["bonding-curve", mint] |
| Bonding Curve V2 | ["bonding-curve-v2", mint] |
| Creator Vault | ["creator-vault", creator] |
Instruction Discriminators
| Instruction | Hex | Notes |
|---|---|---|
| buy_exact_sol_in (V2) | 38fc74089edfcd5f | Current production buy |
| sell (V2) | 33e685a4017f83ad | Current production sell |
| buy (V1/legacy) | 66063d1201daebea | Legacy, still seen occasionally |
| create | 181ec828051c0777 | Token creation |
Buy Instruction Data (24 bytes)
[0..8]: discriminator
[8..16]: spendable_sol_in u64 LE (total SOL budget, fees deducted internally)
[16..24]: min_tokens_out u64 LE (slippage floor)Sell Instruction Data (24 bytes)
[0..8]: discriminator
[8..16]: amount_tokens u64 LE (tokens to sell, raw)
[16..24]: min_sol_output u64 LE (minimum SOL out, lamports)Price Impact & Sizing
def price_impact(v_sol: int, v_tok: int, sol_in: int) -> float:
"""Calculate price impact for a buy as a percentage."""
spot = v_sol / v_tok
tokens = buy_tokens(v_sol, v_tok, v_tok, sol_in)
if tokens == 0:
return float('inf')
exec_price = sol_in / tokens
return (exec_price / spot - 1) * 100
# Example: 1 SOL buy at genesis
# impact = price_impact(30_000_000_000, 1_073_000_000_000_000, 1_000_000_000)
# ≈ 3.3% price impactFiles
References
references/bonding_curve_math.md— Complete mathematical derivations with worked examplesreferences/graduation_process.md— Graduation threshold, migration, PumpSwap mechanicsreferences/instruction_reference.md— Full instruction and event layouts for parsing
Scripts
scripts/curve_calculator.py— Interactive bonding curve calculator: price, impact, fill %scripts/parse_events.py— Parse PumpFun events from transaction data
PumpFun Bonding Curve — Mathematical Reference
Invariant
PumpFun uses a virtual constant-product market maker (CPMM):
k = V_sol × V_tok = constantWhere V_sol and V_tok are virtual reserves that include both real (withdrawable) and phantom reserves.
Initial State
| Variable | Value | Raw |
|---|---|---|
| V_sol (virtual SOL) | 30 SOL | 30,000,000,000 lamports |
| V_tok (virtual tokens) | ~1.073B | 1,073,000,000,000,000 |
| R_tok (real tokens) | ~793M | 793,000,000,000,000 |
| R_sol (real SOL) | 0 | 0 |
| Supply | 1B | 1,000,000,000,000,000 |
| k | — | 3.219 × 10²⁵ |
The virtual-real gap: V_tok - R_tok ≈ 280M tokens are virtual — they shape the curve but cannot be withdrawn.
Spot Price
At any point on the curve:
P = V_sol / V_tok (in lamports per raw token)
P_human = (V_sol / 10⁹) / (V_tok / 10⁶) (SOL per token)Price progression examples
| Fill % | R_sol | V_sol | V_tok | Price (SOL) | Multiplier |
|---|---|---|---|---|---|
| 0% | 0 | 30B | 1.073T | 2.796e-8 | 1.0x |
| 5% | 3.75B | 33.75B | 953.7B | 3.539e-8 | 1.27x |
| 25% | 21.9B | 51.9B | 620.7B | 8.354e-8 | 2.99x |
| 50% | 45.8B | 75.8B | 424.7B | 1.784e-7 | 6.38x |
| 75% | 63.8B | 93.8B | 343.2B | 2.733e-7 | 9.78x |
| 100% | 85B | 115B | 279.9B | 4.109e-7 | 14.70x |
A token that goes from launch to graduation (100% fill) achieves approximately 14.7x price appreciation.
Buy Formula
Given SOL input Δsol (after 1% fee is deducted externally):
V_sol' = V_sol + Δsol
V_tok' = ⌊k / V_sol'⌋ + 1 (ceiling division match)
tokens_out = V_tok - V_tok'
result = min(tokens_out, R_tok) (can't buy more than real reserves)The +1 on V_tok' matches on-chain rounding behavior. Validated against 9K+ live trades.
Worked Example: Buy 1 SOL at genesis
V_sol = 30,000,000,000
V_tok = 1,073,000,000,000,000
k = 30,000,000,000 × 1,073,000,000,000,000 = 3.219e25
sol_in = 1,000,000,000 (1 SOL, before fee; actual to curve = 990,000,000)
V_sol' = 30,990,000,000
V_tok' = 3.219e25 / 30,990,000,000 + 1 = 1,038,722,168,441,433
tokens_out = 1,073,000,000,000,000 - 1,038,722,168,441,433 = 34,277,831,558,567
Result: ~34.28M tokens (34,277,831,558,567 raw / 10^6 = 34,277,831.56)Sell Formula
Given Δtok tokens to sell:
V_tok' = V_tok + Δtok
V_sol' = ⌊k / V_tok'⌋
sol_out = V_sol - V_sol' - 1 (floor division match)
result = min(sol_out, R_sol) (can't withdraw more than real SOL)The -1 is critical for matching on-chain floor division behavior.
Buy Cost Formula
Given exact tokens_wanted:
V_tok' = V_tok - tokens_wanted
V_sol' = ⌊k / V_tok'⌋ + 1
cost = V_sol' - V_solReturns infinity if tokens_wanted >= V_tok.
Price Impact
impact = (execution_price / spot_price - 1) × 100%
Where:
spot_price = V_sol / V_tok
execution_price = sol_in / tokens_outImpact at genesis for various sizes
| Buy Size | Tokens | Impact | % of Supply |
|---|---|---|---|
| 0.1 SOL | ~3.5M | 0.33% | 0.35% |
| 1 SOL | ~34.3M | 3.33% | 3.43% |
| 5 SOL | ~152M | 16.7% | 15.2% |
| 10 SOL | ~268M | 33.3% | 26.8% |
Impact grows super-linearly due to the constant-product curve.
Fee Structure
| Action | Fee | Deducted From |
|---|---|---|
| Buy | 1% (100 bps) | SOL input (before curve) |
| Sell | 1% (100 bps) | SOL output (after curve) |
| Create | Free | — |
Fee is applied by the on-chain program, NOT by the curve math. Always calculate curve math with pre-fee amounts, then apply fee externally.
Roundtrip Cost
Roundtrip loss = 1 - (1 - 0.01) × (1 - 0.01) × (1 - impact_buy) × (1 - impact_sell)
≈ 2% + 2 × avg_impact
For a 1 SOL buy at genesis: ~2% + 2 × 3.3% ≈ 8.6% roundtrip lossEdge Cases
1. Zero reserves: If R_tok = 0, no more buys possible (curve depleted) 2. Zero real SOL: If R_sol = 0, sells return 0 (no SOL to withdraw) 3. Dust trades: For amounts < 100K lamports, the +1/-1 rounding corrections become significant 4. Completed curve: After graduation (complete = true), all buy/sell calls revert
PumpFun Graduation — Process & PumpSwap Migration
Graduation Threshold
A token graduates when its realSolReserves reaches approximately 85 SOL (85,000,000,000 lamports).
At graduation:
- Virtual SOL ≈ 115 SOL (30 initial + 85 real)
- Virtual Tokens ≈ 279.9M
- Spot price ≈ 4.109 × 10⁻⁷ SOL/token
- Market cap ≈ 115 SOL × supply ratio ≈ $12-14K USD (varies with SOL price)
- Price is approximately 14.7x the initial launch price
Graduation Rate
Only approximately 1.4% of tokens launched on PumpFun ever graduate. The vast majority die before reaching the 85 SOL threshold.
Graduation Event Sequence
1. A buy transaction pushes realSolReserves past the graduation threshold 2. The PumpFun program sets complete = true on the bonding curve account 3. A CompleteEvent is emitted (discriminator 5f72619cd42e9808) 4. The bonding curve stops accepting new trades (attempts revert) 5. Liquidity (~$12K worth) is automatically deposited to the destination DEX 6. Token becomes tradeable on the destination DEX
CompleteEvent Layout
After the 8-byte discriminator:
user: pubkey 32 bytes (wallet that triggered graduation)
mint: pubkey 32 bytes (token mint address)
bondingCurve: pubkey 32 bytes (bonding curve account)
timestamp: i64 8 bytes (unix seconds)Total: 104 bytes after discriminator.
Migration Targets
PumpSwap (March 2025+, current default)
- Program:
pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA - Type: Constant-product AMM
- Fee: 1% (100 bps), same as bonding curve
- Migration fee: None (free)
- Creator revenue: 0.05% of PumpSwap trading volume
- 95%+ of current graduations go to PumpSwap
Raydium V4 (legacy, before March 2025)
- Program:
675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 - Type: Constant-product AMM
- Fee: 0.25% (25 bps)
- Migration fee: 6 SOL (deducted from reserves)
- Status: No longer the default migration target
PumpSwap Mechanics
Instruction Semantics Inversion
PumpSwap uses base=WSOL, quote=token. This creates an unintuitive naming:
| Action You Want | PumpSwap Instruction | What It Does |
|---|---|---|
| Buy tokens (spend SOL) | sell | "Sell" SOL base to receive token quote |
| Sell tokens (receive SOL) | buy | "Buy" SOL base by spending token quote |
This is a common source of bugs. The discriminators:
- PumpSwap
buy(selling tokens):66063d1201daebea - PumpSwap
sell(buying tokens):33e685a4017f83ad
PumpSwap Pool Layout
Offset 0: discriminator 8 bytes
Offset 8: bump 1 byte
Offset 9: index 2 bytes
Offset 11: creator 32 bytes (pool creator, NOT token creator)
Offset 43: base_mint 32 bytes (always WSOL)
Offset 75: quote_mint 32 bytes (the token)
Offset 107: lp_mint 32 bytes
Offset 139: pool_base_account 32 bytes (SOL vault)
Offset 171: pool_quote_account 32 bytes (token vault)
Offset 203: lp_supply 8 bytes
Offset 211: coin_creator 32 bytes (original token creator)PumpSwap PDAs
| PDA | Seeds | Program |
|---|---|---|
| Global Config | ["global_config"] | PumpSwap |
| Event Authority | ["__event_authority"] | PumpSwap |
| Creator Vault Authority | ["creator_vault", coin_creator] | PumpSwap |
| Fee Config | ["fee_config", pumpswap_program_id] | Fee Program |
Tracking Graduation Progress
Fill Percentage
GRAD_THRESHOLD = 85_000_000_000 # 85 SOL in lamports
def fill_percentage(real_sol_reserves: int) -> float:
"""Calculate how close a token is to graduation."""
return (real_sol_reserves / GRAD_THRESHOLD) * 100.0Fill Velocity
Track the rate of SOL accumulation to predict graduation timing:
def estimated_time_to_graduation(
current_sol: int,
previous_sol: int,
time_delta_seconds: float,
) -> float | None:
"""Estimate seconds until graduation based on current velocity."""
remaining = GRAD_THRESHOLD - current_sol
if remaining <= 0:
return 0
velocity = (current_sol - previous_sol) / time_delta_seconds # lamports/sec
if velocity <= 0:
return None # not progressing
return remaining / velocityMonitoring via gRPC
Subscribe to the PumpFun program via Yellowstone gRPC to receive:
- TradeEvents: Every buy/sell with updated reserves
- CompleteEvents: Graduation trigger
Filter by program ID 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P in your subscription.
Fee Comparison Post-Graduation
| DEX | Fee | Notes |
|---|---|---|
| PumpFun bonding curve | 1.00% | Before graduation |
| PumpSwap | 1.00% | After graduation (PumpSwap) |
| Raydium V4 | 0.25% | After graduation (legacy) |
| Raydium CPMM | 0.25% | — |
| Orca Whirlpool | 0.30% | — |
| Meteora DLMM | 0.30% | — |
Note: PumpSwap's 1% fee is higher than other Solana DEXes. This is relevant for cost modeling.
PumpFun — Instruction & Event Reference
Program IDs
| Program | Address | Purpose |
|---|---|---|
| PumpFun | 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P | Bonding curve trades |
| PumpSwap | pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA | Post-graduation AMM |
| Fee Program | pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ | Fee handling |
| Metaplex Metadata | metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s | Token metadata |
Well-Known Accounts
| Account | Address |
|---|---|
| Global | 4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf |
| Fee Recipient | 62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV |
| Event Authority | Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1 |
---
Instruction Discriminators
Discriminators are the first 8 bytes of instruction data.
PumpFun Instructions
| Instruction | Hex | Use |
|---|---|---|
| buy_exact_sol_in (V2) | 38fc74089edfcd5f | Current buy |
| sell (V2) | 33e685a4017f83ad | Current sell |
| buy (V1/legacy) | 66063d1201daebea | Legacy buy |
| create | 181ec828051c0777 | Token creation |
| initialize | afaf6d1f0d989bed | Global state init |
| setParams | a51f8635bdb482ff | Admin config |
Buy V2 — Instruction Data (24 bytes)
Bytes 0-7: discriminator 0x38fc74089edfcd5f
Bytes 8-15: spendable_sol_in u64 LE (total SOL budget)
Bytes 16-23: min_tokens_out u64 LE (slippage floor)Note: spendable_sol_in is the TOTAL SOL budget. The 1% fee is deducted internally by the program before applying to the curve.
Sell V2 — Instruction Data (24 bytes)
Bytes 0-7: discriminator 0x33e685a4017f83ad
Bytes 8-15: amount_tokens u64 LE (tokens to sell, raw units)
Bytes 16-23: min_sol_output u64 LE (minimum SOL received)Buy V2 — Account Layout (17 accounts)
0: global (readonly)
1: feeRecipient (writable)
2: mint (readonly)
3: bondingCurve (writable)
4: associatedBondingCurve (writable) ATA(bondingCurve, mint)
5: associatedUser (writable) ATA(user, mint)
6: user (signer)
7: systemProgram (readonly)
8: tokenProgram (readonly)
9: creatorVault (writable) PDA: ["creator-vault", creator]
10: eventAuthority (readonly)
11: program (readonly)
12: globalVolumeAccumulator (readonly)
13: userVolumeAccumulator (writable) PDA: ["user_volume_accumulator", user]
14: feeConfig (readonly) PDA: ["fee_config", pumpfun_program]
15: feeProgram (readonly)
16: bondingCurveV2 (readonly) PDA: ["bonding-curve-v2", mint]Sell V2 — Account Layout (15 accounts)
0: global (readonly)
1: feeRecipient (writable)
2: mint (readonly)
3: bondingCurve (writable)
4: associatedBondingCurve (writable)
5: associatedUser (writable)
6: user (signer)
7: systemProgram (readonly)
8: creatorVault (writable)
9: tokenProgram (readonly)
10: eventAuthority (readonly)
11: program (readonly)
12: feeConfig (readonly)
13: feeProgram (readonly)
14: bondingCurveV2 (readonly) (remaining account)Note: Sell has 15 accounts vs buy's 17. Sell does NOT include volume accumulators. Account ordering differs — creatorVault and tokenProgram swap positions between buy (8=tokenProgram, 9=creatorVault) and sell (8=creatorVault, 9=tokenProgram).
---
Event Discriminators
Events use Anchor's convention: sha256("event:<EventName>")[0..8]
| Event | Hex Discriminator |
|---|---|
| CreateEvent | 1b72a94ddeeb6376 |
| TradeEvent | bddb7fd34ee661ee |
| CompleteEvent | 5f72619cd42e9808 |
CreateEvent Layout (after discriminator)
name: string (4-byte LE length prefix + UTF-8 data)
symbol: string (4-byte LE length prefix + UTF-8 data)
uri: string (4-byte LE length prefix + UTF-8 data)
mint: pubkey 32 bytes
bondingCurve: pubkey 32 bytes
user: pubkey 32 bytes (the creator)TradeEvent Layout (after discriminator)
mint: pubkey 32 bytes
solAmount: u64 8 bytes LE
tokenAmount: u64 8 bytes LE
isBuy: bool 1 byte (1=buy, 0=sell)
user: pubkey 32 bytes
timestamp: i64 8 bytes LE (unix seconds)
virtualSolReserves: u64 8 bytes LE
virtualTokenReserves: u64 8 bytes LE
realSolReserves: u64 8 bytes LE
realTokenReserves: u64 8 bytes LETotal: 121 bytes. Every field is fixed-size — no variable-length data.
CompleteEvent Layout (after discriminator)
user: pubkey 32 bytes
mint: pubkey 32 bytes
bondingCurve: pubkey 32 bytes
timestamp: i64 8 bytes LETotal: 104 bytes.
---
Parsing Pattern
Events are emitted via CPI (Cross-Program Invocation), so they appear in inner instructions, not top-level instructions. The discriminator may not be at byte offset 0.
def find_event(data: bytes, discriminator: bytes) -> int:
"""Find event discriminator anywhere in instruction data.
Args:
data: Raw instruction data bytes.
discriminator: 8-byte event discriminator.
Returns:
Byte offset of the event data (after discriminator), or -1 if not found.
"""
idx = data.find(discriminator)
if idx == -1:
return -1
return idx + 8 # skip discriminator, return start of event data---
Bonding Curve Account Layout
Account data for the bonding curve PDA (["bonding-curve", mint]):
Offset 0: discriminator u64 8 bytes
Offset 8: virtualTokenReserves u64 8 bytes
Offset 16: virtualSolReserves u64 8 bytes
Offset 24: realTokenReserves u64 8 bytes
Offset 32: realSolReserves u64 8 bytes
Offset 40: tokenTotalSupply u64 8 bytes
Offset 48: complete bool 1 byte
Offset 49: creator pubkey 32 bytesTotal minimum: 81 bytes.
import struct
def parse_bonding_curve(data: bytes) -> dict:
"""Parse bonding curve account data."""
if len(data) < 81:
return {}
return {
"virtualTokenReserves": struct.unpack_from("<Q", data, 8)[0],
"virtualSolReserves": struct.unpack_from("<Q", data, 16)[0],
"realTokenReserves": struct.unpack_from("<Q", data, 24)[0],
"realSolReserves": struct.unpack_from("<Q", data, 32)[0],
"tokenTotalSupply": struct.unpack_from("<Q", data, 40)[0],
"complete": bool(data[48]),
"creator": data[49:81], # 32-byte pubkey
}---
PDA Seeds
| PDA | Seeds | Program |
|---|---|---|
| Bonding Curve | ["bonding-curve", mint_pubkey] | PumpFun |
| Bonding Curve V2 | ["bonding-curve-v2", mint_pubkey] | PumpFun |
| Mint Authority | ["mint-authority"] | PumpFun |
| Global | ["global"] | PumpFun |
| Creator Vault | ["creator-vault", creator_pubkey] | PumpFun |
| Global Volume Acc | ["global_volume_accumulator"] | PumpFun |
| User Volume Acc | ["user_volume_accumulator", user_pubkey] | PumpFun |
| Fee Config | ["fee_config", pumpfun_program_id] | Fee Program |
#!/usr/bin/env python3
"""PumpFun bonding curve calculator.
Calculates token prices, buy/sell amounts, price impact, fill percentage,
and graduation estimates for PumpFun tokens. Can use live on-chain data
or default initial parameters.
Usage:
python scripts/curve_calculator.py
SOL_INPUT="2.5" python scripts/curve_calculator.py
Dependencies:
uv pip install httpx
Environment Variables:
SOLANA_RPC_URL: Solana RPC endpoint (optional, for live data)
TOKEN_MINT: Token mint to fetch live curve data (optional)
SOL_INPUT: SOL amount to simulate buying (default: 1.0)
TOKENS_TO_SELL: Token amount to simulate selling (default: 0, skip)
"""
import os
import struct
import sys
import time
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
RPC_URL = os.getenv("SOLANA_RPC_URL", "")
TOKEN_MINT = os.getenv("TOKEN_MINT", "")
SOL_INPUT = float(os.getenv("SOL_INPUT", "1.0"))
TOKENS_TO_SELL = float(os.getenv("TOKENS_TO_SELL", "0"))
# PumpFun constants
INITIAL_VIRTUAL_SOL = 30_000_000_000 # 30 SOL
INITIAL_VIRTUAL_TOKEN = 1_073_000_000_000_000 # ~1.073B tokens (6 dec)
INITIAL_REAL_TOKEN = 793_000_000_000_000 # ~793M tokens
TOKEN_DECIMALS = 6
GRADUATION_THRESHOLD = 85_000_000_000 # 85 SOL
FEE_BPS = 100 # 1%
PUMPFUN_PROGRAM = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
# ── Bonding Curve Math ──────────────────────────────────────────────
def buy_tokens(v_sol: int, v_tok: int, r_tok: int, sol_in: int) -> int:
"""Calculate tokens received for SOL input.
Args:
v_sol: Virtual SOL reserves (lamports).
v_tok: Virtual token reserves (raw).
r_tok: Real token reserves (raw).
sol_in: SOL input (lamports, before fee).
Returns:
Tokens received (raw units).
"""
k = v_sol * v_tok
new_v_sol = v_sol + sol_in
new_v_tok = k // new_v_sol + 1
tokens_out = v_tok - new_v_tok
return min(tokens_out, r_tok)
def sell_tokens(v_sol: int, v_tok: int, r_sol: int, tokens_in: int) -> int:
"""Calculate SOL received for selling tokens.
Args:
v_sol: Virtual SOL reserves (lamports).
v_tok: Virtual token reserves (raw).
r_sol: Real SOL reserves (lamports).
tokens_in: Tokens to sell (raw).
Returns:
SOL received (lamports, before fee).
"""
k = v_sol * v_tok
new_v_tok = v_tok + tokens_in
new_v_sol = k // new_v_tok
sol_out = v_sol - new_v_sol - 1
return min(max(sol_out, 0), r_sol)
def buy_cost(v_sol: int, v_tok: int, tokens_wanted: int) -> int:
"""Calculate SOL needed to buy exact token amount.
Returns:
SOL cost in lamports (before fee).
"""
if tokens_wanted >= v_tok:
return 2**64 - 1
k = v_sol * v_tok
new_v_tok = v_tok - tokens_wanted
new_v_sol = k // new_v_tok + 1
return new_v_sol - v_sol
def price_impact(v_sol: int, v_tok: int, sol_in: int) -> float:
"""Calculate price impact for a buy.
Returns:
Price impact as a percentage.
"""
spot = v_sol / v_tok
tokens = buy_tokens(v_sol, v_tok, v_tok, sol_in)
if tokens == 0:
return float('inf')
exec_price = sol_in / tokens
return (exec_price / spot - 1) * 100
def market_cap_sol(v_sol: int, v_tok: int, total_supply: int) -> float:
"""Calculate market cap in SOL.
Returns:
Market cap in SOL.
"""
return (total_supply * v_sol) / v_tok / 1e9
# ── Live Data ───────────────────────────────────────────────────────
def fetch_bonding_curve(mint: str) -> Optional[dict]:
"""Fetch live bonding curve state from on-chain.
Args:
mint: Token mint address.
Returns:
Parsed curve state dict, or None.
"""
if not RPC_URL:
return None
# Derive bonding curve PDA
# For simplicity, we'll look up using getProgramAccounts with memcmp
# on the mint address at the correct offset in the account data
try:
# Use getAccountInfo on the known PDA
# This requires the PDA address — for demo, we show the approach
print(" Note: Live curve fetch requires PDA derivation.")
print(" Using initial parameters instead.")
return None
except Exception:
return None
# ── Display ─────────────────────────────────────────────────────────
def format_sol(lamports: int) -> str:
"""Format lamports as SOL."""
return f"{lamports / 1e9:.6f} SOL"
def format_tokens(raw: int) -> str:
"""Format raw token amount."""
amount = raw / 10**TOKEN_DECIMALS
if amount >= 1e9:
return f"{amount / 1e9:.2f}B"
if amount >= 1e6:
return f"{amount / 1e6:.2f}M"
if amount >= 1e3:
return f"{amount / 1e3:.2f}K"
return f"{amount:.2f}"
def print_curve_state(v_sol: int, v_tok: int, r_sol: int, r_tok: int) -> None:
"""Print current curve state."""
spot = v_sol / v_tok
spot_human = (v_sol / 1e9) / (v_tok / 10**TOKEN_DECIMALS)
fill_pct = (r_sol / GRADUATION_THRESHOLD) * 100
total_supply = 1_000_000_000_000_000
print(f"\n{'='*60}")
print(f"PUMPFUN BONDING CURVE STATE")
print(f"{'='*60}")
print(f"\n--- Reserves ---")
print(f" Virtual SOL: {format_sol(v_sol)}")
print(f" Virtual Token: {format_tokens(v_tok)}")
print(f" Real SOL: {format_sol(r_sol)}")
print(f" Real Token: {format_tokens(r_tok)}")
print(f"\n--- Pricing ---")
print(f" Spot Price: {spot_human:.10f} SOL/token")
print(f" Market Cap: {market_cap_sol(v_sol, v_tok, total_supply):.2f} SOL")
print(f"\n--- Graduation ---")
print(f" Fill: {fill_pct:.2f}%")
print(f" SOL to grad: {format_sol(max(0, GRADUATION_THRESHOLD - r_sol))}")
if fill_pct >= 100:
print(f" Status: GRADUATED")
elif fill_pct >= 90:
print(f" Status: NEAR GRADUATION")
elif fill_pct >= 50:
print(f" Status: MID-CURVE")
else:
print(f" Status: EARLY")
def print_buy_simulation(v_sol: int, v_tok: int, r_sol: int, r_tok: int, sol_amount: float) -> None:
"""Simulate and display a buy."""
sol_lamports = int(sol_amount * 1e9)
fee_lamports = sol_lamports * FEE_BPS // 10000
sol_after_fee = sol_lamports - fee_lamports
tokens = buy_tokens(v_sol, v_tok, r_tok, sol_after_fee)
impact = price_impact(v_sol, v_tok, sol_after_fee)
total_supply = 1_000_000_000_000_000
pct_supply = tokens / total_supply * 100
# Post-buy state
new_v_sol = v_sol + sol_after_fee
k = v_sol * v_tok
new_v_tok = k // new_v_sol + 1
new_r_sol = r_sol + sol_after_fee
new_fill = (new_r_sol / GRADUATION_THRESHOLD) * 100
print(f"\n--- Buy Simulation: {sol_amount} SOL ---")
print(f" Fee (1%): {format_sol(fee_lamports)}")
print(f" SOL to curve: {format_sol(sol_after_fee)}")
print(f" Tokens out: {format_tokens(tokens)}")
print(f" % of supply: {pct_supply:.2f}%")
print(f" Price impact: {impact:.2f}%")
print(f" New fill: {new_fill:.2f}%")
# Immediate sell value
sell_value = sell_tokens(new_v_sol, new_v_tok, new_r_sol, tokens)
sell_after_fee = sell_value * (10000 - FEE_BPS) // 10000
roundtrip_loss = (1 - sell_after_fee / sol_lamports) * 100
print(f"\n Roundtrip Analysis:")
print(f" Immediate sell: {format_sol(sell_after_fee)}")
print(f" Roundtrip loss: {roundtrip_loss:.2f}%")
def print_sell_simulation(v_sol: int, v_tok: int, r_sol: int, token_amount: float) -> None:
"""Simulate and display a sell."""
raw_tokens = int(token_amount * 10**TOKEN_DECIMALS)
sol_out = sell_tokens(v_sol, v_tok, r_sol, raw_tokens)
sol_after_fee = sol_out * (10000 - FEE_BPS) // 10000
print(f"\n--- Sell Simulation: {format_tokens(raw_tokens)} tokens ---")
print(f" SOL from curve: {format_sol(sol_out)}")
print(f" Fee (1%): {format_sol(sol_out - sol_after_fee)}")
print(f" SOL received: {format_sol(sol_after_fee)}")
def print_impact_table(v_sol: int, v_tok: int, r_tok: int) -> None:
"""Print price impact table for various buy sizes."""
print(f"\n--- Price Impact Table ---")
print(f" {'Buy Size':>10} {'Tokens':>12} {'Impact':>8} {'% Supply':>10}")
print(f" {'─'*10} {'─'*12} {'─'*8} {'─'*10}")
total_supply = 1_000_000_000_000_000
for sol in [0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0]:
lam = int(sol * 1e9)
lam_after_fee = lam * (10000 - FEE_BPS) // 10000
tokens = buy_tokens(v_sol, v_tok, r_tok, lam_after_fee)
impact = price_impact(v_sol, v_tok, lam_after_fee)
pct = tokens / total_supply * 100
print(f" {sol:>9.1f} {format_tokens(tokens):>12} {impact:>7.2f}% {pct:>9.2f}%")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run bonding curve calculator."""
# Use live data if available, otherwise initial parameters
v_sol = INITIAL_VIRTUAL_SOL
v_tok = INITIAL_VIRTUAL_TOKEN
r_sol = 0
r_tok = INITIAL_REAL_TOKEN
if TOKEN_MINT and RPC_URL:
curve = fetch_bonding_curve(TOKEN_MINT)
if curve:
v_sol = curve["virtualSolReserves"]
v_tok = curve["virtualTokenReserves"]
r_sol = curve["realSolReserves"]
r_tok = curve["realTokenReserves"]
print(f"Using live data for {TOKEN_MINT}")
else:
print("Using initial curve parameters (genesis state)")
else:
print("Using initial curve parameters (genesis state)")
# Display state
print_curve_state(v_sol, v_tok, r_sol, r_tok)
# Buy simulation
if SOL_INPUT > 0:
print_buy_simulation(v_sol, v_tok, r_sol, r_tok, SOL_INPUT)
# Sell simulation
if TOKENS_TO_SELL > 0:
print_sell_simulation(v_sol, v_tok, r_sol, TOKENS_TO_SELL)
# Impact table
print_impact_table(v_sol, v_tok, r_tok)
print()
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Parse PumpFun events from Solana transaction data.
Demonstrates parsing CreateEvent, TradeEvent, and CompleteEvent from
raw transaction data. Can fetch and parse live transactions or work
with example data in demo mode.
Usage:
python scripts/parse_events.py
python scripts/parse_events.py --demo
TX_SIGNATURE="5abc..." python scripts/parse_events.py
Dependencies:
uv pip install httpx
Environment Variables:
SOLANA_RPC_URL: Solana RPC endpoint
TX_SIGNATURE: Transaction signature to parse (optional)
"""
import base64
import os
import struct
import sys
import time
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
RPC_URL = os.getenv("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com")
TX_SIGNATURE = os.getenv("TX_SIGNATURE", "")
DEMO_MODE = "--demo" in sys.argv
PUMPFUN_PROGRAM = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
# Event discriminators (hex)
CREATE_DISC = bytes.fromhex("1b72a94ddeeb6376")
TRADE_DISC = bytes.fromhex("bddb7fd34ee661ee")
COMPLETE_DISC = bytes.fromhex("5f72619cd42e9808")
# Instruction discriminators (hex)
BUY_V2_DISC = bytes.fromhex("38fc74089edfcd5f")
SELL_V2_DISC = bytes.fromhex("33e685a4017f83ad")
BUY_V1_DISC = bytes.fromhex("66063d1201daebea")
CREATE_IX_DISC = bytes.fromhex("181ec828051c0777")
TOKEN_DECIMALS = 6
# ── Parsers ─────────────────────────────────────────────────────────
def parse_trade_event(data: bytes) -> Optional[dict]:
"""Parse a PumpFun TradeEvent from raw bytes.
Args:
data: Raw bytes starting AFTER the 8-byte discriminator.
Returns:
Parsed trade event dict, or None on failure.
"""
if len(data) < 121:
return None
try:
mint = base64.b64encode(data[0:32]).decode() # or use base58
sol_amount = struct.unpack_from("<Q", data, 32)[0]
token_amount = struct.unpack_from("<Q", data, 40)[0]
is_buy = bool(data[48])
user = base64.b64encode(data[49:81]).decode()
timestamp = struct.unpack_from("<q", data, 81)[0]
v_sol = struct.unpack_from("<Q", data, 89)[0]
v_tok = struct.unpack_from("<Q", data, 97)[0]
r_sol = struct.unpack_from("<Q", data, 105)[0]
r_tok = struct.unpack_from("<Q", data, 113)[0]
return {
"type": "TradeEvent",
"mint_bytes": data[0:32],
"sol_amount": sol_amount,
"token_amount": token_amount,
"is_buy": is_buy,
"user_bytes": data[49:81],
"timestamp": timestamp,
"virtual_sol": v_sol,
"virtual_token": v_tok,
"real_sol": r_sol,
"real_token": r_tok,
}
except (struct.error, IndexError):
return None
def parse_create_event(data: bytes) -> Optional[dict]:
"""Parse a PumpFun CreateEvent from raw bytes.
Args:
data: Raw bytes starting AFTER the 8-byte discriminator.
Returns:
Parsed create event dict, or None on failure.
"""
try:
offset = 0
# Parse strings (4-byte LE length prefix + UTF-8 data)
def read_string(d: bytes, off: int) -> tuple[str, int]:
length = struct.unpack_from("<I", d, off)[0]
off += 4
text = d[off:off + length].decode("utf-8", errors="replace")
return text, off + length
name, offset = read_string(data, offset)
symbol, offset = read_string(data, offset)
uri, offset = read_string(data, offset)
if offset + 96 > len(data):
return None
mint = data[offset:offset + 32]
bonding_curve = data[offset + 32:offset + 64]
creator = data[offset + 64:offset + 96]
return {
"type": "CreateEvent",
"name": name,
"symbol": symbol,
"uri": uri,
"mint_bytes": mint,
"bonding_curve_bytes": bonding_curve,
"creator_bytes": creator,
}
except (struct.error, IndexError, UnicodeDecodeError):
return None
def parse_complete_event(data: bytes) -> Optional[dict]:
"""Parse a PumpFun CompleteEvent from raw bytes.
Args:
data: Raw bytes starting AFTER the 8-byte discriminator.
Returns:
Parsed complete event dict, or None on failure.
"""
if len(data) < 104:
return None
try:
user = data[0:32]
mint = data[32:64]
bonding_curve = data[64:96]
timestamp = struct.unpack_from("<q", data, 96)[0]
return {
"type": "CompleteEvent",
"user_bytes": user,
"mint_bytes": mint,
"bonding_curve_bytes": bonding_curve,
"timestamp": timestamp,
}
except (struct.error, IndexError):
return None
def find_and_parse_events(data: bytes) -> list[dict]:
"""Search for PumpFun events anywhere in instruction data.
Events are embedded in CPI inner instructions, so discriminators
may appear at any offset.
Args:
data: Raw instruction data bytes.
Returns:
List of parsed event dicts.
"""
events = []
# Search for each discriminator
for disc, parser, name in [
(TRADE_DISC, parse_trade_event, "TradeEvent"),
(CREATE_DISC, parse_create_event, "CreateEvent"),
(COMPLETE_DISC, parse_complete_event, "CompleteEvent"),
]:
idx = 0
while idx < len(data):
pos = data.find(disc, idx)
if pos == -1:
break
event_data = data[pos + 8:]
event = parser(event_data)
if event:
events.append(event)
idx = pos + 1 # continue searching for multiple events
return events
# ── Transaction Fetching ────────────────────────────────────────────
def fetch_transaction(signature: str) -> Optional[dict]:
"""Fetch and parse a transaction by signature.
Args:
signature: Transaction signature.
Returns:
Full transaction response, or None.
"""
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
signature,
{
"encoding": "jsonParsed",
"maxSupportedTransactionVersion": 0,
},
],
}
for attempt in range(3):
try:
resp = httpx.post(RPC_URL, json=payload, timeout=30.0)
if resp.status_code == 429:
time.sleep(3.0)
continue
resp.raise_for_status()
data = resp.json()
return data.get("result")
except Exception:
if attempt < 2:
time.sleep(2.0)
continue
return None
def extract_inner_instructions(tx: dict) -> list[bytes]:
"""Extract raw instruction data from inner instructions.
Args:
tx: Full transaction response.
Returns:
List of raw data bytes from inner instructions.
"""
raw_data_list = []
meta = tx.get("meta", {})
inner = meta.get("innerInstructions", [])
for group in inner:
for ix in group.get("instructions", []):
data = ix.get("data")
if data:
try:
raw = base64.b64decode(data)
raw_data_list.append(raw)
except Exception:
# Try base58 decoding (jsonParsed uses base58 for instruction data)
try:
# Simple base58 check — if it's parseable by base64, use that
pass
except Exception:
pass
return raw_data_list
# ── Demo Data ───────────────────────────────────────────────────────
def generate_demo_trade_event() -> bytes:
"""Generate a synthetic TradeEvent for demo purposes.
Returns:
Raw bytes including discriminator + event data.
"""
data = bytearray()
data.extend(TRADE_DISC)
# mint (32 bytes)
data.extend(b"\x01" * 32)
# solAmount (1 SOL)
data.extend(struct.pack("<Q", 1_000_000_000))
# tokenAmount (~34M tokens)
data.extend(struct.pack("<Q", 34_277_831_558_567))
# isBuy
data.extend(b"\x01")
# user (32 bytes)
data.extend(b"\x02" * 32)
# timestamp
data.extend(struct.pack("<q", 1709251200))
# virtualSolReserves (31 SOL after buy)
data.extend(struct.pack("<Q", 30_990_000_000))
# virtualTokenReserves
data.extend(struct.pack("<Q", 1_038_722_168_441_433))
# realSolReserves
data.extend(struct.pack("<Q", 990_000_000))
# realTokenReserves
data.extend(struct.pack("<Q", 758_722_168_441_433))
return bytes(data)
# ── Display ─────────────────────────────────────────────────────────
def display_event(event: dict) -> None:
"""Print a parsed event in human-readable format.
Args:
event: Parsed event dict.
"""
event_type = event.get("type", "Unknown")
if event_type == "TradeEvent":
action = "BUY" if event["is_buy"] else "SELL"
sol = event["sol_amount"] / 1e9
tokens = event["token_amount"] / 10**TOKEN_DECIMALS
fill = (event["real_sol"] / 85_000_000_000) * 100
print(f"\n [{event_type}] {action}")
print(f" SOL: {sol:.6f}")
print(f" Tokens: {tokens:,.2f}")
print(f" V_SOL: {event['virtual_sol'] / 1e9:.6f}")
print(f" V_Token: {event['virtual_token'] / 10**TOKEN_DECIMALS:,.0f}")
print(f" R_SOL: {event['real_sol'] / 1e9:.6f}")
print(f" Fill: {fill:.2f}%")
print(f" Timestamp: {event['timestamp']}")
elif event_type == "CreateEvent":
print(f"\n [{event_type}]")
print(f" Name: {event['name']}")
print(f" Symbol: {event['symbol']}")
print(f" URI: {event['uri'][:60]}...")
elif event_type == "CompleteEvent":
print(f"\n [{event_type}] — GRADUATION!")
print(f" Timestamp: {event['timestamp']}")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Parse PumpFun events."""
if DEMO_MODE:
print("=== DEMO MODE ===")
print("Generating synthetic PumpFun TradeEvent...")
demo_data = generate_demo_trade_event()
events = find_and_parse_events(demo_data)
print(f"\nFound {len(events)} event(s):")
for event in events:
display_event(event)
print("\n--- Event Data Layout ---")
print(f" Total bytes: {len(demo_data)}")
print(f" Discriminator: {demo_data[:8].hex()}")
print(f" Event data: {len(demo_data) - 8} bytes")
print()
return
if TX_SIGNATURE:
print(f"Fetching transaction: {TX_SIGNATURE}")
tx = fetch_transaction(TX_SIGNATURE)
if not tx:
print("Could not fetch transaction.")
sys.exit(1)
print("Extracting inner instructions...")
raw_data_list = extract_inner_instructions(tx)
print(f"Found {len(raw_data_list)} inner instructions")
all_events = []
for raw_data in raw_data_list:
events = find_and_parse_events(raw_data)
all_events.extend(events)
if all_events:
print(f"\nFound {len(all_events)} PumpFun event(s):")
for event in all_events:
display_event(event)
else:
print("\nNo PumpFun events found in this transaction.")
print("Make sure the transaction interacts with the PumpFun program.")
else:
print("Usage:")
print(" python scripts/parse_events.py --demo")
print(" TX_SIGNATURE='5abc...' python scripts/parse_events.py")
print()
if __name__ == "__main__":
main()
Related skills
FAQ
When does a token graduate?
Graduation occurs when realSolReserves reaches ~85 SOL; the skill notes only about 1.4% of PumpFun tokens ever graduate.
What curve does PumpFun use?
A virtual constant-product (CPMM) bonding curve where k = virtualSolReserves x virtualTokenReserves, with a 1% fee applied externally.