Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # PumpFun Claim-and-Burn Bot
- World Cup goal-triggered buyback & burn bot for PumpSwap-graduated tokens.
- ---
- ## PHASE 1: STARTUP & INITIALIZATION
- The script begins by loading your `.env` file manually (no dotenv dependency). It reads RPC URL, private key (BS58-encoded), token mint address, buyback percentage, slippage tolerance, and priority fee. These get stuffed into a `Config` struct that everything references.
- It then opens a Solana RPC connection with `confirmed` commitment level, derives your wallet keypair from the private key using `Keypair.fromSecretKey`, and converts the token mint string into a `PublicKey` object.
- Two SDK instances are created: `OnlinePumpSdk` (handles fee claiming from both pump.fun and PumpSwap programs) and `OnlinePumpAmmSdk` (handles buyback on the AMM). The PumpSwap pool PDA is derived from your mint address via `canonicalPumpPoolPda(mint)` — this uses a deterministic seed + program ID to generate the pool's on-chain address.
- It prints your wallet address, token mint, and data source (ESPN).
- ---
- ## PHASE 2: GRADUATION GATE
- The script enters a blocking loop that runs every 5 seconds. Each iteration calls `connection.getAccountInfo(poolKey)` — a raw Solana RPC call that returns `null` if the account doesn't exist on-chain.
- When your token's bonding curve is fully bought out, the migration to PumpSwap happens automatically (either by the last buyer or a third party). This creates the pool account at the PDA we derived. Once `getAccountInfo` returns a non-null result, the loop breaks and the script logs "Token has graduated! Starting goal tracker."
- You can start the script at any point — even days before graduation. It doesn't matter. It just sits here polling until the pool appears, then seamlessly moves on.
- ---
- ## PHASE 3: GOAL POLLING LOOP (EVERY 15 SECONDS)
- This is the main event loop. It runs indefinitely until you kill the process.
- ### Step A — Fetch Scoreboard
- Calls `https://site.api.espn.com/apis/site/v2/sports/soccer/fifa.world/scoreboard` with a Mozilla User-Agent header. The ESPN API is free, requires no authentication, and returns all World Cup matches in JSON format. Each match object contains an `id` string, a `status` (Scheduled / In Progress / Full Time / etc.), and an array of `competitors` — exactly 2 entries, each with a `homeAway` field ("home" or "away") and a `score` string.
- If the primary URL fails, the script falls back to `https://cdn.espn.com/sports/soccer/fifa.world/scoreboard`. The fetch itself has 3 retries with 2s/4s/6s exponential backoff.
- ### Step B — Diff Against Stored Scores
- The script maintains a `knownScores` object in memory: `Record<string, MatchScore>` where keys are match IDs and values are `{ home: number, away: number }`.
- For each match in the ESPN response:
- - Extract the match ID
- - Parse the home and away scores as integers
- - Look up the match ID in `knownScores`
- - If a previous score exists, calculate `delta = (newHome - oldHome) + (newAway - oldAway)`. If delta > 0, that many goals were scored since last poll.
- - Store the current score, overwriting the old one.
- ### Critical behavior on first run
- `knownScores` is an empty object. Every match gets stored with its current score, but since there's no "previous" entry, `delta` is never calculated and 0 is returned. This means **old goals never trigger buybacks** — if you restart the script mid-tournament, it snapshots all current scores and only acts on *future* goals.
- ### Step C — Return
- Returns the total number of new goals detected across all matches.
- ---
- ## PHASE 4: PER-GOAL EXECUTION (runCycle)
- If `newGoals > 0`, the script iterates from 0 to newGoals-1 and calls `runCycle()` for each one. This means if 2 goals happened between polls, you get 2 separate claim → buyback → burn cycles.
- Each `runCycle()` does the following:
- ### 4A — CLAIM & UNWRAP (`claimAndUnwrap`)
- Calls `pumpSdk.getCreatorVaultBalanceBothPrograms(wallet.publicKey)`. This makes an RPC call to read the on-chain vault balance for your creator account — it queries both the pump.fun program vault (fees earned while on the bonding curve) and the PumpSwap program vault (fees earned after graduation). The two balances are summed and returned as a single `BN` in lamports.
- If the balance is zero, the function logs "No fees to claim.", returns `new BN(0)`, and `runCycle` exits early — no buyback happens.
- If the balance is > 0, it logs the unclaimed amount in SOL (dividing by 1e9). Then it calls `pumpSdk.collectCoinCreatorFeeInstructions(wallet.publicKey, wallet.publicKey)` which generates the raw Solana instructions needed to claim fees from both programs simultaneously and unwrap any WSOL back to native SOL. This is a complex instruction set that handles the token program interactions internally.
- These instructions are prepended with a `ComputeBudgetProgram.setComputeUnitPrice` instruction to set a priority fee (in microlamports, configured in .env). The whole bundle is compiled into a VersionedTransaction (v0) using the latest blockhash, signed by your wallet, and sent via `connection.sendTransaction` with `maxRetries: 3`. The script then waits for `confirmed` commitment.
- On success, it logs the Solscan transaction URL and returns the claimed lamports.
- ### 4B — SPLIT
- Using BN math (safe for large numbers): `buybackLamports = claimedLamports * 60 / 100`. The remainder is `claimedLamports - buybackLamports`. Both amounts are logged in SOL.
- ### 4C — BUYBACK & BURN (`buybackAndBurn`)
- First, it derives your Associated Token Account (ATA) address via `getAssociatedTokenAddress(mint, wallet.publicKey, true, TOKEN_PROGRAM_ID)`. This is deterministic — your token balance lives at this address.
- It fetches the token's decimals via `connection.getTokenSupply(mint)` for the burn log output.
- A sanity check confirms the pool account still exists (it should — we passed the graduation gate).
- **The buy:** `onlinePumpAmmSdk.swapSolanaState(poolKey, wallet.publicKey)` fetches the AMM pool's current state — reserves, curve parameters, etc. This is passed to `PUMP_AMM_SDK.buyQuoteInput(swapState, buybackLamports, slippage)` which calculates the exact swap instructions including minimum tokens out (slippage protection).
- Before sending, the script records the pre-buy token balance from the ATA (or "0" if the ATA doesn't exist yet — `getAccountInfo` returns null). The buy instructions are sent as a versioned tx with priority fee. After confirmation, it reads the post-buy balance.
- **The burn:** `purchased = BigInt(postBalance) - BigInt(preBalance)`. If this is <= 0 (shouldn't happen, but protects against edge cases), it skips. Otherwise it builds `createBurnInstruction(ata, mint, wallet.purchased, purchased)` — a Token Program instruction that destroys the purchased tokens by setting their owner to the null public key (unspendable, effectively removing them from circulation forever). This is sent as a third tx.
- All three transactions (claim, buy, burn) get individual Solscan URLs logged so you can inspect each one.
- ---
- ## PHASE 5: ERROR HANDLING & REPEAT
- The entire poll → runCycle sequence is wrapped in a try/catch. Any error — ESPN fetch failure, RPC timeout, transaction simulation failure, insufficient SOL for gas — is caught. The error message is logged and the script sleeps 15 seconds before retrying.
- Fatal errors (invalid private key format, unresolvable mint address, etc.) are caught by the top-level `.catch()` on `main()`, which logs the full error and calls `process.exit(1)`.
- The only way the script stops is if you Ctrl+C it, the process is killed, or a fatal config error occurs. Otherwise it runs indefinitely — polling ESPN every 15s, detecting goals, and running the claim → buyback → burn pipeline for each one.
Advertisement