Files
pumpfun-bonkfun-bot_github/README.md
T
55ae2afd82 feat(pumpfun): resync with the 2026-09-15 program upgrade (#202)
* feat(idl): refresh to upstream 8109141 and decode omitted OptionU64 args

Vendors pump.json, pump_amm.json and pump_fees.json from
pump-fun/pump-public-docs@8109141 (holder rewards coins, cashback
deprecation). create_v2 gains two trailing optionals: creator_fee_bps
(OptionU64) and is_holder_reward (OptionBool).

_is_optional_type only recognised OptionBool, so a create_v2 that omits
creator_fee_bps failed the whole decode and the coin dropped out of the
instruction path — the same failure #184 fixed for is_cashback_enabled.
Reproduced against two live mainnet create_v2 transactions captured
2026-09-15: one omits creator_fee_bps (157-byte instruction data), one
sends it (156 bytes) — both are added as getTransaction fixtures under
learning-examples/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(learning-examples): accept 125-byte bonding curves in dataSize filters

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(learning-examples): replace unrun/recycled curve-size claims with dated live measurements

The 45s/205-update/24-curve figures and the logsSubscribe/same-tx
extend_account claim in the two graduating-token docstrings were
carried over from before the 125/151 filter change and never
re-verified under it. Replaced with fresh 2026-09-15 measurements: a
length-unfiltered programSubscribe run, a direct 125->151 transition
observed on one curve, and a live run of the Geyser script's own
two-group build_subscribe_request confirming both filter groups
actually deliver. Unreproduced claims are now marked UNVERIFIED
in-line instead of stated flatly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(learning-examples): stop enumerating bonding-curve dataSize lengths

extend_account can grow a curve to any length the program allows, so the
CURVE_ACCOUNT_LENS = (125, 151) allowlist in both graduating-token scripts
was whack-a-mole -- a live 256-byte curve was already missed. Drop the
dataSize/datasize server-side filter entirely (discriminator + complete
memcmp already restrict delivery to BondingCurve accounts) and replace the
allowlist with a MIN_CURVE_LEN client-side floor. Collapse the Geyser
script's two named filter groups back to one now that there's no length to
fan out over. Extend verify_curve_account_sizes.py to assert neither script
constructs a dataSize filter and that 125/151/256-byte curves all decode.

Measured live 2026-09-15, filtered vs unfiltered subscribed simultaneously
so both see identical trade activity: ~0.6-0.7% extra traffic on WebSocket,
statistically zero on Geyser -- dropping the filter is effectively free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(learning-examples): remove untraceable bandwidth figure, session wording

Review of the dataSize-enumeration fix found a docstring figure (90s window,
2/224/866 updates at 256/125/151 bytes) that no command in the fix report
actually produced -- it was a byproduct of the discarded sequential-window
run, never written down with its command+output. Drop it and rely only on
the properly-documented 120s simultaneous measurement already backing the
rest of the paragraph.

Also replace two "carried from an earlier session" UNVERIFIED markers with
the repo's own convention (UNVERIFIED: <what>, no reference to how or when
the file was edited), and give both the update and byte ratio (1.006x /
1.007x) instead of a single derived percentage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(pumpfun): surface is_holder_reward and creator_fee_bps on TokenInfo

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(pumpfun): verify the USDC half of the creator_fee_bps claim

The comment on TokenInfo.creator_fee_bps stated flatly that SOL- and
USDC-paired coins always carry bps 0, but only the SOL half had been
observed live. Found 10 USDC-paired bonding curves (via Dune's indexed
CreateEvent table) and read each one directly through
PumpFunCurveManager.get_pool_state: all 10 show creator_fee_bps=0,
matching every custom-pair coin observed showing nonzero. Dated the
verification in the comment instead of leaving it an unmarked claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(examples): create holder-reward coins; mark cashback legacy-only

