* fix(learning-examples): replace getProgramAccounts scan in get_graduating_tokens
The pump.fun program owns over 10M accounts and no provider will scan it, so
get_graduating_tokens.py could not run at all (#178). getProgramAccountsV2 is
not a fix: it is a provider extension rather than core Agave, and its `limit`
is a scan budget, not a result count, so one filtered answer costs ~1000
sequential pages.
Rewrite discovery onto filtered programSubscribe, which applies dataSize and
memcmp server-side and is accepted even by public api.mainnet-beta.solana.com.
Every write to a curve pushes the full 151-byte account, so progress is
computed per update with no accumulated state. Add a Geyser sibling that
reports the same thing with the slot and signature behind each update.
Also fix two bugs that would have survived the rewrite: the mint lookup
queried SPL Token, which returns nothing for the Token-2022 ATAs that every
create_v2 coin uses, and the threshold was a hardcoded constant rather than
Global.initial_real_token_reserves.
Closes#178
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(learning-examples): drop the graduation cutoff that filtered nothing
zero_prefix_gate offered a cutoff so high that no coin could fail it, so for any
--min-progress below 64.5% the subscription was unfiltered while the banner
reported a filter as active. Offer only the three cutoffs that actually narrow,
and say plainly when none applies.
Rewrite the threshold notes in both scripts in plain English: which cutoffs
exist, whether a given threshold gets one, and the part that matters — the
pre-filter saves bandwidth but does not decide the answer, so the requested
percentage is honoured either way. The banner now names the cutoff as a
percentage instead of byte offsets.
Both directions were checked against mainnet by running the filtered and
unfiltered subscriptions side by side for a minute, on both transports: the
filtered stream matched the below-cutoff set exactly, with 143 of 168 curves
above the cutoff on WebSocket and 128 of 154 on Geyser.
Also document the two scripts in the README example table, and record under
throughput that getProgramAccounts over the whole pump.fun program is no longer
served by any provider.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Audited every script in learning-examples/ against mainnet.
Broken — found by running them, all invisible offline:
- listen_geyser.py crashed with IndexError after ~11 coins: it never resolved
v0 address-lookup-table accounts, which geyser reports in
meta.loaded_writable_addresses / loaded_readonly_addresses. Resolving them
removes the crash and brings detections level with the WebSocket listeners,
35 coins each per 150 s.
- compare_listeners.py logged 13,090,862 error lines / 888 MB in 150 s and never
printed its own 30-second report: the inner recv() loop caught ConnectionClosed
in a broad handler that only logged, so every following recv() raised at once
and the outer reconnect handler was unreachable. Now 12 KB and exit 0. Same
shape fixed in compare_migration_listeners.py, listen_blocksubscribe.py and
extract_blocksubscribe_transactions.py; the last two also gained the reconnect
loop their siblings already had.
- decode_from_gettransaction.py matched instructions on account count instead of
discriminator, reporting a real 19-account create_v2 as claim_cashback with
every account under the wrong name. It also walked only top-level
instructions, and in 40 consecutive pump.fun transactions there was 1
top-level pump instruction against 8 inner ones.
- decode_from_blocksubscribe.py crashed on every real create_v2: on chain the
trailing args are variable length, 0001 in one tx and 00 in another, so
is_cashback_enabled can be absent entirely.
- poll_bonding_curve_progress.py polled a hardcoded dead mint and took no argv.
Obsolete:
- Delete listen_blocksubscribe_old_raydium.py. Seven minutes on mainnet produced
0 initialize2 events while the wrapper listener caught 3 real migrations.
- Delete the duplicate geyser stubs and protos under listen-new-tokens/. The
protos were byte-identical to src/geyser/proto and the stubs had drifted; both
geyser examples now import src.geyser.generated.
- Recapture all four fixtures. The old ones were from Aug 2024 and included a
49-byte pre-creator bonding curve.
Behind the protocol:
- fetch_price.py, get_bonding_curve_status.py, poll_bonding_curve_progress.py
and decode_from_getaccountinfo.py never read quote_mint and scaled by a
hardcoded 1e9. Against a live USDC-paired curve the price was off by 1000x.
- get_pumpswap_pools.py stopped parsing at coin_creator and missed the i128
virtual_quote_reserves. Live pools carry 17.5845 SOL of them, which
under-prices by 3.5-23.9% when ignored.
Duplication and naming:
- Merge manual_buy_cu_optimized.py into manual_buy.py --cu-optimized. The
deleted file's docstring said 512 KB while its code used 16 MB; simulation
confirms 512 KB and 4 MB both fail MaxLoadedAccountsDataSizeExceeded on
Token-2022 mints, so 16 MB is the correct value.
- Merge listen_logsubscribe_abc.py into listen_logsubscribe.py. Its ATA
derivation hardcoded the legacy token program, so every Associated BC it
printed for a Token2022 coin was an address that does not exist on chain.
Fixed on merge and cross-checked 59/59 against on-chain accounts.
- Remove 19 dead symbols. BREAKING_FEE_RECIPIENTS is still live in the PumpSwap
scripts and stays there.
- Normalize naming: kebab-case directories, RPC method names as one lowercase
token, scripts verb-first. Rules documented in CLAUDE.md.
get_graduating_tokens.py is knowingly left broken: getProgramAccounts over the
whole pump program is now rejected by providers and it needs a
getProgramAccountsV2 rewrite, which belongs in its own PR.
Verified: both offline gates pass, all 41 examples parse, every read-only script
exercised on mainnet against SOL- and USDC-paired coins, no new ruff findings
(427 -> 413). No script that spends real funds was run.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three small ergonomic fixes uncovered while running every learning
example end-to-end:
copytrading/listen_wallet_transactions.py
Was hardcoded to WALLET_TO_TRACK = "...". Now accepts argv[1] so
you can run it without editing the file.
pumpswap/get_pumpswap_pools.py
Same pattern (TOKEN_MINT placeholder) and the underlying
getProgramAccounts call would also time out at the default 30s
on busy mainnet RPCs — bumped both AsyncClient timeouts to 120s.
bonding-curve-progress/get_graduating_tokens.py
Bumped RPC timeout 120s → 240s for the same reason. The threshold
scan over all pump.fun bonding curves still occasionally exceeds
even that on overloaded RPCs (it's a known-heavy query); a smaller
threshold or memcmp-narrowed filter is the proper long-term fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refreshes the read-only learning examples (Phase A3) against the post
Feb–Apr 2026 IDL changes:
- CreateEvent now has 15 fields (timestamp, 4×u64 reserves, token_program,
is_mayhem_mode, is_cashback_enabled). Listener parsers were truncating
after `creator`; the v2 ones were also reading is_mayhem_mode at the
wrong offset.
- BondingCurve account is now 83 bytes (added is_cashback_coin).
- PumpSwap Pool account is now 245 bytes (added is_cashback_coin) — the
listen_programsubscribe dataSize filter was matching nothing.
- IDL instruction `createV2` was renamed to `create_v2`; the legacy
`create` instruction added a `creator: pubkey` arg in March 2026.
- decode_from_blockSubscribe.py decoder now handles bool, i64, u32, u16,
u8, and the OptionBool defined type (1-byte wire format).
Files touched (all under learning-examples/):
bonding-curve-progress/get_bonding_curve_status.py — V4 struct (cashback)
bonding-curve-progress/poll_bonding_curve_progress.py
decode_from_blockSubscribe.py
decode_from_getAccountInfo.py
decode_from_getTransaction.py
listen-migrations/compare_migration_listeners.py
listen-migrations/listen_programsubscribe.py
listen-new-tokens/compare_listeners.py
listen-new-tokens/listen_blocksubscribe.py
listen-new-tokens/listen_geyser.py
listen-new-tokens/listen_logsubscribe.py
listen-new-tokens/listen_logsubscribe_abc.py
Verified live against mainnet — listeners decode tokens including
mayhem-mode and cashback-enabled curves correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>