""" Pump.fun Token Buy Script with Compute Unit Optimization This script is identical to manual_buy.py but adds SetLoadedAccountsDataSizeLimit instruction. By default, Solana transactions can load up to 64MB of account data (costing 16k CU). By setting a lower limit (512KB), we reduce CU consumption and improve transaction priority. Key difference from manual_buy.py: - Adds set_loaded_accounts_data_size_limit(512_000) before other instructions NOTE: The CU savings from this optimization are NOT visible in transaction "consumed CU" metrics, which only show execution CU. The 16k CU loaded accounts overhead is counted separately for transaction priority/cost calculation. This makes the real impact hard to measure directly, but it improves priority. Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit """ import asyncio import base64 import hashlib import json import os import random import struct import base58 import websockets from construct import Flag, Int64ul, Struct from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts from solders.compute_budget import set_compute_unit_price from solders.instruction import AccountMeta, Instruction from solders.keypair import Keypair from solders.message import Message from solders.pubkey import Pubkey from solders.transaction import Transaction, VersionedTransaction from spl.token.instructions import ( create_idempotent_associated_token_account, get_associated_token_address, ) # Discriminators EXPECTED_DISCRIMINATOR = struct.pack(" None: """Parse bonding curve data progressively based on available bytes. Args: data: Raw account data including discriminator Raises: ValueError: If discriminator is invalid or data is too short """ if len(data) < 8: raise ValueError("Data too short to contain discriminator") if data[:8] != EXPECTED_DISCRIMINATOR: raise ValueError("Invalid curve state discriminator") # Parse base fields (always present) offset = 8 base_data = data[offset:] parsed = self._BASE_STRUCT.parse(base_data) self.__dict__.update(parsed) # Calculate offset after base struct offset += self._BASE_STRUCT.sizeof() # Parse creator if bytes remaining (added in V2) if len(data) >= offset + 32: creator_bytes = data[offset : offset + 32] self.creator = Pubkey.from_bytes(creator_bytes) offset += 32 else: self.creator = None # Parse mayhem mode flag if bytes remaining (added in V3) if len(data) >= offset + 1: self.is_mayhem_mode = bool(data[offset]) else: self.is_mayhem_mode = False async def get_pump_curve_state( conn: AsyncClient, curve_address: Pubkey ) -> BondingCurveState: response = await conn.get_account_info(curve_address, encoding="base64") if not response.value or not response.value.data: raise ValueError("Invalid curve state: No data") data = response.value.data if data[:8] != EXPECTED_DISCRIMINATOR: raise ValueError("Invalid curve state discriminator") return BondingCurveState(data) def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: raise ValueError("Invalid reserve state") return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / ( curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS ) def _find_creator_vault(creator: Pubkey) -> Pubkey: derived_address, _ = Pubkey.find_program_address( [b"creator-vault", bytes(creator)], PUMP_PROGRAM, ) return derived_address def _find_global_volume_accumulator() -> Pubkey: derived_address, _ = Pubkey.find_program_address( [b"global_volume_accumulator"], PUMP_PROGRAM, ) 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 def _find_fee_config() -> Pubkey: derived_address, _ = Pubkey.find_program_address( [b"fee_config", bytes(PUMP_PROGRAM)], PUMP_FEE_PROGRAM, ) return derived_address def _find_bonding_curve_v2(mint: Pubkey) -> Pubkey: derived_address, _ = Pubkey.find_program_address( [b"bonding-curve-v2", bytes(mint)], PUMP_PROGRAM, ) return derived_address async def get_fee_recipient( client: AsyncClient, curve_state: BondingCurveState ) -> Pubkey: """Determine the correct fee recipient based on mayhem mode. Mayhem mode tokens use a different fee recipient (reserved_fee_recipient from Global account) instead of the standard fee recipient. This function checks the bonding curve state and returns the appropriate fee recipient. Args: client: Solana RPC client to fetch Global account data curve_state: Parsed bonding curve state containing is_mayhem_mode flag Returns: Appropriate fee recipient pubkey (mayhem or standard) """ if not curve_state.is_mayhem_mode: return PUMP_FEE # Fetch Global account to get reserved_fee_recipient for mayhem mode tokens response = await client.get_account_info(PUMP_GLOBAL, encoding="base64") if not response.value or not response.value.data: # Fallback to standard fee if Global account cannot be fetched return PUMP_FEE data = response.value.data # Parse reserved_fee_recipient from Global account # Offset calculation based on pump_fun_idl.json Global struct: # discriminator(8) + initialized(1) + authority(32) + fee_recipient(32) + # initial_virtual_token_reserves(8) + initial_virtual_sol_reserves(8) + # initial_real_token_reserves(8) + token_total_supply(8) + fee_basis_points(8) + # withdraw_authority(32) + enable_migrate(1) + pool_migration_fee(8) + # creator_fee_basis_points(8) + fee_recipients[7](224) + set_creator_authority(32) + # admin_set_creator_authority(32) + create_v2_enabled(1) + whitelist_pda(32) = 483 RESERVED_FEE_RECIPIENT_OFFSET = 483 if len(data) < RESERVED_FEE_RECIPIENT_OFFSET + 32: # Fallback if account data is too short return PUMP_FEE reserved_fee_recipient_bytes = data[ RESERVED_FEE_RECIPIENT_OFFSET : RESERVED_FEE_RECIPIENT_OFFSET + 32 ] return Pubkey.from_bytes(reserved_fee_recipient_bytes) def set_loaded_accounts_data_size_limit(bytes_limit: int) -> Instruction: """ Create SetLoadedAccountsDataSizeLimit instruction to reduce CU consumption. Solana defaults to 64MB loaded data limit (16k CU cost: 8 CU per 32KB). By setting a lower limit, you reduce CU consumption and improve tx priority. Args: bytes_limit: Max account data size in bytes (e.g., 512_000 = 512KB) Returns: Compute Budget instruction (discriminator 4) """ data = struct.pack("= len(static_keys) for idx in ix.accounts): continue account_keys = [ str( transaction.message.account_keys[ index ] ) for index in ix.accounts ] decoded_args = ( decode_create_instruction( ix_data, create_ix, account_keys ) ) # Add token program info to decoded args decoded_args["token_program"] = str( token_program ) decoded_args["is_token_2022"] = ( token_program == TOKEN_2022_PROGRAM ) return decoded_args async def main(): print("Waiting for a new token creation...") token_data = await listen_for_create_transaction() print("New token created:") print(json.dumps(token_data, indent=2)) print("\nWaiting 15 seconds for things to stabilize...") await asyncio.sleep(15) mint = Pubkey.from_string(token_data["mint"]) bonding_curve = Pubkey.from_string(token_data["bondingCurve"]) associated_bonding_curve = Pubkey.from_string(token_data["associatedBondingCurve"]) creator_vault = _find_creator_vault(Pubkey.from_string(token_data["creator"])) token_program = Pubkey.from_string(token_data["token_program"]) # Fetch the token price async with AsyncClient(RPC_ENDPOINT) as client: curve_state = await get_pump_curve_state(client, bonding_curve) token_price_sol = calculate_pump_curve_price(curve_state) # Amount of SOL to spend (adjust as needed) amount = 0.000_001 # 0.00001 SOL slippage = 0.3 # 30% slippage tolerance print(f"Bonding curve address: {bonding_curve}") print( f"Token Program: {token_program} ({'Token2022' if token_data['is_token_2022'] else 'Standard Token'})" ) print(f"Token price: {token_price_sol:.10f} SOL") print( f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..." ) print("CU Optimization: Enabled (16MB account data limit)") await buy_token( mint, bonding_curve, associated_bonding_curve, creator_vault, token_program, amount, slippage, ) if __name__ == "__main__": asyncio.run(main())