create_v2 now rejects is_cashback_enabled=[true] with 6082
CashbackDeprecated as of the 2026-09-15 upgrade. Add ENABLE_HOLDER_REWARD
to mint_and_buy_v2.py so the trailing create_v2 args can reach
is_holder_reward instead, and relabel the still-live cashback code paths
in core.py, instruction_builder.py and curve_manager.py with the
deprecation date. No cashback account handling is removed anywhere -
existing cashback coins keep trading and accruing exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(pumpfun): resolve quote mints to their real token program

A 2026-09-15 pump.fun upgrade lets create_v2 pair a coin with a Token-2022
quote mint (error 6064 now accepts "SPL Token or Token-2022"), and such
coins already trade on mainnet. quote_token_program() only knew WSOL/USDC
and silently defaulted everything else to SPL Token, deriving associated
token accounts that don't exist on chain for any other quote mint.

Add an async resolver that reads a mint's owner from chain and caches it
for the process, pre-seeded with WSOL/USDC so they stay free. The bot
warms this cache once at startup for every configured quote mint; the
hot path (event parsing, address resolution) only ever reads the cache,
preserving extreme_fast_mode's zero-RPC contract between detection and
submission. quote_token_program() itself is untouched so its
existing call sites keep their non-optional Pubkey return.

Also fixes the standalone pump_v2.py learning-examples helper, which had
the same bug in a form that ignored its argument entirely, and gives
simulate_bot_buy_path.py a quote_amounts entry so it can exercise a
non-SOL-paired coin instead of unconditionally skipping it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(core): name Anchor error codes when reporting a reverted transaction

