mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-11-02 06:16:10 +00:00
13b9ec2baf60c335588fe99ce8d85aae9c49d537
293
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
546b94af6f |
fix(examples): add bonding_curve_v2 to learning examples (#160)
* fix(examples): add bonding_curve_v2 remaining account to learning examples Add the required bonding_curve_v2 PDA as a remaining account to buy/sell instructions in manual_buy, manual_sell, mint_and_buy, and mint_and_buy_v2 learning examples, matching the pump.fun program upgrade. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(examples): add bonding_curve_v2 to manual_buy_geyser and manual_buy_cu_optimized Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
4a48f545c1 |
feat(pumpfun): update IDLs and add bonding_curve_v2 + cashback support (#159)
Update all 3 pump.fun IDL files (pump_fun, pump_swap, pump_fees) with new cashback rewards and fee-sharing features. Fix IDL parser to handle tuple struct types (OptionBool). Add bonding_curve_v2 remaining account to all buy/sell instructions as required by the pump.fun program upgrade. Key changes: - Fix IDL parser crash on tuple struct fields (string vs dict) - Add bonding_curve_v2 PDA derivation and append as remaining account - Add is_cashback_coin field to TokenInfo and BondingCurve decoding - Propagate is_cashback_enabled from CreateEvent/create_v2 to TokenInfo - Conditionally include user_volume_accumulator for cashback sell txs Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
0b6779ca34 |
Fix buy/sell transactions silently failing on-chain (#157)
* fix(core): disable account_data_size limit and add meta.err checking The 12.5MB setLoadedAccountsDataSizeLimit was causing all buy transactions to fail with MaxLoadedAccountsDataSizeExceeded since pump.fun migrated to Token-2022. Transactions landed on-chain (fees paid) but inner instructions were rejected — the bot paid gas for nothing. Changes: - Disable account_data_size in all bot configs (Token-2022 needs >12.5MB) - Add meta.err check in get_buy_transaction_details() for clear error reporting when transactions fail on-chain - Include tx signature in platform_aware.py error messages for debugging Tested: full buy→sell→cleanup cycle works on pump_fun with geyser listener. * fix(core): check meta.err in confirm_transaction to detect failed txs Solana transactions can be "confirmed" (included in a block) but still fail execution if inner program instructions are rejected. Previously, confirm_transaction only checked that the tx landed on-chain, causing silent failures in buy, sell, and cleanup operations. Now fetches the transaction result after confirmation and checks meta.err, returning False when the transaction failed. This fixes the ATA cleanup issue where sells were silently failing with Custom: 6003 errors, leaving non-zero token balances. * fix(core): treat failed tx fetch as unconfirmed in confirm_transaction When _get_transaction_result returns None (RPC failure, timeout, etc.), the function was falling through to return True — silently treating an unknown state as success. Now returns False with a warning log. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
c5b0161d05 |
build(deps): bump the uv group across 1 directory with 2 updates (#156)
--- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.13.3 dependency-type: direct:production dependency-group: uv - dependency-name: protobuf dependency-version: 6.33.5 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> |
||
|
|
40ad32b8d0 |
feat(core): add RPC rate limiting and retry handling (#154)
* feat(core): add RPC rate limiting, retry logic, and 429 handling Addresses #44 — users on free-tier RPC endpoints hit HTTP 429 errors during buy transactions due to no rate limiting or retry handling. - Add TokenBucketRateLimiter (new file: src/core/rpc_rate_limiter.py) - Gate all RPC methods through rate limiter (both post_rpc and solana-py calls) - Rewrite post_rpc() with retry loop, exponential backoff, jitter, and specific 429 detection with Retry-After header support - Replace per-call aiohttp session with shared persistent session - Wire node.max_rps from YAML bot config through to SolanaClient - Fix cleanup manager and learning example to use SolanaClient abstraction instead of bypassing it via get_client() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(core): address CodeRabbitAI review feedback on rate limiting PR Validate max_rps > 0 in TokenBucketRateLimiter to prevent ZeroDivisionError and infinite loops with fractional values. Add asyncio.Lock to _get_session to fix race condition, handle non-numeric Retry-After headers gracefully, replace dead json.JSONDecodeError with aiohttp.ContentTypeError, and combine burn+close into a single transaction in cleanup example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: document RPC rate limiting feature in README - Add section on built-in RPC rate limiting with token bucket algorithm - Document configurable max RPS and automatic retry logic - Update roadmap to mark "Configurable RPS" as completed - Clarify benefits of rate limiting for provider compliance Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: use math.ceil for burst_size to handle fractional max_rps - Replace int(max_rps) with math.ceil(max_rps) in burst_size calculation - Prevents infinite loop when max_rps < 1.0 (e.g., 0.5 RPS would result in burst_size=0) - Ensures burst_size is always at least 1 for valid fractional rates - Addresses CodeRabbit feedback on rpc_rate_limiter.py:27 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(core): validate burst_size and fix table pipe consistency Address remaining CodeRabbitAI review feedback: add burst_size validation guard, fix TRY003 lint (use msg variable for ValueError), break long line under 88 chars, and fix MD055 table pipe style in README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(core): separate 429 retry budget from error retries in post_rpc 429 responses no longer count against max_retries — they use a dedicated max_429_retries counter (default 10) so free-tier users hitting rate limits won't exhaust retries prematurely. Also refresh the aiohttp session inside the retry loop to avoid stale references after network failures, and fix cleanup log message accuracy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Anton Sauchyk <antonsauchyk@gmail.com> |
||
|
|
31955103dc |
feat: add Windows compatibility for uvloop dependency (#155)
Fixes chainstacklabs/pumpfun-bonkfun-bot#152 Changes: - Make uvloop optional with platform-specific markers (Unix only) - Add winloop as Windows alternative for performance optimization - Update code to gracefully fall back to standard asyncio when event loop libraries are unavailable - Both bot_runner.py and universal_trader.py now detect platform and use appropriate event loop implementation This resolves the blocking installation issue for Windows users while maintaining performance benefits on all platforms. Windows users can now install and run the bot with winloop for improved performance, or use standard asyncio as fallback. Related: https://github.com/smypmsa/gh-triage-reports/blob/main/issues/chainstacklabs-pumpfun-bonkfun-bot/issue-152-2026-02-08.md Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
9d93d859a8 |
Reduce RPC delays when selling (#150)
* feat(trading): enhance sell execution with token amount and price parameters to reduce RPC delays * feat(trading): add method to fetch actual token balance after transaction to improve accuracy * feat(trading): update account data size limit and enhance price calculations |
||
|
|
03a4e7bcbc |
Add mayhem mode support and Token2022 integration (#149)
* feat: mayhem update in idl * feat(examples): update bonding curve scripts * feat(example): update listen_blocksubscribe * feat(examples): update geyser listener * feat(examples): update all new token listeners * feat(examples): add comments, fix printing, formatting * feat(examples): pumpswap buy and sell update with mayhem mode * fix(examples): sell pump amm fee recipient * feat(examples): update decode scripts * feat(examples): update fetch price * feat(examples): buy and sell bonding curve scripts * feat(examples): add mint with mayhem mode enabled * feat(examples): improve listening to wallet txs * feat(examples): migration listener improvements * feat(examples): global vol accumulator is not writable * feat(examples): support token/token2022 programs in buy instructions * feat(examples): token/token2022 for pumpswap buy * feat(examples): token/token2022 supprot for sell instructions * feat(bot): support create_v2 with token2022, mayhem mode, other fixes * fix(bot): support only token2022 in logs and pumportal listeners * feat(bot): token2022 support in cleanup flow * fix(bot): update token program handling and improve price validation in trading logic * feat(bot): enhance token program handling for LetsBonk integration |
||
|
|
7ae8b560bd | feat(pumpswap): update pool account to writable for buy/sell instructions (#146) |