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>
This commit is contained in:
Anton Sauchyk
2026-04-27 17:43:31 +02:00
co-authored by Claude Opus 4.7
parent 9b4841890c
commit c25e2edb57
4 changed files with 157 additions and 29 deletions
+40 -5
View File
@@ -22,6 +22,7 @@ import base64
import hashlib
import json
import os
import random
import struct
import base58
@@ -53,6 +54,20 @@ PUMP_EVENT_AUTHORITY = Pubkey.from_string(
)
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
# 8 breaking-upgrade fee recipients (pump.fun program upgrade 2026-04-28).
# One must be appended (mutable) AFTER bonding-curve-v2 on every buy/sell.
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
BREAKING_FEE_RECIPIENTS = [
Pubkey.from_string("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
Pubkey.from_string("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
Pubkey.from_string("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
Pubkey.from_string("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
Pubkey.from_string("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
Pubkey.from_string("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
Pubkey.from_string("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
Pubkey.from_string("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
]
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
TOKEN_2022_PROGRAM = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
@@ -333,6 +348,12 @@ async def buy_token(
is_signer=False,
is_writable=False,
),
# 18th account: breaking-upgrade fee recipient (mutable) — required from 2026-04-28
AccountMeta(
pubkey=random.choice(BREAKING_FEE_RECIPIENTS),
is_signer=False,
is_writable=True,
),
]
discriminator = struct.pack("<Q", 16927863322537952870)
@@ -350,7 +371,10 @@ async def buy_token(
# CU OPTIMIZATION: Limit account data to 512KB (down from 64MB default)
# This reduces CU cost from 16k to ~128 CU and improves tx priority.
# Must be placed FIRST in the instruction list.
cu_limit_ix = set_loaded_accounts_data_size_limit(512_000)
# 16MB limit: works for cashback-coin Token-2022 buys on mainnet.
# Smaller values (48MB) trigger MaxLoadedAccountsDataSizeExceeded for
# Token-2022 mints with extensions. Still 4× smaller than the 64MB default.
cu_limit_ix = set_loaded_accounts_data_size_limit(16_384_000)
msg = Message(
[cu_limit_ix, set_compute_unit_price(1_000), idempotent_ata_ix, buy_ix],
@@ -402,16 +426,21 @@ def decode_create_instruction(ix_data, ix_def, accounts):
args = {}
offset = 8
for arg in ix_def["args"]:
if arg["type"] == "string":
t = arg["type"]
if t == "string":
length = struct.unpack_from("<I", ix_data, offset)[0]
offset += 4
value = ix_data[offset : offset + length].decode("utf-8")
offset += length
elif arg["type"] == "pubkey":
elif t == "pubkey":
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
offset += 32
elif t == "bool" or (isinstance(t, dict) and "defined" in t):
# `bool` and `OptionBool` (struct {bool}) both serialize as 1 byte
value = bool(ix_data[offset])
offset += 1
else:
raise ValueError(f"Unsupported type: {arg['type']}")
raise ValueError(f"Unsupported type: {t}")
args[arg["name"]] = value
args["mint"] = str(accounts[0])
@@ -499,6 +528,12 @@ async def listen_for_create_transaction():
for instr in idl["instructions"]
if instr["name"] == instruction_name
)
# Skip txs that use Address Lookup Tables — their
# instruction account indices reference ALT-loaded keys
# not present in transaction.message.account_keys.
static_keys = transaction.message.account_keys
if any(idx >= len(static_keys) for idx in ix.accounts):
continue
account_keys = [
str(
transaction.message.account_keys[
@@ -554,7 +589,7 @@ async def main():
print(
f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..."
)
print("CU Optimization: Enabled (512KB account data limit)")
print("CU Optimization: Enabled (16MB account data limit)")
await buy_token(
mint,
bonding_curve,