diff --git a/learning-examples/manual_buy.py b/learning-examples/manual_buy.py index 9a2c889..1622d91 100644 --- a/learning-examples/manual_buy.py +++ b/learning-examples/manual_buy.py @@ -3,6 +3,7 @@ import base64 import hashlib import json import os +import random import struct import base58 @@ -43,6 +44,20 @@ SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") LAMPORTS_PER_SOL = 1_000_000_000 +# 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"), +] + # RPC ENDPOINTS RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT") RPC_WEBSOCKET = os.environ.get("SOLANA_NODE_WSS_ENDPOINT") @@ -295,6 +310,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("= len(static_keys) for idx in ix.accounts): + continue account_keys = [ - str( - transaction.message.account_keys[ - index - ] - ) + str(static_keys[index]) for index in ix.accounts ] decoded_args = ( diff --git a/learning-examples/manual_buy_cu_optimized.py b/learning-examples/manual_buy_cu_optimized.py index cc6ed44..6af2faf 100644 --- a/learning-examples/manual_buy_cu_optimized.py +++ b/learning-examples/manual_buy_cu_optimized.py @@ -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("= 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, diff --git a/learning-examples/manual_buy_geyser.py b/learning-examples/manual_buy_geyser.py index b6b3b62..42d07cb 100644 --- a/learning-examples/manual_buy_geyser.py +++ b/learning-examples/manual_buy_geyser.py @@ -1,6 +1,7 @@ import asyncio import json import os +import random import struct import sys @@ -39,6 +40,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") SYSTEM_TOKEN_2022_PROGRAM = Pubkey.from_string( @@ -365,19 +380,11 @@ async def buy_token( ) amount_lamports = int(amount * LAMPORTS_PER_SOL) - # Fetch bonding curve state to calculate price and determine fee recipient - # NOTE: Price calculation is commented out to speed up testing - using fixed values - # For production, uncomment the lines below to: - # 1. Calculate proper token amounts based on current price - # 2. Detect mayhem mode and use correct fee recipient - # curve_state = await get_pump_curve_state(client, bonding_curve) - # token_price_sol = calculate_pump_curve_price(curve_state) - # token_amount = amount / token_price_sol - # fee_recipient = await get_fee_recipient(client, curve_state) - - # Testing values - replace with code above for production - token_amount = 100 # Fixed token amount - fee_recipient = PUMP_FEE # Standard fee recipient (doesn't detect mayhem mode) + # Fetch bonding curve state for price + mayhem-mode-aware fee recipient. + curve_state = await get_pump_curve_state(client, bonding_curve) + token_price_sol = calculate_pump_curve_price(curve_state) + token_amount = amount / token_price_sol + fee_recipient = await get_fee_recipient(client, curve_state) # Calculate maximum SOL to spend with slippage max_amount_lamports = int(amount_lamports * (1 + slippage)) @@ -433,6 +440,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(" 1 else "...") # Pass mint as argv[1] or hardcode here # Global constants PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") @@ -42,6 +44,20 @@ UNIT_BUDGET = 100_000 RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT") +# 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"), +] + class BondingCurveState: """Bonding curve state parser with progressive field parsing. @@ -95,9 +111,16 @@ class BondingCurveState: # Parse mayhem mode flag if bytes remaining (added in V3) if len(data) >= offset + 1: self.is_mayhem_mode = bool(data[offset]) + offset += 1 else: self.is_mayhem_mode = False + # Parse cashback flag if bytes remaining (added in V4 — late-Feb 2026 cashback upgrade) + if len(data) >= offset + 1: + self.is_cashback_coin = bool(data[offset]) + else: + self.is_cashback_coin = False + async def get_pump_curve_state( conn: AsyncClient, curve_address: Pubkey @@ -155,6 +178,14 @@ def _find_bonding_curve_v2(mint: Pubkey) -> Pubkey: return derived_address +def _find_user_volume_accumulator(user: Pubkey) -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [b"user_volume_accumulator", bytes(user)], + PUMP_PROGRAM, + ) + return derived_address + + async def get_fee_recipient( client: AsyncClient, curve_state: BondingCurveState ) -> Pubkey: @@ -320,13 +351,31 @@ async def sell_token( is_signer=False, is_writable=False, ), - # Remaining account: bonding_curve_v2 (readonly, required for all coins) + ] + # For cashback coins, insert user_volume_accumulator before bonding-curve-v2 (17 accounts total). + if curve_state.is_cashback_coin: + accounts.append( + AccountMeta( + pubkey=_find_user_volume_accumulator(payer.pubkey()), + is_signer=False, + is_writable=True, + ) + ) + accounts.extend([ + # bonding_curve_v2 (readonly, required for all coins) AccountMeta( pubkey=_find_bonding_curve_v2(mint), is_signer=False, is_writable=False, ), - ] + # Breaking-upgrade fee recipient (mutable) — required from 2026-04-28. + # 16 accounts non-cashback / 17 accounts cashback. + AccountMeta( + pubkey=random.choice(BREAKING_FEE_RECIPIENTS), + is_signer=False, + is_writable=True, + ), + ]) discriminator = struct.pack("