feat(pump): integrate idl parser manager

This commit is contained in:
smypmsa
2025-08-02 15:10:24 +00:00
parent 9a09862f0d
commit 93332794a3
5 changed files with 284 additions and 251 deletions
+2 -2
View File
@@ -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']()
+120 -152
View File
@@ -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
+139 -88
View File
@@ -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:
+22 -8
View File
@@ -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
)
+1 -1
View File
@@ -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: