"""Buy the next pump.fun coin to be created, using buy_v2. WARNING: this submits a real transaction and spends real funds. Usage: uv run learning-examples/manual_buy.py uv run learning-examples/manual_buy.py --cu-optimized `--cu-optimized` adds a SetLoadedAccountsDataSizeLimit instruction. A transaction may load up to 64 MB of account data by default, which is billed at 16k CU toward the fee and priority calculation. Declaring a smaller ceiling lowers that share. The saving does not show up in a transaction's reported `unitsConsumed`, which only covers execution, so it is hard to measure directly from a receipt. Do not lower the limit too far: 16 MB is still 4x smaller than the default and leaves room for Token-2022 mints with extensions, while 512 KB is rejected with MaxLoadedAccountsDataSizeExceeded on exactly those coins. Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit """ import asyncio import base64 import hashlib import json import os import struct import sys import base58 import pump_v2 import tx_status import websockets from dotenv import load_dotenv 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 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, ) # Here and later all the discriminators are precalculated. See learning-examples/calculate_discriminator.py EXPECTED_DISCRIMINATOR = pump_v2.BONDING_CURVE_DISCRIMINATOR TOKEN_DECIMALS = 6 COMPUTE_BUDGET_PROGRAM = Pubkey.from_string( "ComputeBudget111111111111111111111111111111" ) # 16 MB. Enough for Token-2022 mints carrying extensions, and still 4x below the # 64 MB default; 4-8 MB is rejected with MaxLoadedAccountsDataSizeExceeded. LOADED_ACCOUNTS_DATA_SIZE_LIMIT = 16_384_000 # Global constants PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") PUMP_EVENT_AUTHORITY = Pubkey.from_string( "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" ) PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ") SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111") SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") TOKEN_2022_PROGRAM = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" ) SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") LAMPORTS_PER_SOL = 1_000_000_000 # RPC ENDPOINTS load_dotenv() RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT") RPC_WEBSOCKET = os.environ.get("SOLANA_NODE_WSS_ENDPOINT") # logsSubscribe frames exceed the websockets library's 1 MiB default, which # closes the connection with 1009 ("message too big"). WEBSOCKET_MAX_MESSAGE_BYTES = 32 * 1024 * 1024 # The bonding curve account and the v2 instruction layout live in pump_v2 so # every example shares one copy. See learning-examples/pump_v2.py. BondingCurveState = pump_v2.BondingCurveState async def get_pump_curve_state( conn: AsyncClient, curve_address: Pubkey ) -> pump_v2.BondingCurveState: """Fetch and parse a bonding curve account. Args: conn: Solana RPC client curve_address: Bonding curve address Returns: Parsed curve state Raises: ValueError: If the account is missing or not a bonding curve """ 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") return pump_v2.BondingCurveState(response.value.data) def calculate_pump_curve_price(curve_state: pump_v2.BondingCurveState) -> float: """Price of one whole token in whole quote units. Args: curve_state: Parsed curve state Returns: Price in the curve's quote asset Raises: ValueError: If reserves are empty """ price = curve_state.price_per_token() if price <= 0: raise ValueError("Invalid reserve state") return price def set_loaded_accounts_data_size_limit(bytes_limit: int) -> Instruction: """Build a SetLoadedAccountsDataSizeLimit compute-budget instruction. solders does not ship a helper for this one, so encode it by hand: the compute-budget program takes a 1-byte discriminator (4) and a u32 limit. Args: bytes_limit: Max account data the transaction may load, in bytes Returns: The compute-budget instruction """ data = struct.pack("= len(static_keys) for idx in ix.accounts ): continue account_keys = [ str(static_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(*, cu_optimized: bool = False): if cu_optimized: print("Compute-unit optimization enabled (SetLoadedAccountsDataSizeLimit)") 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)) sleep_duration_sec = 15 print(f"Waiting for {sleep_duration_sec} seconds for things to stabilize...") await asyncio.sleep(sleep_duration_sec) 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 = pump_v2.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..." ) await buy_token( mint, bonding_curve, associated_bonding_curve, creator_vault, token_program, amount, slippage, cu_optimized=cu_optimized, ) if __name__ == "__main__": asyncio.run(main(cu_optimized="--cu-optimized" in sys.argv))