mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 15:27:44 +00:00
feat(pump): integrate idl parser manager
This commit is contained in:
@@ -107,12 +107,12 @@ class PlatformRegistry:
|
||||
address_provider = impl_classes['address_provider']()
|
||||
|
||||
# For platforms with IDL support, pass the parser to relevant classes
|
||||
if idl_parser and platform in [Platform.LETS_BONK]: # Add other IDL-supported platforms here
|
||||
if idl_parser and platform in [Platform.LETS_BONK, Platform.PUMP_FUN]:
|
||||
instruction_builder = impl_classes['instruction_builder'](idl_parser=idl_parser)
|
||||
curve_manager = impl_classes['curve_manager'](client, idl_parser=idl_parser)
|
||||
event_parser = impl_classes['event_parser'](idl_parser=idl_parser)
|
||||
else:
|
||||
# Fallback for platforms without IDL support (like pump.fun)
|
||||
# Fallback for platforms without IDL support
|
||||
instruction_builder = impl_classes['instruction_builder']()
|
||||
curve_manager = impl_classes['curve_manager'](client)
|
||||
event_parser = impl_classes['event_parser']()
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
Pump.Fun implementation of CurveManager interface.
|
||||
|
||||
This module handles pump.fun-specific bonding curve operations
|
||||
by implementing the CurveManager interface with all curve logic self-contained.
|
||||
by implementing the CurveManager interface using IDL-based decoding.
|
||||
"""
|
||||
|
||||
import struct
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from solders.pubkey import Pubkey
|
||||
@@ -14,56 +12,26 @@ from solders.pubkey import Pubkey
|
||||
from core.client import SolanaClient
|
||||
from core.pubkeys import LAMPORTS_PER_SOL, TOKEN_DECIMALS
|
||||
from interfaces.core import CurveManager, Platform
|
||||
from utils.idl_parser import IDLParser
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Bonding curve discriminator for pump.fun
|
||||
CURVE_DISCRIMINATOR = bytes([23, 203, 71, 8, 209, 70, 227, 3])
|
||||
|
||||
|
||||
@dataclass
|
||||
class BondingCurveState:
|
||||
"""Represents the state of a pump.fun bonding curve."""
|
||||
|
||||
virtual_token_reserves: int
|
||||
virtual_sol_reserves: int
|
||||
real_token_reserves: int
|
||||
real_sol_reserves: int
|
||||
token_total_supply: int
|
||||
complete: bool
|
||||
creator: Pubkey
|
||||
|
||||
@property
|
||||
def token_reserves(self) -> float:
|
||||
"""Token reserves in decimal form."""
|
||||
return self.virtual_token_reserves / 10**TOKEN_DECIMALS
|
||||
|
||||
@property
|
||||
def sol_reserves(self) -> float:
|
||||
"""SOL reserves in decimal form."""
|
||||
return self.virtual_sol_reserves / LAMPORTS_PER_SOL
|
||||
|
||||
def calculate_price(self) -> float:
|
||||
"""Calculate current token price in SOL."""
|
||||
if self.virtual_token_reserves <= 0:
|
||||
return 0.0
|
||||
|
||||
# Price = sol_reserves / token_reserves
|
||||
price_lamports = self.virtual_sol_reserves / self.virtual_token_reserves
|
||||
return price_lamports * (10**TOKEN_DECIMALS) / LAMPORTS_PER_SOL
|
||||
|
||||
|
||||
class PumpFunCurveManager(CurveManager):
|
||||
"""Pump.Fun implementation of CurveManager interface."""
|
||||
"""Pump.Fun implementation of CurveManager interface using IDL-based decoding."""
|
||||
|
||||
def __init__(self, client: SolanaClient):
|
||||
"""Initialize pump.fun curve manager.
|
||||
def __init__(self, client: SolanaClient, idl_parser: IDLParser):
|
||||
"""Initialize pump.fun curve manager with injected IDL parser.
|
||||
|
||||
Args:
|
||||
client: Solana RPC client
|
||||
idl_parser: Pre-loaded IDL parser for pump.fun platform
|
||||
"""
|
||||
self.client = client
|
||||
self._idl_parser = idl_parser
|
||||
|
||||
logger.info("Pump.Fun curve manager initialized with injected IDL parser")
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
@@ -84,22 +52,10 @@ class PumpFunCurveManager(CurveManager):
|
||||
if not account.data:
|
||||
raise ValueError(f"No data in bonding curve account {pool_address}")
|
||||
|
||||
curve_state = self._decode_curve_state(account.data)
|
||||
# Decode bonding curve state using injected IDL parser
|
||||
curve_state_data = self._decode_curve_state_with_idl(account.data)
|
||||
|
||||
return {
|
||||
"virtual_token_reserves": curve_state.virtual_token_reserves,
|
||||
"virtual_sol_reserves": curve_state.virtual_sol_reserves,
|
||||
"real_token_reserves": curve_state.real_token_reserves,
|
||||
"real_sol_reserves": curve_state.real_sol_reserves,
|
||||
"token_total_supply": curve_state.token_total_supply,
|
||||
"complete": curve_state.complete,
|
||||
"creator": str(curve_state.creator),
|
||||
|
||||
# Calculated fields for convenience
|
||||
"price_per_token": curve_state.calculate_price(),
|
||||
"token_reserves_decimal": curve_state.token_reserves,
|
||||
"sol_reserves_decimal": curve_state.sol_reserves,
|
||||
}
|
||||
return curve_state_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get curve state: {e!s}")
|
||||
@@ -114,8 +70,18 @@ class PumpFunCurveManager(CurveManager):
|
||||
Returns:
|
||||
Current token price in SOL
|
||||
"""
|
||||
curve_state = await self._get_curve_state_object(pool_address)
|
||||
return curve_state.calculate_price()
|
||||
pool_state = await self.get_pool_state(pool_address)
|
||||
|
||||
# Use virtual reserves for price calculation
|
||||
virtual_token_reserves = pool_state["virtual_token_reserves"]
|
||||
virtual_sol_reserves = pool_state["virtual_sol_reserves"]
|
||||
|
||||
if virtual_token_reserves <= 0:
|
||||
return 0.0
|
||||
|
||||
# Price = sol_reserves / token_reserves
|
||||
price_lamports = virtual_sol_reserves / virtual_token_reserves
|
||||
return price_lamports * (10**TOKEN_DECIMALS) / LAMPORTS_PER_SOL
|
||||
|
||||
async def calculate_buy_amount_out(
|
||||
self,
|
||||
@@ -133,12 +99,15 @@ class PumpFunCurveManager(CurveManager):
|
||||
Returns:
|
||||
Expected amount of tokens to receive (in raw token units)
|
||||
"""
|
||||
curve_state = await self._get_curve_state_object(pool_address)
|
||||
pool_state = await self.get_pool_state(pool_address)
|
||||
|
||||
virtual_token_reserves = pool_state["virtual_token_reserves"]
|
||||
virtual_sol_reserves = pool_state["virtual_sol_reserves"]
|
||||
|
||||
# Use virtual reserves for bonding curve calculation
|
||||
# Formula: tokens_out = (amount_in * virtual_token_reserves) / (virtual_sol_reserves + amount_in)
|
||||
numerator = amount_in * curve_state.virtual_token_reserves
|
||||
denominator = curve_state.virtual_sol_reserves + amount_in
|
||||
numerator = amount_in * virtual_token_reserves
|
||||
denominator = virtual_sol_reserves + amount_in
|
||||
|
||||
if denominator == 0:
|
||||
return 0
|
||||
@@ -162,12 +131,15 @@ class PumpFunCurveManager(CurveManager):
|
||||
Returns:
|
||||
Expected amount of SOL to receive (in lamports)
|
||||
"""
|
||||
curve_state = await self._get_curve_state_object(pool_address)
|
||||
pool_state = await self.get_pool_state(pool_address)
|
||||
|
||||
virtual_token_reserves = pool_state["virtual_token_reserves"]
|
||||
virtual_sol_reserves = pool_state["virtual_sol_reserves"]
|
||||
|
||||
# Use virtual reserves for bonding curve calculation
|
||||
# Formula: sol_out = (amount_in * virtual_sol_reserves) / (virtual_token_reserves + amount_in)
|
||||
numerator = amount_in * curve_state.virtual_sol_reserves
|
||||
denominator = curve_state.virtual_token_reserves + amount_in
|
||||
numerator = amount_in * virtual_sol_reserves
|
||||
denominator = virtual_token_reserves + amount_in
|
||||
|
||||
if denominator == 0:
|
||||
return 0
|
||||
@@ -184,100 +156,61 @@ class PumpFunCurveManager(CurveManager):
|
||||
Returns:
|
||||
Tuple of (token_reserves, sol_reserves) in raw units
|
||||
"""
|
||||
curve_state = await self._get_curve_state_object(pool_address)
|
||||
return (curve_state.virtual_token_reserves, curve_state.virtual_sol_reserves)
|
||||
pool_state = await self.get_pool_state(pool_address)
|
||||
return (pool_state["virtual_token_reserves"], pool_state["virtual_sol_reserves"])
|
||||
|
||||
async def _get_curve_state_object(self, curve_address: Pubkey) -> BondingCurveState:
|
||||
"""Get the current state of a bonding curve as a BondingCurveState object.
|
||||
|
||||
Args:
|
||||
curve_address: Address of the bonding curve
|
||||
|
||||
Returns:
|
||||
BondingCurveState with current curve data
|
||||
|
||||
Raises:
|
||||
ValueError: If curve data is invalid or inaccessible
|
||||
"""
|
||||
try:
|
||||
account = await self.client.get_account_info(curve_address)
|
||||
if not account.data:
|
||||
raise ValueError(f"No data in bonding curve account {curve_address}")
|
||||
|
||||
curve_state = self._decode_curve_state(account.data)
|
||||
return curve_state
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get curve state: {e!s}")
|
||||
raise ValueError(f"Invalid bonding curve state: {e!s}")
|
||||
|
||||
def _decode_curve_state(self, data: bytes) -> BondingCurveState:
|
||||
"""Decode bonding curve state from raw account data.
|
||||
def _decode_curve_state_with_idl(self, data: bytes) -> dict[str, Any]:
|
||||
"""Decode bonding curve state data using injected IDL parser.
|
||||
|
||||
Args:
|
||||
data: Raw account data
|
||||
|
||||
Returns:
|
||||
Decoded BondingCurveState
|
||||
Dictionary with decoded bonding curve state
|
||||
|
||||
Raises:
|
||||
ValueError: If data format is invalid
|
||||
ValueError: If IDL parsing fails
|
||||
"""
|
||||
if len(data) < 8:
|
||||
raise ValueError("Curve data too short")
|
||||
# Use injected IDL parser to decode BondingCurve account data
|
||||
decoded_curve_state = self._idl_parser.decode_account_data(
|
||||
data,
|
||||
"BondingCurve",
|
||||
skip_discriminator=True
|
||||
)
|
||||
|
||||
# Check discriminator
|
||||
if not data.startswith(CURVE_DISCRIMINATOR):
|
||||
raise ValueError("Invalid curve discriminator")
|
||||
if not decoded_curve_state:
|
||||
raise ValueError("Failed to decode bonding curve state with IDL parser")
|
||||
|
||||
offset = 8
|
||||
# Extract the fields we need for trading calculations
|
||||
# Based on the BondingCurve structure from the IDL
|
||||
curve_data = {
|
||||
"virtual_token_reserves": decoded_curve_state.get("virtual_token_reserves", 0),
|
||||
"virtual_sol_reserves": decoded_curve_state.get("virtual_sol_reserves", 0),
|
||||
"real_token_reserves": decoded_curve_state.get("real_token_reserves", 0),
|
||||
"real_sol_reserves": decoded_curve_state.get("real_sol_reserves", 0),
|
||||
"token_total_supply": decoded_curve_state.get("token_total_supply", 0),
|
||||
"complete": decoded_curve_state.get("complete", False),
|
||||
"creator": decoded_curve_state.get("creator", ""),
|
||||
}
|
||||
|
||||
try:
|
||||
# Decode based on pump.fun BondingCurve structure:
|
||||
# - virtual_token_reserves: u64 (8 bytes)
|
||||
# - virtual_sol_reserves: u64 (8 bytes)
|
||||
# - real_token_reserves: u64 (8 bytes)
|
||||
# - real_sol_reserves: u64 (8 bytes)
|
||||
# - token_total_supply: u64 (8 bytes)
|
||||
# - complete: bool (1 byte)
|
||||
# - padding: 7 bytes
|
||||
# - creator: Pubkey (32 bytes)
|
||||
|
||||
virtual_token_reserves = struct.unpack_from("<Q", data, offset)[0]
|
||||
offset += 8
|
||||
|
||||
virtual_sol_reserves = struct.unpack_from("<Q", data, offset)[0]
|
||||
offset += 8
|
||||
|
||||
real_token_reserves = struct.unpack_from("<Q", data, offset)[0]
|
||||
offset += 8
|
||||
|
||||
real_sol_reserves = struct.unpack_from("<Q", data, offset)[0]
|
||||
offset += 8
|
||||
|
||||
token_total_supply = struct.unpack_from("<Q", data, offset)[0]
|
||||
offset += 8
|
||||
|
||||
complete = bool(struct.unpack_from("<B", data, offset)[0])
|
||||
offset += 1
|
||||
|
||||
# Skip padding
|
||||
offset += 7
|
||||
|
||||
creator = Pubkey.from_bytes(data[offset:offset + 32])
|
||||
|
||||
return BondingCurveState(
|
||||
virtual_token_reserves=virtual_token_reserves,
|
||||
virtual_sol_reserves=virtual_sol_reserves,
|
||||
real_token_reserves=real_token_reserves,
|
||||
real_sol_reserves=real_sol_reserves,
|
||||
token_total_supply=token_total_supply,
|
||||
complete=complete,
|
||||
creator=creator,
|
||||
# Calculate additional metrics
|
||||
if curve_data["virtual_token_reserves"] > 0:
|
||||
curve_data["price_per_token"] = (
|
||||
(curve_data["virtual_sol_reserves"] / curve_data["virtual_token_reserves"])
|
||||
* (10**TOKEN_DECIMALS) / LAMPORTS_PER_SOL
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to decode curve state: {e}")
|
||||
else:
|
||||
curve_data["price_per_token"] = 0
|
||||
|
||||
# Add convenience decimal fields
|
||||
curve_data["token_reserves_decimal"] = curve_data["virtual_token_reserves"] / 10**TOKEN_DECIMALS
|
||||
curve_data["sol_reserves_decimal"] = curve_data["virtual_sol_reserves"] / LAMPORTS_PER_SOL
|
||||
|
||||
logger.debug(f"Decoded curve state: virtual_token_reserves={curve_data['virtual_token_reserves']}, "
|
||||
f"virtual_sol_reserves={curve_data['virtual_sol_reserves']}, "
|
||||
f"price={curve_data['price_per_token']:.8f} SOL")
|
||||
|
||||
return curve_data
|
||||
|
||||
# Additional convenience methods for pump.fun specific operations
|
||||
async def calculate_expected_tokens(self, pool_address: Pubkey, sol_amount: float) -> float:
|
||||
@@ -321,8 +254,8 @@ class PumpFunCurveManager(CurveManager):
|
||||
Returns:
|
||||
True if curve is complete, False otherwise
|
||||
"""
|
||||
curve_state = await self._get_curve_state_object(pool_address)
|
||||
return curve_state.complete
|
||||
pool_state = await self.get_pool_state(pool_address)
|
||||
return pool_state.get("complete", False)
|
||||
|
||||
async def get_curve_progress(self, pool_address: Pubkey) -> dict[str, Any]:
|
||||
"""Get bonding curve completion progress information.
|
||||
@@ -333,11 +266,11 @@ class PumpFunCurveManager(CurveManager):
|
||||
Returns:
|
||||
Dictionary with progress information
|
||||
"""
|
||||
curve_state = await self._get_curve_state_object(pool_address)
|
||||
pool_state = await self.get_pool_state(pool_address)
|
||||
|
||||
# Calculate progress based on SOL raised vs target
|
||||
# This is approximate since the exact target isn't stored in the curve state
|
||||
sol_raised = curve_state.real_sol_reserves / LAMPORTS_PER_SOL
|
||||
sol_raised = pool_state["real_sol_reserves"] / LAMPORTS_PER_SOL
|
||||
|
||||
# Estimate progress based on typical pump.fun graduation requirements
|
||||
# (This could be made more accurate with additional on-chain data)
|
||||
@@ -345,10 +278,45 @@ class PumpFunCurveManager(CurveManager):
|
||||
progress_percentage = min((sol_raised / estimated_target_sol) * 100, 100.0)
|
||||
|
||||
return {
|
||||
"complete": curve_state.complete,
|
||||
"complete": pool_state.get("complete", False),
|
||||
"sol_raised": sol_raised,
|
||||
"estimated_target_sol": estimated_target_sol,
|
||||
"progress_percentage": progress_percentage,
|
||||
"tokens_available": curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS,
|
||||
"tokens_available": pool_state["virtual_token_reserves"] / 10**TOKEN_DECIMALS,
|
||||
"market_cap_sol": sol_raised, # Approximate market cap
|
||||
}
|
||||
}
|
||||
|
||||
def validate_curve_state_structure(self, pool_address: Pubkey) -> bool:
|
||||
"""Validate that the curve state structure matches IDL expectations.
|
||||
|
||||
Args:
|
||||
pool_address: Address of the bonding curve
|
||||
|
||||
Returns:
|
||||
True if structure is valid, False otherwise
|
||||
"""
|
||||
try:
|
||||
# This would be used during development/testing to ensure
|
||||
# the IDL parsing is working correctly
|
||||
pool_state = self.get_pool_state(pool_address)
|
||||
|
||||
required_fields = [
|
||||
"virtual_token_reserves", "virtual_sol_reserves",
|
||||
"real_token_reserves", "real_sol_reserves",
|
||||
"token_total_supply", "complete"
|
||||
]
|
||||
|
||||
for field in required_fields:
|
||||
if field not in pool_state:
|
||||
logger.error(f"Missing required field: {field}")
|
||||
return False
|
||||
|
||||
if field != "complete" and not isinstance(pool_state[field], int):
|
||||
logger.error(f"Field {field} is not an integer: {type(pool_state[field])}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Curve state validation failed: {e}")
|
||||
return False
|
||||
@@ -2,32 +2,43 @@
|
||||
Pump.Fun implementation of EventParser interface.
|
||||
|
||||
This module parses pump.fun-specific token creation events from various sources
|
||||
by implementing the EventParser interface.
|
||||
by implementing the EventParser interface with IDL-based parsing.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import struct
|
||||
from time import monotonic
|
||||
from typing import Any, Final
|
||||
from typing import Any
|
||||
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.transaction import VersionedTransaction
|
||||
|
||||
from core.pubkeys import SystemAddresses
|
||||
from interfaces.core import EventParser, Platform, TokenInfo
|
||||
from platforms.pumpfun.address_provider import PumpFunAddresses
|
||||
from utils.idl_parser import IDLParser
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PumpFunEventParser(EventParser):
|
||||
"""Pump.Fun implementation of EventParser interface."""
|
||||
"""Pump.Fun implementation of EventParser interface with IDL-based parsing."""
|
||||
|
||||
# Discriminators for pump.fun instructions
|
||||
CREATE_DISCRIMINATOR: Final[int] = 8576854823835016728
|
||||
CREATE_DISCRIMINATOR_BYTES: Final[bytes] = struct.pack("<Q", CREATE_DISCRIMINATOR)
|
||||
|
||||
# Discriminator for program logs
|
||||
LOGS_CREATE_DISCRIMINATOR: Final[int] = 8530921459188068891
|
||||
def __init__(self, idl_parser: IDLParser):
|
||||
"""Initialize pump.fun event parser with injected IDL parser.
|
||||
|
||||
Args:
|
||||
idl_parser: Pre-loaded IDL parser for pump.fun platform
|
||||
"""
|
||||
self._idl_parser = idl_parser
|
||||
|
||||
# Get discriminators from injected IDL parser
|
||||
discriminators = self._idl_parser.get_instruction_discriminators()
|
||||
self._create_discriminator_bytes = discriminators["create"]
|
||||
self._create_discriminator = struct.unpack("<Q", self._create_discriminator_bytes)[0]
|
||||
|
||||
logger.info("Pump.Fun event parser initialized with injected IDL parser")
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
@@ -74,7 +85,7 @@ class PumpFunEventParser(EventParser):
|
||||
accounts: list[int],
|
||||
account_keys: list[bytes]
|
||||
) -> TokenInfo | None:
|
||||
"""Parse token creation from pump.fun instruction data.
|
||||
"""Parse token creation from pump.fun instruction data using injected IDL parser.
|
||||
|
||||
Args:
|
||||
instruction_data: Raw instruction data
|
||||
@@ -84,7 +95,7 @@ class PumpFunEventParser(EventParser):
|
||||
Returns:
|
||||
TokenInfo if token creation found, None otherwise
|
||||
"""
|
||||
if not instruction_data.startswith(self.CREATE_DISCRIMINATOR_BYTES):
|
||||
if not instruction_data.startswith(self._create_discriminator_bytes):
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -97,12 +108,14 @@ class PumpFunEventParser(EventParser):
|
||||
return None
|
||||
return Pubkey.from_bytes(account_keys[account_index])
|
||||
|
||||
# Parse instruction data
|
||||
token_data = self._parse_create_instruction_data(instruction_data)
|
||||
if not token_data:
|
||||
# Parse instruction data using injected IDL parser
|
||||
decoded = self._idl_parser.decode_instruction(instruction_data, account_keys, accounts)
|
||||
if not decoded or decoded['instruction_name'] != 'create':
|
||||
return None
|
||||
|
||||
# Extract account information
|
||||
|
||||
args = decoded.get('args', {})
|
||||
|
||||
# Extract account information based on IDL account order
|
||||
mint = get_account_key(0)
|
||||
bonding_curve = get_account_key(2)
|
||||
associated_bonding_curve = get_account_key(3)
|
||||
@@ -112,13 +125,13 @@ class PumpFunEventParser(EventParser):
|
||||
return None
|
||||
|
||||
# Create creator vault
|
||||
creator = Pubkey.from_string(token_data["creator"]) if token_data.get("creator") else user
|
||||
creator = Pubkey.from_string(args.get("creator", str(user))) if args.get("creator") else user
|
||||
creator_vault = self._derive_creator_vault(creator)
|
||||
|
||||
return TokenInfo(
|
||||
name=token_data["name"],
|
||||
symbol=token_data["symbol"],
|
||||
uri=token_data["uri"],
|
||||
name=args.get("name", ""),
|
||||
symbol=args.get("symbol", ""),
|
||||
uri=args.get("uri", ""),
|
||||
mint=mint,
|
||||
platform=Platform.PUMP_FUN,
|
||||
bonding_curve=bonding_curve,
|
||||
@@ -129,7 +142,8 @@ class PumpFunEventParser(EventParser):
|
||||
creation_timestamp=monotonic(),
|
||||
)
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse create instruction: {e}")
|
||||
return None
|
||||
|
||||
def parse_token_creation_from_geyser(
|
||||
@@ -172,7 +186,8 @@ class PumpFunEventParser(EventParser):
|
||||
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse geyser transaction: {e}")
|
||||
return None
|
||||
|
||||
def get_program_id(self) -> Pubkey:
|
||||
@@ -189,7 +204,7 @@ class PumpFunEventParser(EventParser):
|
||||
Returns:
|
||||
List of discriminator bytes to match
|
||||
"""
|
||||
return [self.CREATE_DISCRIMINATOR_BYTES]
|
||||
return [self._create_discriminator_bytes]
|
||||
|
||||
def parse_token_creation_from_block(self, block_data: dict) -> TokenInfo | None:
|
||||
"""Parse token creation from block data (for block listener).
|
||||
@@ -208,43 +223,96 @@ class PumpFunEventParser(EventParser):
|
||||
if not isinstance(tx, dict) or "transaction" not in tx:
|
||||
continue
|
||||
|
||||
# Decode base64 transaction data
|
||||
tx_data_encoded = tx["transaction"][0]
|
||||
tx_data_decoded = base64.b64decode(tx_data_encoded)
|
||||
transaction = VersionedTransaction.from_bytes(tx_data_decoded)
|
||||
|
||||
for ix in transaction.message.instructions:
|
||||
program_id = transaction.message.account_keys[ix.program_id_index]
|
||||
|
||||
# Check if instruction is from pump.fun program
|
||||
if str(program_id) != str(self.get_program_id()):
|
||||
continue
|
||||
|
||||
ix_data = bytes(ix.data)
|
||||
|
||||
# Check for create discriminator
|
||||
if len(ix_data) >= 8:
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
# Decode base64 transaction data if needed
|
||||
tx_data = tx["transaction"]
|
||||
if isinstance(tx_data, list) and len(tx_data) > 0:
|
||||
try:
|
||||
tx_data_encoded = tx_data[0]
|
||||
tx_data_decoded = base64.b64decode(tx_data_encoded)
|
||||
transaction = VersionedTransaction.from_bytes(tx_data_decoded)
|
||||
|
||||
if discriminator == self.CREATE_DISCRIMINATOR:
|
||||
# Token creation should have substantial data and many accounts
|
||||
if len(ix_data) <= 8 or len(ix.accounts) < 10:
|
||||
for ix in transaction.message.instructions:
|
||||
program_id = transaction.message.account_keys[ix.program_id_index]
|
||||
|
||||
# Check if instruction is from pump.fun program
|
||||
if str(program_id) != str(self.get_program_id()):
|
||||
continue
|
||||
|
||||
ix_data = bytes(ix.data)
|
||||
|
||||
# Check for create discriminator
|
||||
if len(ix_data) >= 8:
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
|
||||
if discriminator == self._create_discriminator:
|
||||
# Token creation should have substantial data and many accounts
|
||||
if len(ix_data) <= 8 or len(ix.accounts) < 10:
|
||||
continue
|
||||
|
||||
# Parse the instruction
|
||||
token_info = self.parse_token_creation_from_instruction(
|
||||
ix_data, ix.accounts, transaction.message.account_keys
|
||||
)
|
||||
if token_info:
|
||||
return token_info
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse block transaction: {e}")
|
||||
continue
|
||||
|
||||
# Handle already decoded transaction data
|
||||
elif isinstance(tx_data, dict) and "message" in tx_data:
|
||||
try:
|
||||
message = tx_data["message"]
|
||||
if "instructions" not in message or "accountKeys" not in message:
|
||||
continue
|
||||
|
||||
for ix in message["instructions"]:
|
||||
if "programIdIndex" not in ix or "accounts" not in ix or "data" not in ix:
|
||||
continue
|
||||
|
||||
program_idx = ix["programIdIndex"]
|
||||
if program_idx >= len(message["accountKeys"]):
|
||||
continue
|
||||
|
||||
program_id_str = message["accountKeys"][program_idx]
|
||||
if program_id_str != str(self.get_program_id()):
|
||||
continue
|
||||
|
||||
# Parse the instruction
|
||||
token_info = self.parse_token_creation_from_instruction(
|
||||
ix_data, ix.accounts, transaction.message.account_keys
|
||||
)
|
||||
if token_info:
|
||||
return token_info
|
||||
# Decode instruction data
|
||||
ix_data = base64.b64decode(ix["data"])
|
||||
|
||||
if len(ix_data) >= 8:
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
|
||||
if discriminator == self._create_discriminator:
|
||||
if len(ix_data) <= 8 or len(ix["accounts"]) < 10:
|
||||
continue
|
||||
|
||||
# Convert account keys to bytes for parsing
|
||||
account_keys_bytes = [
|
||||
Pubkey.from_string(key).to_bytes()
|
||||
for key in message["accountKeys"]
|
||||
]
|
||||
|
||||
token_info = self.parse_token_creation_from_instruction(
|
||||
ix_data, ix["accounts"], account_keys_bytes
|
||||
)
|
||||
if token_info:
|
||||
return token_info
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse decoded block transaction: {e}")
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse block data: {e}")
|
||||
return None
|
||||
|
||||
def _parse_create_instruction_data(self, data: bytes) -> dict | None:
|
||||
"""Parse the create instruction data from pump.fun.
|
||||
"""Parse the create instruction data from pump.fun using injected IDL parser.
|
||||
|
||||
Args:
|
||||
data: Raw instruction data
|
||||
@@ -257,47 +325,30 @@ class PumpFunEventParser(EventParser):
|
||||
|
||||
# Check for the correct instruction discriminator
|
||||
discriminator = struct.unpack("<Q", data[:8])[0]
|
||||
if discriminator not in [self.CREATE_DISCRIMINATOR, self.LOGS_CREATE_DISCRIMINATOR]:
|
||||
if discriminator != self._create_discriminator:
|
||||
return None
|
||||
|
||||
offset = 8
|
||||
parsed_data = {}
|
||||
|
||||
try:
|
||||
# Parse fields based on CreateEvent structure
|
||||
fields = [
|
||||
("name", "string"),
|
||||
("symbol", "string"),
|
||||
("uri", "string"),
|
||||
]
|
||||
# Use IDL parser to decode the instruction data
|
||||
# For log data parsing, we need to create dummy account info
|
||||
dummy_accounts = list(range(20)) # Assume enough accounts
|
||||
dummy_account_keys = [b'\x00' * 32] * 20 # Dummy keys
|
||||
|
||||
# For instruction data, we also have creator info
|
||||
if discriminator == self.CREATE_DISCRIMINATOR:
|
||||
fields.extend([
|
||||
("creator", "publicKey"),
|
||||
])
|
||||
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "string":
|
||||
if offset + 4 > len(data):
|
||||
return None
|
||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
||||
offset += 4
|
||||
if offset + length > len(data):
|
||||
return None
|
||||
value = data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
elif field_type == "publicKey":
|
||||
if offset + 32 > len(data):
|
||||
return None
|
||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
||||
offset += 32
|
||||
|
||||
parsed_data[field_name] = value
|
||||
|
||||
return parsed_data
|
||||
decoded = self._idl_parser.decode_instruction(data, dummy_account_keys, dummy_accounts)
|
||||
if not decoded or decoded['instruction_name'] != 'create':
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
# Extract the arguments
|
||||
args = decoded.get('args', {})
|
||||
return {
|
||||
"name": args.get("name", ""),
|
||||
"symbol": args.get("symbol", ""),
|
||||
"uri": args.get("uri", ""),
|
||||
"creator": args.get("creator", ""),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse instruction data with IDL: {e}")
|
||||
return None
|
||||
|
||||
def _derive_creator_vault(self, creator: Pubkey) -> Pubkey:
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
Pump.Fun implementation of InstructionBuilder interface.
|
||||
|
||||
This module builds pump.fun-specific buy and sell instructions
|
||||
by implementing the InstructionBuilder interface.
|
||||
by implementing the InstructionBuilder interface with IDL-based discriminators.
|
||||
"""
|
||||
|
||||
import struct
|
||||
from typing import Final
|
||||
|
||||
from solders.instruction import AccountMeta, Instruction
|
||||
from solders.pubkey import Pubkey
|
||||
@@ -14,14 +13,29 @@ from spl.token.instructions import create_idempotent_associated_token_account
|
||||
|
||||
from core.pubkeys import TOKEN_DECIMALS, SystemAddresses
|
||||
from interfaces.core import AddressProvider, InstructionBuilder, Platform, TokenInfo
|
||||
from utils.idl_parser import IDLParser
|
||||
from utils.logger import get_logger
|
||||
|
||||
# Discriminators for pump.fun instructions
|
||||
BUY_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 16927863322537952870)
|
||||
SELL_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 12502976635542562355)
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
"""Pump.Fun implementation of InstructionBuilder interface."""
|
||||
"""Pump.Fun implementation of InstructionBuilder interface with IDL-based discriminators."""
|
||||
|
||||
def __init__(self, idl_parser: IDLParser):
|
||||
"""Initialize pump.fun instruction builder with injected IDL parser.
|
||||
|
||||
Args:
|
||||
idl_parser: Pre-loaded IDL parser for pump.fun platform
|
||||
"""
|
||||
self._idl_parser = idl_parser
|
||||
|
||||
# Get discriminators from injected IDL parser
|
||||
discriminators = self._idl_parser.get_instruction_discriminators()
|
||||
self._buy_discriminator = discriminators["buy"]
|
||||
self._sell_discriminator = discriminators["sell"]
|
||||
|
||||
logger.info("Pump.Fun instruction builder initialized with injected IDL parser")
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
@@ -82,7 +96,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
|
||||
# Build instruction data: discriminator + token_amount + max_sol_cost
|
||||
instruction_data = (
|
||||
BUY_DISCRIMINATOR +
|
||||
self._buy_discriminator +
|
||||
struct.pack("<Q", minimum_amount_out) + # token amount in raw units
|
||||
struct.pack("<Q", amount_in) # max SOL cost in lamports
|
||||
)
|
||||
@@ -139,7 +153,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
|
||||
# Build instruction data: discriminator + token_amount + min_sol_output
|
||||
instruction_data = (
|
||||
SELL_DISCRIMINATOR +
|
||||
self._sell_discriminator +
|
||||
struct.pack("<Q", amount_in) + # token amount in raw units
|
||||
struct.pack("<Q", minimum_amount_out) # min SOL output in lamports
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ class IDLManager:
|
||||
# Define IDL paths for each platform
|
||||
self._idl_paths = {
|
||||
Platform.LETS_BONK: os.path.join(project_root, "idl", "raydium_launchlab_idl.json"),
|
||||
# Platform.PUMP_FUN: os.path.join(project_root, "idl", "pump_fun_idl.json"), # Future
|
||||
Platform.PUMP_FUN: os.path.join(project_root, "idl", "pump_fun_idl.json"),
|
||||
}
|
||||
|
||||
def get_parser(self, platform: Platform, verbose: bool = False) -> IDLParser:
|
||||
|
||||
Reference in New Issue
Block a user