Files
pumpfun-bonkfun-bot/learning-examples/pumpswap/sample_cashback_pumpswap.py
T

135 lines
4.5 KiB
Python
Raw Normal View History

"""Post-cutover sampler: find a successful cashback PumpSwap buy/sell on mainnet.
Goal: identify the seed/position of the +1 account that the program requires for
cashback pools (27-account buy / 26-account sell vs 26 / 24 non-cashback).
Strategy:
1. Pull recent signatures for pAMM program.
2. For each tx, fetch full tx, find the pAMM buy/sell ix.
3. Resolve the pool account from the ix; fetch its data; check byte 244
(is_cashback_coin) — only proceed if it's 1.
4. Print the full account list with counts so we can diff against the known
26/24 non-cashback layout in manual_buy/sell_pumpswap.py.
Usage:
uv run learning-examples/pumpswap/sample_cashback_pumpswap.py [LIMIT]
Env: SOLANA_NODE_RPC_ENDPOINT (defaults to public mainnet)
"""
import asyncio
import os
import sys
from solana.rpc.async_api import AsyncClient
from solders.pubkey import Pubkey
from solders.signature import Signature
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea")
SELL_DISCRIMINATOR = bytes.fromhex("33e685a4017f83ad")
# Pool layout: byte 244 = is_cashback_coin (per CLAUDE.md / PR #167 notes).
POOL_IS_CASHBACK_OFFSET = 244
RPC = os.environ.get(
"SOLANA_NODE_RPC_ENDPOINT", "https://api.mainnet-beta.solana.com"
)
async def is_cashback_pool(client: AsyncClient, pool: Pubkey) -> bool | None:
resp = await client.get_account_info(pool, encoding="base64")
if resp.value is None:
return None
data = resp.value.data
if len(data) <= POOL_IS_CASHBACK_OFFSET:
return False
return data[POOL_IS_CASHBACK_OFFSET] == 1
def classify_ix(ix_data: bytes) -> str | None:
if ix_data.startswith(BUY_DISCRIMINATOR):
return "buy"
if ix_data.startswith(SELL_DISCRIMINATOR):
return "sell"
return None
async def inspect_tx(client: AsyncClient, sig: Signature) -> dict | None:
"""Return diagnostic dict if this tx contains a cashback buy/sell."""
resp = await client.get_transaction(
sig, encoding="base64", max_supported_transaction_version=0
)
if resp.value is None or resp.value.transaction.meta is None:
return None
if resp.value.transaction.meta.err is not None:
return None # only successful txs
tx = resp.value.transaction.transaction
msg = tx.message
account_keys = list(msg.account_keys)
# include loaded addresses from ALTs
loaded = resp.value.transaction.meta.loaded_addresses
if loaded is not None:
account_keys.extend(loaded.writable)
account_keys.extend(loaded.readonly)
for ix in msg.instructions:
program_id = account_keys[ix.program_id_index]
if program_id != PUMP_AMM_PROGRAM_ID:
continue
kind = classify_ix(bytes(ix.data))
if kind is None:
continue
# PumpSwap convention: account index 0 of the ix is the pool.
if not ix.accounts:
continue
pool = account_keys[ix.accounts[0]]
cashback = await is_cashback_pool(client, pool)
if not cashback:
continue
return {
"sig": str(sig),
"kind": kind,
"pool": str(pool),
"n_accounts": len(ix.accounts),
"accounts": [str(account_keys[i]) for i in ix.accounts],
}
return None
async def main(limit: int = 200) -> None:
async with AsyncClient(RPC) as client:
print(f"Sampling up to {limit} recent pAMM signatures from {RPC}")
sigs_resp = await client.get_signatures_for_address(
PUMP_AMM_PROGRAM_ID, limit=limit
)
sigs = [s.signature for s in sigs_resp.value if s.err is None]
print(f" got {len(sigs)} successful signatures")
for sig in sigs:
try:
hit = await inspect_tx(client, sig)
except (ValueError, RuntimeError) as e:
print(f" [skip] {sig}: {e}")
continue
if hit is None:
continue
print()
print(f"=== CASHBACK {hit['kind'].upper()} ===")
print(f" sig: {hit['sig']}")
print(f" pool: {hit['pool']}")
print(f" count: {hit['n_accounts']} accounts")
for i, a in enumerate(hit["accounts"]):
print(f" [{i:2d}] {a}")
return
print("No cashback PumpSwap buy/sell found in window.")
if __name__ == "__main__":
n = int(sys.argv[1]) if len(sys.argv) > 1 else 200
asyncio.run(main(n))