mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-11-02 06:16:10 +00:00
b04a375506996e0a32fef3192754dc15566b8609
302
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b04a375506 |
fix(cookbook): resolve quote-mint decimals instead of assuming 9 (#222)
QuoteControl admits mints from 4 to 12 decimals, so the default was wrong for most quote assets and silently so. Parsers return raw quote reserves; callers scale off the mint account. Same class elsewhere: pumpswap refuses non-SOL-quoted pools instead of capping in the wrong unit, letsbonk exact_out/exact_in scale by base_decimals, wallet_trades decodes through to quote_mint (fixing an ix_name offset that left later fields three bytes short), balances read at confirmed, cleanup_accounts takes a required mint. Verified on mainnet with real funds; wallet left holding SOL only. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2fc7d99c7e |
fix(tests): match stub hosts on subdomains in the link verifier
`is_checkable` compared the hostname against STUB_HOSTS by equality, so the offline verifiers' `mainnet.example.com`, `rpc.example.com` and `x.example.com` stubs were treated as real URLs and `--live` failed trying to resolve them. Match the host or any subdomain of it instead. Anchored on a leading dot, so `notexample.com` and `example.com.evil.net` still count as real. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
de7fd2aa90 | docs: compact docs and docstrings, split CLAUDE.md per Anthropic guidance (#221) | ||
|
|
2ba40c0771 |
feat(monitoring): add shreds listener for pre-execution token detection (#220)
* feat(monitoring): add shreds listener for pre-execution token detection Adds a `shreds` listener backed by geyser's SubscribeDeshred, which delivers transactions when entries form from shreds, before execution. pump.fun only; bots/bot-sniper-5-shreds.yaml ships disabled. With no TransactionStatusMeta there is no CreateEvent, so the listener decodes the create instruction and resolves what the buy needs itself. It marks its tokens state_from_event, because there is no curve to read before the create executes: trading on the instruction or not trading at all are the only options. Two fixes to the shared instruction-decode path, which geyser and blocks also reach when logs are absent: - `user` is account 5 on create_v2, not 7. create_v2 drops the metaplex accounts legacy create carries, so index 7 is the token program and every address derived from the wallet was wrong. - A holder-reward coin's creator is PDA(["holder-rewards", mint]). The program ignores args.creator on those coins and writes that PDA into BondingCurve.creator, so creator_vault must derive from it or the buy is rejected on a seeds constraint. Subscribes on the mint authority rather than the program id: it appears in create/create_v2 and in no trade instruction, which keeps the stream to creations instead of every pump.fun transaction. Verified live on mainnet with extreme_fast_mode on and off and both the time_based and tp_sl exits, each ending with the wallet holding SOL only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: drop reported measurement statistics from prose Removes sample counts, traffic percentages, latency percentiles, window durations and "measured on <date>" claims from docstrings, comments, docs and bot YAML. The fact each measurement supported is kept and stated plainly. Numbers that are load-bearing rather than reporting stay: protocol and packet limits, the CU figures used to tune compute budgets, and dates pinning on-chain or dependency upgrades. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1e2de43292 |
feat(geyser): upgrade to yellowstone proto 13.0.0-rc4 and read the transaction v1 config (#219)
The vendored geyser protos predated transaction v1 (SIMD-0385, live since 2026-09-15). Message.config -- the inline compute budget that v1 moved off the ComputeBudget instructions -- is field 7, which the old stubs had no definition for. Protobuf skips an unknown field rather than failing, so the gap was silent: frames still decoded and only the budget went missing. Pin to yellowstone v16.0.0-rc10+solana.4.3.0 (commit 301e9cf, proto 13.0.0-rc4), the build the endpoint itself reports from GetVersion. Not master: master has diverged and is missing block_footer, bank_id and VATDebit. Regenerate the stubs (gencode 6.31.1 -> 7.35.1) and rewrite the emitted relative imports to the absolute form this repo requires. Fields adopted: Message.config and TransactionStatusMeta.cost_units. Over geyser there is no version number on the wire and `versioned` is true for v0 and v1 alike, so config presence is the version test; the listener and the geyser example both report it. CommitmentLevel lost members 3-6 to a new SlotStatus enum -- the one breaking change, and nothing here sent anything but PROCESSED. Adds a cookbook example for SubscribeDeshred, the pre-execution stream, and tools/compare_deshred_latency.py to measure it. Deshred is deliberately not wired into the bot: it carries no TransactionStatusMeta, so no CreateEvent and no state_from_event, and a router-created coin is structurally invisible on it because the create is an inner instruction and inner instructions are produced by execution. Measured 4 of 118 creates missed in a 300s window, against a 6.4ms median lead on the creates it does see. verify_transaction_v1.py gains four geyser checks on a committed v1 SubscribeUpdate, pinning the log route and the instruction fallback separately, as the block listener already does. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7c6d626e1c |
fix(cookbook): trade non-canonical PumpSwap pools (#218)
pumpswap_buy_token and pumpswap_sell_token failed on every pool that did not graduate from a pump.fun bonding curve, with BuybackFeeRecipientNotAuthorized (6053). The scripts always sent the pool-v2 account. That account belongs only to a canonical pool; the buyback fee recipient and its quote ATA are always the last two accounts and are read positionally, so on a non-canonical pool pool-v2 shifted them by one and the program read the pool-v2 PDA as the recipient. Pool.coin_creator is the discriminator, set for canonical pools and left at Pubkey::default() otherwise. The IDL cannot answer this: it lists 23 accounts for buy against 25-26 on chain, 21 for sell against 23-26, and it is byte-identical to upstream. Upstream's BREAKING_FEE_RECIPIENT.md has the rule, in a qualifier that is easy to read past: the pair goes after pool-v2 "for coins that graduate from bonding curve", and is required either way. Fixing that exposed a second bug the first had been hiding. Both scripts hardcoded TOKEN_DECIMALS = 6, "standard for most pump.fun tokens" — true for every graduated coin, and wrong for the non-canonical pools, which routinely carry 9. Out by a factor of 1000, the quote and the slippage floor scale together, so the sell reverted ExceededSlippage (6004) rather than merely mispricing: Left: 2990, Right: 1500000. get_mint_info now returns the token program and the decimals from the one account read that was already happening, and raises instead of defaulting. Verified on mainnet. Simulation across 10 pools: 5 non-canonical that all failed 6053 before, 4 canonical that must not regress, and the pool from the original report. Then real round trips on both kinds — a 9-decimal non-canonical pool and a 6-decimal Token-2022 canonical one — buy and sell landed in each, wallet returned to SOL. verify_pumpswap_account_layout guards both bugs; checked against the pre-fix code first, where it fails 4 of 4. Suite is 21/21. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ebbfa9f124 |
chore(deps): upgrade to solders 0.29 / solana 0.40 and bring the rest of the dependencies current (#217)
* chore(deps): upgrade solders to 0.29 and solana to 0.40 in cookbook and tools solana 0.40 moves TxOpts (now TxOptsModel), MemcmpOpts and TokenAccountOpts from solana.rpc.types to solana.rpc.core, and spl.token's *Params dataclasses to spl.token.models as pydantic models — validated and keyword-only. Three call sites only failed against mainnet: MemcmpOpts.bytes now takes a base58 str, SyncNativeParams was built positionally, and confirm_transaction was passed a bare "confirmed". Also moves every cookbook and tools send path off the legacy Transaction onto VersionedTransaction + MessageV0, and teaches the getTransaction decoder the json and base64 encodings alongside jsonParsed — the base64 v1 fixture needs solders 0.29 to deserialize at all. src/ is untouched and still on the old imports; the offline suite is 3/19 until that lands. Verified on mainnet: a 0.003 SOL buy_v2/sell_v2 round trip confirmed and the wallet returned to SOL only, all six listeners and every read-only script run live, five trade paths simulated with error=None. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): move the bot onto solders 0.29 and solana 0.40 Same three API moves as the cookbook commit, now in src: TxOpts is TxOptsModel in solana.rpc.core, and spl.token's BurnParams/CloseAccountParams come from spl.token.models. build_and_send_transaction builds a VersionedTransaction from MessageV0 with no lookup tables, so the bot no longer sends a legacy transaction anywhere. verify_transaction_v1 proved the log route by stripping the logs and expecting nothing back, which only held while solders could not read a v1 envelope. On 0.29 it can, so the check is inverted: make the envelope unreadable, keep the logs, and the create must still arrive. A second check pins the envelope fallback, so a decode regression is reported rather than silently absorbed by the log route. Offline: 19/19. Live on mainnet, funds returned: - tools/simulate_bot_buy_path: zero-RPC buy path, err=None, 107,015 CU - tools/simulate_v2_trades: buy_v2 95,573 CU, buy+sell 174,190 — both inside the existing 180k/120k limits, so no retune - tools/live_v2_round_trip: buy_v2 + sell_v2 both landed - tools/live_listener_matrix: all four listeners detected; logs and pumpportal ran full round trips; geyser and blocks both drew a coin on an unconfigured quote mint and correctly refused to size a buy. Every ATA closed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): bring the rest of the dependencies to current Every remaining dependency is now at its latest release, with the floors raised to the versions actually tested rather than the oldest that resolves: grpcio 1.73.1 -> 1.84.0 grpcio-tools 1.73.1 -> 1.84.0 protobuf 6.33.5 -> 7.36.2 uvloop 0.21.0 -> 0.22.1 pyyaml 6.0.2 -> 6.0.3 python-dotenv 1.2.2 -> 1.2.3 construct 2.10.70 (floor) websockets 17.1 (floor) ruff 0.12.4 -> 0.16.8 aiohttp and base58 were already current. winloop is left at its old floor: it is Windows-only and cannot be installed, let alone tested, here. protobuf 7 arrives with grpcio-tools 1.84 and is the only major. The committed geyser stubs are gencode 6.31.1 and still pass ValidateProtobufRuntimeVersion against it; they are not regenerated, because the committed files import each other absolutely and protoc does not emit that, so the output would need rewriting. Noted in CLAUDE.md with the verification date. ruff 0.16 needs no config change. The repo-wide baseline moves 1680 -> 1713 because the rules moved, not the code; CLAUDE.md carries the new figure so it is not misread as a regression. Verified per dependency, not just in aggregate: all four bot configs load under the new pyyaml/dotenv; the logs, blocks and pumpportal listeners run under the uvloop policy bot_runner installs; both geyser paths (token listener and the account-filtered graduation watcher) run under grpcio 1.84 / protobuf 7. Offline 19/19, including verify_listener_cancellation for websockets. One more live buy_v2/sell_v2 round trip on the final set, both landed, ATA closed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(utils): redact RPC credentials from every log record The provider endpoints carry their API key in the URL and the HTTP clients log a full request URL at INFO, so anything that raises the root logger to INFO prints the key. tools/cleanup_accounts.py did, four times, in a terminal. It was already silencing httpx and httpcore by name. That stopped working the moment solana-py 0.40 switched to httpx2: the rename took the guard with it and nothing said so. Naming the clients is now a convenience on top of the real control — install_secret_redaction masks the value itself, at log-record creation, whichever logger emits it. It redacts the *rendered* message rather than record.msg plus the string arguments. The first version of this did the latter and still leaked: httpx2 passes its URL as a URL object, so the type check skipped the only argument carrying the key. Confirmed by forcing httpx2 back to INFO and watching the key print anyway. The host is kept, so a log line still says which provider answered. Installed from get_logger, from both logging.basicConfig sites, and from setup_file_logging before it attaches the handler — logs/ outlives a terminal. verify_no_rpc_credentials_logged covers all of it, including the non-str argument. Checked against the defeated version first: it fails there and passes here. Suite is 20/20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(agents): make a real bot run the verification, not the simulations The ground rule said never to run a bot with real funds, which points at the tools/ harnesses instead. Those call the buyer, seller and listeners directly: they skip bot_runner, config loading, the trader loop, the exit strategy and cleanup, so passing there says nothing about the paths only a full run reaches. Verification is a real run, with explicit approval for that session, covering every mode the change touches and ending with the wallet holding SOL only. Also records why a run can look clean when nothing happened: pump_bot runs every bots/*.yaml, three of the four committed configs ship enabled: false, and with all of them disabled it prints nothing and exits 0. The log file is the tell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(utils): keep the redaction comment provider-neutral The parameter list is what matters, not which provider uses which spelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
41b971147a |
refactor(cookbook): restructure learning-examples, add missing actions, fix quote decimals (#216)
* refactor(tests): move the regression verifiers into tests/regression verify_*.py are not standalone examples: each imports src/, stubs the RPC client, and asserts one previously fixed bug stays fixed. Their checks are unchanged; PROJECT_ROOT, the usage lines and the bonk fixture lookup are adjusted for the new depth. run_all.py runs the set and reports failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(tools): move the dev harness and listener benchmarks into tools/ simulate_*, live_*, both compare_* scripts and the account cleanup import src/ and several spend real funds — they exercise the bot rather than teach anything. compare_listeners.py reaches the repo root one level higher than before; nothing else changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(cookbook): reorganise learning-examples into cookbook/ The directory is a reference collection — one runnable script per action — but nothing said so: no index, scripts grouped by the transport they happened to use, fixtures loose at the root. Now grouped by platform, then by what you are doing: pumpfun/{listen,read,trade,graduation,decode}, pumpswap, letsbonk, legacy. Fixtures sit beside the script that reads them, manual_sell.py becomes sell_token.py, and the legacy create example moves to legacy/ and says why. pump_v2.py and tx_status.py stay at the cookbook root as the only two shared helpers; subdirectories add that root to sys.path before importing them. cookbook/README.md indexes every script and marks the ones that spend funds. Also fixes create_v2's account list: the five mayhem accounts are mandatory whatever is_mayhem_mode says, so mint_and_buy_v2.py could never have worked with mayhem off. 11 accounts fails AnchorError 3005 (AccountNotEnoughKeys) on sol_vault, 16 succeeds with the argument either way — simulateTransaction against mainnet. Seven scripts gain a module docstring they were missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(cookbook): add buy, create, balance and status examples buy_token.py <MINT> buy_v2 against one coin; --dry-run simulates create_token.py create_v2 on its own get_balances.py SOL and tokens across both token programs check_tx_status.py <SIG> success, reverted, or not visible yet Buying previously meant manual_buy.py, which waits for the next coin created anywhere before it trades — there was no way to buy a coin you already know. Creating one meant bundling a buy with it, and the transaction-status helper was a library with no entry point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(cookbook): move pump_v2 next to the scripts that use it pump_v2.py holds the buy_v2/sell_v2 account layouts, and only the pump.fun trade scripts import it — it sat at the cookbook root because tx_status.py does, not because anything outside pumpfun/trade/ needed it. The five callers now pick it up as a plain sibling; legacy/ and the layout verifier reach into that directory. tx_status.py stays at the root, where all four platforms use it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(cookbook): name every script protocol_verb_noun_variant manual_buy.py did not just buy — it waited for the next coin created anywhere, then bought, so the verb was wrong. Protocol was encoded three different ways: directory, suffix, or not at all. And three scripts sat under pumpfun/ that have nothing to do with pump.fun. Filenames are now <protocol>_<verb>_<noun>[_<variant>].py, so a basename says which chain, what it does and which instruction version without opening it: manual_buy.py -> pumpfun_snipe_token_blocksubscribe.py manual_buy_geyser.py -> pumpfun_snipe_token_geyser.py buy_token.py -> pumpfun_buy_token_v2.py mint_and_buy_v2.py -> pumpfun_create_and_buy_token_v2.py manual_buy_pumpswap.py -> pumpswap_buy_token.py manual_buy_exact_in.py -> letsbonk_buy_token_exact_in.py legacy/mint_and_buy.py -> legacy/pumpfun_create_and_buy_token_v1.py The sniper was named for logsSubscribe in an earlier draft; it sends blockSubscribe, and the name now says so. Balances, transaction status and the Anchor discriminator calculator are not pump.fun-specific and move to a new cookbook/solana/. The two non-runnable helpers take no verb — pump_v2.py becomes pumpfun_instructions_v2.py beside its callers, tx_status.py becomes solana_transaction_status.py in solana/. Both are imported under a short alias so the 106 call sites stay readable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cookbook): resolve a coin's quote mint before pricing it quote_units() defaulted to 9 decimals for an unresolved mint, and every trade script called it before resolve_quote_token_program() warmed the decimals cache. pump.fun's QuoteControl registry admits mints at 6, 8 and 9 decimals — 79 of the 170 admitted are tokenized equities, 8 decimals for xStocks and 6 for Backpack Securities — so those coins were sized against the wrong power of ten. The error compounds rather than cancels: price_per_token() divides by the same wrong unit, so the price reads low by the factor, the token amount comes out high by it, and the inflated cap authorises the overspend instead of catching it. 10x on an xStock pair, 1000x on a Backpack one. This is not hypothetical. A 75s sample of live curve updates on 2026-09-22 caught 8 stock-paired curves out of 124 — IONQ across 5 of them, plus QUBT, PLTRx and COPX. quote_units() now raises for an unresolved mint instead of guessing, the four trade paths resolve before they price, and pumpfun_read_price.py reads decimals from chain rather than from a two-entry table. The geyser sniper never resolved at all: it also passed SPL Token as the quote token program, which fails the account constraints for any Token-2022 quote, and created no quote ATA. verify_quote_decimals_resolved.py checks the fail-closed contract, that resolution costs no extra RPC call, and that no function prices before it resolves — confirmed to fail when the ordering is put back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * feat(cookbook): read the quote-mint registry and Token-2022 mint state Two reads that were unreachable without knowing an undocumented PDA seed. pumpfun_read_quote_mints.py decodes QuoteControl (PDA ["quote-control"]), the second quote-mint registry added in the 2026-09-15 upgrade. Reading only Global.whitelisted_quote_mints under-reports: a coin can be paired with a mint Global has never heard of. 170 admitted today, 79 of them tokenized equities, each with the initial virtual quote reserves that set a new curve's opening price. --stocks narrows to the equities, identified by the Token-2022 extension set create_v2 requires of them rather than by a name lookup. solana_read_token2022_mint.py reads the extensions that change what a balance means: scaled UI amount, pausable, permanent delegate, transfer hook, default account state. It exists mainly for one trap — the RPC returns both `multiplier` and `newMultiplier` with an effective timestamp, and once that timestamp passes the field named `multiplier` is the stale one. On AAPLx it passed on 2026-08-08, so the obvious field has been wrong for six weeks. This compares against the clock and says which is in force. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * feat(cookbook): add the exact-quote-in buy buy_exact_quote_in_v2 was uncovered. It is the mirror of buy_v2 — buy_v2 fixes the tokens received and caps the spend, this fixes the spend and floors the tokens — and it takes the same 27 accounts in the same order, so it reuses build_v2_accounts and differs only in discriminator and arguments. Worth having because pinning the spend is the natural direction once a coin is priced in something other than SOL: "spend exactly one AAPLx" is something you can mean, where a token count is a number derived from a price that moved while you derived it. Verified by simulateTransaction against a live COPX-paired coin: the program executes the full buy and stops at TransferChecked with insufficient funds, which is as far as a wallet holding none of the quote asset can go. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * feat(cookbook): add the exact-SOL-in buy buy_exact_sol_in was uncovered. SOL only — it pre-dates non-SOL quote assets and carries no quote accounts, so the script checks the curve and points at the exact-quote version for anything else rather than failing obscurely. Its account list is the reason to have it. The IDL lists 16; the program needs 18, the extras being the bonding-curve-v2 PDA and a buyback fee recipient. Determined by simulation rather than assumed: 16 accounts fails AnchorError 6062 (BuybackFeeRecipientMissing), 18 simulates clean at 96k CU. The v2 instructions are complete in the IDL and this family is not, which is worth a newcomer knowing before they trust the file. The docstring carries the IDL's own fee arithmetic, since fees come out of the amount you name rather than on top of it, and the printed estimate ignores that split while min_tokens_out is what the program enforces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * feat(cookbook): add the creator-fee and cashback payouts Two payout paths that had no script. Both take no arguments and, per the IDL, no signer — the funds can only reach the wallet they already belong to, so either can be run for anyone, which --creator and --user expose. collect_creator_fee_v2 sweeps a creator vault. The vault is per creator and per quote asset, so a creator whose coins are priced in several assets collects each separately; --quote picks which. claim_cashback_v2 pays out an accrued cashback balance. create_v2 has rejected new cashback coins since 2026-09-15 (error 6082), but coins minted before that keep accruing and stay claimable, which is why the cashback handling elsewhere in this repo is not dead code. Both verified by simulateTransaction against mainnet: 10 accounts each, 8 data bytes, no error. Also repairs two cookbook README rows that the earlier rename mangled — pumpswap_buy_token.py had been rewritten to pumpswap_pumpfun_buy_token_v2.py because its name contains the string that rename matched on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * refactor(cookbook): take every script's input on the command line A reference collection only works if you can run a script against your own coin without editing it. Several carried the coin as a module-level constant instead: TOKEN_MINT = Pubkey.from_string(sys.argv[1] if len(sys.argv) > 1 else "...") which reads as optional but raises at import, so running the script the way its own usage line described it died on ValueError: Invalid Base58 string before printing anything. Nine scripts did this. Four more took their amount and slippage from environment variables that no usage line mentioned. Every script now builds an ArgumentParser in main(): required values are positionals, tunables are --options, and the literals live in DEFAULT_* constants that supply the defaults. --help works on all 32 that take input; the seven listeners take none and are exempt. The decode scripts keep defaulting to the fixture beside them, so they still run bare. Behaviour is unchanged. create_and_buy's mayhem flag defaulted to on, so it takes --mayhem/--no-mayhem via BooleanOptionalAction rather than a store_true that would have silently flipped it off. Two fixes found while threading the values through: the wallet listener read a global `wallet` from four functions that no longer had one, and find_pool raised IndexError on a coin that has not graduated instead of saying so. verify_cookbook_arguments.py enforces the shape — no module-level sys.argv, no placeholder values, argparse wherever there is input, and every default a usable value. Confirmed to fail when the old pattern is put back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * fix(docs): replace three dead links and check the rest The Anchor discriminator citation in anchor_calculate_discriminator.py pointed at book.anchor-lang.com/anchor_bts/discriminator.html, which Anchor removed when it restructured its docs. Two links in the README had also moved: docs.chainstack.com/quickstart/ and docs.chainstack.com/docs/trader-nodes. book.anchor-lang.com/anchor_bts/discriminator.html -> www.anchor-lang.com/docs/basics/idl (has a Discriminators section) docs.chainstack.com/quickstart/ -> docs.chainstack.com/reference/blockchain-apis docs.chainstack.com/docs/trader-nodes -> docs.chainstack.com/docs/solana-trader-nodes All 28 real URLs in the repo now return 200, checked by fetching each one. verify_documentation_links.py keeps them honest: offline it asserts every URL is https and that none of the four dead endpoints has come back, and --live fetches each and fails on 4xx/5xx. It skips the unroutable hosts the offline verifiers point stub clients at, and example.com, which RFC 2606 reserves for exactly the placeholder metadata URI a test coin is created with. The discriminator script also still asked you to edit a constant, which the argument rule had just outlawed, so it takes the name on the command line and prints the u64, the byte list and the hex. Cross-checks against the constants in pumpfun_instructions_v2.py: global:buy_v2 gives [184, 23, 238, 97, 103, 197, 211, 61], which is BUY_V2_DISCRIMINATOR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * fix(cookbook,tools): detect coins via logs so v1 transactions are not skipped Solana transaction v1 has been live since 2026-09-15 and solders cannot deserialize one until 0.29. Three scripts detected coins by deserializing the transaction envelope, so each went blind to v1: cookbook/pumpfun/listen/pumpfun_listen_tokens_blocksubscribe.py 30,080 "skipping a v1 transaction" lines in 150s tools/compare_listeners.py 601 "Failed to process transaction: io error: unexpected end of file" in 100s, which its own comment read as a transport difference cookbook/pumpfun/trade/pumpfun_snipe_token_blocksubscribe.py skipped v1 silently, so a coin created in one was never sniped All three now route on meta.logMessages, which the RPC has already decoded and which reads the same for every version. The blocks example keeps the envelope decode as a genuine fallback for providers that return blocks without logs; the sniper derives the associated bonding curve from the mint and curve instead of reading the instruction's account list, verified against pumpfun_derive_curve_addresses.py on a live coin. Measured live after the change: the blocks example decoded 88 v0, 6 v1 and 4 legacy creates with zero skips and zero errors, and compare_listeners' block lane now appears alongside logs and geyser for the same coins. Upgrading solders was tried first and reverted. 0.29 does decode v1 — 0.26, 0.27.1 and 0.28 all raise "io error: unexpected end of file" on the committed fixture — but it requires solana>=0.40, which moved TxOpts, MemcmpOpts and TokenAccountOpts out of solana.rpc.types. 22 files import them and 16 of 19 verifiers failed on the import alone, so that is a migration of its own. verify_transaction_v1.py gains a check that no cookbook or tools listener detects by opening the envelope. It found the sniper, which this had missed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR * fix(cookbook): label the transaction version and restore the lost fields The blocks listener printed "Found CreateEvent in a 0 transaction", which reads as a count rather than what it is — Solana's transaction version. It now prints v0, v1 or legacy. Routing detection through the CreateEvent also dropped two fields the instruction path used to fill, so every detection showed "Token Standard: N/A" and no Associated BC. Both come from the event's token_program: the standard is that program, and the associated bonding curve is an ordinary ATA of the curve under it. Verified against pumpfun_derive_curve_addresses.py on a live coin — curve E94izXvHd7dd... derives to He94dArnuprt..., matching what the listener now prints. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ5HdjTadNUad8i6xis3TR --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f4dca24cdd |
docs(agents): compact the verification sections and refresh stale facts
Six per-issue "Verifying ..." sections (245 lines, 40% of the file) restated rationale that already lives in each verifier's docstring, at similar length. Replace them with one table covering all 18 scripts and an Invariants section keeping only the rules that constrain new code rather than a bug already fixed. Also fixes what had gone stale: the four verifiers from #206/#211 were never listed, config_loader.py sits at the package root and not under core/, and the ruff baseline is ~1660 rather than ~1700. 618 -> 477 lines. Protocol notes and listener pitfalls are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
13b9ec2baf |
fix(monitoring,core): keep detecting tokens after the Solana transaction v1 cutover (#212)
* fix(monitoring,core): keep detecting tokens after the Solana transaction v1 cutover Solana transaction v1 (SIMD-0296/0385) went live at epoch 1035 on 2026-09-15. `maxSupportedTransactionVersion` turns out to be a whole-frame setting, not a per-transaction filter: asking blockSubscribe for 0 does not skip the v1 transactions in a block, it nulls out `value.block` for the entire notification, which reads as a skipped slot. Measured against mainnet 2026-09-16, 60s per run on the pump program: 0 delivered 1 block and 177 nulls, 1 delivered 78 blocks and no nulls. solders 0.26 cannot deserialize a v1 envelope either, but it does not need to. The RPC has already decoded it by the time `meta.logMessages` is emitted, and the platform parsers prefer the CreateEvent in those logs anyway, for the canonical creator. What broke was the dispatch: _process_block_transactions decoded the bytes first only to learn which program a transaction touched, and swallowed the failure in a bare `except Exception: pass`. It now tries _parse_from_logs on every transaction first and keeps the byte decode as a fallback, so v1 coins arrive with state_from_event=True and extreme_fast_mode keeps its zero-RPC contract. Also raises every other reader to v1, fixes the null-block crash in listen_blocksubscribe.py, and records that PumpPortal does not push coins created in a v1 transaction — 0 of 6 measured, against near-complete v0 coverage. That one is upstream and not fixable here. Closes #197 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LDiKnRFW49m3T1rKYiz98 * fix(examples): survive a v1 envelope and a null block per notification Raising maxSupportedTransactionVersion to 1 means these examples now receive the v1 transactions the RPC used to withhold, and solders 0.26 cannot decode one. manual_buy.py had no handler at all around from_bytes, so the first v1 transaction in a block would have ended listen_for_create_transaction outright — a regression introduced by the version bump itself. Guards the null `value.block` in the same three places while here: the key is present and the value is not for a skipped slot, so the membership test raises TypeError and costs the whole notification. Same root cause as #196. Found by CodeRabbit on #212. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LDiKnRFW49m3T1rKYiz98 * fix(examples): raise the shared confirm helper to v1 and decode create_v2's OptionU64 Two gaps the first pass missed, both found by running the changed code against mainnet instead of only exercising it offline. tx_status.py confirmed through solana-py's snake_case `max_supported_transaction_version=0`, a spelling the source scan in verify_transaction_v1.py did not know about — so the sweep reported clean while the helper every example confirms through still rejected v1. Verified live: at 0 it raises UnsupportedTransactionVersionMessage on a v1 signature, at 1 it reads meta.err. The scan now covers both spellings, and setting the threshold above 1 makes it name all seven call sites, so the check is not vacuous. listen_blocksubscribe.py then turned out to have a third cause behind #197 that no offline check could reach, because until this branch the example never received a block to decode: its local create_v2 decoder raises on OptionU64, the arg added by the 2026-09-15 program upgrade. Live before the fix, 90s: 0 creates decoded, 8 tracebacks. After: 58 creates, 0 tracebacks. Trailing option args are reported unset rather than as a fabricated default, matching utils/idl_parser.py and the three wire forms that are live on chain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LDiKnRFW49m3T1rKYiz98 * fix(examples): decode create_v2's trailing optional args in manual_buy Found by running the script against mainnet with real funds. It crashed before it could buy: File "learning-examples/manual_buy.py", line 316, in decode_create_instruction value = bool(ix_data[offset]) IndexError: index out of range decode_create_instruction treated every `defined` arg as a 1-byte OptionBool and read it unconditionally. create_v2's trailing args are positional and legally absent from the wire, and the 2026-09-15 upgrade added creator_fee_bps as an 8-byte OptionU64, so the fixed read walks off the end on the shorter forms — which is most coins. Same defect class as the OptionU64 crash in listen_blocksubscribe.py, and invisible until this branch let either script receive a block at all. Absent trailing options are now reported as None, meaning unset, matching utils/idl_parser.py. Verified live: the script decodes a create with creator_fee_bps and is_holder_reward both null, then buys 69,387 tokens for 0.003 SOL, confirmed on-chain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LDiKnRFW49m3T1rKYiz98 * refactor(examples): drop one v1 fixture and trim the other Two fixtures was one too many. The v0 one was captured to prove the byte-decode path still works, but it never did: the listener routes on logs first, so the fixture was caught there and the check passed without exercising what its name claimed. Feeding it through with the logs stripped shows the byte fallback does not parse a v0 create_v2 either — the parser's own logs-first branch was always doing the detection. So the check now reuses the create fixture already committed for verify_create_v2_optional_args. The v1 fixture stays, because a v1 transaction cannot be synthesized locally — solders 0.26 cannot build one — and every verifier here runs offline. Trimmed from 30KB to 13KB by dropping what nothing reads: innerInstructions, token balances and pre/post balances. What is left is the envelope, the version and the logs the listener parses. Adds the check that was missing: strip the logs and the v1 create must vanish. That is what makes the log route load-bearing rather than decorative, and it will start failing the day solders learns to decode a v1 envelope, which is exactly when the routing could be simplified again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LDiKnRFW49m3T1rKYiz98 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e1c09f6605 | fix(trading,core): make exit sells safe to retry, bound RPC wall time, detect bonk tokens (#211) | ||
|
|
a53175f1eb | fix(trading,monitoring): keep landed buys, retry reverted sells, stop dropping blocks (#206) | ||
|
|
5ac482256b |
docs: record that quote-mint decimals are resolved from chain
The quote-asset notes covered resolving a quote mint's token program but not its decimals, which is the more dangerous of the two: amounts like max_sol_cost are in the mint's raw units, so assuming 9 decimals for a 6-decimal mint overstates the cap by 1000x and effectively disables slippage protection. Both facts come from the same getAccountInfo, so the read costs nothing extra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
a0540fdc9e |
fix(trading): price tp/sl exits off the trigger price and retry a reverted sell (#193)
* fix(trading): price tp/sl exits off the trigger price and retry a reverted sell `_monitor_position_until_exit` handed `position.entry_price` to the sell while the `current_price` that had just triggered the exit sat in the same scope, one line up. `PlatformAwareSeller.execute` does not read a price - the `token_price` it receives *is* the slippage floor - so a stop-loss priced off the entry demands more quote asset than the curve can pay and reverts with 6003 `TooLittleSolReceived`, during the very drop the stop-loss exists to escape. On a take-profit the same mistake runs the other way and the floor protects nothing. `current_price` costs no extra RPC call; `_handle_time_based_exit` has nothing fresher and keeps passing the buy price. The `break` also sat outside both branches of `if sell_result.success:`, so the loop exited whether the sell landed or not - contradicting the "Keep monitoring in case sell can be retried" comment directly above it. The seller's `max_retries` covers transaction submission only, so an on-chain revert was never retried and the position was abandoned mid-crash with `is_active=True`. A failed exit sell now retries on the next price check, re-reading the price so the floor tracks the market, bounded by `trade.max_exit_sell_attempts` (default 3, validated to 1..100) so a permanently reverting token cannot pin the bot on one position. The counter resets if the price recovers out of the exit band, and giving up is logged loudly since the tokens are still held. Fixes #189 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(learning-examples): machine-check the tp/sl exit price and retry path Drives the real `_monitor_position_until_exit` with a stub curve manager serving a scripted price series and a stub seller that records the price it is handed. Offline, no network and no funds moved, per the `verify_*` convention. Eight checks: both exit kinds sell at the triggering price, the entry-price floor is arithmetically unpayable on a drop while the trigger-price floor is payable, a reverted sell is retried and a landing retry closes the position, retries stay bounded, a price recovery resets the counter, a successful sell still closes on the first attempt, and the cap comes from `trade.max_exit_sell_attempts` wired through bot_runner and config_loader. Mutation-tested rather than trusted on a green run - reintroducing the stale entry price drops it to 4/8, giving up after one failure to 4/8, and ignoring the config knob to 7/8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: document the tp/sl exit path and its verifier The caller owns the sell's slippage floor, which is not obvious from `PlatformAwareSeller.execute` - it never reads a price, it just turns `token_price` into `min_quote_output`. Records that, why an exit must price off the triggering price, and that the seller's `max_retries` covers submission only so an on-chain revert has to be retried in the monitor loop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b367191267 |
fix(ci): restore the undefined warningMessage in spam-detection workflow (#187)
The github-script step references warningMessage in createComment, but its
definition was removed in
|
||
|
|
9ab74b91e0 |
build(deps): bump aiohttp in the uv group across 1 directory (#188)
Bumps the uv group with 1 update in the / directory: [aiohttp](https://github.com/aio-libs/aiohttp). Updates `aiohttp` from 3.14.1 to 3.14.3 - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.14.1...v3.14.3) --- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.14.3 dependency-type: direct:production dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9a3f2c0bfb |
fix(utils): decode omitted trailing optional args in IDL instruction decoder (#185)
create_v2 instructions can legally omit the trailing is_cashback_enabled OptionBool from the wire; decode_instruction rejected that form, so every consumer of parse_token_creation_from_instruction silently dropped those coins (the committed blocksubscribe fixture carries one such 145-byte create_v2). - When the arg buffer is exhausted and every remaining arg is option-typed (native option or pump.fun's OptionBool), report them as unset (None) instead of failing the decode; mandatory args still fail. - Count trailing optional args as 0 bytes in the instruction min-size table. - Decode Anchor's native option type (1-byte tag + value), previously unsupported entirely - update_buyback_config's Option<u64> could not even be sized. - Add learning-examples/verify_create_v2_optional_args.py machine-checking the omitted, present, truncated-mandatory and native-option forms plus the event-parser path against the committed fixture. - Drop the append-0x00 workaround in verify_extreme_fast_zero_rpc.py and update the stale CLAUDE.md note about the strict decoder. Fixes #184 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c16fc856d0 |
fix(deps): bump setuptools to 83.0.0 for CVE-2026-59890
Resolves Dependabot alert #33 (GHSA-h35f-9h28-mq5c): MANIFEST.in exclusion bypass in sdist builds via NFC/NFD Unicode normalization collision on macOS. setuptools is a transitive dependency of grpcio-tools (dev group); lockfile-only change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
04442302f4 |
docs: sync extreme-fast refresh description with zero-RPC behavior
CLAUDE.md's coin-creation note and simulate_bot_buy_path's docstring still described the pre-#183 unconditional curve refresh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7bc6f25508 |
feat(trading): zero-RPC extreme-fast buys and listener race hardening (#170) (#183)
Extreme-fast mode submits with no RPC calls between detection and buy when the listener parsed the on-chain CreateEvent (geyser/logs/blocks): TokenInfo.state_from_event carries the canonical creator, mayhem/cashback flags and quote_mint, so the pre-buy curve refresh is skipped. The geyser listener now delegates to the event-first parser instead of inlining instruction decoding, which also catches create_v2 transactions that omit the trailing is_cashback_enabled byte. Listeners without event data (pumpportal) keep the refresh, hardened per issue #170: bonding curve derived from the mint instead of the payload's bondingCurveKey, curve and mint read in one slot-consistent getMultipleAccounts (correcting the guessed token program), and an unreadable curve now skips the token within trade.curve_refresh_budget instead of submitting a buy built from guessed accounts. Machine-checked by verify_extreme_fast_zero_rpc.py and verify_pumpportal_buy_path.py; validated live via simulate_bot_buy_path on both geyser and pumpportal paths. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bc59a2257d |
ci(auto-assign): assign new issues to smypmsa
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
678fa19fe4 |
fix(learning-examples): replace getProgramAccounts scan in get_graduating_tokens (#182)
* 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> |
||
|
|
dcf66ec1b3 |
docs(agents): correct stale layer chain, ruff baseline, and fund-spending script rule
Verified every claim in CLAUDE.md against the code after the recent debugging, IDL, bot, and learning-example updates. Four were wrong: - The dependency chain listed `utils` before `interfaces`, but utils/idl_manager.py imports interfaces.core and interfaces imports nothing internal. It also made trading and monitoring peers when trading imports monitoring, and omitted cleanup and geyser entirely. - Ruff baseline said ~2400 errors; `uv run ruff check` reports 1676. - `live_*` was presented as the marker for scripts that spend real funds. Only 2 of the 13 scripts that submit transactions carry that prefix — manual_*, mint_and_buy* and cleanup_accounts.py spend too. - compare_listeners.py was cited without its path, ambiguous against listen-migrations/compare_migration_listeners.py. The protocol section (v2 account counts, BondingCurve offsets, PumpSwap i128, PDA seeds, fee-recipient sets, tx-status rules) checks out against idl/pump_fun_idl.json and src/; both offline verifiers still pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
aa3ac28943 |
build(deps): bump the uv group across 1 directory with 2 updates (#180)
--- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.2.2 dependency-type: direct:production dependency-group: uv - dependency-name: aiohttp dependency-version: 3.14.1 dependency-type: direct:production dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
a33f4fd035 |
fix(learning-examples): repair crashing listeners and decoders, prune obsolete paths, unify naming (#181)
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> |
||
|
|
5c1fb7eb28 |
docs: fix README callout placement, flag walkthrough as lagging
Move the "Also by Chainstack" callout back to its original position just below the walkthrough link, in its original blockquote style, so it stays visible without scrolling. The previous README rewrite had relocated it to the bottom of the file. Note that the linked walkthrough doc lags behind the code, and point readers at the README for setup and configuration instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7727015548 |
docs: consolidate agent docs, rewrite README, prune deps (#179)
Rewrite README.md around setup and configuration: fix the clone URL, document the actual .env variable names, add tables for bots/*.yaml and the learning-examples directories, and drop the empty changelog, the 2025 roadmap, and the protocol deep-dives that duplicated CLAUDE.md. Make CLAUDE.md the single agent guide and symlink AGENTS.md to it. AGENTS.md carried wrong env var names, a stale Python floor, and a config key that does not exist; its safety rules move into CLAUDE.md. Document that `uv pip install -e .` puts src/ on sys.path, so imports are `from utils.logger import ...` rather than `from src.utils...`. Delete .cursor/rules/, .kiro/steering/, and .windsurf/rules/ - three byte-identical copies of rules referencing APIs that do not exist in src/. All three tools read AGENTS.md natively. Fix pyproject.toml: - requires-python >=3.9 -> >=3.11; the code uses `X | None` (3.10+) and ruff already targets py311 - drop borsh-construct and construct-typing, neither of which is imported anywhere (construct-typing still resolves via solana) - move grpcio-tools to the dev group; it is protoc, needed only to regenerate the geyser_pb2 stubs, never at runtime - move dev deps from [project.optional-dependencies] to [dependency-groups] so `uv sync` installs ruff, making the documented `ruff check` / `ruff format` commands actually available Also gitignore .claude/settings.local.json, which is per-developer. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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
|
||
|
|
02343b775b |
feat(pumpfun): migrate to buy_v2/sell_v2 and support non-SOL quote assets (#176)
Refresh the vendored IDLs from pump-fun/pump-public-docs @ 9c82f61 and move all pump.fun trading onto the v2 instruction interface. This is required, not optional: legacy buy/sell cannot trade coins paired against a quote asset other than SOL, and USDC is already whitelisted in the on-chain Global account. Protocol changes absorbed: - buy_v2 (27 accounts) / sell_v2 (26 accounts) replace the legacy instructions. Every account is mandatory and the order is identical for all coins, so the conditional cashback/mayhem account lists are gone. Legacy remains available via PumpFunInstructionBuilder(use_legacy_instructions=True). - BondingCurve is 151 bytes: virtual_sol_reserves -> virtual_quote_reserves, real_sol_reserves -> real_quote_reserves, plus quote_mint at offset 83. Old field names are kept as aliases so existing callers keep working. - v2 instruction data drops the track_volume OptionBool; amounts are in the quote mint's raw units rather than always lamports. - create_v2 carries a non-SOL quote mint as optional remaining accounts 17-19, and CreateEvent gained quote_mint, so extreme_fast_mode can resolve the quote asset without an extra fetch. USDC support: new trade.quote_amounts and filters.allowed_quote_mints config, accepting "sol"/"usdc" aliases or raw mints. Amounts are per-quote-mint because 1 USDC and 1 SOL are not interchangeable. A coin whose quote mint has no configured amount is skipped rather than traded at the wrong size, so SOL-only configs are unaffected. Bug fixes found while verifying: - The logs and blocks listeners set no websocket max_size, so any frame over 1 MiB closed the connection with 1009 and the token in it was lost. Raised to 32 MiB. - PumpSwap priced against the raw quote vault balance, ignoring the new Pool.virtual_quote_reserves (i128 at offset 245; live pools are 301 bytes). Upstream's note that this field is 0 everywhere is out of date: a live pool carries 17.58 SOL against a 148 SOL vault, a 10.15% price error. - The seller read curve state once at confirmed commitment and silently fell back to create-time values, risking a stale creator_vault and ConstraintSeeds. It now retries at processed, matching the buyer. - Account cleanup would burn wrapped SOL when force_burn was set, destroying value that closing the account returns. WSOL is now closed without burning. - The mint scripts treated a landed transaction as a successful one, so a reverted buy printed as success. They now assert the on-chain result. Compute unit limits retuned from mainnet measurements: buy 100k -> 180k, sell 60k -> 120k. Mint-and-buy is no longer atomic, because create_v2 plus buy_v2 exceeds the 1232-byte transaction limit; both mint scripts send two transactions. Adds learning-examples/pump_v2.py as one shared, standalone v2 toolkit for the example scripts, and three verification scripts: an offline layout check against the IDL, a no-funds mainnet simulation, and a live listener matrix that buys, sells and closes the ATA per listener. Verified on mainnet: all four listeners (geyser, logs, blocks, pumpportal) and all eight example scripts completed a real buy, sell and ATA close, each confirmed by reading the transaction result back rather than trusting confirmation alone. The USDC path is verified structurally only; no USDC-paired coin could be found on-chain to exercise it. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3b88a06d9d |
build(deps-dev): bump setuptools in the uv group across 1 directory (#174)
Bumps the uv group with 1 update in the / directory: [setuptools](https://github.com/pypa/setuptools). Updates `setuptools` from 80.9.0 to 83.0.0 - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v80.9.0...v83.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-version: 83.0.0 dependency-type: direct:development dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
8d561dd421 |
build(deps): bump idna in the uv group across 1 directory (#173)
Bumps the uv group with 1 update in the / directory: [idna](https://github.com/kjd/idna). Updates `idna` from 3.10 to 3.15 - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](https://github.com/kjd/idna/compare/v3.10...v3.15) --- updated-dependencies: - dependency-name: idna dependency-version: '3.15' dependency-type: indirect dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
9ab686b453 |
build(deps): bump python-dotenv in the uv group across 1 directory (#166)
Bumps the uv group with 1 update in the / directory: [python-dotenv](https://github.com/theskumar/python-dotenv). Updates `python-dotenv` from 1.1.1 to 1.2.1 - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v1.1.1...v1.2.1) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.2.1 dependency-type: direct:production dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
27929e97be |
perf(extreme-fast-mode): use processed commitment + tight retry for BC refresh
Before: BC fetch in extreme_fast_mode used default (confirmed) commitment and retried up to 10 times with growing backoff (~10s total). Geyser/logs fire on processed, so the BC isn't yet at confirmed commitment when the bot reads it — most fetches failed and burned the full retry budget, adding ~10s to detect→confirmed. After: read at processed commitment to match the listener event horizon, and cap retries at 4 with 150ms gaps (~600ms ceiling). Most reads succeed on the first attempt. Mainnet benchmark (n=4 successful buys, geyser listener): buy_slot − create_slot: min=2 median=3 max=9 (~0.8s — 3.6s on chain) wall detect→confirmed: median 2-3s (1s resolution) Best case 2 slots = next slot after creation + tx propagation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
20e3e163d8 |
fix(trading): refresh BC state on extreme_fast_mode buy + letsbonk pumpportal token-program default
Two related fixes from mainnet validation of geyser/logs/blocks/pumpportal: 1. extreme_fast_mode buys previously skipped the BC fetch entirely. After the 2026-04-28 cutover, fee_recipient (mayhem mode) and creator_vault (PFEE-delegated BC.creator) cannot be derived from listener-side data alone — the program rejects with NotAuthorized (0x1770) / ConstraintSeeds (0x7d6). Add a short retry loop that fetches the curve state once before submitting the buy, so the instruction builder picks the correct fee_recipient and creator_vault. The ~150ms cost is small relative to a failed tx and 5s sell wait. 2. LetsBonkPumpPortalProcessor returned TokenInfo without token_program_id. The universal builder then defaulted to Token-2022 and the buy failed with IncorrectProgramId at GetAccountDataSize on the user-ATA create ix. LetsBonk tokens are predominantly regular SPL Token, so default to TOKEN_PROGRAM in the processor. Note: the pumpportal listener still has unrelated data-race issues (the PumpPortal stream sometimes fires before the on-chain BC is RPC-readable, and bondingCurveKey occasionally references a different mint than the detected token). These predate the 2026-04-28 cutover and need a separate fix (e.g., verifying mint+BC consistency on the tx side before submit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2ece1c9a2d |
fix(trading): refresh creator_vault from on-chain BC state before sell
Post-2026-04-28, BC.creator can be delegated to a PFEE-program-owned PDA after the initial creator buy. The create-time creator_vault cached on TokenInfo goes stale before the sell lands, producing ConstraintSeeds (0x7d6) on the Sell instruction. The sell flow already re-fetches pool_state to refresh is_mayhem_mode and is_cashback_coin; extend it to also refresh token_info.creator and re-derive creator_vault from the current BC.creator. Mainnet validation: geyser listener buy+sell on BEE — buy 2TSqZHZh…k7uN, sell 3TevcP5g…f8nQ, err=None on both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d0dea6d4ac |
fix(blocks-listener): skip failed txs + use CreateEvent log for canonical creator
Two bugs found by mainnet validation post-2026-04-28 cutover: 1. Blocks listener processed failed txs, including failed create_v2s. The bot then tried to buy a non-existent mint (InvalidMint at ATA creation). Fix: filter `meta.err is not None` in _process_block_transactions. 2. Blocks listener parsed `args.creator` from the create_v2 ix payload to derive creator_vault. Post-cutover, BC.creator is sometimes set to a PFEE-program-owned PDA distinct from args.creator (e.g., Companions: args.creator=DdZG8dw, BC.creator=3hPZm9). The Anchor seeds constraint on creator_vault then fails with ConstraintSeeds (0x7d6). Fix: in parse_token_creation_from_block, prefer parsing the CreateEvent log (which carries the canonical creator the program wrote to BC.creator) over args.creator from the ix payload. Mainnet validation: - buy 2uBrNqqW…bPDG, sell 32EKJjrK…9fXv on PUMP (Pumpcoin), err=None. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cc85daaeb3 |
chore(pumpswap): post-2026-04-28 cutover + cashback layout (#168)
* chore(pumpswap+docs): post-2026-04-28-cutover PumpSwap unlock + IDL docs Drops the `INCLUDE_BREAKING_FEE_ACCOUNTS = False` gate in the PumpSwap learning examples so the +2 breaking-fee accounts (fee recipient readonly + its quote-mint ATA mutable) are always appended after `pool-v2`. Mainnet pump-amm rejects this format pre-cutover (verified 6023 Overflow), so this PR is intentionally **draft until 2026-04-28 16:00 UTC** when the cutover happens; mark it ready-for-review and live- test then. Also captures the protocol gotchas we learned during this migration: - README: a new "2026-04-28 program upgrade" section + an explicit note that the vendored IDL is incomplete (missing `bonding-curve-v2` and `pool-v2` PDAs that the on-chain program actually requires) with pointers to cross-check against on-chain txs. - CLAUDE.md: a new "Pump.fun protocol notes" subsection summarising the same gotchas plus the BC/Pool/CreateEvent layout details and the extreme_fast_mode gotcha. Open question (call out at review time, resolve post-cutover): - BREAKING_FEE_RECIPIENT.md shows PumpSwap cashback account counts of 27 buy / 26 sell vs 26 / 24 non-cashback — the extra cashback account seed/position isn't documented. Need to sample a real successful cashback PumpSwap tx after cutover and add the branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(pumpswap): wire cashback layout for post-2026-04-28 program upgrade Cashback PumpSwap pools require extra writable accounts inserted before pool-v2. Identified layouts by sampling on-chain post-cutover txs: - buy: insert user_volume_accumulator_quote_ata (27 accounts vs 26) ref tx 4JaWdExj…fvjK - sell: insert user_volume_accumulator_quote_ata + user_volume_accumulator (26 accounts vs 24) ref tx 4ei1cJV7…NP3 Detect via pool account byte 244 (is_cashback_coin). Adds the standalone sample_cashback_pumpswap.py used to reverse-engineer the layouts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
22a0c23450 |
feat(pumpfun): align src/ with 2026-04-28 program upgrade + cashback path (#167)
Mirrors the four learning-examples commits ( |
||
|
|
bda9099275 |
chore(examples): accept argv override and bump RPC timeouts
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>
|
||
|
|
ad6d2cc755 |
fix(examples): add pool-v2 account to pumpswap, repair geyser listener
PumpSwap (manual_buy_pumpswap.py / manual_sell_pumpswap.py)
- Real on-chain pump-swap buys use 24 accounts; the IDL is incomplete
(lists 23). Missing 24th account is pool-v2 PDA, derived from seed
["pool-v2", base_mint] under the pump-amm program.
- Without it, pump-amm throws AnchorError 6023 (Overflow) at buy.rs:400/414
after the trade transfers complete (the source of the earlier "pre-existing
overflow" — was actually a missing account, not a math bug).
- find_pool_v2(base_mint) added; appended to both buy and sell account lists
after fee_program. The April-28 breaking-fee accounts (still gated behind
INCLUDE_BREAKING_FEE_ACCOUNTS) come after pool-v2 — matching the doc's
"All accounts till bonding-curve-v2 and pool-v2 remain the same."
- AsyncClient timeout bumped to 120s so the heavy getProgramAccounts pool
lookup doesn't time out on slower RPC paths.
- Live-verified on mainnet with 9PwadsGz...pump:
buy 2xgdJJsDMzoMyxJzrCATKwGfpX7M... err=None (24 accounts)
sell 4vdBkGpCMQYEWncj5k1suGzKPUMQ... err=None (24 accounts)
manual_buy_geyser.py
- Fixes IndexError when create tx references ALT-loaded keys
(matches the same fix applied to manual_buy.py).
- Re-enables the 15s sleep + on-chain price/fee_recipient lookup that
were commented out for testing — needed for buys on real pools.
- Live-verified: 45wSgX2Pu1LLZWLgu3e9qdhRaGJjqwMqQP5HdeV7zMBqNXziB2uEgxQd5bAqvAhBLFnAqzbuK3LAWowhxZxdemJ
(18-account buy, err=None) on Token-2022 cashback-enabled INSIDER mint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
57ffe8bfbb |
feat(examples): apply 18-account buy upgrade to mint_and_buy + pumpswap
Extends the 2026-04-28 program-upgrade prep (commit
|
||
|
|
c25e2edb57 |
feat(examples): add 18-account buy / 16-17-account sell for 2026-04-28 upgrade
pump.fun program upgrade scheduled for 2026-04-28 16:00 UTC adds one new
mutable account (one of 8 fee recipients) at the end of buy/sell ixs,
after bonding-curve-v2. The new format is already accepted on mainnet
ahead of the cutover, so applying now keeps scripts working on both
sides of the upgrade.
Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
Per file:
manual_buy.py — 18-account buy. Also fixes a pre-existing
listener crash on Address-Lookup-Table create
txs (skips them) and adds bool/OptionBool
handling to decode_create_instruction so
create_v2 doesn't raise on its trailing args.
manual_buy_cu_optimized.py — 18-account buy. Bumps the CU-optimization
account-data limit to 16MB (tested) — older
values trigger MaxLoadedAccountsDataSizeExceeded
on Token-2022/cashback coins. Same listener
ALT-skip and decoder bool fixes.
manual_buy_geyser.py — 18-account buy. Re-enables price calculation
and mayhem-mode-aware fee_recipient detection
(was scaffolded out for testing).
manual_sell.py — 16-account non-cashback / 17-account cashback
sell. Reads is_cashback_coin from BC byte 82
and inserts user_volume_accumulator before
bonding-curve-v2 when needed. TOKEN_MINT now
accepts argv[1] override.
Live-verified on mainnet:
buy manual_buy.py 3AVnLC3sdBD598cs3... + 3bWU9bB8U9aemA2BFhS2...
buy manual_buy_cu_optimized 2PfH5rHw62o3KkQ3N4z8...
sell manual_sell.py 3E7dyPBPBRe95BtPQXmb... + 5L4wWSzPV36m6XqXAswB...
All four show the expected 18 (buy) / 16 (sell non-cashback) account count
with one of the 8 BREAKING_FEE_RECIPIENTS as the trailing mutable account.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9b4841890c |
fix(examples): align listeners and decoders with refreshed pump.fun IDL
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>
|
||
|
|
156cf508a0 |
chore(idl): refresh pump.fun IDLs from upstream
Replace idl/pump_fun_idl.json and idl/pump_fees.json with the latest from
pump-fun/pump-public-docs @ 7de0b95 (2026-04-23). pump_swap_idl.json was
already up to date.
Drops the idl/upstream/ vendor copy added in
|
||
|
|
7864504c84 |
docs(idl): vendor upstream pump.fun IDLs for reference
Snapshot of pump-fun/pump-public-docs @ 7de0b95 (2026-04-23) under idl/upstream/ — pump.json, pump_amm.json, pump_fees.json and their .ts counterparts. Used for local diffing against the IDLs the bot loads from idl/; not imported at runtime. README links to the new directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4290b54f31 |
build(deps): bump aiohttp in the uv group across 1 directory (#164)
--- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.13.4 dependency-type: direct:production dependency-group: uv ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
02692793a9 | docs: add cli and skill links | ||
|
|
73fe95efe4 |
docs: mention OpenClaw in pumpclaw ecosystem note
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
f8eb5eb8a0 |
docs: add ecosystem links to pumpfun-cli and pumpclaw
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |