Files
pumpfun-bonkfun-bot/learning-examples/bonding-curve-progress/get_bonding_curve_status.py
T

229 lines
7.8 KiB
Python
Raw Normal View History

"""
Module for checking the status of a token's bonding curve on the Solana network using
the Pump.fun program. It allows querying the bonding curve state and completion status.
"""
2025-08-05 15:18:22 +00:00
import argparse
import asyncio
2025-03-05 07:03:32 +00:00
import os
import struct
from typing import Final
2025-03-05 07:03:32 +00:00
from construct import Bytes, Flag, Int64ul, Struct
from dotenv import load_dotenv
from solana.rpc.async_api import AsyncClient
from solders.pubkey import Pubkey
load_dotenv()
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
# Change to token you want to query
2025-08-05 15:18:22 +00:00
TOKEN_MINT = "..."
# Constants
2025-08-11 05:35:25 +00:00
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string(
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
)
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399)
2025-03-05 07:03:32 +00:00
class BondingCurveState:
"""
Represents the state of a bonding curve account.
2025-08-11 05:35:25 +00:00
Attributes:
virtual_token_reserves: Virtual token reserves in the curve
virtual_sol_reserves: Virtual SOL reserves in the curve
real_token_reserves: Real token reserves in the curve
real_sol_reserves: Real SOL reserves in the curve
token_total_supply: Total token supply in the curve
complete: Whether the curve has completed and liquidity migrated
is_mayhem_mode: Whether the curve is in mayhem mode
"""
2025-08-11 05:35:25 +00:00
# V2: Struct with creator field (81 bytes total: 8 discriminator + 73 data)
_STRUCT_V2 = Struct(
"virtual_token_reserves" / Int64ul,
"virtual_sol_reserves" / Int64ul,
"real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul,
"complete" / Flag,
"creator" / Bytes(32), # Added new creator field - 32 bytes for Pubkey
)
# V3: Struct with creator + mayhem mode (82 bytes total: 8 discriminator + 74 data)
_STRUCT_V3 = Struct(
"virtual_token_reserves" / Int64ul,
"virtual_sol_reserves" / Int64ul,
"real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul,
"complete" / Flag,
"creator" / Bytes(32),
"is_mayhem_mode" / Flag, # Added mayhem mode flag - 1 byte
)
# V4: V3 + is_cashback_coin (83 bytes total: 8 discriminator + 75 data) — added in the late-Feb 2026 cashback upgrade
_STRUCT_V4 = Struct(
"virtual_token_reserves" / Int64ul,
"virtual_sol_reserves" / Int64ul,
"real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul,
"complete" / Flag,
"creator" / Bytes(32),
"is_mayhem_mode" / Flag,
"is_cashback_coin" / Flag,
)
def __init__(self, data: bytes) -> None:
"""Parse bonding curve data."""
if data[:8] != EXPECTED_DISCRIMINATOR:
raise ValueError("Invalid curve state discriminator")
total_length = len(data)
if total_length == 81: # V2: Creator only
parsed = self._STRUCT_V2.parse(data[8:])
2025-08-11 05:35:25 +00:00
self.__dict__.update(parsed)
self.creator = Pubkey.from_bytes(self.creator)
self.is_mayhem_mode = False
self.is_cashback_coin = False
elif total_length == 82: # V3: Creator + mayhem
parsed = self._STRUCT_V3.parse(data[8:])
self.__dict__.update(parsed)
self.creator = Pubkey.from_bytes(self.creator)
self.is_cashback_coin = False
elif total_length >= 83: # V4: Creator + mayhem + cashback
parsed = self._STRUCT_V4.parse(data[8:])
self.__dict__.update(parsed)
self.creator = Pubkey.from_bytes(self.creator)
else:
raise ValueError(f"Unexpected bonding curve size: {total_length} bytes")
2025-03-05 07:03:32 +00:00
def get_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]:
"""
Derives the associated bonding curve address for a given mint.
2025-08-11 05:35:25 +00:00
Args:
mint: The token mint address
program_id: The program ID for the bonding curve
2025-08-11 05:35:25 +00:00
Returns:
Tuple of (bonding curve address, bump seed)
"""
2025-03-05 07:03:32 +00:00
return Pubkey.find_program_address([b"bonding-curve", bytes(mint)], program_id)
2025-03-05 07:03:32 +00:00
async def get_bonding_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
"""
Fetches and validates the state of a bonding curve account.
2025-08-11 05:35:25 +00:00
Args:
conn: AsyncClient connection to Solana RPC
curve_address: Address of the bonding curve account
2025-08-11 05:35:25 +00:00
Returns:
BondingCurveState object containing parsed account data
2025-08-11 05:35:25 +00:00
Raises:
ValueError: If account data is invalid or missing
"""
2025-04-01 14:57:01 +00:00
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)
2025-03-05 07:03:32 +00:00
async def check_token_status(mint_address: str) -> None:
"""
Checks and prints the status of a token and its bonding curve.
2025-08-11 05:35:25 +00:00
Args:
mint_address: The token mint address as a string
"""
try:
mint = Pubkey.from_string(mint_address)
bonding_curve_address, bump = get_bonding_curve_address(mint, PUMP_PROGRAM_ID)
2025-03-05 07:03:32 +00:00
print("\nToken status:")
print("-" * 50)
print(f"Token mint: {mint}")
print(f"Bonding curve: {bonding_curve_address}")
if bump is not None:
print(f"Bump seed: {bump}")
print("-" * 50)
2025-03-05 07:03:32 +00:00
# Check completion status
async with AsyncClient(RPC_ENDPOINT) as client:
try:
2025-03-05 07:03:32 +00:00
curve_state = await get_bonding_curve_state(
client, bonding_curve_address
)
print("\nBonding curve status:")
print("-" * 50)
print(f"Creator: {curve_state.creator}")
2025-03-05 07:03:32 +00:00
print(
f"Mayhem Mode: {'✅ Enabled' if curve_state.is_mayhem_mode else '❌ Disabled'}"
2025-03-05 07:03:32 +00:00
)
print(
f"Cashback Coin: {'✅ Enabled' if curve_state.is_cashback_coin else '❌ Disabled'}"
)
print(
f"Completed: {'✅ Migrated' if curve_state.complete else '❌ Bonding curve'}"
)
print("\nBonding curve reserves:")
print(f"Virtual Token: {curve_state.virtual_token_reserves:,}")
print(
f"Virtual SOL: {curve_state.virtual_sol_reserves:,} lamports"
)
print(f"Real Token: {curve_state.real_token_reserves:,}")
print(
f"Real SOL: {curve_state.real_sol_reserves:,} lamports"
)
print(f"Total Supply: {curve_state.token_total_supply:,}")
if curve_state.complete:
2025-03-05 07:03:32 +00:00
print(
"\nNote: This bonding curve has completed and liquidity has been migrated to PumpSwap."
2025-03-05 07:03:32 +00:00
)
print("-" * 50)
2025-03-05 07:03:32 +00:00
except ValueError as e:
print(f"\nError accessing bonding curve: {e}")
2025-03-05 07:03:32 +00:00
except ValueError as e:
print(f"\nError: Invalid address format - {e}")
except Exception as e:
print(f"\nUnexpected error: {e}")
2025-03-05 07:03:32 +00:00
def main() -> None:
"""Main entry point for the token status checker."""
2025-08-05 15:18:22 +00:00
parser = argparse.ArgumentParser(description="Check token bonding curve status")
2025-08-11 05:35:25 +00:00
parser.add_argument(
"mint_address", nargs="?", help="The token mint address", default=TOKEN_MINT
)
2025-08-05 15:18:22 +00:00
args = parser.parse_args()
2025-08-11 05:35:25 +00:00
2025-08-05 15:18:22 +00:00
asyncio.run(check_token_status(args.mint_address))
2025-03-05 07:03:32 +00:00
if __name__ == "__main__":
2025-03-05 07:03:32 +00:00
main()