mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-11-02 06:16:10 +00:00
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>
163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
"""Verify cleanup harvests withheld transfer fees before closing a token account.
|
|
|
|
A Token-2022 coin with a transfer fee withholds the fee inside the account that
|
|
receives it — the bot's own account, on a buy — and Token-2022 refuses to close
|
|
an account still holding withheld fees (custom error 0x23). Cleanup used to send
|
|
a bare CloseAccount, which reverts for every LaunchLab reward coin, leaving its
|
|
rent locked and the wallet not back to SOL only.
|
|
|
|
Fixture: `raw_stonkfun_token_accounts_from_getaccountinfo.json`, two real
|
|
Token-2022 token accounts: one holding withheld fees from a StonkFun reward
|
|
coin, one from a standard coin with no transfer-fee extension.
|
|
|
|
Offline machine checks, no network and no funds moved:
|
|
|
|
A. The withheld amount reads from the account's TransferFeeAmount
|
|
extension; an account without the extension, and a classic SPL account,
|
|
read as zero.
|
|
B. Cleanup of the account holding withheld fees sends HarvestWithheldTokensToMint
|
|
(mint and account, writable, no signer) before CloseAccount, in one
|
|
transaction.
|
|
C. Cleanup of an account with nothing withheld sends CloseAccount alone.
|
|
|
|
Usage:
|
|
uv run tests/regression/verify_cleanup_harvests_withheld_fees.py
|
|
"""
|
|
|
|
import asyncio
|
|
import base64
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
|
|
|
from solders.pubkey import Pubkey # noqa: E402
|
|
|
|
from cleanup import manager as cleanup_manager # noqa: E402
|
|
from cleanup.manager import AccountCleanupManager, withheld_transfer_fee # noqa: E402
|
|
from core.pubkeys import SystemAddresses # noqa: E402
|
|
|
|
FIXTURE = Path(__file__).with_name(
|
|
"raw_stonkfun_token_accounts_from_getaccountinfo.json"
|
|
)
|
|
HARVEST_WITHHELD_TO_MINT = bytes([26, 4])
|
|
CLOSE_ACCOUNT = bytes([9])
|
|
|
|
|
|
def _accounts() -> dict:
|
|
return json.loads(FIXTURE.read_text())
|
|
|
|
|
|
class _CleanupClient:
|
|
"""Serves one token account and records what cleanup sends."""
|
|
|
|
def __init__(self, data: bytes) -> None:
|
|
self.data = data
|
|
self.sent: list = []
|
|
|
|
async def get_account_info(self, _address: Pubkey) -> SimpleNamespace:
|
|
return SimpleNamespace(data=self.data, owner=SystemAddresses.TOKEN_2022_PROGRAM)
|
|
|
|
async def get_token_account_balance(self, _address: Pubkey) -> int:
|
|
return 0
|
|
|
|
async def build_and_send_transaction(self, instructions: list, *_a, **_k) -> str:
|
|
self.sent.append(instructions)
|
|
return "STUB_SIGNATURE"
|
|
|
|
async def confirm_transaction(self, *_a, **_k) -> bool:
|
|
return True
|
|
|
|
|
|
async def _no_sleep(_seconds: float) -> None:
|
|
return None
|
|
|
|
|
|
def _cleanup(entry: dict) -> list:
|
|
address = Pubkey.from_string(entry["address"])
|
|
client = _CleanupClient(base64.b64decode(entry["data_base64"]))
|
|
wallet = SimpleNamespace(
|
|
pubkey=Pubkey.from_string(entry["owner"]),
|
|
keypair=None,
|
|
get_associated_token_address=lambda _mint, _program: address,
|
|
)
|
|
cleanup_manager.asyncio.sleep = _no_sleep
|
|
manager = AccountCleanupManager(client, wallet, priority_fee_manager=None)
|
|
asyncio.run(
|
|
manager.cleanup_ata(
|
|
Pubkey.from_string(entry["mint"]), SystemAddresses.TOKEN_2022_PROGRAM
|
|
)
|
|
)
|
|
return client.sent
|
|
|
|
|
|
def check_a_withheld_amount_reads() -> bool:
|
|
"""A: withheld amount from the extension; zero without one."""
|
|
accounts = _accounts()
|
|
withheld = withheld_transfer_fee(
|
|
base64.b64decode(accounts["with_withheld_fee"]["data_base64"])
|
|
)
|
|
none = withheld_transfer_fee(
|
|
base64.b64decode(accounts["without_transfer_fee_extension"]["data_base64"])
|
|
)
|
|
classic = withheld_transfer_fee(bytes(165))
|
|
ok = withheld > 0 and none == 0 and classic == 0
|
|
if not ok:
|
|
print(f" withheld={withheld} without_extension={none} classic={classic}")
|
|
return ok
|
|
|
|
|
|
def check_b_harvest_before_close() -> bool:
|
|
"""B: harvest (mint, account; writable, unsigned) then close, one transaction."""
|
|
entry = _accounts()["with_withheld_fee"]
|
|
sent = _cleanup(entry)
|
|
if len(sent) != 1 or len(sent[0]) != 2: # noqa: PLR2004
|
|
print(f" sent {sent}")
|
|
return False
|
|
harvest, close = sent[0]
|
|
metas = [(str(m.pubkey), m.is_writable, m.is_signer) for m in harvest.accounts]
|
|
ok = (
|
|
harvest.program_id == SystemAddresses.TOKEN_2022_PROGRAM
|
|
and bytes(harvest.data) == HARVEST_WITHHELD_TO_MINT
|
|
and metas == [(entry["mint"], True, False), (entry["address"], True, False)]
|
|
and bytes(close.data) == CLOSE_ACCOUNT
|
|
)
|
|
if not ok:
|
|
print(f" harvest={harvest} close={close}")
|
|
return ok
|
|
|
|
|
|
def check_c_close_alone_without_withheld() -> bool:
|
|
"""C: nothing withheld -> CloseAccount alone."""
|
|
sent = _cleanup(_accounts()["without_transfer_fee_extension"])
|
|
ok = len(sent) == 1 and [bytes(ix.data) for ix in sent[0]] == [CLOSE_ACCOUNT]
|
|
if not ok:
|
|
print(f" sent {sent}")
|
|
return ok
|
|
|
|
|
|
def main() -> int:
|
|
"""Run every check and report."""
|
|
checks = [
|
|
("A: withheld amount reads from the account", check_a_withheld_amount_reads),
|
|
("B: harvest precedes close", check_b_harvest_before_close),
|
|
(
|
|
"C: close alone when nothing is withheld",
|
|
check_c_close_alone_without_withheld,
|
|
),
|
|
]
|
|
passed = 0
|
|
for label, check in checks:
|
|
ok = check()
|
|
print(f"{'PASS' if ok else 'FAIL'} {label}")
|
|
passed += ok
|
|
print(f"\n{passed}/{len(checks)} checks passed")
|
|
return 0 if passed == len(checks) else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|