import asyncio import json import os import struct import sys import base58 import grpc from construct import Bytes, 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 from spl.token.instructions import ( create_idempotent_associated_token_account, get_associated_token_address, ) sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from src.geyser.generated import ( geyser_pb2, geyser_pb2_grpc, ) # Here and later all the discriminators are precalculated. See learning-examples/calculate_discriminator.py EXPECTED_DISCRIMINATOR = struct.pack(" None: """Parse bonding curve data.""" if data[:8] != EXPECTED_DISCRIMINATOR: raise ValueError("Invalid curve state discriminator") parsed = self._STRUCT.parse(data[8:]) self.__dict__.update(parsed) # Convert raw bytes to Pubkey for creator field if hasattr(self, "creator") and isinstance(self.creator, bytes): self.creator = Pubkey.from_bytes(self.creator) 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 async def create_geyser_connection(): """Establish a secure connection to the Geyser endpoint using the configured auth type.""" if AUTH_TYPE == "x-token": auth = grpc.metadata_call_credentials( lambda _, callback: callback((("x-token", GEYSER_API_TOKEN),), None) ) else: # Default to basic auth auth = grpc.metadata_call_credentials( lambda _, callback: callback( (("authorization", f"Basic {GEYSER_API_TOKEN}"),), None ) ) creds = grpc.composite_channel_credentials(grpc.ssl_channel_credentials(), auth) channel = grpc.aio.secure_channel(GEYSER_ENDPOINT, creds) return geyser_pb2_grpc.GeyserStub(channel) def create_subscription_request(): """Create a subscription request for Pump.fun transactions.""" request = geyser_pb2.SubscribeRequest() request.transactions["pump_filter"].account_include.append(str(PUMP_PROGRAM)) request.transactions["pump_filter"].failed = False request.commitment = geyser_pb2.CommitmentLevel.PROCESSED return request def decode_create_instruction_geyser(ix_data: bytes, keys, accounts) -> dict: """Decode a create instruction from Geyser transaction data.""" # Skip past the 8-byte discriminator prefix offset = 8 # Extract account keys in base58 format def get_account_key(index): if index >= len(accounts): return "N/A" account_index = accounts[index] return base58.b58encode(keys[account_index]).decode() # Read string fields (prefixed with length) def read_string(): nonlocal offset # Get string length (4-byte uint) length = struct.unpack_from("