Files
d8a0ff7fe5 feat(platforms,cookbook): add StonkFun on a shared Raydium LaunchLab base (#235)
letsbonk.fun and StonkFun now share one LaunchLab implementation that prices
from real reserves, trades any quote mint, spends exactly the buy amount, and
accounts for the curve fee and Token-2022 transfer fees. Cleanup harvests
withheld fees before closing an account.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 22:29:07 +02:00

164 lines
6.0 KiB
Python

"""Verify every cookbook script takes its input on the command line.
The cookbook is a reference collection: someone reads a script, then runs it
against their own coin. That only works if the coin is an argument. Scripts used
to carry it as a module-level constant instead:
TOKEN_MINT = Pubkey.from_string(sys.argv[1] if len(sys.argv) > 1 else "...")
which reads as "optional", but `Pubkey.from_string("...")` raises at import, so
running the script the way its own usage line describes it — with no argument —
died on `ValueError: Invalid Base58 string` before printing anything. Others took
their amount and slippage from environment variables, which no usage line
mentioned at all.
The rule is that a value the caller would vary per run is an argument. A default
is fine, and encouraged; a default that is not a real value is not a default.
Offline machine checks, no network and no funds moved:
A. No runnable script reads `sys.argv` at module level.
B. No script contains a placeholder standing in for a real address.
C. Every script that takes input builds an `ArgumentParser`, so `--help` works
and a bad address is rejected with a usage message rather than a traceback.
D. Every `ArgumentParser` default is a usable value, not a placeholder.
Usage:
uv run tests/regression/verify_cookbook_arguments.py
"""
import ast
import re
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
COOKBOOK = PROJECT_ROOT / "cookbook"
# A value that looks like configuration but cannot be used as one.
PLACEHOLDER = re.compile(r'"(\.\.\.|YOUR_[A-Z_]+|<[A-Z_]+>)"')
# Scripts that genuinely take no input: they subscribe to a program and print
# what arrives. Nothing to parameterise, so nothing to get wrong.
NO_INPUT = {
"pumpfun_listen_tokens_logsubscribe.py",
"pumpfun_listen_tokens_blocksubscribe.py",
"pumpfun_listen_tokens_geyser.py",
"pumpfun_listen_tokens_deshred.py",
"pumpfun_listen_migrations_logsubscribe.py",
"pumpfun_listen_migrations_programsubscribe.py",
"pumpfun_capture_transactions_blocksubscribe.py",
"stonkfun_listen_tokens_geyser.py",
"stonkfun_listen_tokens_blocksubscribe.py",
}
def runnable_scripts() -> list[Path]:
"""Every cookbook script with a `__main__` guard.
Returns:
The runnable scripts, sorted. Import-only helpers are excluded.
"""
return sorted(
p for p in COOKBOOK.rglob("*.py") if '__name__ == "__main__"' in p.read_text()
)
def check_no_module_level_argv() -> None:
"""Input must be read inside a function, not while the module imports."""
offenders = []
for path in runnable_scripts():
tree = ast.parse(path.read_text())
for node in tree.body:
for sub in ast.walk(node):
if isinstance(sub, ast.Attribute) and sub.attr == "argv":
offenders.append(f"{path.relative_to(PROJECT_ROOT)}:{sub.lineno}")
assert not offenders, (
"sys.argv read at module level, so the script fails before main() runs:\n "
+ "\n ".join(sorted(set(offenders)))
)
def check_no_placeholders() -> None:
"""A placeholder is not a default; it is a crash with extra steps."""
offenders = []
for path in runnable_scripts():
for number, line in enumerate(path.read_text().splitlines(), 1):
if PLACEHOLDER.search(line) and not line.lstrip().startswith("#"):
offenders.append(
f"{path.relative_to(PROJECT_ROOT)}:{number}: {line.strip()[:70]}"
)
assert not offenders, "placeholder values standing in for real input:\n " + (
"\n ".join(offenders)
)
def check_scripts_use_argparse() -> None:
"""Anything taking input parses it, so --help and errors work."""
offenders = [
str(path.relative_to(PROJECT_ROOT))
for path in runnable_scripts()
if path.name not in NO_INPUT and "ArgumentParser" not in path.read_text()
]
assert not offenders, "scripts taking input without argparse:\n " + "\n ".join(
offenders
)
def check_defaults_are_usable() -> None:
"""Every argparse default must be a value the script can actually run with."""
offenders = []
for path in runnable_scripts():
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if not (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and node.func.attr == "add_argument"
):
continue
for keyword in node.keywords:
if keyword.arg != "default":
continue
if isinstance(keyword.value, ast.Constant) and isinstance(
keyword.value.value, str
):
if PLACEHOLDER.search(f'"{keyword.value.value}"'):
offenders.append(
f"{path.relative_to(PROJECT_ROOT)}:{node.lineno}: "
f"default={keyword.value.value!r}"
)
assert not offenders, "argparse defaults that are not usable values:\n " + (
"\n ".join(offenders)
)
def main() -> None:
"""Run every check and report."""
print("=" * 72)
print("Verifying cookbook scripts take their input as arguments")
print("=" * 72)
scripts = runnable_scripts()
print(f"{len(scripts)} runnable scripts, {len(NO_INPUT)} of them taking no input\n")
for label, check in [
("no module-level sys.argv", check_no_module_level_argv),
("no placeholder values", check_no_placeholders),
("input is parsed with argparse", check_scripts_use_argparse),
("every default is usable", check_defaults_are_usable),
]:
check()
print(f"{label} -> OK")
missing = sorted(NO_INPUT - {p.name for p in scripts})
if missing:
print(f"\nNote: exempt scripts no longer present: {', '.join(missing)}")
sys.exit(1)
print("\nALL CHECKS PASSED")
if __name__ == "__main__":
main()