mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-18 09:48:05 +00:00
chore(pumpswap): post-2026-04-28 cutover + cashback layout (#168)
* chore(pumpswap+docs): post-2026-04-28-cutover PumpSwap unlock + IDL docs Drops the `INCLUDE_BREAKING_FEE_ACCOUNTS = False` gate in the PumpSwap learning examples so the +2 breaking-fee accounts (fee recipient readonly + its quote-mint ATA mutable) are always appended after `pool-v2`. Mainnet pump-amm rejects this format pre-cutover (verified 6023 Overflow), so this PR is intentionally **draft until 2026-04-28 16:00 UTC** when the cutover happens; mark it ready-for-review and live- test then. Also captures the protocol gotchas we learned during this migration: - README: a new "2026-04-28 program upgrade" section + an explicit note that the vendored IDL is incomplete (missing `bonding-curve-v2` and `pool-v2` PDAs that the on-chain program actually requires) with pointers to cross-check against on-chain txs. - CLAUDE.md: a new "Pump.fun protocol notes" subsection summarising the same gotchas plus the BC/Pool/CreateEvent layout details and the extreme_fast_mode gotcha. Open question (call out at review time, resolve post-cutover): - BREAKING_FEE_RECIPIENT.md shows PumpSwap cashback account counts of 27 buy / 26 sell vs 26 / 24 non-cashback — the extra cashback account seed/position isn't documented. Need to sample a real successful cashback PumpSwap tx after cutover and add the branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(pumpswap): wire cashback layout for post-2026-04-28 program upgrade Cashback PumpSwap pools require extra writable accounts inserted before pool-v2. Identified layouts by sampling on-chain post-cutover txs: - buy: insert user_volume_accumulator_quote_ata (27 accounts vs 26) ref tx 4JaWdExj…fvjK - sell: insert user_volume_accumulator_quote_ata + user_volume_accumulator (26 accounts vs 24) ref tx 4ei1cJV7…NP3 Detect via pool account byte 244 (is_cashback_coin). Adds the standalone sample_cashback_pumpswap.py used to reverse-engineer the layouts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
22a0c23450
commit
cc85daaeb3
@@ -92,6 +92,7 @@ BREAKING_FEE_RECIPIENTS = [
|
||||
POOL_DISCRIMINATOR_SIZE = 8
|
||||
POOL_BASE_MINT_OFFSET = 43 # Where base_mint field starts in pool account data
|
||||
POOL_MAYHEM_MODE_OFFSET = 243 # Where is_mayhem_mode flag is stored
|
||||
POOL_IS_CASHBACK_OFFSET = 244 # Where is_cashback_coin flag is stored
|
||||
POOL_MAYHEM_MODE_MIN_SIZE = 244 # Minimum size for pool data with mayhem flag
|
||||
|
||||
# GlobalConfig structure offsets
|
||||
@@ -246,6 +247,16 @@ def find_pool_v2(base_mint: Pubkey) -> Pubkey:
|
||||
return derived_address
|
||||
|
||||
|
||||
def find_user_volume_accumulator(user: Pubkey) -> Pubkey:
|
||||
"""Derive the per-user volume accumulator PDA (pump-amm). Required for
|
||||
cashback-pool sells (along with its WSOL ATA)."""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[b"user_volume_accumulator", bytes(user)],
|
||||
PUMP_AMM_PROGRAM_ID,
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Mayhem Mode Fee Handling
|
||||
# ============================================================================
|
||||
@@ -279,18 +290,11 @@ async def get_reserved_fee_recipient_pumpswap(client: AsyncClient) -> Pubkey:
|
||||
|
||||
async def get_pumpswap_fee_recipients(
|
||||
client: AsyncClient, pool: Pubkey
|
||||
) -> tuple[Pubkey, Pubkey]:
|
||||
"""Determine the correct fee recipient based on pool's mayhem mode status.
|
||||
|
||||
This function checks if mayhem mode is enabled for the pool and returns
|
||||
the appropriate fee recipient and their WSOL token account.
|
||||
|
||||
Args:
|
||||
client: Solana RPC client
|
||||
pool: Address of the AMM pool
|
||||
) -> tuple[Pubkey, Pubkey, bool]:
|
||||
"""Determine fee recipient + whether the pool is cashback.
|
||||
|
||||
Returns:
|
||||
Tuple of (fee_recipient_pubkey, fee_recipient_token_account)
|
||||
Tuple of (fee_recipient_pubkey, fee_recipient_token_account, is_cashback)
|
||||
"""
|
||||
response = await client.get_account_info(pool, encoding="base64")
|
||||
if not response.value or not response.value.data:
|
||||
@@ -299,12 +303,13 @@ async def get_pumpswap_fee_recipients(
|
||||
|
||||
pool_data = response.value.data
|
||||
|
||||
# Check if mayhem mode flag exists and is enabled
|
||||
is_mayhem_mode = len(pool_data) >= POOL_MAYHEM_MODE_MIN_SIZE and bool(
|
||||
pool_data[POOL_MAYHEM_MODE_OFFSET]
|
||||
)
|
||||
is_cashback = len(pool_data) > POOL_IS_CASHBACK_OFFSET and bool(
|
||||
pool_data[POOL_IS_CASHBACK_OFFSET]
|
||||
)
|
||||
|
||||
# Select appropriate fee recipient
|
||||
if is_mayhem_mode:
|
||||
fee_recipient = await get_reserved_fee_recipient_pumpswap(client)
|
||||
else:
|
||||
@@ -315,7 +320,7 @@ async def get_pumpswap_fee_recipients(
|
||||
fee_recipient, SOL, SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
return (fee_recipient, fee_recipient_token_account)
|
||||
return (fee_recipient, fee_recipient_token_account, is_cashback)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -475,9 +480,18 @@ async def sell_pump_swap(
|
||||
print(f"Selling {token_balance_decimal} tokens")
|
||||
print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL")
|
||||
|
||||
# Get fee recipient based on mayhem mode
|
||||
fee_recipient, fee_recipient_token_account = await get_pumpswap_fee_recipients(
|
||||
client, market
|
||||
# Get fee recipient based on mayhem mode + detect cashback pool
|
||||
(
|
||||
fee_recipient,
|
||||
fee_recipient_token_account,
|
||||
is_cashback,
|
||||
) = await get_pumpswap_fee_recipients(client, market)
|
||||
|
||||
# Cashback-pool sells append two writable accounts (user_volume_accumulator
|
||||
# quote ATA, then the accumulator PDA itself) BEFORE pool-v2.
|
||||
user_volume_accumulator = find_user_volume_accumulator(payer.pubkey())
|
||||
user_volume_accumulator_quote_ata = get_associated_token_address(
|
||||
user_volume_accumulator, SOL, SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
# Build account list for sell instruction
|
||||
@@ -516,23 +530,38 @@ async def sell_pump_swap(
|
||||
),
|
||||
AccountMeta(pubkey=find_fee_config(), is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_FEE_PROGRAM, is_signer=False, is_writable=False),
|
||||
# pool-v2 PDA (per-base-mint) — the last "pre-upgrade" account.
|
||||
AccountMeta(pubkey=find_pool_v2(base_mint), is_signer=False, is_writable=False),
|
||||
]
|
||||
# 2 new accounts required from 2026-04-28 16:00 UTC pump-swap program upgrade,
|
||||
# AFTER pool-v2: breaking-upgrade fee recipient (readonly, second-to-last) and
|
||||
# its quote-mint ATA (mutable, last). Set to True to opt in.
|
||||
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
|
||||
INCLUDE_BREAKING_FEE_ACCOUNTS = False # flip True after 2026-04-28 16:00 UTC
|
||||
if INCLUDE_BREAKING_FEE_ACCOUNTS:
|
||||
breaking_fee_recipient = random.choice(BREAKING_FEE_RECIPIENTS)
|
||||
breaking_fee_quote_ata = get_associated_token_address(
|
||||
breaking_fee_recipient, SOL, SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
# Cashback pools require user_volume_accumulator_quote_ata + the accumulator
|
||||
# PDA itself (both writable) BEFORE pool-v2. Confirmed against on-chain
|
||||
# post-cutover cashback sell
|
||||
# (sig 4ei1cJV7uaENJeb5p8prVKiTApTouTh94r9HqPNbj7oJH52X8mEiXhrVNKUgtB9WeZB8jZANnmuSkdTuJ59y8NP3).
|
||||
if is_cashback:
|
||||
accounts.extend([
|
||||
AccountMeta(pubkey=breaking_fee_recipient, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=breaking_fee_quote_ata, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=user_volume_accumulator_quote_ata,
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=user_volume_accumulator, is_signer=False, is_writable=True
|
||||
),
|
||||
])
|
||||
# pool-v2 PDA (per-base-mint) — the last "pre-upgrade" account.
|
||||
accounts.append(
|
||||
AccountMeta(pubkey=find_pool_v2(base_mint), is_signer=False, is_writable=False)
|
||||
)
|
||||
# 2 accounts required by the 2026-04-28 pump-swap upgrade, appended AFTER
|
||||
# pool-v2: breaking-fee recipient (readonly) + its quote-mint ATA (mutable).
|
||||
# Sell counts: 24 non-cashback / 26 cashback.
|
||||
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
|
||||
breaking_fee_recipient = random.choice(BREAKING_FEE_RECIPIENTS)
|
||||
breaking_fee_quote_ata = get_associated_token_address(
|
||||
breaking_fee_recipient, SOL, SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
accounts.extend([
|
||||
AccountMeta(pubkey=breaking_fee_recipient, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=breaking_fee_quote_ata, is_signer=False, is_writable=True),
|
||||
])
|
||||
|
||||
# Instruction data format: discriminator (8 bytes) + amount (8 bytes) + min_out (8 bytes)
|
||||
# All integers are little-endian (<)
|
||||
|
||||
Reference in New Issue
Block a user