diff --git a/learning-examples/get_graduating_tokens.py b/learning-examples/get_graduating_tokens.py new file mode 100644 index 0000000..1695c13 --- /dev/null +++ b/learning-examples/get_graduating_tokens.py @@ -0,0 +1,84 @@ +import asyncio +import os +import struct + +from dotenv import load_dotenv +from solana.rpc.async_api import AsyncClient +from solana.rpc.types import MemcmpOpts +from solders.pubkey import Pubkey + +load_dotenv() + +RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT1") +PUMP_PROGRAM_ID = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") +# Known 8-byte discriminator for bonding curve accounts +# This ensures we're only fetching bonding curve accounts +BONDING_CURVE_DISCRIMINATOR_BYTES = bytes.fromhex("17b7f83760d8ac60") + + +async def get_bonding_curves_by_reserves(): + """ + Fetch bonding curve accounts from the Pump.fun program that have real_token_reserves + below a defined threshold and are not marked as complete (i.e., not migrated). + """ + + # Define the reserve threshold we're interested in + # 100 trillion (in token base units) + threshold = 100_000_000_000_000 + + # Convert the threshold into 8-byte little-endian format (as stored on-chain) + threshold_bytes = threshold.to_bytes(8, 'little') + + # Extract the 2 most significant bytes (bytes 6 and 7 in little-endian) + # This helps us pre-filter for values less than 2^48 (~281T) + msb_prefix = threshold_bytes[6:] + + async with AsyncClient(RPC_ENDPOINT, commitment="processed", timeout=120) as client: + # Define on-chain filters for getProgramAccounts + filters = [ + MemcmpOpts(offset=0, bytes=BONDING_CURVE_DISCRIMINATOR_BYTES), # only bonding curve accounts + MemcmpOpts(offset=30, bytes=msb_prefix), # real_token_reserves < ~281T + MemcmpOpts(offset=48, bytes=b'\x00'), # complete == False (not migrated) + ] + + # Query accounts matching filters + response = await client.get_program_accounts( + PUMP_PROGRAM_ID, + encoding="base64", + filters=filters + ) + + result = [] + for acc in response.value: + raw = acc.account.data + + # Parse the account layout according to known struct (49 bytes): + # [discriminator][virtual_token_reserves][virtual_sol_reserves] + # [real_token_reserves][real_sol_reserves][token_total_supply][complete] + # Example: https://solscan.io/account/ASwuqjAGjWVhojrdot9yrnXTV4hMdTEryAWkGn4UUJma#anchorData + + offset = 8 # skip 8-byte discriminator + offset += 8 # skip virtual_token_reserves + offset += 8 # skip virtual_sol_reserves + + # Extract real_token_reserves (u64 = 8 bytes, little-endian) + real_token_reserves = struct.unpack("