Anton Sauchyk 836d873d27 fix: report on-chain trade outcomes correctly, and repair every broken learning example (#177)
* fix(tx-status): read meta.err before reporting a trade succeeded

confirm_transaction answers "did this signature land in a block?", never
"did it succeed". A landed transaction can have reverted, and RPC reports
that only in meta.err. Reading whether a call threw instead of reading
meta.err produced false results in both directions.

False success — a reverted trade reported as confirmed:

- ten examples (pump.fun, pumpswap, letsbonk) printed "Transaction
  confirmed" without checking meta.err
- src/cleanup/manager.py discarded confirm_transaction's boolean and
  logged "Closed successfully" unconditionally, so a reverted close
  reported rent as reclaimed while the account stayed open
- learning-examples/cleanup_accounts.py did the same

False failure — a good trade reported as unconfirmed:

- _get_transaction_result omitted maxSupportedTransactionVersion, so the
  RPC answered -32015 for every versioned (v0) transaction. meta.err was
  unreadable and a successful trade read back as failed. The bot sends
  legacy transactions, which is the only reason this was survivable.
- confirm_transaction raised TypeError on a base58 str (solana-py wants a
  Signature) while _get_transaction_result raised on a Signature (not
  JSON serializable). Both were swallowed by a broad except into "not
  confirmed". The annotations pointed the wrong way too:
  build_and_send_transaction returns Signature, not str.

Changes:

- add learning-examples/tx_status.py — assert_transaction_succeeded and
  confirm_and_assert, replacing the copy duplicated in mint_and_buy{,_v2}
- wire it into the ten examples that confirmed without checking
- read the boolean in both cleanup paths
- normalize str/Signature at the client boundary; correct the annotations
- send maxSupportedTransactionVersion: 0 on getTransaction
- split verify_transaction_succeeded out of confirm_transaction so the
  meta.err check can run against a transaction that landed earlier

Two reporting bugs found while testing the above:

- live_v2_round_trip read balances at solana-py's default (finalized)
  commitment while confirming trades at "confirmed", so the end read saw
  pre-trade state and it printed "net change: +0.000000000 SOL" after a
  real round trip. Verified: on a busy account finalized trails confirmed
  by ~263k lamports.
- cleanup_accounts produced no output at all, success or failure, because
  get_logger attaches no handler and only the bot installs one. httpx is
  pinned to WARNING alongside it — the RPC endpoint carries an API key.

Adds learning-examples/verify_tx_status_checks.py: offline stub checks, a
scan that fails if an example confirms without checking meta.err, an AST
check that nothing in src/ discards the boolean, a guard that
getTransaction opts into v0, and --live, which replays issue #175's three
signatures against mainnet and requires both layers to reject them on
meta.err (Custom: 6062) rather than on a failed fetch. The two src/ guards
were mutation-tested: reintroducing each bug makes them fail.

The BuybackFeeRecipientMissing (6062) half of #175 was already fixed by
the buy_v2/sell_v2 migration in 02343b7.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(client): retry RPC timeouts instead of letting them escape post_rpc

aiohttp signals a request timeout with asyncio.TimeoutError, which is not
an aiohttp.ClientError. post_rpc caught only the latter, so every RPC
timeout propagated to the caller unretried — and str() on it is empty, so
whatever logged it printed a blank reason.

Found while running learning-examples/live_listener_matrix.py: three of
the four listeners died mid-run with "CRASHED: " and no message. The
endpoint was answering getHealth in ~100ms while getAccountInfo hung past
60s, and every caller that touches it (sol_balance, ata_is_closed,
AccountCleanupManager.cleanup_ata) went down with it. With the retry in
place the same run degrades to a logged failure and completes.

Also makes the verifier report a raising check as a failure rather than
aborting the run — several checks assert that a call does NOT raise, so
the raise is the finding and the remaining checks still need to report.

Covered by "RPC timeouts are retried, not raised" in
verify_tx_status_checks.py, mutation-tested against the one-exception
version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(examples): raise websocket frame limit, load .env, drop dead code

Running every learning example turned up three separate reasons a shipped
script could not work at all.

blockSubscribe examples were completely non-functional. websockets
defaults max_size to 1 MiB and Solana's blockSubscribe frames run well
past that, so the connection died with a 1009 close on the first real
block and the retry loop spun. listen_blocksubscribe.py logged 158,613
"message too big" errors in 35s and decoded zero tokens; compare_listeners
produced 10.1M error lines and its block column never reported anything.
Pass max_size=WEBSOCKET_MAX_MESSAGE_BYTES (32 MiB, the value the bot's own
listeners already use) at all 12 example connect sites — logsSubscribe and
programSubscribe included, since they have the same latent ceiling.

After: listen_blocksubscribe decodes tokens with 0 errors, and
compare_listeners reports provider_1_block alongside geyser and logs.

Seven examples read SOLANA_* from the environment but never called
load_dotenv(), so they only ran with variables already exported —
manual_buy, manual_buy_cu_optimized, manual_buy_geyser, manual_sell,
fetch_price, blockSubscribe_extract_transactions and
sample_cashback_pumpswap. manual_buy died on
"None isn't a valid URI: scheme isn't ws or wss" against a normal .env
checkout.

Dead code and artifacts:

- unused datetime import in listen_pumpportal
- initial_real_token_reserves computed and never read in mint_and_buy and
  mint_and_buy_v2
- gitignore blockSubscribe-transactions/, which a shipped example writes
  into 1200+ files deep and which nothing ignored
- stop tracking trades/trades.log; .gitignore has listed it all along, but
  a tracked file ignores .gitignore

ruff over src/ and learning-examples/ goes 2396 -> 2393 findings: the three
removals, nothing new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix: read balances at confirmed, derive letsbonk platform_config per pool

Running every example against mainnet surfaced four more bugs, three of
them the same root cause as the commitment bug already in this branch:
solana-py defaults to "finalized", but trades confirm at "confirmed", and
finalization lags far enough behind that anything read in between is
pre-trade state.

get_token_account_balance defaulted to finalized. Cleanup reads it to
decide whether to burn before closing, so right after a sell it saw the
pre-sell amount and built a burn for tokens the account no longer held —
the burn + close reverted with InsufficientFunds and the rent stayed
locked. Observed live: "Burning 35766666 tokens" on an account the sell
had already emptied, then Custom(1). Defaults to confirmed now, which is
also what confirm_transaction uses.

manual_sell_pumpswap read the user's base balance at finalized, which
failed two ways in one session: "could not find account" when the ATA had
been created by a buy moments earlier, and a stale non-zero balance whose
transfer then reverted with insufficient funds. Pool vault reads move to
confirmed too, so quotes are not priced off stale reserves.

The letsbonk examples hardcoded platform_config. LaunchLab pools do not
share one — partner launches carry their own — so every buy/sell against
such a pool failed with ConstraintAddress (2012):

  AnchorError caused by account: platform_config
  Left:  5thqcDwKp5QQ8US4XRMoseGeGbmLKMmoKZmS6zHrQAsA
  Right: FfYek5vEz23cMkWsdJwG2oa6EphsvXSHrGpdALN4g6W1

PoolState carries platform_config at field 18 and these scripts already
parse the pool, so they now take the pool's own value and keep the
constant only as a fallback. src/platforms/letsbonk already documented
this; only the examples were stale. All four scripts now simulate and land
(buy_exact_in, sell_exact_in, buy_exact_out, sell_exact_out).

They also discarded simulation logs on failure, printing an error number
with no indication of which account or constraint broke. They print the
program logs now — that is how the above was diagnosed.

Runnability and credentials:

- fetch_price, cleanup_accounts and the four letsbonk scripts hardcoded a
  placeholder ("...", "YOUR_TOKEN_MINT_ADDRESS_HERE") and could not run at
  all without editing the source. They take argv[1] now, matching
  manual_sell and the pumpswap scripts. cleanup_accounts takes "2022" as
  argv[2] for Token-2022 mints, which every pump.fun coin is.
- six scripts printed the RPC/WSS endpoint, which carries an API key, into
  stdout. They print only the host now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(cleanup): never burn wrapped SOL, and label what actually happened

learning-examples/cleanup_accounts.py burned any non-zero balance before
closing. The token program rejects a burn of native (wrapped) SOL with
NativeNotSupported (error 10), so the burn + close transaction reverted and
a WSOL account could never be cleaned up — the pumpswap examples leave one
behind on every sell. Closing a WSOL account already returns both the
wrapped lamports and the rent, so there is nothing to burn first.

src/cleanup/manager.py already had this guard; only the example was
missing it. Observed live: "Burning 2206381 tokens" then Custom(10), with
2.2m lamports stranded until the guard went in.

The success line also claimed "Burned and closed" for the unwrap path,
which burns nothing. It now reports Unwrapped/Burned/Closed to match the
instructions actually built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* chore: drop captured-output artifacts and a spent one-off investigation script

learning-examples/decoded_buy_tx_from_getTransaction.json and
decoded_create_tx_from_getTransaction.json are not JSON — they are captured
stdout from the decode scripts, several objects concatenated, so json.load
raises "Extra data" on both. Nothing reads them and nothing can. The
raw_*.json fixtures stay: all three are valid input and were re-checked
through decode_from_getTransaction / decode_from_blockSubscribe /
decode_from_getAccountInfo.

learning-examples/pumpswap/sample_cashback_pumpswap.py was added by #168 to
find the position of the extra account that cashback PumpSwap pools
require. That question is answered — the layouts are in
platforms/pumpfun/instruction_builder.py and machine-checked against the
IDL by verify_v2_account_layout.py. It is undocumented, unlike every other
pumpswap example in the README, and nothing imports it.

Not touched: logs/ (run history), .cursor/.kiro/.windsurf (deliberate
mirrors of the same rules for other editors), and the raw_*.json fixtures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

* fix(tx-status): fail closed on missing metadata, stop retrying landed reverts

Addresses the must-fix findings from review. Two of them were wrong in code
this branch added.

assert_transaction_succeeded folded meta=None into err=None and returned
success. Missing execution metadata means the outcome is unknown, not that
it succeeded — which is the exact mistake this module exists to prevent. It
raises now.

It also always read at "confirmed" while confirm_and_assert accepted a
commitment argument, so confirming at "finalized" and then reading status at
"confirmed" could report success before the finalization the caller asked
for. The commitment is threaded through.

The manual buy/sell retry loops built one message and blockhash before the
loop, so a landed revert was retried by resubmitting byte-identical signed
bytes — three attempts with backoff that could never succeed. Reverts now
raise TransactionRevertedError (a RuntimeError subclass, so existing
handlers keep working) and the loops treat it as terminal. Rebuilding and
re-signing per attempt is the fuller fix but a bigger change to these
scripts than this branch should carry.

cleanup_accounts guessed the mint's token program, defaulting to legacy SPL
unless argv[2] was "2022". The ATA address differs between programs, so a
wrong guess derives an address that does not exist and the script reports
"already closed" for an account it never looked at. It reads the owner off
the mint account instead, which is authoritative, and rejects anything not
owned by a token program. Verified live: pump.fun coins resolve to
Token-2022, letsbonk and USDC to legacy. argv[2] is gone — nothing to guess.

urlsplit(...).netloc keeps any user:pass@ userinfo, so the endpoint
redaction added earlier still printed credentials for providers that put the
key there. Uses .hostname in all five sites.

Four checks added to verify_tx_status_checks.py, each mutation-tested:
missing metadata, revert-is-terminal, commitment propagation, and a scan
that fails if any example goes back to netloc.

Skipped, both minor and pre-existing repo-wide rather than introduced here:
validating that RPC/WSS env vars are non-None before connecting (every
example has this shape; fixing two of them would just make it inconsistent),
and the cryptic base58 error when a letsbonk script runs with no argument,
which matches the "..." placeholder convention the pumpswap and manual_sell
examples already use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFrxcZfCf9C76voCb7M8Pa

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 13:44:35 +02:00
2025-01-14 07:41:05 +07:00
2025-08-21 09:21:44 +08:00
2025-04-30 13:38:32 +02:00
2025-01-07 08:26:19 +07:00

Labs

Chainstack is the leading suite of services connecting developers with Web3 infrastructure

HomepageSupported protocolsChainstack blogBlockchain API reference
Start for free

The project allows you to create bots for trading on pump.fun and letsbonk.fun. Its core feature is to snipe new tokens. Besides that, learning examples contain a lot of useful scripts for different types of listeners (new tokens, migrations) and deep dive into calculations required for trading.

For the full walkthrough, see Solana: Creating a trading and sniping pump.fun bot.

For near-instantaneous transaction propagation, you can use the Chainstack Solana Trader nodes.

For instant updates from the network, you can enable Yellowstone gRPC Geyser plugin (Jito ShredStream enabled by default).

The official maintainers are in the MAINTAINERS.md file. Leave your feedback by opening Issues.

Also by Chainstack — if you prefer a terminal interface or want to give an AI agent trading capabilities:

  • pumpfun-cli — CLI for trading, launching, and managing tokens on pump.fun; buy, sell, wallet management, and smart routing between bonding curve and PumpSwap AMM.
  • pumpclaw — agent skill that equips AI assistants (OpenClaw, Claude Code, Cursor, Codex) with the ability to operate pumpfun-cli.

🚨 SCAM ALERT: Issues section is often targeted by scam bots willing to redirect you to an external resource and drain your funds. I have enabled a GitHub actions script to detect the common patterns and tag them, which obviously is not 100% accurate. This is also why you will see deleted comments in the issues—I only delete the scam bot comments targeting your private keys. Not everyone is a scammer though, sometimes there are helpful outside devs who comment and I absolutely appreciate it.

⚠️ NOT FOR PRODUCTION: This code is for learning purposes only. We assume no responsibility for the code or its usage. Modify for your needs and learn from it (examples, issues, and PRs contain valuable insights).


🚀 Getting started

Prerequisites

  • Install uv, a fast Python package manager.

If Python is already installed, uv will detect and use it automatically.

Installation

1️⃣ Clone the repository

git clone https://github.com/chainstacklabs/pump-fun-bot.git
cd pump-fun-bot

2️⃣ Set up a virtual environment

# Create virtual environment
uv sync

# Activate (Unix/macOS)
source .venv/bin/activate  

# Activate (Windows)
.venv\Scripts\activate

Virtual environments help keep dependencies isolated and prevent conflicts.

3️⃣ Configure the bot

# Copy example config
cp .env.example .env  # Unix/macOS

# Windows
copy .env.example .env

Edit the .env file and add your Solana RPC endpoints and private key.

Edit .yaml templates in the bots/ directory. Each file is a separate instance of a trading bot. Examine its parameters and apply your preferred strategy.

For example, to run the pump.fun bot, set platform: "pump_fun"; to run the bonk.fun bot, set platform: "lets_bonk".

4️⃣ Install the bot as a package

uv pip install -e .

Why -e (editable mode)? Lets you modify the code without reinstalling the package—useful for development!

Running the bot

# Option 1: run as installed package
pump_bot

# Option 2: run directly
uv run src/bot_runner.py

You're all set! 🎉


Note on throughput & limits

Solana is an amazing piece of web3 architecture, but it's also very complex to maintain.

Chainstack is daily (literally, including weekends) working on optimizing our Solana infrastructure to make it the best in the industry.

That said, all node providers have their own setup recommendations & limits, like method availability, requests per second (RPS), free and paid plan specific limitations and so on.

So please make sure you consult the docs of the node provider you are going to use for the bot here. And obviously the public RPC nodes won't work for the heavier use case scenarios like this bot.

For Chainstack, all of the details and limits you need to be aware of are consolidated here: Throughput guidelines <— we are always keeping this piece up to date so you can rely on it.

Built-in RPC Rate Limiting

The bot now includes built-in RPC rate limiting to prevent hitting provider limits:

  • Token bucket algorithm: Smoothly controls request rate while allowing short bursts
  • Configurable max RPS: Set max_rps parameter in SolanaClient (defaults to 25 RPS)
  • Automatic retry logic: Handles 429 (Too Many Requests) errors with exponential backoff
  • Shared session management: Reuses connections for improved performance

This helps ensure reliable operation within your node provider's rate limits without manual throttling.

IDLs

The IDLs under idl/ are vendored from pump-fun/pump-public-docs. To refresh, copy pump.json, pump_amm.json, pump_fees.json from that repo into pump_fun_idl.json, pump_swap_idl.json, pump_fees.json respectively, and reference the upstream commit hash in your commit message.

Currently vendored from upstream commit 9c82f61.

The IDL under-reports the legacy instructions. It omits two PDAs that the on-chain program requires on the pre-v2 path:

  • bonding-curve-v2 — required on every legacy BC buy (18 accounts) and sell (16/17 accounts). Seed: ["bonding-curve-v2", mint] under the pump program.
  • pool-v2 — required on every PumpSwap buy/sell. Seed: ["pool-v2", base_mint] under the pump-amm program. Without it, pump-amm throws AnchorError 6023 (Overflow) after the trade transfers complete — a misleading error code for a missing-account issue.

The buy_v2 / sell_v2 account lists are complete in the IDL — that's the point of the v2 interface. For anything else, cross-check against a recent successful on-chain tx (getSignaturesForAddress + getTransaction) before trusting the IDL.

Quote assets: SOL and USDC (v2 trade instructions)

Pump.fun added support for quote assets other than SOL, with USDC first. The bonding curve carries a quote_mint field (Pubkey::default() for SOL-paired coins), and trading non-SOL-paired coins requires the newer buy_v2 / sell_v2 instructions — the legacy buy / sell cannot do it at all.

The bot uses buy_v2 (27 accounts) and sell_v2 (26 accounts) for every pump.fun trade. Every account is mandatory and the order is identical for all coins, regardless of quote asset, mayhem mode, or cashback — no more conditional account lists.

To trade a non-SOL quote asset, give it a spend amount. Amounts are in that mint's own whole units, so usdc: 1.0 is one USDC and is not comparable to buy_amount:

trade:
  buy_amount: 0.0001 # SOL-paired coins
  quote_amounts:
    usdc: 1.0 # USDC-paired coins

filters:
  allowed_quote_mints: ["sol", "usdc"] # omit to allow any configured quote

Keys accept the aliases sol / wsol / usdc or a raw base58 mint address. A coin whose quote mint has no configured amount is skipped with a log line rather than bought with a wrongly-scaled amount. SOL always falls back to buy_amount, so existing configs keep working untouched.

Buying a USDC-paired coin requires USDC in the wallet plus a little SOL for fees and ATA rent.

Verify the v2 wiring after any program upgrade:

uv run learning-examples/verify_v2_account_layout.py    # offline: layouts, PDAs, encoding
uv run learning-examples/simulate_v2_trades.py <MINT>   # mainnet simulation, no funds moved
uv run learning-examples/simulate_bot_buy_path.py       # whole bot buy path, simulated

PumpSwap: quote against effective reserves

The PumpSwap Pool account gained a trailing virtual_quote_reserves field (an i128 at offset 245; fields end at 261, live accounts are 301 bytes). Price must be computed from effective quote reserves:

effective_quote_reserves = pool_quote_token_account.amount + Pool::virtual_quote_reserves

⚠️ Upstream's release note says virtual_quote_reserves is 0 on every pool. That is out of date. Verified on mainnet: pool 6Bv1JM1deBPeEeovhoRajFtbMVDKQidC9mXYRQWfGoXz holds 17.584505433 SOL of virtual reserves against a 148.455 SOL vault — quoting off the raw vault balance under-prices by ~10.6%. Note it is i128, not u64; an 8-byte read only works while the high half happens to be zero.

learning-examples/pumpswap/manual_buy_pumpswap.py and manual_sell_pumpswap.py read and apply it. Note that pump-amm has no buy_v2/sell_v2 — the AMM instruction names are unchanged; only the pool layout and quoting moved.

2026-04-28 program upgrade

Pump.fun shipped a breaking program upgrade on 2026-04-28 16:00 UTC (BREAKING_FEE_RECIPIENT.md). This section describes the legacy instruction path, which the bot retains only as a fallback (PumpFunInstructionBuilder(..., use_legacy_instructions=True)) — see the quote-assets section above for the v2 path used by default:

  • BC buy ix is now 18 accounts (was 17). Trailing account is one of 8 BREAKING_FEE_RECIPIENTS (mutable), AFTER bonding-curve-v2.
  • BC sell ix is now 16 accounts non-cashback / 17 cashback (was 15/16). Same trailing fee recipient.
  • PumpSwap buy/sell get +2 accounts appended after pool-v2: a fee recipient (readonly) and its quote-mint ATA (mutable). Counts: buy = 26 non-cashback / 27 cashback; sell = 24 / 26. Cashback pools insert user_volume_accumulator_quote_ata (writable) BEFORE pool-v2 on buys; sells insert both that ATA and user_volume_accumulator (both writable) BEFORE pool-v2. Detect cashback via pool data byte 244.

The 8 fee recipients are randomized per tx in code (per pump.fun's recommendation to spread program-tx throughput).

Changelog

Quick note on a couple on a few new scripts in /learning-examples:

(this is basically a changelog now)

Also, here's a quick doc: Listening to pump.fun migrations to Raydium

Bonding curve state check

get_bonding_curve_status.py — checks the state of the bonding curve associated with a token. When the bonding curve state is completed, the token is migrated to Raydium.

To run:

uv run learning-examples/bonding-curve-progress/get_bonding_curve_status.py TOKEN_ADDRESS

Listening to the Pump AMM migration

When the bonding curve state completes, the liquidity and the token graduate to Pump AMM (PumpSwap).

listen_logsubscribe.py — listens to the migration events of the tokens from bonding curves to AMM and prints the signature of the migration, the token address, and the liquidity pool address on Pump AMM.

listen_blocksubscribe_old_raydium.py — listens to the migration events of the tokens from bonding curves to AMM and prints the signature of the migration, the token address, and the liquidity pool address on Pump AMM (previously, tokens migrated to Raydium).

Note that it's using the blockSubscribe method that not all providers support, but Chainstack does and I (although obviously biased) found it pretty reliable.

To run:

uv run learning-examples/listen-migrations/listen_logsubscribe.py

uv run learning-examples/listen-migrations/listen_blocksubscribe_old_raydium.py

The following two new additions are based on this question associatedBondingCurve #26

You can take the compute the associatedBondingCurve address following the Solana docs PDA description logic. Take the following as input as seed (order seems to matter):

  • bondingCurve address
  • the Solana system token program address: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
  • the token mint address

And compute against the Solana system associated token account program address: ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.

The implications of this are kinda huge:

  • you can now use logsSubscribe to snipe the tokens and you are not limited to the blockSubscribe method
  • see which one is faster
  • not every provider supports blockSubscribe on lower tier plans or at all, but everyone supports logsSubscribe

The following script showcase the implementation.

Compute associated bonding curve

compute_associated_bonding_curve.py — computes the associated bonding curve for a given token.

To run:

uv run learning-examples/compute_associated_bonding_curve.py and then enter the token mint address.

Listen to new tokens

listen_logsubscribe_abc.py — listens to new tokens and prints the signature, the token address, the user, the bonding curve address, and the associated bonding curve address using just the logsSubscribe method. Basically everything you need for sniping using just logsSubscribe (with some limitations) and no extra calls like doing getTransaction to get the missing data. It's just computed on the fly now.

To run:

uv run learning-examples/listen-new-tokens/listen_logsubscribe_abc.py

So now you can run compare_listeners.py see which one is faster.

uv run learning-examples/listen-new-tokens/compare_listeners.py

Also here's a doc on this: Solana: Listening to pump.fun token mint using only logsSubscribe


Pump.fun bot development roadmap (March - April 2025, mostly completed)

As of March 21, 2025, the bot from the refactored/main-v2 branch is signficantly better over the main version, so the suggestion is to FAFO with v2.

As of April 30, 2025, all changes from refactored/main-v2 are merged into the main version.

Stage Feature Comments Implementation status
Stage 1: General updates & QoL Lib updates Updating to the latest libraries
Error handling Improving error handling
Configurable RPS Ability to set RPS in the config to match your provider's and plan RPS (preferably Chainstack 🤩)
Dynamic priority fees Ability to set dynamic priority fees
Review & optimize json, jsonParsed, base64 Improve speed and traffic for calls, not just getBlock. Helpful overview.
Stage 2: Bonding curve and migration management logsSubscribe integration Integrate logsSubscribe instead of blockSubscribe for sniping minted tokens into the main bot
Dual subscription methods Keep both logsSubscribe & blockSubscribe in the main bot for flexibility and adapting to Solana node architecture changes
Transaction retries Do retries instead of cooldown and/or keep the cooldown
Bonding curve status tracking Checking a bonding curve status progress. Predict how soon a token will start the migration process
Account closure script Script to close the associated bonding curve account if the rest of the flow txs fails
PumpSwap migration listening pump_fun migrated to their own DEX — PumpSwap, so we need to FAFO with that instead of Raydium (and attempt logSubscribe implementation)
Stage 3: Trading experience Take profit/stop loss Implement take profit, stop loss exit strategies
Market cap-based selling Sell when a specific market cap has been reached Not started
Copy trading Enable copy trading functionality Not started
Token analysis script Script for basic token analysis (market cap, creator investment, liquidity, token age) Not started
Archive node integration Use Solana archive nodes for historical analysis (accounts that consistently print tokens, average mint to raydium time) Not started
Geyser implementation Leverage Solana Geyser for real-time data stream processing
Stage 4: Minting experience Token minting Ability to mint tokens (based on user request - someone minted 18k tokens)

Languages
Python 100%