mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-19 10:18:05 +00:00
feat(letsbonk): integrate idl parser
This commit is contained in:
@@ -5,7 +5,7 @@ This module handles LetsBonk (Raydium LaunchLab) specific pool operations
|
|||||||
by implementing the CurveManager interface using IDL-based decoding.
|
by implementing the CurveManager interface using IDL-based decoding.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import struct
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from solders.pubkey import Pubkey
|
from solders.pubkey import Pubkey
|
||||||
@@ -14,13 +14,11 @@ from core.client import SolanaClient
|
|||||||
from core.pubkeys import LAMPORTS_PER_SOL, TOKEN_DECIMALS
|
from core.pubkeys import LAMPORTS_PER_SOL, TOKEN_DECIMALS
|
||||||
from interfaces.core import CurveManager, Platform
|
from interfaces.core import CurveManager, Platform
|
||||||
from platforms.letsbonk.address_provider import LetsBonkAddressProvider
|
from platforms.letsbonk.address_provider import LetsBonkAddressProvider
|
||||||
|
from utils.idl_parser import IDLParser
|
||||||
from utils.logger import get_logger
|
from utils.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
# Pool state discriminator for Raydium LaunchLab
|
|
||||||
POOL_STATE_DISCRIMINATOR = bytes([247, 237, 227, 245, 215, 195, 222, 70])
|
|
||||||
|
|
||||||
|
|
||||||
class LetsBonkCurveManager(CurveManager):
|
class LetsBonkCurveManager(CurveManager):
|
||||||
"""LetsBonk (Raydium LaunchLab) implementation of CurveManager interface."""
|
"""LetsBonk (Raydium LaunchLab) implementation of CurveManager interface."""
|
||||||
@@ -33,6 +31,22 @@ class LetsBonkCurveManager(CurveManager):
|
|||||||
"""
|
"""
|
||||||
self.client = client
|
self.client = client
|
||||||
self.address_provider = LetsBonkAddressProvider()
|
self.address_provider = LetsBonkAddressProvider()
|
||||||
|
self._idl_parser = self._load_idl_parser()
|
||||||
|
|
||||||
|
logger.info("LetsBonk curve manager initialized with IDL-based account parsing")
|
||||||
|
|
||||||
|
def _load_idl_parser(self) -> IDLParser:
|
||||||
|
"""Load the IDL parser for LetsBonk (Raydium LaunchLab)."""
|
||||||
|
# Get the IDL file path relative to the project root
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
project_root = os.path.join(current_dir, "..", "..", "..")
|
||||||
|
idl_path = os.path.join(project_root, "idl", "raydium_launchlab_idl.json")
|
||||||
|
idl_path = os.path.normpath(idl_path)
|
||||||
|
|
||||||
|
if not os.path.exists(idl_path):
|
||||||
|
raise FileNotFoundError(f"IDL file not found at {idl_path}")
|
||||||
|
|
||||||
|
return IDLParser(idl_path, verbose=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platform(self) -> Platform:
|
def platform(self) -> Platform:
|
||||||
@@ -53,8 +67,8 @@ class LetsBonkCurveManager(CurveManager):
|
|||||||
if not account.data:
|
if not account.data:
|
||||||
raise ValueError(f"No data in pool state account {pool_address}")
|
raise ValueError(f"No data in pool state account {pool_address}")
|
||||||
|
|
||||||
# Decode pool state (simplified - in production you'd use IDL parser)
|
# Decode pool state using IDL parser
|
||||||
pool_state_data = self._decode_pool_state(account.data)
|
pool_state_data = self._decode_pool_state_with_idl(account.data)
|
||||||
|
|
||||||
return pool_state_data
|
return pool_state_data
|
||||||
|
|
||||||
@@ -160,95 +174,84 @@ class LetsBonkCurveManager(CurveManager):
|
|||||||
pool_state = await self.get_pool_state(pool_address)
|
pool_state = await self.get_pool_state(pool_address)
|
||||||
return (pool_state["virtual_base"], pool_state["virtual_quote"])
|
return (pool_state["virtual_base"], pool_state["virtual_quote"])
|
||||||
|
|
||||||
def _decode_pool_state(self, data: bytes) -> dict[str, Any]:
|
def _decode_pool_state_with_idl(self, data: bytes) -> dict[str, Any]:
|
||||||
"""Decode pool state data from raw bytes.
|
"""Decode pool state data using IDL parser.
|
||||||
|
|
||||||
This is a simplified decoder. In production, you should use the IDL parser.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: Raw account data
|
data: Raw account data
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with decoded pool state
|
Dictionary with decoded pool state
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If IDL parsing fails
|
||||||
"""
|
"""
|
||||||
if len(data) < 8:
|
# Use IDL parser to decode PoolState account data
|
||||||
raise ValueError("Pool state data too short")
|
decoded_pool_state = self._idl_parser.decode_account_data(
|
||||||
|
data,
|
||||||
|
"PoolState",
|
||||||
|
skip_discriminator=True
|
||||||
|
)
|
||||||
|
|
||||||
# Skip discriminator
|
if not decoded_pool_state:
|
||||||
offset = 8
|
raise ValueError("Failed to decode pool state with IDL parser")
|
||||||
|
|
||||||
# Based on the PoolState structure from the IDL:
|
# Extract the fields we need for trading calculations
|
||||||
# - authority: Pubkey (32 bytes)
|
# Based on the PoolState structure from the IDL
|
||||||
# - base_mint: Pubkey (32 bytes)
|
pool_data = {
|
||||||
# - quote_mint: Pubkey (32 bytes)
|
"virtual_base": decoded_pool_state.get("virtual_base", 0),
|
||||||
# - base_vault: Pubkey (32 bytes)
|
"virtual_quote": decoded_pool_state.get("virtual_quote", 0),
|
||||||
# - quote_vault: Pubkey (32 bytes)
|
"real_base": decoded_pool_state.get("real_base", 0),
|
||||||
# - status: u8 (1 byte)
|
"real_quote": decoded_pool_state.get("real_quote", 0),
|
||||||
# - virtual_base: u64 (8 bytes)
|
"status": decoded_pool_state.get("status", 0),
|
||||||
# - virtual_quote: u64 (8 bytes)
|
"supply": decoded_pool_state.get("supply", 0),
|
||||||
# - real_base: u64 (8 bytes)
|
}
|
||||||
# - real_quote: u64 (8 bytes)
|
|
||||||
# ... and more fields
|
|
||||||
|
|
||||||
try:
|
# Calculate additional metrics
|
||||||
# Skip to the fields we need
|
if pool_data["virtual_base"] > 0:
|
||||||
offset += 32 * 5 # Skip 5 pubkeys (authority, mints, vaults)
|
pool_data["price_per_token"] = (
|
||||||
offset += 1 # Skip status
|
(pool_data["virtual_quote"] / pool_data["virtual_base"])
|
||||||
|
* (10**TOKEN_DECIMALS) / LAMPORTS_PER_SOL
|
||||||
# Read virtual reserves
|
)
|
||||||
virtual_base = struct.unpack_from("<Q", data, offset)[0]
|
else:
|
||||||
offset += 8
|
pool_data["price_per_token"] = 0
|
||||||
|
|
||||||
virtual_quote = struct.unpack_from("<Q", data, offset)[0]
|
logger.debug(f"Decoded pool state: virtual_base={pool_data['virtual_base']}, "
|
||||||
offset += 8
|
f"virtual_quote={pool_data['virtual_quote']}, "
|
||||||
|
f"price={pool_data['price_per_token']:.8f} SOL")
|
||||||
# Read real reserves
|
|
||||||
real_base = struct.unpack_from("<Q", data, offset)[0]
|
return pool_data
|
||||||
offset += 8
|
|
||||||
|
|
||||||
real_quote = struct.unpack_from("<Q", data, offset)[0]
|
|
||||||
offset += 8
|
|
||||||
|
|
||||||
return {
|
|
||||||
"virtual_base": virtual_base,
|
|
||||||
"virtual_quote": virtual_quote,
|
|
||||||
"real_base": real_base,
|
|
||||||
"real_quote": real_quote,
|
|
||||||
"price_per_token": (virtual_quote / virtual_base) * (10**TOKEN_DECIMALS) / LAMPORTS_PER_SOL if virtual_base > 0 else 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to decode pool state: {e}")
|
|
||||||
# Return some default values for testing
|
|
||||||
return {
|
|
||||||
"virtual_base": 1_000_000_000, # 1000 tokens with 6 decimals
|
|
||||||
"virtual_quote": 1_000_000_000, # 1 SOL
|
|
||||||
"real_base": 1_000_000_000,
|
|
||||||
"real_quote": 1_000_000_000,
|
|
||||||
"price_per_token": 0.001, # 0.001 SOL per token
|
|
||||||
}
|
|
||||||
|
|
||||||
async def get_pool_info(self, pool_address: Pubkey) -> dict[str, Any]:
|
def validate_pool_state_structure(self, pool_address: Pubkey) -> bool:
|
||||||
"""Get detailed pool information including status and progress.
|
"""Validate that the pool state structure matches IDL expectations.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pool_address: Address of the pool state
|
pool_address: Address of the pool state
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with pool information
|
True if structure is valid, False otherwise
|
||||||
"""
|
"""
|
||||||
pool_state = await self.get_pool_state(pool_address)
|
try:
|
||||||
|
# This would be used during development/testing to ensure
|
||||||
# Calculate additional metrics
|
# the IDL parsing is working correctly
|
||||||
sol_raised = pool_state["real_quote"] / LAMPORTS_PER_SOL
|
pool_state = self.get_pool_state(pool_address)
|
||||||
tokens_sold = (pool_state["virtual_base"] - pool_state["real_base"]) / 10**TOKEN_DECIMALS
|
|
||||||
|
required_fields = [
|
||||||
return {
|
"virtual_base", "virtual_quote",
|
||||||
"virtual_base_reserves": pool_state["virtual_base"],
|
"real_base", "real_quote"
|
||||||
"virtual_quote_reserves": pool_state["virtual_quote"],
|
]
|
||||||
"real_base_reserves": pool_state["real_base"],
|
|
||||||
"real_quote_reserves": pool_state["real_quote"],
|
for field in required_fields:
|
||||||
"sol_raised": sol_raised,
|
if field not in pool_state:
|
||||||
"tokens_sold": tokens_sold,
|
logger.error(f"Missing required field: {field}")
|
||||||
"current_price": pool_state["price_per_token"],
|
return False
|
||||||
}
|
|
||||||
|
if 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"Pool state validation failed: {e}")
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user