mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-16 08:48:04 +00:00
feat(pumpfun): align src/ with 2026-04-28 program upgrade + cashback path (#167)
Mirrors the four learning-examples commits (9b48418,c25e2ed,57ffe8b,ad6d2cc) into the production code paths under src/. Affects only pump_fun; lets_bonk untouched. PumpSwap is not implemented under src/ so the pool-v2 + breaking-fee-recipient pumpswap accounts are out of scope here. Changes ------- src/platforms/pumpfun/address_provider.py - Adds BREAKING_FEE_RECIPIENTS: ClassVar[list[Pubkey]] with the 8 addresses pump.fun published for the 2026-04-28 upgrade. - Adds pick_breaking_fee_recipient() (random.choice across the 8 to spread program-tx throughput per pump.fun's recommendation). - get_buy_instruction_accounts and get_sell_instruction_accounts now expose "breaking_fee_recipient" alongside the existing keys. src/platforms/pumpfun/instruction_builder.py - BC buy ix: appends breaking_fee_recipient (mutable) AFTER bonding_curve_v2 → 18 accounts total (was 17). - BC sell ix: appends breaking_fee_recipient (mutable) AFTER bonding_curve_v2 for both cashback and non-cashback paths → 16 accounts non-cashback / 17 cashback (was 15 / 16). - get_required_accounts_for_{buy,sell} updated to include the new account so priority-fee scraping covers it. src/platforms/pumpfun/event_parser.py - is_mayhem_mode now extracted into TokenInfo on both paths (CreateEvent logs path and create / create_v2 instruction path); previously only is_cashback_coin was carried forward, leaving extreme_fast_mode buys to use the wrong fee_recipient on Mayhem-Mode tokens. src/trading/platform_aware.py - Buy non-extreme path now also refreshes is_cashback_coin from pool_state (was only refreshing is_mayhem_mode). - Sell path now refreshes both flags from curve state before building the sell ix, so a coin that flips cashback after buy still gets the correct 16-vs-17-account layout. Wrapped in try/except so a transient RPC failure logs a warning and falls back to token_info defaults rather than failing the sell outright. Live-validation (mainnet, all four bots/*.yaml configs) ------------------------------------------------------- bot-sniper-2-logs.yaml buy 4GVJJwYtGixSPAQimgUhNYjEUBG6TB9XeQjKxs9EWpw2QfWuq7LHmUivPrsjhESgaPsPJQ9N6CCxECqxm9csqrH9 (18 accts) → sell 2ijgoPgxPmcSX4mFqz1PVkZ3yAvu36vj3Jy1yWZofxrK7jR92j2DWP3tRNy9PmUd1kiTaoYzVsrpGr6286KCnTp7 (16 accts) ✓ bot-sniper-3-blocks.yaml buy 4siRLG7tYk9iHyXAvnp3DH54vQXVEDHmCqZRmFgYhnRB9mVGjhh43ywohKjg8CpWctt5TyCtDkLRTRWT6VDwVyZi → sell 46jShWJ3sfuCP4YgTcpXWZdyqVmw4ULQSTC3Kwvbc9hE2y4yDosBww8Un8anXZpR1Xf96rN9QusV1G92oNcbp1o3 ✓ bot-sniper-1-geyser.yaml buy 4UEZy7LPm7Rxgw22d1bduQHTWHHHYJSpuaoq2iCyPFpnBPGt4C3QUCu9x77vwWugXnyGv4pQQeTbEZEjvyxxiDH2 → sell 21hSHVPKtqse25XybxhdGrpqK1fSxKKeNZGDCD5j568mzRPfyYR1TQs6mAc2moHKT7kaAAfdMC8DPt8VXmu2rviF ✓ bot-sniper-4-pp.yaml buy 4P91y4iPK6y1gPjz65VL6XAcVkT4DbqjHE5vHfX8PLnzqViCy1qZQ44eWiNDUPzoWCejibB7UJPKYAEj4K3Z3c3S ✓ ; sell hit slippage protection (Custom 6003 — program-side, the ix passed deserialization) 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
bda9099275
commit
22a0c23450
@@ -6,7 +6,7 @@ by implementing the AddressProvider interface.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Final
|
from typing import ClassVar, Final
|
||||||
|
|
||||||
from solders.pubkey import Pubkey
|
from solders.pubkey import Pubkey
|
||||||
from spl.token.instructions import get_associated_token_address
|
from spl.token.instructions import get_associated_token_address
|
||||||
@@ -43,6 +43,29 @@ class PumpFunAddresses:
|
|||||||
FEE_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
FEE_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||||
"pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
|
"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: ClassVar[list[Pubkey]] = [
|
||||||
|
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"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pick_breaking_fee_recipient() -> Pubkey:
|
||||||
|
"""Pick one of the 8 breaking-upgrade fee recipients at random.
|
||||||
|
|
||||||
|
Spreads load across recipients per pump.fun's recommendation.
|
||||||
|
"""
|
||||||
|
import random
|
||||||
|
|
||||||
|
return random.choice(PumpFunAddresses.BREAKING_FEE_RECIPIENTS)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_global_volume_accumulator() -> Pubkey:
|
def find_global_volume_accumulator() -> Pubkey:
|
||||||
@@ -354,6 +377,7 @@ class PumpFunAddressProvider(AddressProvider):
|
|||||||
"fee_config": self.derive_fee_config(),
|
"fee_config": self.derive_fee_config(),
|
||||||
"fee_program": PumpFunAddresses.FEE_PROGRAM,
|
"fee_program": PumpFunAddresses.FEE_PROGRAM,
|
||||||
"bonding_curve_v2": self.derive_bonding_curve_v2(token_info.mint),
|
"bonding_curve_v2": self.derive_bonding_curve_v2(token_info.mint),
|
||||||
|
"breaking_fee_recipient": PumpFunAddresses.pick_breaking_fee_recipient(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_sell_instruction_accounts(
|
def get_sell_instruction_accounts(
|
||||||
@@ -405,4 +429,5 @@ class PumpFunAddressProvider(AddressProvider):
|
|||||||
"fee_program": PumpFunAddresses.FEE_PROGRAM,
|
"fee_program": PumpFunAddresses.FEE_PROGRAM,
|
||||||
"bonding_curve_v2": self.derive_bonding_curve_v2(token_info.mint),
|
"bonding_curve_v2": self.derive_bonding_curve_v2(token_info.mint),
|
||||||
"user_volume_accumulator": self.derive_user_volume_accumulator(user),
|
"user_volume_accumulator": self.derive_user_volume_accumulator(user),
|
||||||
|
"breaking_fee_recipient": PumpFunAddresses.pick_breaking_fee_recipient(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,7 @@ class PumpFunEventParser(EventParser):
|
|||||||
creator=creator,
|
creator=creator,
|
||||||
creator_vault=creator_vault,
|
creator_vault=creator_vault,
|
||||||
token_program_id=token_program_id,
|
token_program_id=token_program_id,
|
||||||
|
is_mayhem_mode=fields.get("is_mayhem_mode", False),
|
||||||
is_cashback_coin=fields.get("is_cashback_enabled", False),
|
is_cashback_coin=fields.get("is_cashback_enabled", False),
|
||||||
creation_timestamp=monotonic(),
|
creation_timestamp=monotonic(),
|
||||||
)
|
)
|
||||||
@@ -384,6 +385,7 @@ class PumpFunEventParser(EventParser):
|
|||||||
creator=creator,
|
creator=creator,
|
||||||
creator_vault=creator_vault,
|
creator_vault=creator_vault,
|
||||||
token_program_id=token_program_id,
|
token_program_id=token_program_id,
|
||||||
|
is_mayhem_mode=bool(args.get("is_mayhem_mode", False)),
|
||||||
is_cashback_coin=is_cashback,
|
is_cashback_coin=is_cashback,
|
||||||
creation_timestamp=monotonic(),
|
creation_timestamp=monotonic(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -149,6 +149,12 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
|||||||
is_signer=False,
|
is_signer=False,
|
||||||
is_writable=False,
|
is_writable=False,
|
||||||
),
|
),
|
||||||
|
# 18th account: breaking-upgrade fee recipient (mutable) — required from 2026-04-28
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=accounts_info["breaking_fee_recipient"],
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Build instruction data: discriminator + token_amount + max_sol_cost + track_volume
|
# Build instruction data: discriminator + token_amount + max_sol_cost + track_volume
|
||||||
@@ -271,6 +277,14 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
|||||||
is_writable=False,
|
is_writable=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# 16/17th account: breaking-upgrade fee recipient (mutable) — required from 2026-04-28
|
||||||
|
sell_accounts.append(
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=accounts_info["breaking_fee_recipient"],
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Build instruction data: discriminator + token_amount + min_sol_output + track_volume
|
# Build instruction data: discriminator + token_amount + min_sol_output + track_volume
|
||||||
# Encode OptionBool for track_volume: [1, 1] = Some(true)
|
# Encode OptionBool for track_volume: [1, 1] = Some(true)
|
||||||
@@ -319,6 +333,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
|||||||
accounts_info["fee_config"],
|
accounts_info["fee_config"],
|
||||||
accounts_info["fee_program"],
|
accounts_info["fee_program"],
|
||||||
accounts_info["bonding_curve_v2"],
|
accounts_info["bonding_curve_v2"],
|
||||||
|
accounts_info["breaking_fee_recipient"],
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_required_accounts_for_sell(
|
def get_required_accounts_for_sell(
|
||||||
@@ -347,6 +362,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
|||||||
accounts_info["fee_config"],
|
accounts_info["fee_config"],
|
||||||
accounts_info["fee_program"],
|
accounts_info["fee_program"],
|
||||||
accounts_info["bonding_curve_v2"],
|
accounts_info["bonding_curve_v2"],
|
||||||
|
accounts_info["breaking_fee_recipient"],
|
||||||
]
|
]
|
||||||
|
|
||||||
def calculate_token_amount_raw(self, token_amount_decimal: float) -> int:
|
def calculate_token_amount_raw(self, token_amount_decimal: float) -> int:
|
||||||
|
|||||||
@@ -77,8 +77,13 @@ class PlatformAwareBuyer(Trader):
|
|||||||
f"(mint: {token_info.mint}) - cannot execute buy with zero/invalid price"
|
f"(mint: {token_info.mint}) - cannot execute buy with zero/invalid price"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set is_mayhem_mode from bonding curve state
|
# Set mayhem-mode and cashback flags from bonding-curve state
|
||||||
|
# so the instruction builder picks the correct fee_recipient and
|
||||||
|
# account-list shape (cashback sells use 17 accounts, non-cashback 16).
|
||||||
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
|
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
|
||||||
|
token_info.is_cashback_coin = pool_state.get(
|
||||||
|
"is_cashback_coin", token_info.is_cashback_coin
|
||||||
|
)
|
||||||
token_amount = self.amount / token_price_sol
|
token_amount = self.amount / token_price_sol
|
||||||
|
|
||||||
# Calculate minimum token amount with slippage
|
# Calculate minimum token amount with slippage
|
||||||
@@ -304,6 +309,28 @@ class PlatformAwareSeller(Trader):
|
|||||||
)
|
)
|
||||||
address_provider = implementations.address_provider
|
address_provider = implementations.address_provider
|
||||||
instruction_builder = implementations.instruction_builder
|
instruction_builder = implementations.instruction_builder
|
||||||
|
curve_manager = implementations.curve_manager
|
||||||
|
|
||||||
|
# Refresh mayhem-mode and cashback flags from curve state.
|
||||||
|
# The sell account list is 16 (non-cashback) vs 17 (cashback), and
|
||||||
|
# fee_recipient differs in mayhem mode — both can change between
|
||||||
|
# buy and sell, so re-read from chain instead of trusting create-time
|
||||||
|
# flags carried in token_info.
|
||||||
|
try:
|
||||||
|
pool_address = self._get_pool_address(token_info, address_provider)
|
||||||
|
pool_state = await curve_manager.get_pool_state(pool_address)
|
||||||
|
token_info.is_mayhem_mode = pool_state.get(
|
||||||
|
"is_mayhem_mode", token_info.is_mayhem_mode
|
||||||
|
)
|
||||||
|
token_info.is_cashback_coin = pool_state.get(
|
||||||
|
"is_cashback_coin", token_info.is_cashback_coin
|
||||||
|
)
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
logger.warning(
|
||||||
|
f"Could not refresh curve flags before sell ({e}); "
|
||||||
|
f"using token_info values is_mayhem_mode={token_info.is_mayhem_mode}, "
|
||||||
|
f"is_cashback_coin={token_info.is_cashback_coin}"
|
||||||
|
)
|
||||||
|
|
||||||
# Use pre-known amount and price (no RPC delay)
|
# Use pre-known amount and price (no RPC delay)
|
||||||
token_balance_decimal = token_amount
|
token_balance_decimal = token_amount
|
||||||
|
|||||||
Reference in New Issue
Block a user