confirm_transaction only answers whether a transaction landed; meta.err
carries the revert. Logging it raw as {'InstructionError': [idx,
{'Custom': n}]} told an operator nothing, and pump.fun's 2026-09-15
upgrade added 22 new error codes to the pump program and 10 to
pump-amm, so an unnamed number is now more likely to be wrong than
useful. IDLParser.describe_error_code() looks a Custom(N) code up in
an IDL's error table; SolanaClient logs the match alongside the raw
meta.err at both revert sites, so a buy reverting with
BuybackFeeRecipientMissing (issue #175) now prints "6062
BuybackFeeRecipientMissing" instead of a bare 6062.

SolanaClient is shared across pump.fun and letsbonk.fun and has no
record of which program a given transaction invoked, so this only
checks pump.fun's table -- the program the bot's own buy_v2/sell_v2
call directly. A revert on a different program is described against
the wrong table if the code number collides, or left unnamed
otherwise; see the docstring on _describe_program_error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(core): correct get_account_info's return type annotation

It was typed dict[str, Any] but solana-py's AsyncClient.get_account_info
actually returns a solders Account object. Every real caller already
reads attributes (.data, .owner) rather than subscripting, so this was
latent rather than a live bug -- but the wrong annotation is what let
those callers work by accident, and would mislead anyone who trusted
the type and reached for result["owner"] instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(core): tag the revert description with the table it came from

The name appended to a reverted transaction's log line looked like a
fact about whichever program actually reverted, but it is only ever
looked up in pump.fun's error table. This bot also trades letsbonk.fun,
so a letsbonk revert whose numeric code happens to also exist in
pump's table would get a confident, wrong name -- worse than the bare
number it replaced, since a wrong name stops the reader from checking.
Prefixing the description with "pump.fun IDL:" carries the same
limitation the docstring already stated out to the log line itself, so
it's visible without reading the source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: resync CLAUDE.md and README.md with the 2026-09-15 program upgrade

The bonding curve is 125 bytes now (not 151, no more padding), create_v2
takes eight args instead of six, cashback creation is deprecated (6082
CashbackDeprecated) while existing cashback coins keep trading, quote
assets are no longer just SOL/USDC (QuoteControl, not Global, is the
authoritative mint registry), and holder-reward coins were undocumented
entirely. Add verify_curve_account_sizes.py to both docs' verification
lists, retire the superseded CU comments in instruction_builder.py (the
180k/120k limits are untouched), and fix two stale cashback comments plus
a docstring in client.py that showed a msg suffix code 6062 doesn't have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(trading): refresh quote_token_program_id alongside quote_mint

_refresh_quote_mint synced token_info.quote_mint from freshly-read curve
state but left quote_token_program_id at its construction-time value. A
stale non-None program id wins the `or` in
PumpFunAddressProvider.resolve_quote, so a curve refresh that reveals a
Token-2022 quote mint still resolves the associated_quote_* ATAs under SPL
Token -- an address that does not exist on chain.

cached_quote_token_program is a synchronous dict read, so this keeps
extreme_fast_mode's zero-RPC-call contract between detection and
submission.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(examples): resolve the quote mint's real token program in manual buy/sell

manual_buy.py and manual_sell.py derived the quote ATA's owning program
through pump_v2.quote_token_program, which defaults any mint outside the
WSOL/USDC map to SPL Token. A pump.fun upgrade verified 2026-09-15 lets
create_v2 pair a coin with a Token-2022 quote mint (error 6064 now accepts
"SPL Token or Token-2022"), so that default derives an ATA that does not
exist on a Token-2022-quoted coin -- and these scripts submit real
transactions, so the failure burns one.

Both now call pump_v2.resolve_quote_token_program (free for WSOL/USDC via
its cache, one RPC call otherwise) and thread the resolved program into
both the buy_v2/sell_v2 instruction and the quote ATA creation.

Verified against a real Token-2022-quoted pump.fun coin found via Dune
(pumpdotfun_solana.pump_evt_createevent, mint G5Ve125Poi7KoDRP5uLsgu5NQdm3KoqfyUkq4cqtTo4r,
quote_mint Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh, confirmed Token-2022-owned
by RPC read): the old default derives associated_quote_bonding_curve
FViEZvbcYjoSwGE2b37HiCXmPT3N7kSd1nQavFzcD7Yb, which does not exist on chain;
the fixed resolution derives DfxcBKXTLDqobNEmR5JkaBeoKqNbJE1vKbtQVbowXz4x,
which exists on chain and is owned by the Token-2022 program.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(core): correct get_multiple_accounts's return type annotation

Declared as list[Any] while it actually returns a list of solders Account
objects (or None for missing accounts) -- the same stale-annotation
pattern already corrected on get_account_info.

Checked the one caller, PumpFunCurveManager.get_pool_state_and_token_program,
which already unpacks the two entries and reads .data / .owner off them
directly rather than subscripting -- no live bug behind the wrong
annotation here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(examples): use the public decode API in the optional-args verifier

check_omitted_option_u64_decodes and check_option_u64_present_decodes
reached into IDLParser._decode_instruction_args behind # noqa: SLF001
instead of the public decode_instruction(data, keys, accounts) already
used by every other check in this file. The fixtures already carry valid
accounts/keys (previously discarded as data, _, _), so the public method
expresses the same check -- confirmed it returns byte-for-byte identical
decoded args as the private call did.

Confirmed the check stays load-bearing: reverting _is_optional_type to a
narrow == "OptionBool" comparison drops the verifier to 2/7 checks passed,
exit 1, including the two checks touched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: correct unverified claims from the final branch review

Text-only fixes from the whole-branch review — no code defects found.

- CLAUDE.md: create_v2 trailing-arg claim was circular (sourced from our own
  minter script) and wrong — the three committed fixtures show 0, 1 and 2
  trailing args, never 3. Rewritten with the counted byte lengths and dated;
  the unobserved 3-arg form is now marked UNVERIFIED instead of asserted.
- src/interfaces/core.py, CLAUDE.md: corrected an inflated "25 SOL-paired"
  sample to the actual 21 (25 coins captured live, 4 of them custom-pair).
- instruction_builder.py: buy_v2/sell_v2 CU comments cited figures from a
  task brief that were never re-measured on this branch. Re-measured via
  simulate_v2_trades.py on 2026-09-15 (106,677 / ~77,196 CU) and updated the
  comments; the configured 180k/120k limits are unchanged.
- simulate_bot_buy_path.py: removed a working-session reference ("this
  task") from a comment.
- get_graduating_tokens.py, get_graduating_tokens_geyser.py's sibling docs
  (pump_v2.py, fetch_price.py, get_bonding_curve_status.py,
  verify_pumpportal_buy_path.py): made the curve-length story consistent
  (125/151/256, don't enumerate lengths) instead of a stale "125 or 151"
  framing; resolved an UNVERIFIED marker that mint_and_buy_v2.py already
  answers (extend_account can share a transaction with create_v2); removed
  a redundant restated clause in the bandwidth-measurement paragraph.

Verified: all 7 verify_*/simulate offline+mainnet checks still exit 0,
buy_v2/sell_v2 account counts unchanged at 27/26.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(trading): resolve quote-mint decimals instead of defaulting to 9

quote_decimals()/quote_units_per_token() defaulted every quote mint outside
the WSOL/USDC map to SOL's 9 decimals, mis-scaling max_sol_cost/min_sol_output
by up to 1000x for a custom Token-2022 quote mint (verified: 6 decimals).
Extend resolve_quote_token_program's existing mint-account fetch to also
parse and cache decimals from the same read (byte offset 44, shared by SPL
Token and Token-2022), so the fix costs zero extra RPC calls and the
zero-RPC extreme_fast_mode contract holds. Mirrored the same treatment in
learning-examples/pump_v2.py's standalone copy.

Also decode is_holder_reward in the instruction-only create_v2 parse path
(a landed transaction proves it, since the program rejects the flag when
globally disabled) while leaving creator_fee_bps at its default, since
upstream docs say it's ignored for SOL/USDC-paired coins and nothing reads
it today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(examples): strengthen optional-arg verifier assertions

Fix verify_create_v2_optional_args.py to properly assert value equality
rather than just presence when checking optional arguments:

- check_omitted_option_u64_decodes: verify both creator_fee_bps and
  is_holder_reward are exactly None (not just present in dict)
- check_option_u64_present_decodes: decode the fixture, verify
  creator_fee_bps shape is {"field_0": <value>}, and assert the actual
  value (0) rather than just non-null
- Fixture decodes to creator_fee_bps={'field_0': 0}, is_holder_reward=None

Also fix README.md line 166: "Three examples" → "Four examples" to match
the four verification commands listed.

Temporary revert of idl_parser.py's _is_optional_type confirms the
strengthened assertions are load-bearing: changing from startswith("Option")
back to == "OptionBool" causes 4 of 7 checks to fail.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EkvFUgSiZoaF7w4xugrgBc

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 09:36:44 +02:00

13 KiB
Raw Blame History

Labs

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

• Homepage • Supported protocols • Chainstack blog • Blockchain API reference •
• Start for free •

A Solana trading bot for pump.fun and letsbonk.fun. Its core feature is sniping new tokens: it watches for token creation, buys, and exits on a strategy you configure. learning-examples/ contains standalone scripts covering every piece of the flow — listeners, price math, manual buys and sells — useful on their own even if you never run the bot.

For the full walkthrough, see Solana: Creating a trading and sniping pump.fun bot. It explains the concepts well but lags behind the code, so treat this README as the source of truth for setup and configuration.

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 the 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: The Issues section is regularly targeted by scam bots that try to redirect you to an external site and drain your funds. A GitHub Action tags the common patterns, which is not 100% accurate. Deleted comments in issues are scam bots after your private keys — genuine outside devs are welcome and appreciated.

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


Getting started

1. Prerequisites

Install uv, a fast Python package manager. The project needs Python 3.11+; uv uses an existing install if it's new enough, otherwise it fetches one for you.

2. Clone and install

git clone https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
cd pumpfun-bonkfun-bot

uv sync                        # create .venv and install dependencies
source .venv/bin/activate      # Unix/macOS — Windows: .venv\Scripts\activate
uv pip install -e .            # install the bot as an editable package

3. Set your credentials

cp .env.example .env

Fill in .env:

Variable Purpose
SOLANA_NODE_RPC_ENDPOINT HTTPS RPC endpoint
SOLANA_NODE_WSS_ENDPOINT WebSocket endpoint (for logs / blocks listeners)
SOLANA_PRIVATE_KEY Base58 private key of the trading wallet
GEYSER_ENDPOINT, GEYSER_API_TOKEN, GEYSER_AUTH_TYPE Only for the geyser listener

Public RPC nodes will not work for this workload — see throughput below.

4. Configure a bot

Each YAML file in bots/ is one bot instance. They ship with commented defaults; start from the one matching the listener you want:

File Listener Ships with
bot-sniper-1-geyser.yaml geyser — fastest, needs a Geyser endpoint pump_fun
bot-sniper-2-logs.yaml logs — logsSubscribe, supported everywhere pump_fun
bot-sniper-3-blocks.yaml blocks — blockSubscribe, not supported by every provider pump_fun
bot-sniper-4-pp.yaml pumpportal — third-party aggregator lets_bonk

Set platform: "pump_fun" or platform: "lets_bonk". pump.fun supports all four listeners; letsbonk.fun supports blocks, geyser, and pumpportal but not logs. The bot validates the pairing at startup and refuses to run an invalid one.

Set enabled: false to keep a config around without running it. Every bot with enabled: true starts when you run the bot.

5. Run

pump_bot                   # as an installed package
uv run src/bot_runner.py   # or directly

Logs land in logs/{bot_name}_{timestamp}.log.

Configuration reference

The YAML files are commented inline. The sections that matter most:

  • trade — buy_amount (in SOL), slippage, exit_strategy (time_based, tp_sl, manual), and extreme_fast_mode, which skips the bonding-curve price check and buys a fixed token amount instead. Faster, less precise. See Extreme fast mode for the zero-RPC behavior and its two knobs, trust_create_event and curve_refresh_budget.
  • priority_fees — fixed or dynamic. Dynamic costs an extra RPC call, which slows the buy.
  • filters — listener_type, max_token_age, name/creator matching, marry_mode (buy only, never sell), yolo_mode (trade continuously).
  • retries — attempts and the wait windows around creation, buy, and the next token.
  • cleanup — when to close leftover token accounts: disabled, on_fail, after_sell, post_session.
  • node.max_rps — cap requests per second to match your provider's plan.

Extreme fast mode: zero-RPC buys

With extreme_fast_mode: true the bot buys a fixed token amount (extreme_fast_token_amount) instead of fetching the curve price first. For tokens detected through the on-chain CreateEvent — the geyser, logs and blocks listeners — the buy is built entirely from the event: the canonical creator, mayhem/cashback flags and quote mint are all in it, so no RPC call happens between detecting the token and submitting the buy. That is the point of the mode; a single account read costs ~40–50 ms even on a good endpoint, a tenth of a slot.

The pumpportal listener can't do this — its payload carries none of those fields — so it performs one batched account read (bonding curve + mint in a single slot-consistent getMultipleAccounts) before buying. If the curve isn't readable within trade.curve_refresh_budget seconds (default 2.0), the token is skipped: a buy built from guessed accounts reverts on-chain with NotAuthorized (6000) or ConstraintSeeds (2006) and still costs the fee. The same skip applies to any token whose event data was incomplete.

trade.trust_create_event: false turns the zero-RPC path off and forces the pre-buy read for every listener — the safe fallback if pump.fun changes what the CreateEvent carries.

Machine checks: learning-examples/verify_extreme_fast_zero_rpc.py (the zero-RPC contract per listener) and learning-examples/verify_pumpportal_buy_path.py (the refresh/skip path). Neither moves funds.

Non-SOL quote assets

pump.fun supports quote assets other than SOL, USDC first. 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. 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 needs USDC in the wallet plus a little SOL for fees and ATA rent.

Learning examples

Standalone scripts, runnable with uv run <path>. No bot config needed — they read .env directly.

Path What it covers
listen-new-tokens/ One listener per method (logs, blocks, geyser, pumpportal) plus compare_listeners.py to race them
listen-migrations/ Detect a token graduating from the bonding curve to PumpSwap, via the migration wrapper program or new pool accounts
bonding-curve-progress/ Curve state, progress polling, and a live watch for coins close to graduating — over WebSocket (get_graduating_tokens.py) or Geyser (get_graduating_tokens_geyser.py), both taking --min-progress
pumpswap/ Manual buy/sell against the PumpSwap AMM, and pool discovery
letsbonk-buy-sell/ Manual exact-in / exact-out buys and sells on letsbonk.fun
copy-trading/ Watch another wallet's transactions
manual_buy.py, manual_sell.py, fetch_price.py The minimal pump.fun trade and price path. manual_buy.py --cu-optimized adds a SetLoadedAccountsDataSizeLimit instruction
mint_and_buy_v2.py Create a coin and buy it in one go
decode_from_*.py, calculate_discriminator.py Decoding account data, transactions, and Anchor discriminators
cleanup_accounts.py Close leftover empty token accounts

Most of these take the mint or curve address as the first argument, and print usage if you leave it off. The decode_from_*.py scripts fall back to the saved fixtures beside them (raw_*.json), which are recaptured from mainnet rather than hand-edited — a stale fixture makes a working decoder look broken and a broken one look fine.

Four examples double as verification scripts to run after any pump.fun program upgrade:

uv run learning-examples/verify_v2_account_layout.py     # offline: account layouts, PDAs, encoding
uv run learning-examples/verify_curve_account_sizes.py   # offline: 125/151/256-byte curves all decode
uv run learning-examples/simulate_v2_trades.py <MINT>    # mainnet simulation, no funds moved
uv run learning-examples/verify_tx_status_checks.py      # offline: every example checks meta.err

Related docs: Listening to pump.fun migrations · Sniping with only logsSubscribe

Throughput and rate limits

Every node provider has its own limits — method availability, requests per second, plan-specific caps. Consult your provider's docs before running the bot, and don't expect public RPC nodes to hold up.

One case worth knowing about: getProgramAccounts over the whole pump.fun program is no longer served by anyone. That program owns more than 10 million accounts, so providers reject the request or time out no matter which filters you pass. Use a filtered subscription instead — bonding-curve-progress/get_graduating_tokens.py shows the pattern.

For Chainstack, the numbers you need are in the throughput guidelines, kept up to date.

The bot rate-limits itself with a token bucket: node.max_rps in the YAML (25 by default) smooths the request rate while allowing short bursts, and 429s are retried with exponential backoff.

For faster execution, Chainstack offers Solana Trader nodes for transaction propagation and the Yellowstone gRPC Geyser plugin for streaming updates.

IDLs

The IDLs under idl/ are vendored from pump-fun/pump-public-docs — currently upstream commit 8109141. To refresh, copy pump.json, pump_amm.json, and pump_fees.json into pump_fun_idl.json, pump_swap_idl.json, and pump_fees.json, and note the upstream commit in your commit message. Don't hand-edit them.

The buy_v2 / sell_v2 account lists are complete in the IDL — that's the point of the v2 interface. The legacy buy / sell lists are not: the IDL omits PDAs the on-chain program requires. For anything outside v2, cross-check against a recent successful on-chain transaction before trusting the IDL.

CLAUDE.md documents the protocol gotchas in detail — account layouts, quote-mint handling, fee recipients, and what the IDL gets wrong.

Contributing

Maintainers are listed in MAINTAINERS.md. Open an Issue for feedback or bugs.

Lint and format the files you changed (uv sync installs ruff for you):

uv run ruff check --fix path/to/changed.py
uv run ruff format path/to/changed.py

Running ruff check over the whole repo reports a large backlog of pre-existing errors — that's a known baseline, so scope it to your own files.

Then test your change with a learning example rather than by running a bot with real funds.