-
Notifications
You must be signed in to change notification settings - Fork 310
Add mayhem mode support and Token2022 integration #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
learning-examples/listen-migrations/listen_logsubscribe.py (4)
97-102: Add type hints and complete docstring tois_transaction_successful.Function lacks type hints and Google-style docstring documentation.
-def is_transaction_successful(logs): +def is_transaction_successful(logs: list[str]) -> bool: + """Check if a transaction succeeded by examining log messages. + + Args: + logs: List of log messages from the transaction. + + Returns: + True if transaction succeeded, False if errors found in logs. + """ for log in logs: if "AnchorError thrown" in log or "Error" in log: - print(f"[ERROR] Transaction failed: {log}") + logger.error(f"Transaction failed: {log}") return False return True
105-123: Add type hints and complete docstring toprint_transaction_details; use logger.Function lacks type hints, incomplete docstring, and uses
print()instead of logger.-def print_transaction_details(log_data): +def print_transaction_details(log_data: dict) -> None: + """Parse and log migration transaction details. + + Args: + log_data: Transaction log data containing "logs" key with log messages. + """ logs = log_data.get("logs", []) parsed_data = {} for log in logs: if log.startswith("Program data:"): try: data = base64.b64decode(log.split(": ")[1]) parsed_data = parse_migrate_instruction(data) if parsed_data: - print("[INFO] Parsed from Program data:") + logger.info("Parsed from Program data:") for key, value in parsed_data.items(): - print(f" {key}: {value}") + logger.info(f" {key}: {value}") except Exception as e: - print(f"[ERROR] Failed to decode Program data: {e}") + logger.exception("Failed to decode Program data") if not parsed_data: - print("[ERROR] Failed to parse migration data: parsed data is empty") + logger.error("Failed to parse migration data: parsed data is empty")
125-194: Add type hints tolisten_for_migrationsand replace allprint()with logger.The main async function lacks return type hint and systematically uses
print()instead of logging.-async def listen_for_migrations(): +async def listen_for_migrations() -> None: while True: try: - print("\n[INFO] Connecting to WebSocket ...") + logger.info("Connecting to WebSocket ...") async with websockets.connect(WSS_ENDPOINT) as websocket: subscription_message = json.dumps( { "jsonrpc": "2.0", "id": 1, "method": "logsSubscribe", "params": [ {"mentions": [str(MIGRATION_PROGRAM_ID)]}, {"commitment": "processed"}, ], } ) await websocket.send(subscription_message) - print( - f"[INFO] Listening for migration instructions from program: {MIGRATION_PROGRAM_ID}" - ) + logger.info( + f"Listening for migration instructions from program: {MIGRATION_PROGRAM_ID}" + ) response = await websocket.recv() - print(f"[INFO] Subscription response: {response}") + logger.info(f"Subscription response: {response}") while True: try: response = await asyncio.wait_for(websocket.recv(), timeout=60) data = json.loads(response) if "method" in data and data["method"] == "logsNotification": log_data = data["params"]["result"]["value"] logs = log_data.get("logs", []) signature = log_data.get("signature", "N/A") - print(f"\n[INFO] Transaction signature: {signature}") + logger.info(f"Transaction signature: {signature}") if is_transaction_successful(logs): if not any( "Program log: Instruction: Migrate" in log for log in logs ): - print("[INFO] Skipping: no migrate instruction") + logger.info("Skipping: no migrate instruction") continue if any( "Program log: Bonding curve already migrated" in log for log in logs ): - print( + logger.info( "[INFO] Skipping: bonding curve already migrated" ) continue - print("[INFO] Processing migration instruction...") + logger.info("Processing migration instruction...") print_transaction_details(log_data) else: - print("[INFO] Skipping failed transaction.") + logger.info("Skipping failed transaction.") except TimeoutError: - print( + logger.warning( "[INFO] Timeout waiting for WebSocket message, retrying..." ) except Exception as e: - print(f"[ERROR] An error occurred: {e}") + logger.exception("An error occurred") break except Exception as e: - print(f"[ERROR] Connection error: {e}") - print("[INFO] Reconnecting in 5 seconds...") + logger.exception("Connection error") + logger.info("Reconnecting in 5 seconds...") await asyncio.sleep(5)Note: When using
logger.exception(), do not include the exception in the message—it's automatically appended. Per guidelines: "Log exceptions with context using logging.exception()".
1-50: Add type hints to all public functions, use centralized logger, and complete Google-style docstrings.This learning example must follow the same coding standards as production code per guidelines. Verification confirms systematic violations across all 4 public functions:
- Missing type hints on
parse_migrate_instruction,is_transaction_successful,print_transaction_details, andlisten_for_migrations- 19 print() calls instead of centralized logger—replace with module-level logger via
from src.utils.logger import get_logger- Incomplete docstrings—add
Args:andReturns:sections in Google styleApply this diff:
+import logging + import asyncio import base64 import json import os import struct import base58 import websockets from dotenv import load_dotenv from solders.pubkey import Pubkey +from src.utils.logger import get_logger + load_dotenv() +logger = get_logger(__name__) + WSS_ENDPOINT = os.environ.get("SOLANA_NODE_WSS_ENDPOINT") MIGRATION_PROGRAM_ID = Pubkey.from_string( "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg" ) -def parse_migrate_instruction(data): +def parse_migrate_instruction(data: bytes) -> dict | None: """Parse migration event from the migration wrapper program. + Args: + data: Binary event data from the migration wrapper program. + + Returns: + Parsed event data as a dictionary, or None if parsing fails. + Note: This parses the event emitted by the migration wrapper program (39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg), which has a different structure than CompletePumpAmmMigrationEvent in pump_fun_idl.json. """ if len(data) < 8: - print(f"[ERROR] Data length too short: {len(data)} bytes") + logger.error(f"Data length too short: {len(data)} bytes") return NoneThen replace all 19
print()calls with appropriate logger levels:
print(f"[ERROR]...")→logger.error(...)print(f"[INFO]...")→logger.info(...)Add type hints to remaining functions:
is_transaction_successful(logs: list) -> bool,print_transaction_details(log_data: dict) -> None, andlisten_for_migrations() -> None.learning-examples/listen-new-tokens/listen_pumpportal.py (1)
99-102: Use proper logging instead of print statements for errors.The error handling is a good addition, but should use the centralized logger instead of print statements for consistency with project standards.
First, add the import at the top of the file:
from src.utils.logger import get_logger logger = get_logger(__name__)Then apply this diff to use proper logging:
except json.JSONDecodeError: - print(f"\nReceived non-JSON message: {message}") + logger.warning("Received non-JSON message: %s", message) except Exception as e: - print(f"\nAn error occurred: {e}") + logger.exception("Error processing WebSocket message")Similarly, update the error handling in the
main()function (lines 109-112) and connection closed message (line 97) to use logging.As per coding guidelines.
src/platforms/pumpfun/curve_manager.py (1)
304-342:validate_curve_state_structureincorrectly calls asyncget_pool_statewithout awaitingAt Line 316,
pool_state = self.get_pool_state(pool_address)assigns the coroutine object, not the decoded dict, becauseget_pool_stateisasync. The subsequent checks (if field not in pool_state,isinstance(pool_state[field], int)) will fail at runtime.Consider updating this method to be async and await the call, or otherwise running the coroutine in a controlled way consistent with how
CurveManageris defined:- def validate_curve_state_structure(self, pool_address: Pubkey) -> bool: + async def validate_curve_state_structure(self, pool_address: Pubkey) -> bool: @@ - pool_state = self.get_pool_state(pool_address) + pool_state = await self.get_pool_state(pool_address)If
CurveManager.validate_curve_state_structureis currently synchronous in the interface, you’ll need to adjust the interface and call sites accordingly or explicitly execute the coroutine here using your event-loop strategy instead of calling it directly.learning-examples/copytrading/listen_wallet_transactions.py (1)
124-258: Progressive TradeEvent parsing looks good; hardenix_namedecoding and tidy long linesThe progressive parsing and grouping of extended fields looks consistent with the documented layout: core fields are length‑guarded, extended blocks are only parsed when enough bytes remain, and sensible defaults maintain backward compatibility. That part looks solid.
One robustness gap: decoding
ix_namecan raiseUnicodeDecodeErrorif the data are malformed or not valid UTF‑8. Because the base64/log payload is fully external, this would currently bubble up and kill the listener instead of just omitting the name. Consider catching this locally and falling back to an empty string:- # Parse string field (ix_name): 4 bytes for length + string data - if len(data) >= offset + 4: - string_length = struct.unpack("<I", data[offset : offset + 4])[0] - offset += 4 - if len(data) >= offset + string_length: - ix_name = data[offset : offset + string_length].decode("utf-8") - else: - ix_name = "" - else: - ix_name = "" + # Parse string field (ix_name): 4 bytes for length + string data + if len(data) >= offset + 4: + string_length = struct.unpack("<I", data[offset : offset + 4])[0] + offset += 4 + if len(data) >= offset + string_length: + try: + ix_name = data[offset : offset + string_length].decode("utf-8") + except UnicodeDecodeError: + ix_name = "" + else: + ix_name = "" + else: + ix_name = ""Also, a couple of the new dict entries are well over the 88‑char limit and use single quotes. You can keep things Ruff‑friendly and consistent with the rest of the file by switching to double quotes and wrapping with parentheses, e.g.:
- "fee_recipient": base58.b58encode(fee_recipient).decode() if fee_recipient != b'\x00' * 32 else None, + "fee_recipient": ( + base58.b58encode(fee_recipient).decode() + if fee_recipient != b"\x00" * 32 + else None + ), @@ - "creator": base58.b58encode(creator).decode() if creator != b'\x00' * 32 else None, + "creator": ( + base58.b58encode(creator).decode() + if creator != b"\x00" * 32 + else None + ),learning-examples/manual_buy_geyser.py (1)
341-373: Updatetrack_volumeencoding inmanual_buy_geyser.pyandmanual_buy_cu_optimized.pyto use OptionBool formatVerification confirms the encoding mismatch. The current
manual_buy_geyser.py(line 431) andmanual_buy_cu_optimized.py(line 331) encodetrack_volumeas a single byte usingstruct.pack("<B", 1), producing0x01. However,manual_buy.py,mint_and_buy.py,mint_and_buy_v2.py, andmanual_sell.pyall encode it as OptionBool (two bytes[1, 1]), with explicit documentation:[0] = None,[1, 0] = Some(false),[1, 1] = Some(true).Since all files target the same
PUMP_PROGRAMand share the same buy instruction discriminator, this encoding mismatch will cause instruction data to be mis-parsed, leading to transaction rejections on-chain.Fix:
- Replace
struct.pack("<B", 1)withbytes([1, 1])in both files- Add the OptionBool documentation comment for clarity
🧹 Nitpick comments (56)
learning-examples/listen-migrations/listen_programsubscribe.py (1)
60-111: Consider adding type hints and structured logging as per coding guidelines.The parsing logic for the new
is_mayhem_modefield is correct. However, the function could be improved to align with the project's coding guidelines.As per coding guidelines, consider these improvements:
+from typing import Any +from src.utils.logger import get_logger + +logger = get_logger(__name__) + -def parse_market_account_data(data): - """Parse Pool account data according to pump_swap_idl.json structure. +def parse_market_account_data(data: bytes) -> dict[str, Any]: + """Parse Pool account data according to pump_swap_idl.json structure. + + Args: + data: Raw binary account data from Solana RPC. - Total 11 fields including the new is_mayhem_mode field added with mayhem update. - """ + Returns: + Dictionary containing parsed pool account fields including: + pool_bump, index, creator, base_mint, quote_mint, lp_mint, + pool_base_token_account, pool_quote_token_account, lp_supply, + coin_creator, and is_mayhem_mode. + """ parsed_data = {} offset = 8 # Discriminator # ... fields definition ... try: # ... parsing logic ... except Exception as e: - print(f"[ERROR] Failed to parse market data: {e}") + logger.exception("Failed to parse market data") return parsed_dataNote: The import path
src.utils.loggershould be adjusted if this learning example has a different module structure.learning-examples/listen-migrations/compare_migration_listeners.py (1)
315-367: Consider extracting shared parsing logic to reduce duplication.The
parse_market_account_datafunction is duplicated fromlisten_programsubscribe.py. While this is acceptable for learning examples, consider extracting it to a shared utility module for easier maintenance.Additionally, apply the same type hints and logging improvements suggested for
listen_programsubscribe.py:+from typing import Any +from src.utils.logger import get_logger + +logger = get_logger(__name__) + -def parse_market_account_data(data): +def parse_market_account_data(data: bytes) -> dict[str, Any]: """ Parse binary Pool account data according to pump_swap_idl.json structure + + Args: + data: Raw binary account data from Solana RPC. + + Returns: + Dictionary containing parsed pool account fields. + """learning-examples/copytrading/listen_wallet_transactions.py (1)
276-305: Extended display logic is correct and improves event context; minor style cleanupsThe new display logic correctly uses
ix_name, fee/creator fields, and real vs virtual reserves fromdecode_trade_event, which gives much richer context for each trade and aligns well with the “log significant trading events with context” guidance. Based on learnings.Minor polish to consider:
- Use double quotes consistently and avoid nested f‑strings to keep things readable and formatter‑friendly.
- Split the long
For example:
- ix_name = trade_data.get('ix_name', '') - trade_type = 'BUY' if trade_data['is_buy'] else 'SELL' - print(f" Type: {trade_type}{f' ({ix_name})' if ix_name else ''}") + ix_name = trade_data.get("ix_name", "") + trade_type = "BUY" if trade_data["is_buy"] else "SELL" + ix_suffix = f" ({ix_name})" if ix_name else "" + print(f" Type: {trade_type}{ix_suffix}") @@ - if trade_data['fee'] > 0 or trade_data['fee_basis_points'] > 0: - print(f" Fee: {trade_data['fee'] / 1_000_000_000:.6f} SOL ({trade_data['fee_basis_points']} bps)") + if trade_data["fee"] > 0 or trade_data["fee_basis_points"] > 0: + fee_sol = trade_data["fee"] / 1_000_000_000 + print( + f" Fee: {fee_sol:.6f} SOL " + f"({trade_data['fee_basis_points']} bps)" + ) @@ - print(f" Virtual Reserves: {trade_data['virtual_sol_reserves'] / 1_000_000_000:.6f} SOL / {trade_data['virtual_token_reserves']:,} tokens") + virt_sol = trade_data["virtual_sol_reserves"] / 1_000_000_000 + virt_tokens = trade_data["virtual_token_reserves"] + print(f" Virtual Reserves: {virt_sol:.6f} SOL / {virt_tokens:,} tokens")These are non‑blocking, but will make the example script better aligned with the repo’s Python style.
learning-examples/listen-new-tokens/compare_listeners.py (3)
261-305: Create/CreateV2 instruction decoders look correct; Ruff warnings are mostly noise hereThe new
decode_create_instructioncorrectly:
- Skips the 8‑byte discriminator.
- Reads
name,symbol,urias length‑prefixed UTF‑8 strings and a 32‑bytecreatorpubkey.- Maps account indices (
mint,bondingCurve,user) consistently with the IDL comments used elsewhere.- Tags
token_standard="legacy"andis_mayhem_mode=False, which the static analysis “password” warning (S105) is incorrectly flagging.For this diagnostic script, the broad
except Exceptionwith a simple error print is a reasonable trade‑off, even though Ruff (BLE001/TRY300) prefers more specific exceptions. If you want to satisfy Ruff, you could narrow theexceptto(struct.error, UnicodeDecodeError, IndexError, ValueError)and keep the behavior the same.
307-355: CreateV2 decoder matches the Token2022 + mayhem layout
decode_create_v2_instructionmirrors the legacy decoder, with the additions you need:
- Reads the same string triplet, plus a 32‑byte
creatorpubkey.- Parses
is_mayhem_modefrom the trailing flag byte.- Uses account indices
[0]=mint, [2]=bondingCurve, [5]=userconsistent with other pump.fun decoders.- Tags
token_standard="token2022".Same note as above: the broad
except Exceptionis acceptable for a learning tool, but if you want Ruff‑clean code you can tighten the exception list.
777-858: Geyser gRPC listener’s dual‑path decoding is consistent with the other listenersIn the Geyser path:
is_create/is_create_v2checks mirror the discriminator logic used in the block listener.- Account keys are extracted via
msg.account_keysand base58‑encoded, with bounds checks.- The same decoders are used (
decode_create_instruction/decode_create_v2_instruction), and the token is de‑duplicated viaknown_tokens.You might optionally consider using
decoded.get("mint")instead of assumingaccount_keys[0]is always the mint, but given the pump.fun IDL and the other code in this repo, the current assumption is consistent.src/platforms/pumpfun/pumpportal_processor.py (1)
8-11: PumpPortal processor now correctly tags Token2022 program usageThis update ties the PumpPortal path into the new token‑program‑aware model:
- Using
SystemAddresses.TOKEN_2022_PROGRAMastoken_program_idmatches pump.fun’s CreateV2 default and the rest of the codebase.- Passing
token_program_idintoderive_associated_bonding_curveensures the ATA derivation matches the actual token program.- Returning
token_program_idinTokenInfolets downstream trading/cleanup flows use the correct SPL program.Given PumpPortal doesn’t expose token standard yet, the “assume Token2022” comment is a reasonable documented trade‑off. If PumpPortal later adds a token‑standard field, it would be straightforward to branch here to support legacy tokens explicitly.
Also applies to: 84-90, 91-110
learning-examples/decode_from_getTransaction.py (1)
29-46: Create/CreateV2 decoding and mayhem metadata are wired correctly for this exampleUnpacking three strings, skipping the 8‑byte discriminator and 32‑byte creator, and then reading a trailing byte as
is_mayhem_modeis consistent between legacy and CreateV2 paths, and the addedtoken_standard/is_mayhem_modekeys make downstream inspection much clearer.Since this script is decoding externally sourced transaction data, consider adding minimal length checks (or try/except around
struct.unpack_from) to avoid uncaughtstruct.erroron truncated payloads, even in a learning‑example context.Also applies to: 49-75
src/trading/universal_trader.py (1)
194-201: Token program tracking for cleanup is correct; wrap long lines and handle None safelyUsing
self.traded_token_programs: dict[str, Pubkey]keyed bystr(mint)and then buildingmints_listplus a paralleltoken_program_idslist in_cleanup_resources()is a reasonable way to feedhandle_cleanup_post_session()with per‑mint program context.Two small follow‑ups:
- Line length/style – The
self.traded_token_programsdeclaration and the list comprehension fortoken_program_idsare long enough that they likely exceed the 88‑character limit. You can keep them functionally identical while satisfying style by splitting, e.g.:- self.traded_token_programs: dict[str, Pubkey] = {} # Maps mint (as string) to token_program_id + # Maps mint (as string) to token_program_id + self.traded_token_programs: dict[str, Pubkey] = {} ... - token_program_ids = [ - self.traded_token_programs.get(str(mint)) - for mint in mints_list - ] + token_program_ids = [ + self.traded_token_programs.get(str(mint)) + for mint in mints_list + ]
- None token_program_id – If any
TokenInfoarrives withouttoken_program_idset,self.traded_token_programs.get(str(mint))will produceNone. Please confirm thatAccountCleanupManager.cleanup_ata()(and the post‑session cleanup mode you use) can safely handle aNoneprogram ID or apply a sensible default (e.g., legacy TOKEN vs TOKEN2022).Also applies to: 324-344
src/platforms/pumpfun/instruction_builder.py (1)
154-162: Consider de-duplicating track_volume_bytes constantThe
track_volume_bytes = bytes([1, 1])literal is duplicated in both buy and sell builders. It’s small, but factoring it into a module-level constant would make the OptionBool encoding explicit and reduce drift if the layout ever changes.For example:
+TRACK_VOLUME_SOME_TRUE = bytes([1, 1]) + ... - track_volume_bytes = bytes([1, 1]) + track_volume_bytes = TRACK_VOLUME_SOME_TRUE ... - track_volume_bytes = bytes([1, 1]) + track_volume_bytes = TRACK_VOLUME_SOME_TRUEPure readability/maintainability improvement; behavior stays the same.
Also applies to: 256-264
learning-examples/bonding-curve-progress/poll_bonding_curve_progress.py (2)
20-21: Hardcoded TOKEN_MINT is fine for example usage, but consider documenting itUsing a concrete mint here makes the script runnable out of the box, which is nice. If you expect people to point this at arbitrary tokens, consider either:
- Reading
TOKEN_MINTfrom env/CLI, or- Explicitly mentioning in the module docstring that this is an example mint and should be replaced.
No functional issue as-is.
84-107: Optional: avoid magic offsets when parsing creator / mayhem fieldsThe parsing logic is correct given the current layout:
- Base struct:
<QQQQQ?>= 41 bytes after the 8-byte discriminator.- Creator occupies bytes
[49:81](8 + 41 .. +32).is_mayhem_modelives at index 81 when present.The explicit constants (
49,81,73,74) are a bit magical, though. For readability and future-proofing, consider deriving them fromstruct.calcsizeand the running offset instead of hard-coding:base_size = struct.calcsize("<QQQQQ?") base_offset = 8 + base_size # 49 ... if len(data) >= 8 + base_size + 32: creator_bytes = data[base_offset : base_offset + 32] ... if len(data) >= 8 + base_size + 32 + 1: result["is_mayhem_mode"] = bool(data[base_offset + 32])This keeps the implementation self-documenting if the base struct ever changes.
src/cleanup/modes.py (2)
19-35: Verify token_program_id propagation and list alignment for post-session cleanupPlumbing
token_program_idinto all three cleanup helpers and down intoAccountCleanupManager.cleanup_atais the right move for TOKEN vs TOKEN_2022 awareness.Two things to double‑check:
- Call sites: Ensure all existing callers of these helpers are updated to pass the new
token_program_id(or list thereof), otherwise this will be a breaking change.- Length mismatches in
handle_cleanup_post_session: Usingzip(mints, token_program_ids)will silently ignore extra items if the lists differ in length. If that’s undesirable, consider either asserting equality or logging a warning when they don’t match.Example:
if len(mints) != len(token_program_ids): logger.warning( "Mismatched mints (%d) and token_program_ids (%d); extra entries will be ignored", len(mints), len(token_program_ids), ) for mint, token_program_id in zip(mints, token_program_ids): await manager.cleanup_ata(mint, token_program_id)Behavior is otherwise sound.
Also applies to: 37-53, 55-71
19-28: Consider adding type hints and docstrings to cleanup entrypointsThese async helpers are part of the public cleanup flow and now gained new parameters (
token_program_id,token_program_ids). To stay aligned with the project-wide guidelines (type hints + Google-style docstrings), it would be good to:
- Add parameter/return type annotations, and
- Document the semantics of
cleanup_mode,cleanup_with_prior_fee,force_burn, and the meaning oftoken_program_id.This will make future changes around TOKEN vs TOKEN_2022 migrations less error-prone.
Also applies to: 37-46, 55-64
learning-examples/decode_from_getAccountInfo.py (1)
14-23: _STRUCT is now unused – clarify intent or removeThe
_STRUCTdefinition withconstruct.Structis no longer used for parsing since__init__switched to manual byte offsets. If it’s intended purely as documentation of the layout, consider:
- Adding a comment to that effect, or
- Removing
_STRUCTand theconstructimports to avoid an unnecessary runtime dependency.This is a small clean-up, but it’ll reduce confusion for readers wondering why
constructis imported here.learning-examples/listen-new-tokens/listen_logsubscribe.py (3)
34-71: Event discriminator and print helper integration look goodDefining
CREATE_EVENT_DISCRIMINATORand addingprint_token_infogives this listener the same “new token detected” UX as the blockSubscribe variant and ensures you don’t accidentally treat non-Create events as tokens.If you find yourself tweaking the output format often, consider centralizing
print_token_infoin a shared helper module to avoid divergence between listeners, but it’s fine as-is for learning examples.
74-141: Create / CreateV2 parsers are robust; could be DRY’d slightlyBoth parsers:
- Validate minimum length,
- Parse length-prefixed UTF‑8 strings with explicit boundary checks,
- Parse 32‑byte pubkeys safely, and
- Provide helpful context (offset, data length, hex preview) on error.
The logic is sound and matches the
listen_logsubscribe_abcreference. If you want to trim duplication, you could extract the common loop into a small helper:def _parse_event_fields(data, offset, fields): parsed = {} for field_name, field_type in fields: ... return parsed, offsetThen both
parse_create_instructionandparse_create_v2_instructioncan reuse it, differing only in the extrais_mayhem_modehandling andtoken_standardvalue.Not a blocker; the current version is already quite defensive.
Also applies to: 143-211
247-299: Core logs loop logic is solid; small Ruff cleanup opportunityThe main logs loop correctly:
- Detects both
CreateandCreateV2instructions,- Filters program logs to
Program data:entries,- Guards on
CREATE_EVENT_DISCRIMINATORto avoid mis-parsing other events, and- Delegates to the appropriate parser before printing via
print_token_info.Two minor polish points:
- Ruff F541: This line doesn’t need an f-string:
- print(f"⚠️ Parsing failed for CreateEvent") + print("⚠️ Parsing failed for CreateEvent")
- Exception granularity (BLE001): You’re catching bare
Exceptionin a couple of places. For a learning/integration script that must never crash, this is understandable, but if you want to appease Ruff you could narrow it to the expected failure types (ValueError,KeyError,base64.binascii.Error,json.JSONDecodeError, etc.) in the inner loops and leave a broad catch only at the outermost level.Functionality-wise, this block looks good; I’d just fix the F541 warning.
src/platforms/pumpfun/address_provider.py (1)
34-39: MAYHEM_FEE constant looks good; consider simplifying the container classThe hardcoded
MAYHEM_FEEwith an inline note about offset483on the Global account is clear and matches the manual parsing approach used elsewhere. Given Ruff’sRUF009about function calls in dataclass defaults and the fact thatPumpFunAddressesis essentially a namespace of constants, you might consider converting it to a plain class (or module-level constants) instead of a@dataclassto avoid the dataclass-default semantics. This is optional and mostly about style/tooling noise.learning-examples/listen-new-tokens/listen_logsubscribe_abc.py (1)
112-156: Robust Create/CreateV2 parsing with good diagnostics; minor nitsThe updated
parse_create_instruction/parse_create_v2_instructionadd useful safety checks (length guards for strings and pubkeys) and detailed diagnostics on failure, which is exactly what you want in a debugging/learning script.Two small optional tweaks:
- The bare
except Exceptionblocks are fine for an example script, but if you ever promote this logic into production code, narrowing toValueError(and maybeUnicodeDecodeError) would avoid swallowing unexpected issues.print(f"⚠️ Parsing failed for CreateEvent")doesn’t interpolate anything; you can drop thefprefix to satisfy Ruff’s F541 without changing behavior.Otherwise, the mayhem flag handling and
token_standarddefaults look consistent with the rest of the repo.Also applies to: 159-209
learning-examples/listen-new-tokens/listen_blocksubscribe.py (2)
128-176: Create/CreateV2 decoders match IDL layout and handle mayhem mode cleanlyBoth decoders:
- Use the IDL’s
argsto parse string and pubkey fields, matching the patterns in other modules.- Map account indices to
mint,bondingCurve,associatedBondingCurve, anduserin line with the documented layouts.- Set
token_standardandis_mayhem_modeconsistently with the rest of the codebase (legacy = no mayhem; V2 reads the optional trailing byte).Optional robustness tweak: if you want to hard-fail on future IDL changes, you could assert the expected number of args or account indices; but for a learning script this is fine.
Also applies to: 178-234
237-247: Block listener enhancements for CreateV2 and ALTs are well-structured; consider printing signaturesThe
listen_and_decode_createloop now:
- Extracts
loadedAddresseswhere available and passes them intoget_account_keys.- Uses discriminators to branch between legacy Create and CreateV2.
- Picks
createV2from the IDL with a sane fallback tocreateif needed.- Skips transactions with unresolved accounts and logs ALT usage counts.
This is a solid and readable flow. One improvement to consider, to stay consistent with the log/geyser listeners, is to extract the transaction signature and pass it into
print_token_infoso users can quickly click through to the transaction when comparing listener behavior.You might also add a quick
if len(ix_data) < 8: continueguard beforestruct.unpack("<Q", ix_data[:8])as cheap protection against any unexpected short data.Also applies to: 290-368
learning-examples/fetch_price.py (1)
6-7: Versioned BondingCurveState parsing is well thought outThe split into
_STRUCT_V1/_STRUCT_V2/_STRUCT_V3with version auto-detection vialen(data) - 8matches the expected layout:
- V1: base reserves + supply + complete.
- V2: adds 32-byte
creator.- V3: adds
is_mayhem_mode.Setting
creator = Noneandis_mayhem_mode = Falsefor V1 preserves backward compatibility, while the bytes→Pubkeyconversion for V2/V3 gives a nicer API to callers. PullingRPC_ENDPOINTfrom the environment fits the example’s configuration story.If you ever expect a V4 layout, you might want to explicitly assert
data_length in {41, 73, 74}and raise on anything else, so parse failures surface as a clear “unsupported version” instead of a generic construct error. For now, this is perfectly reasonable for a learning script.Also applies to: 12-18, 20-73
learning-examples/bonding-curve-progress/get_bonding_curve_status.py (2)
42-66: Versioned BondingCurveState parsing looks sound; consider aligning with progressive parser.The V2/V3 layouts, size comments, and discriminator checks look correct, and the
is_mayhem_modedefaulting toFalsefor V2 keeps older accounts safe. The explicit length checks and descriptiveValueErrorfor unexpected sizes are also good.Given you already have a progressive parser pattern in
learning-examples/manual_buy.pythat uses a base struct plus “bytes remaining” logic, it might be worth unifying on that approach here to avoid future drift if the on-chain BondingCurve struct gains more fields. That would also remove the need for hard-coded total lengths.Also applies to: 73-90
92-103: PDA helper is correct; consider centralizing the bonding-curve derivation.
get_bonding_curve_addresscorrectly returns(Pubkey, bump)and matches the other helpers shown in the relevant snippets. At this point you have several variants of this helper across learning examples; factoring it into a shared utility (or at least keeping signature/return-type consistent everywhere) would reduce duplication and potential mismatches between scripts.learning-examples/manual_sell.py (5)
46-61: Progressive BondingCurveState parser matches other scripts; consider de-duplicating.The base-struct + offset-based parsing for
creatorandis_mayhem_modeis a nice, forward-compatible improvement over fixed-size layouts and matches the pattern used inlearning-examples/manual_buy.py.Given this logic now exists in multiple learning examples, consider extracting a shared
BondingCurveStatehelper (or a small parsing function) to avoid future divergence between buy/sell scripts when the on-chain layout changes.Also applies to: 63-99
120-131: Associated bonding-curve ATA derivation correctly parameterizes token program.Adding
token_program_idintofind_associated_bonding_curveand using it in the PDA seeds matches the Token2022-aware ATA layout and aligns with the other snippets you provided.Just make sure all callers (e.g., other learning scripts like
mint_and_buy.py) are updated to supply the correct token program so you don’t accidentally derive the old ATA for Token2022 mints.
150-195: Mayhem-aware fee recipient logic is reasonable but relies on a fragile fixed offset.
get_fee_recipientcorrectly falls back toPUMP_FEEwhen mayhem mode is off or when the Global account can’t be fetched/parsed, which is a good safety net.The hard-coded
RESERVED_FEE_RECIPIENT_OFFSET = 483, though well-documented, is inherently brittle: any change to theGloballayout in the pump.fun IDL will silently break this parser. If possible, consider one of:
- Deriving the offset from an IDL-driven decoder (similar to how you’re handling BondingCurve), or
- Centralizing this offset in a single module shared by all scripts that parse Global, so it only needs updating in one place.
Given this is a learning example, it’s acceptable, but worth tightening over time.
213-229: Token program detection is straightforward; consider handling RPC failures explicitly.
get_token_program_idcorrectly inspects the mint’s owner and distinguishes between the legacy token program and Token2022, raising a clearValueErrorfor unknown owners.To make failures easier to debug, you might want to:
- Wrap the
get_account_infocall in a smalltry/exceptthat logs network/RPC errors separately from “unknown owner” cases, and- Potentially surface a more actionable message if the account exists but is owned by an unexpected program.
Not strictly required for a learning script but would improve diagnosability.
359-367: Main flow correctly resolves token program and threads it through the sell path.Resolving
token_program_idonce, deriving the associated bonding-curve ATA with it, and passing it through tosell_tokenis clean and avoids duplicated RPC calls.As a final safety check, you may want to log/print which token program was selected (standard vs Token2022) before executing the sale so that misconfigurations are obvious at run time.
Also applies to: 379-385
src/platforms/pumpfun/event_parser.py (2)
50-58: create_v2 discriminator wiring is good; consider exposing it viaget_instruction_discriminators.The optional initialization of
_create_v2_instruction_discriminator_bytesand_create_v2_instruction_discriminatoris clean and guarded for older IDLs.However,
get_instruction_discriminators()still returns only[self._create_instruction_discriminator_bytes]. If anything relies on that list to pre-filter instructions (e.g., Geyser/block listeners), it will ignore purecreate_v2instructions. It would be safer to include the v2 discriminator when available:- def get_instruction_discriminators(self) -> list[bytes]: + def get_instruction_discriminators(self) -> list[bytes]: """Get instruction discriminators for token creation. @@ - return [self._create_instruction_discriminator_bytes] + discriminators = [self._create_instruction_discriminator_bytes] + if self._create_v2_instruction_discriminator_bytes: + discriminators.append(self._create_v2_instruction_discriminator_bytes) + return discriminatorsAlso applies to: 69-72
595-610: New helpers are fine placeholders; tighten unused args and exception handling.
_derive_associated_bonding_curve’s optionaltoken_program_iddefaulting toSystemAddresses.TOKEN_PROGRAMkeeps call sites simple while supporting Token2022._parse_bonding_curve_stateand_get_is_mayhem_mode_from_curveare intentionally conservative, but:
_parse_bonding_curve_statecatches a bareException; consider narrowing this or at least logging atinfoorwarningwhen verbose mode is enabled so decoding issues are more visible._get_is_mayhem_mode_from_curvedoesn’t usebonding_curve_address; renaming the parameter to_bonding_curve_addresswould make the “unused by design” intent clear and satisfy linters.These are minor polish items and don’t affect current behavior.
Also applies to: 621-639, 641-658
learning-examples/manual_buy_cu_optimized.py (3)
72-88: Progressive BondingCurveState parsing is consistent; consider sharing implementation.This BondingCurveState is the same progressive parser pattern as in other scripts (base struct + optional creator + optional
is_mayhem_mode), which is good for forward compatibility and for powering mayhem-aware features.Given the same logic now exists in multiple files (
manual_buy.py,manual_sell.py, this script), a shared helper (even underlearning-examples/) would reduce duplication and ensure any future layout change is reflected everywhere at once.Also applies to: 89-126
183-228: Mayhem-mode fee recipient logic matches manual_sell; same brittleness caveat on the offset.
get_fee_recipienthere mirrors the implementation inmanual_sell.py, including the 483-byte offset into the Global account. The behavior and fallbacks are sensible, but the same concern applies: this offset is tightly coupled to the on-chain struct layout.If you find yourself updating this offset in more than one place, consider centralizing it or deriving it via an IDL-based parser so you don’t have to maintain a magic constant in multiple scripts.
413-419: create vs create_v2 detection and token_program propagation are well-structured; watch for new arg types.Computing both
create_discriminatorandcreate_v2_discriminator, mapping them toinstruction_name("create"/"create_v2") and to the appropriate token program, and then exportingtoken_programandis_token_2022throughdecoded_argsis a clean way to keep the rest of the script agnostic.One thing to keep an eye on: if
create_v2ever introduces argument types beyond"string"and"pubkey",decode_create_instructionwill raiseValueError("Unsupported type: ..."). If the IDL evolves, you may need to extenddecode_create_instruction’s type handling to cover the new types.Also applies to: 471-504
idl/pump_swap_idl.json (2)
1752-2090:is_mayhem_modeis threaded consistently through create_pool, BondingCurve, Pool, and events.The addition of
is_mayhem_mode: boolto:
create_poolargs,BondingCurveandPoolaccount types, andCreatePoolEvent,is internally consistent and matches the mayhem-mode feature described in the PR summary.
Because these fields change the serialized layout of the affected accounts, make sure all decoders (including any hand-written parsers or offset-based readers outside the IDL-driven paths) have been updated accordingly. The progressive parsers you added in the Python code are a good hedge here.
Also applies to: 4162-4193, 4462-4550, 4916-4965
3011-3096: New mayhem-related admin instructions, events, and errors align; watch for offset consumers.The new pieces:
set_reserved_fee_recipientsandtoggle_mayhem_modeinstructions,ReservedFeeRecipientsEvent(both in the events list and the type definitions),- Additional
GlobalConfigfields (whitelist_pda,reserved_fee_recipient,mayhem_mode_enabled,reserved_fee_recipients), and- Error codes
MayhemModeDisabled,OnlyPumpPoolsMayhemMode,MayhemModeInDesiredState,NotEnoughRemainingAccounts,all fit together coherently in the IDL.
Given
GlobalConfig’s layout change, double-check any external code that reads it via hard-coded offsets (e.g., to fetch reserved fee recipients) to ensure those offsets were recomputed from this updated IDL, and consider centralizing such offsets to reduce the risk of them drifting from the IDL.Also applies to: 3225-3288, 3799-3811, 4969-4992, 4071-4086, 4819-4838
learning-examples/manual_buy_geyser.py (4)
64-117: BondingCurveState progressive parser matches other scripts; consider adding type hintsThe progressive parsing (base struct, optional creator, optional mayhem flag) looks consistent with the patterns in
learning-examples/manual_buy.pyandlearning-examples/manual_sell.pyand should safely handle future schema extensions.To align with repo guidelines for public APIs in Python, it would be good to:
- Add type hints to
BondingCurveState.__init__(self, data: bytes) -> None.- Optionally pre-check
len(data) >= 8 + self._BASE_STRUCT.sizeof()and raise a clearerValueErrorbeforeconstructparsing, so malformed accounts yield explicit errors instead of low-level parse exceptions.
175-219: Mayhem fee recipient parsing is consistent but duplicated; consider centralizingThe mayhem fee logic (
get_fee_recipient) mirrorsmanual_buy.py/manual_sell.pyand parsesreserved_fee_recipientat offset483, falling back toPUMP_FEEon short or missing data. That matches the IDL comments and should behave correctly whenis_mayhem_modeis set.However, this offset and parsing logic is now duplicated across several files. To reduce drift risk when the IDL changes, consider extracting a shared helper (e.g., in a small utility module) that returns the mayhem fee recipient from
PUMP_GLOBAL, and reuse it here and in the other scripts.You may want to double-check the offset
483against the current on-chainGloballayout in the IDL to ensure it hasn’t shifted.
315-332: Discriminator-based token_program selection: prefer derived discriminator to hard-coded bytesThe discriminator-based selection of
token_program(create vscreate_v2) is correct in spirit and matches the flow inmanual_buy.py, butPUMP_CREATE_V2_DISCRIMINATORis hard-coded as raw bytes here whilemanual_buy.pyderives the create_v2 discriminator from the IDL.To avoid future drift if the IDL changes, consider:
- Reusing the same
calculate_discriminator("global:create_v2")approach asmanual_buy.py, or- At least adding a short comment referencing the IDL version these bytes are derived from.
It’s worth re-confirming that
PUMP_CREATE_V2_DISCRIMINATORmatches the discriminator computed from the latestpump_fun_idl.json.
510-518: Line length likely exceeds 88 charactersThe
print(f"Token Program: {token_program} ({'Token2022' if token_data['is_token_2022'] else 'Standard Token'})")Consider splitting it across lines (e.g., with a temporary
token_typevariable or multiline f-string) to keep within the configured limit.learning-examples/manual_buy.py (3)
51-105: BondingCurveState progressive parsing is aligned; minor robustness improvements optionalThis
BondingCurveStateimplementation mirrors the progressive parser inlearning-examples/manual_sell.py(base struct, optional creator, optionalis_mayhem_mode) and should handle older/newer layouts correctly.Two optional robustness tweaks:
- Add type hints on
__init__(data: bytes -> None) to align with repo guidelines.- Before parsing, check that
len(data) >= 8 + self._BASE_STRUCT.sizeof()and raise a clearValueErrorif not, rather than relying onconstructexceptions.
162-207: Mayhem fee recipient helper duplicates logic; consider a shared utility
get_fee_recipientcorrectly:
- Returns
PUMP_FEEwhenis_mayhem_modeis false.- Fetches
PUMP_GLOBAL, parsesreserved_fee_recipientat offset483, and falls back toPUMP_FEEif the account is missing/too short.Since identical logic now exists in several scripts (and offset
483is non-obvious), centralizing this in a small shared helper (used by manual buy/sell and mint/buy flows) would reduce the chance of a future IDL change breaking one call site but not others.It’s worth re-confirming that offset
483still matches the latestGlobalstruct inpump_fun_idl.json.
485-498: LongSimilar to the Geyser script, the
print(f"Token Program: {token_program} ({'Token2022' if token_data['is_token_2022'] else 'Standard Token'})")Splitting this into multiple lines or extracting
token_typeinto a variable would keep it Ruff-compliant.learning-examples/mint_and_buy_v2.py (3)
326-357: Mayhem fee recipient helper is correct but duplicates offset logic
get_fee_recipient_for_mayhemcorrectly falls back toPUMP_FEEwhen not in mayhem mode or when the Global account is missing/too short, and uses offset483forreserved_fee_recipient, matching the other scripts.Given this same offset and parsing logic appear in multiple files, extracting a common helper (or at least a shared
RESERVED_FEE_RECIPIENT_OFFSETconstant) would reduce maintenance burden.Re-confirm offset
483against the currentGlobalstruct definition inpump_fun_idl.jsonand consider centralizing it.
359-408: Main flow is coherent; one unused variable and possible retry enhancementThe
mainorchestration flow (derive PDAs, compute initial price and expected tokens, build instructions in the right order) looks correct and consistent with the existingmint_and_buy.py.Two minor points:
initial_real_token_reservesis computed but never used; you can safely remove it to appease Ruff (F841).- Transaction submission is a single-shot
send_transactioninside a broadexcept Exception. For a more robust integration (and per repo guidelines), consider:
- Adding a simple retry with exponential backoff, and/or
- Narrowing the exception type and logging additional context.
414-488: Transaction assembly and priority fees are aligned with guidelinesThe transaction build:
- Prepends
set_compute_unit_limitandset_compute_unit_priceto the instruction list, satisfying the “priority fees + CU limit” guidance.- Includes
create_v2,extend_account, user ATA creation, and buy instructions in a sensible order.- Uses
TxOpts(skip_preflight=True, preflight_commitment=Confirmed), which is reasonable for this manual script.The bare
except Exceptionwithraiseis acceptable for an integration script, but if you later move this logic into production code, switching to the centralized logger and more specific exception handling would be preferable.learning-examples/pumpswap/manual_sell_pumpswap.py (4)
105-125: get_market_address_by_base_mint docstring clarifies behavior; consider handling empty resultsThe improved docstring and Memcmp filter usage in
get_market_address_by_base_mintclearly describe how the pool is discovered.One small robustness improvement: if
response.valueis empty,response.value[0]will raiseIndexError. For a demo script that’s acceptable, but if you expect to use it more broadly, consider raising aValueErrorwith a clear message when no pools are found.
228-291: Mayhem fee recipients for pumpswap are well-structured; shared logic with buy script
get_reserved_fee_recipient_pumpswapandget_pumpswap_fee_recipients:
- Enforce that the GlobalConfig and pool accounts exist and have data.
- Check the mayhem flag via
POOL_MAYHEM_MODE_MIN_SIZEandPOOL_MAYHEM_MODE_OFFSET.- Return both the chosen fee recipient and its WSOL ATA, using the standard token program for WSOL.
Since this logic is shared conceptually with
manual_buy_pumpswap.py, keeping the offsets and seeds synchronized between the two files is important; a future refactor could move these helpers into a shared module.Please confirm the mayhem flag offset and reserved fee offset against the current AMM/GlobalConfig IDL to ensure they remain valid.
332-348: get_token_program_id performs the right checks; Ruff TRY003 is just style
get_token_program_idcorrectly:
- Fetches mint account info.
- Raises if the account is missing.
- Branches on owner to distinguish TokenProgram vs Token2022, raising for unknown owners.
Ruff’s TRY003 complaint about the long f-string message is stylistic; if you want to appease it, you can shorten the string or move detailed context into a custom exception, but functionally this is fine.
Because this depends on the RPC client’s response shape and program IDs, re-check against the Solana RPC/SDK docs if you upgrade dependencies.
390-499: sell_pump_swap correctly wires mayhem fees and dynamic token program; consider narrower exception handlingThe updated
sell_pump_swap:
- Accepts
marketandtoken_program_idexplicitly.- Calculates min SOL output with slippage, logs token balance and price.
- Uses
get_pumpswap_fee_recipientsso mayhem-mode pools route fees to the correct recipient and WSOL ATA.- Injects
token_program_idinto the account metas, while still includingSYSTEM_TOKEN_PROGRAMfor WSOL, which matches the expected AMM layout.The main functional concern is around error handling:
- The
try/except Exceptionblock printsError: {e!s}and returnsNone, which hides the original stack trace and can make debugging harder.- For a more robust integration, consider catching narrower exceptions (e.g., RPC-related) and at least logging full details via the centralized logger, or re-raising after logging.
If this flow moves beyond manual experimentation, you may also want to add a basic retry with backoff around
send_transaction, mirroring patterns in other scripts.learning-examples/pumpswap/manual_buy_pumpswap.py (5)
117-193: Market discovery and pool parsing match manual_sell_pumpswap
get_market_address_by_base_mintandget_market_datareplicate the same structure and documentation as in the sell script, ensuring both buy and sell paths agree on how pools are located and deserialized.As with the sell script, you might eventually want to handle the case where
get_program_accountsreturns0items more explicitly, but for a manual script this is acceptable.
275-337: Mayhem fee helpers mirror sell script; shared utility could avoid duplication
get_reserved_fee_recipient_pumpswapandget_pumpswap_fee_recipientsare a direct counterpart to those in the sell script:
- They parse the mayhem flag from the pool account and select the reserved vs standard fee recipient accordingly.
- They derive the fee recipient’s WSOL ATA via the standard token program.
Given the overlap, consider moving these into a shared helper (or at least centralizing the offsets) to keep buy and sell flows from diverging if the IDL changes.
As in the sell script, please re-check
POOL_MAYHEM_MODE_OFFSET,POOL_MAYHEM_MODE_MIN_SIZE, andGLOBALCONFIG_RESERVED_FEE_OFFSETagainst the current IDL.
379-395: get_token_program_id correctly distinguishes Token vs Token2022Same comments as for the sell script:
- Logic is correct (owner-based).
- Raises clear errors for missing/unknown owners.
- Ruff TRY003 warnings about long messages are stylistic; you can shorten or restructure messages if you want lint cleanliness.
398-503: buy_pump_swap wiring is solid: mayhem fees, dynamic token program, WSOL handling, and volume trackingThe
buy_pump_swapfunction:
- Determines
token_program_iddynamically viaget_token_program_id, which is important for Token2022 support.- Computes expected token amount and
max_sol_inputwith slippage, logging both.- Derives global/user volume accumulator PDAs and includes them in the account metas.
- Uses
get_pumpswap_fee_recipientsto honor mayhem-mode fee routing, including the WSOL ATA for the fee recipient.- Builds the account list with
token_program_idandSYSTEM_TOKEN_PROGRAMin the expected positions, and setsglobal_volume_accumulatoras read-only and user accumulator as writable.- Encodes data as
[BUY_DISCRIMINATOR, amount_out, max_in, track_volume_byte], which matches the documented layout for this AMM buy instruction.The only substantive concern is error handling:
- Broad
except Exception as ewithreturn Nonehides stack traces and may complicate debugging.- For anything beyond quick manual experiments, consider narrower exceptions and logging via your central logger.
If you later use this path in production, adding a simple retry/backoff around
send_transaction(as in some other scripts) would also be beneficial.
515-599: WSOL wrapping and ATA creation flow look correct; simulation-before-send is a good safety netWithin
buy_pump_swap:
- Compute budget instructions are added first, honoring the priority-fee guidelines.
- The WSOL ATA is created idempotently with the standard Token program, which is correct.
wrap_amountincludesPROTOCOL_FEE_BUFFER, reducing the risk of underfunding due to protocol fees.transfer+sync_nativecorrectly wrap SOL into WSOL.- A separate ATA is created for the purchased base token using
token_program_idto respect Token2022 mints.- The transaction is simulated before sending, and preflight is skipped only after a successful simulation, which is a sensible pattern in competitive environments.
The structure here is solid; only the broad
Exceptioncatch at the end is a (minor) deviation from Ruff’s BLE rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
learning-examples/cleanup_accounts.py (1)
79-91: AvoidUnboundLocalErrorforclientinfinallyThe updated ATA derivation on Line 85 correctly passes the token program into
get_associated_token_addressand aligns with the new token‑program‑aware wallet API.However, in this same block there is a latent bug: if an exception is raised before
clientis assigned (e.g., badRPC_ENDPOINTorWallet(PRIVATE_KEY)failure), thefinallyblock will raiseUnboundLocalErrorwhen it tries toawait client.close().Consider initializing
clienttoNoneand guarding the close:-async def main(): - try: - client = SolanaClient(RPC_ENDPOINT) +async def main(): + client: SolanaClient | None = None + try: + client = SolanaClient(RPC_ENDPOINT) wallet = Wallet(PRIVATE_KEY) @@ - finally: - await client.close() + finally: + if client is not None: + await client.close()This preserves the new behavior while making the cleanup path safe.
🧹 Nitpick comments (7)
src/platforms/letsbonk/curve_manager.py (1)
197-211: Reserve validation and price computation look correct; consider tightening error styleThe positive-reserve checks and derived
price_per_tokenare consistent with the PumpFun implementation and prevent silent zero/NaN prices, which aligns well with the “Normal Mode with full validation” requirement. Based on learnings.To keep Ruff (
TRY003) quiet and reduce repetition, you might slightly shorten or standardize the error messages, e.g.:- if pool_data["virtual_base"] <= 0: - raise ValueError( - f"Invalid virtual_base: {pool_data['virtual_base']} - cannot calculate price" - ) + if pool_data["virtual_base"] <= 0: + raise ValueError(f"Invalid virtual_base {pool_data['virtual_base']}")Same idea for
virtual_quote.src/platforms/pumpfun/curve_manager.py (2)
193-214: Stricter reserve checks and price_per_token calculation are soundValidating both
virtual_token_reservesandvirtual_sol_reservesbefore computingprice_per_tokenis the right behavior; it avoids silent zero prices and matches how the traders now rely onprice_per_tokenfromget_pool_state.As in the LetsBonk manager, the very long
ValueErrormessages may trip Ruff (TRY003). If needed, consider trimming them or using a shared helper to build the message.- if curve_data["virtual_token_reserves"] <= 0: - raise ValueError( - f"Invalid virtual_token_reserves: {curve_data['virtual_token_reserves']} - cannot calculate price" - ) + if curve_data["virtual_token_reserves"] <= 0: + raise ValueError( + f"Invalid virtual_token_reserves {curve_data['virtual_token_reserves']}" + )Same pattern for
virtual_sol_reserves.
64-85: Aligncalculate_pricebehavior with new decode-basedprice_per_tokensemantics
calculate_price()still returns0.0whenvirtual_token_reserves <= 0, while_decode_curve_state_with_idlnow raises on invalid reserves and always computes a positiveprice_per_token.If anything still calls
calculate_price()directly, it will see different behavior (silent 0.0 vs. hard failure) from callers that go viaget_pool_state()["price_per_token"].Consider either:
- Re-implementing
calculate_price()in terms of the decoded state:async def calculate_price(self, pool_address: Pubkey) -> float: - pool_state = await self.get_pool_state(pool_address) - - # Use virtual reserves for price calculation - virtual_token_reserves = pool_state["virtual_token_reserves"] - virtual_sol_reserves = pool_state["virtual_sol_reserves"] - - if virtual_token_reserves <= 0: - return 0.0 - - # Price = sol_reserves / token_reserves - price_lamports = virtual_sol_reserves / virtual_token_reserves - return price_lamports * (10**TOKEN_DECIMALS) / LAMPORTS_PER_SOL + pool_state = await self.get_pool_state(pool_address) + return float(pool_state["price_per_token"])
- Or deprecating
calculate_price()if it is no longer used.I’d recommend verifying actual usage to avoid subtle behavioral drift between call sites.
Also applies to: 193-214
src/trading/platform_aware.py (1)
68-82: Buy/sell now fail fast on invalid prices, restoring slippage protectionSwitching both buy and sell paths to:
- Fetch
pool_stateviaget_pool_state,- Read
price_per_tokenfrom the decoded state, and- Raise when the price is missing or non-positive,
fixes the previous issue where a zero price effectively disabled min-out slippage guards. This is exactly the safer behavior the earlier review was aiming for and aligns with the “Normal Mode with full validation” requirement. Based on learnings.
Two small polish suggestions:
- Ensure
is_mayhem_modeis always a bool inTokenInfo:- token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False) + token_info.is_mayhem_mode = bool( + pool_state.get("is_mayhem_mode", False) + )Apply similarly in the seller.
- If Ruff’s TRY rules are enforced, you could factor the long
ValueErrorconstruction into a tiny helper (shared between buy and sell) to avoidTRY003/TRY301noise, but that’s optional.Also applies to: 236-251
src/platforms/pumpfun/event_parser.py (3)
91-97: Create/Create_v2 log handling is solid; consider using the actual instruction type for token_program_idThe logs parser now:
- Recognizes both
"Instruction: Create"and"Instruction: Create_v2", and- Correctly decodes the shared
CreateEventfrom program data.Right now, the derived
token_program_idis always set toSystemAddresses.TOKEN_2022_PROGRAM, based on recent pump.fun behavior.To keep this robust if pump.fun ever emits legacy
Createagain, you could leverage the already-computedinstruction_type(or a boolean) to choose the program:- create_instruction_found = False + create_instruction_found = False + saw_create_v2 = False @@ - if "Program log: Instruction: Create" in log or "Program log: Instruction: Create_v2" in log: + if ( + "Program log: Instruction: Create" in log + or "Program log: Instruction: Create_v2" in log + ): create_instruction_found = True - instruction_type = "Create_v2" if "Create_v2" in log else "Create" + if "Create_v2" in log: + saw_create_v2 = True @@ - token_program_id = SystemAddresses.TOKEN_2022_PROGRAM + token_program_id = ( + SystemAddresses.TOKEN_2022_PROGRAM + if saw_create_v2 + else SystemAddresses.TOKEN_PROGRAM + )If you’re confident all new tokens are Create_v2-only, this can be deferred, but it future-proofs the logs path for mixed historical data.
Also applies to: 112-117, 246-253, 260-273
50-59: Expose create_v2 discriminator fromget_instruction_discriminatorsfor full Token2022 coverageYou correctly:
- Initialize
_create_v2_instruction_discriminator_bytes(when present),- Handle create vs create_v2 in
parse_token_creation_from_instruction, and- Detect both in
parse_token_creation_from_block.However,
get_instruction_discriminators()still returns only the legacycreatediscriminator:def get_instruction_discriminators(self) -> list[bytes]: return [self._create_instruction_discriminator_bytes]If any upstream listener uses this list to pre-filter instructions, it will miss
create_v2traffic.Consider:
def get_instruction_discriminators(self) -> list[bytes]: @@ - return [self._create_instruction_discriminator_bytes] + discriminators = [self._create_instruction_discriminator_bytes] + if self._create_v2_instruction_discriminator_bytes: + discriminators.append(self._create_v2_instruction_discriminator_bytes) + return discriminatorsThis keeps backward compatibility while ensuring Token2022
create_v2gets picked up everywhere that relies on this API.Also applies to: 69-72, 426-433, 478-491, 545-553
622-640: New helpers are fine as placeholders; minor cleanup for Ruff warnings
_parse_bonding_curve_stateand_get_is_mayhem_mode_from_curveare sensible hooks for future mayhem-mode support without an RPC client in the parser.To reduce Ruff noise:
_get_is_mayhem_mode_from_curvecurrently ignoresbonding_curve_address. Either mark the parameter as unused or reference it:- def _get_is_mayhem_mode_from_curve(self, bonding_curve_address: Pubkey) -> bool: + def _get_is_mayhem_mode_from_curve(self, bonding_curve_address: Pubkey) -> bool: @@ - # Since event parser doesn't have RPC client access, we cannot fetch - # and parse bonding curve state here. Mayhem mode will be set later - # when traders fetch the bonding curve state. - return False + # Parameter kept for future implementation. + _ = bonding_curve_address + return False
- If
BLE001(catchingException) in_parse_bonding_curve_statebecomes an issue, you can drop the try/except and let the underlyingdecode_account_datahandle errors, given it already catches internally.These are small hygiene tweaks; the functional behavior is already consistent with the comments.
Also applies to: 642-659
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
learning-examples/cleanup_accounts.py(4 hunks)src/platforms/letsbonk/address_provider.py(1 hunks)src/platforms/letsbonk/curve_manager.py(1 hunks)src/platforms/pumpfun/curve_manager.py(1 hunks)src/platforms/pumpfun/event_parser.py(14 hunks)src/platforms/pumpfun/instruction_builder.py(5 hunks)src/trading/platform_aware.py(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/platforms/pumpfun/instruction_builder.py
🧰 Additional context used
📓 Path-based instructions (7)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Enforce max line length of 88 characters in Python (Ruff config)
Use 4 spaces for indentation in Python files
Target Python version 3.11+ features and syntax
Prefer double quotes for strings in Python code
Enable and honor import sorting in Python modules
Apply Ruff rule sets: Security (S), Annotations (ANN), Exceptions (BLE, TRY), Complexity (C90), Pylint conventions (PL), and no commented-out code (ERA)
Order imports as: standard library, third-party, then local imports
Use Google-style docstrings for functions and classes
Provide type hints for all public functions
Use a module-level logger via get_logger(name)
Wrap risky operations in try/except with proper logging
Implement rate limiting and retry mechanisms where external calls are made
Perform comprehensive input validation for externally sourced data
**/*.py: Limit lines to 88 characters
Use 4 spaces for indentation (never tabs)
Use double quotes for strings consistently
Target Python 3.11+ features and syntax
Enable automatic import sorting and organization
Order imports: standard library first, third-party second, local last
Add type hints to all public functions and methods
Use modern typing syntax (e.g., X | Y unions)
Include explicit return type annotations
Use typing.Any for complex/unknown types (from typing import Any)
Use Google-style docstrings for all functions and classes
Wrap external operations (RPC calls, file I/O) in try/except blocks
Log exceptions with context using logging.exception()
Provide meaningful error messages when handling exceptions
Do not suppress exceptions without good reason
Use get_logger(name) with logger from utils.logger
Use appropriate log levels (DEBUG, INFO, WARNING, ERROR) with contextual messages
Never hardcode secrets (private keys, API tokens)
Use environment variables for sensitive configuration
Do not log sensitive information
Validate all external inputs
Comply with Ruff security rules (S)
Comply with Ruff type annotation rules (ANN)
Comply...
Files:
src/trading/platform_aware.pylearning-examples/cleanup_accounts.pysrc/platforms/pumpfun/event_parser.pysrc/platforms/pumpfun/curve_manager.pysrc/platforms/letsbonk/curve_manager.pysrc/platforms/letsbonk/address_provider.py
src/{client,trading,monitoring,platforms}/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Separate concerns into client, trading, monitoring, and platforms packages under src/
Files:
src/trading/platform_aware.pysrc/platforms/pumpfun/event_parser.pysrc/platforms/pumpfun/curve_manager.pysrc/platforms/letsbonk/curve_manager.pysrc/platforms/letsbonk/address_provider.py
src/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
src/**/*.py: Use snake_case for all Python file names
Avoid circular imports between packages
Use asyncio.create_task() for concurrent operations
Implement proper cleanup on shutdown for async tasks/resources
Files:
src/trading/platform_aware.pysrc/platforms/pumpfun/event_parser.pysrc/platforms/pumpfun/curve_manager.pysrc/platforms/letsbonk/curve_manager.pysrc/platforms/letsbonk/address_provider.py
src/trading/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
trading/ may depend on core/, platforms/, and interfaces/
Files:
src/trading/platform_aware.py
learning-examples/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Treat learning-examples Python scripts as integration tests for manual runs
learning-examples/**/*.py: Use learning examples to test new features before integration
Use learning examples to validate platform-specific functionality
Use learning examples for performance benchmarking
Use learning examples for educational purposes for new developersAlways test changes using scripts in learning-examples/ before modifying the main bot
Files:
learning-examples/cleanup_accounts.py
learning-examples/**
📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)
Place integration tests and validation scenarios under learning-examples/
Files:
learning-examples/cleanup_accounts.py
src/platforms/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Implement platform-specific logic under src/platforms/
src/platforms/**/*.py: Platform-specific modules may only import from their own package and core/interfaces
platforms/ may depend on core/ and interfaces/ only
Implement platform-specific functionality via factory pattern using interfacesUpdate platform-specific implementations under src/platforms/ when adding features
Files:
src/platforms/pumpfun/event_parser.pysrc/platforms/pumpfun/curve_manager.pysrc/platforms/letsbonk/curve_manager.pysrc/platforms/letsbonk/address_provider.py
🧠 Learnings (4)
📚 Learning: 2025-08-21T12:08:15.475Z
Learnt from: CR
Repo: chainstacklabs/pumpfun-bonkfun-bot PR: 0
File: .cursor/rules/trading-bot.mdc:0-0
Timestamp: 2025-08-21T12:08:15.475Z
Learning: Applies to **/*.py : Implement wallet balance checks before trading
Applied to files:
src/trading/platform_aware.py
📚 Learning: 2025-08-21T12:08:15.475Z
Learnt from: CR
Repo: chainstacklabs/pumpfun-bonkfun-bot PR: 0
File: .cursor/rules/trading-bot.mdc:0-0
Timestamp: 2025-08-21T12:08:15.475Z
Learning: Applies to **/*.py : Validate token data (e.g., age, metadata) before trading
Applied to files:
src/trading/platform_aware.py
📚 Learning: 2025-08-21T12:08:15.475Z
Learnt from: CR
Repo: chainstacklabs/pumpfun-bonkfun-bot PR: 0
File: .cursor/rules/trading-bot.mdc:0-0
Timestamp: 2025-08-21T12:08:15.475Z
Learning: Applies to **/*.py : Provide a Normal Mode with full validation and price checks
Applied to files:
src/platforms/letsbonk/curve_manager.py
📚 Learning: 2025-08-21T12:08:15.475Z
Learnt from: CR
Repo: chainstacklabs/pumpfun-bonkfun-bot PR: 0
File: .cursor/rules/trading-bot.mdc:0-0
Timestamp: 2025-08-21T12:08:15.475Z
Learning: Applies to **/*.py : Implement platform-specific address providers behind an abstraction
Applied to files:
src/platforms/letsbonk/address_provider.py
🧬 Code graph analysis (4)
src/trading/platform_aware.py (3)
src/platforms/letsbonk/curve_manager.py (1)
get_pool_state(43-64)src/platforms/pumpfun/curve_manager.py (1)
get_pool_state(41-62)src/interfaces/core.py (1)
get_pool_state(243-252)
learning-examples/cleanup_accounts.py (1)
src/core/pubkeys.py (1)
SystemAddresses(42-67)
src/platforms/pumpfun/event_parser.py (2)
src/core/pubkeys.py (1)
SystemAddresses(42-67)src/utils/idl_parser.py (1)
decode_account_data(290-331)
src/platforms/letsbonk/address_provider.py (1)
src/core/pubkeys.py (1)
SystemAddresses(42-67)
🪛 Ruff (0.14.5)
src/trading/platform_aware.py
75-78: Abstract raise to an inner function
(TRY301)
75-78: Avoid specifying long messages outside the exception class
(TRY003)
244-247: Abstract raise to an inner function
(TRY301)
244-247: Avoid specifying long messages outside the exception class
(TRY003)
src/platforms/pumpfun/event_parser.py
637-637: Consider moving this statement to an else block
(TRY300)
638-638: Do not catch blind exception: Exception
(BLE001)
642-642: Unused method argument: bonding_curve_address
(ARG002)
src/platforms/pumpfun/curve_manager.py
199-201: Avoid specifying long messages outside the exception class
(TRY003)
203-205: Avoid specifying long messages outside the exception class
(TRY003)
src/platforms/letsbonk/curve_manager.py
199-201: Avoid specifying long messages outside the exception class
(TRY003)
203-205: Avoid specifying long messages outside the exception class
(TRY003)
🔇 Additional comments (1)
learning-examples/cleanup_accounts.py (1)
42-51: Consistent use ofTOKEN_PROGRAMfor burn and close instructions looks goodUsing
program_id=TOKEN_PROGRAMin both theburnandclose_accountflows keeps all token operations aligned with the configured token program, matching how the ATA is derived. As long asTOKEN_PROGRAMis set correctly for the target mint (legacy vs Token‑2022), this wiring is correct and clear.Also applies to: 57-62
Implements updates recently introduced into IDL of PF bonding curve and AMM.
Summary by CodeRabbit
New Features
Bug Fixes / UX