mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-06 12:07:45 +00:00
feat(core): pump portal fully integrated
This commit is contained in:
@@ -9,11 +9,13 @@ from .address_provider import LetsBonkAddressProvider
|
||||
from .curve_manager import LetsBonkCurveManager
|
||||
from .event_parser import LetsBonkEventParser
|
||||
from .instruction_builder import LetsBonkInstructionBuilder
|
||||
from .pumpportal_processor import LetsBonkPumpPortalProcessor
|
||||
|
||||
# Export implementations for direct use if needed
|
||||
__all__ = [
|
||||
'LetsBonkAddressProvider',
|
||||
'LetsBonkCurveManager',
|
||||
'LetsBonkEventParser',
|
||||
'LetsBonkInstructionBuilder'
|
||||
'LetsBonkInstructionBuilder',
|
||||
'LetsBonkPumpPortalProcessor'
|
||||
]
|
||||
@@ -79,6 +79,52 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
)
|
||||
return pool_state
|
||||
|
||||
def derive_base_vault(self, base_mint: Pubkey, quote_mint: Pubkey | None = None) -> Pubkey:
|
||||
"""Derive the base vault address for a token pair.
|
||||
|
||||
Args:
|
||||
base_mint: Base token mint address
|
||||
quote_mint: Quote token mint (defaults to WSOL)
|
||||
|
||||
Returns:
|
||||
Base vault address
|
||||
"""
|
||||
if quote_mint is None:
|
||||
quote_mint = SystemAddresses.SOL_MINT
|
||||
|
||||
# First derive the pool state address
|
||||
pool_state = self.derive_pool_address(base_mint, quote_mint)
|
||||
|
||||
# Then derive the base vault using pool_vault seed
|
||||
base_vault, _ = Pubkey.find_program_address(
|
||||
[b"pool_vault", bytes(pool_state), bytes(base_mint)],
|
||||
LetsBonkAddresses.PROGRAM
|
||||
)
|
||||
return base_vault
|
||||
|
||||
def derive_quote_vault(self, base_mint: Pubkey, quote_mint: Pubkey | None = None) -> Pubkey:
|
||||
"""Derive the quote vault address for a token pair.
|
||||
|
||||
Args:
|
||||
base_mint: Base token mint address
|
||||
quote_mint: Quote token mint (defaults to WSOL)
|
||||
|
||||
Returns:
|
||||
Quote vault address
|
||||
"""
|
||||
if quote_mint is None:
|
||||
quote_mint = SystemAddresses.SOL_MINT
|
||||
|
||||
# First derive the pool state address
|
||||
pool_state = self.derive_pool_address(base_mint, quote_mint)
|
||||
|
||||
# Then derive the quote vault using pool_vault seed
|
||||
quote_vault, _ = Pubkey.find_program_address(
|
||||
[b"pool_vault", bytes(pool_state), bytes(quote_mint)],
|
||||
LetsBonkAddresses.PROGRAM
|
||||
)
|
||||
return quote_vault
|
||||
|
||||
def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey:
|
||||
"""Derive user's associated token account address.
|
||||
|
||||
@@ -102,15 +148,22 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"""
|
||||
accounts = {}
|
||||
|
||||
# Add pool state - must be present in token_info
|
||||
# Add pool state - derive if not present or use existing
|
||||
if token_info.pool_state:
|
||||
accounts["pool_state"] = token_info.pool_state
|
||||
else:
|
||||
accounts["pool_state"] = self.derive_pool_address(token_info.mint)
|
||||
|
||||
# Add vault addresses - must be present in token_info
|
||||
# Add vault addresses - derive if not present or use existing
|
||||
if token_info.base_vault:
|
||||
accounts["base_vault"] = token_info.base_vault
|
||||
else:
|
||||
accounts["base_vault"] = self.derive_base_vault(token_info.mint)
|
||||
|
||||
if token_info.quote_vault:
|
||||
accounts["quote_vault"] = token_info.quote_vault
|
||||
else:
|
||||
accounts["quote_vault"] = self.derive_quote_vault(token_info.mint)
|
||||
|
||||
# Derive authority PDA
|
||||
accounts["authority"] = self.derive_authority_pda()
|
||||
@@ -174,20 +227,15 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"""
|
||||
additional_accounts = self.get_additional_accounts(token_info)
|
||||
|
||||
# Vault addresses must be present in token_info
|
||||
if not token_info.base_vault or not token_info.quote_vault:
|
||||
raise ValueError(f"Missing required vault addresses for token {token_info.mint}. "
|
||||
f"base_vault: {token_info.base_vault}, quote_vault: {token_info.quote_vault}")
|
||||
|
||||
return {
|
||||
"payer": user,
|
||||
"authority": additional_accounts["authority"],
|
||||
"global_config": LetsBonkAddresses.GLOBAL_CONFIG,
|
||||
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
|
||||
"pool_state": token_info.pool_state,
|
||||
"pool_state": additional_accounts["pool_state"],
|
||||
"user_base_token": self.derive_user_token_account(user, token_info.mint),
|
||||
"base_vault": token_info.base_vault,
|
||||
"quote_vault": token_info.quote_vault,
|
||||
"base_vault": additional_accounts["base_vault"],
|
||||
"quote_vault": additional_accounts["quote_vault"],
|
||||
"base_token_mint": token_info.mint,
|
||||
"quote_token_mint": SystemAddresses.SOL_MINT,
|
||||
"base_token_program": SystemAddresses.TOKEN_PROGRAM,
|
||||
@@ -208,20 +256,15 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"""
|
||||
additional_accounts = self.get_additional_accounts(token_info)
|
||||
|
||||
# Vault addresses must be present in token_info
|
||||
if not token_info.base_vault or not token_info.quote_vault:
|
||||
raise ValueError(f"Missing required vault addresses for token {token_info.mint}. "
|
||||
f"base_vault: {token_info.base_vault}, quote_vault: {token_info.quote_vault}")
|
||||
|
||||
return {
|
||||
"payer": user,
|
||||
"authority": additional_accounts["authority"],
|
||||
"global_config": LetsBonkAddresses.GLOBAL_CONFIG,
|
||||
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
|
||||
"pool_state": token_info.pool_state,
|
||||
"pool_state": additional_accounts["pool_state"],
|
||||
"user_base_token": self.derive_user_token_account(user, token_info.mint),
|
||||
"base_vault": token_info.base_vault,
|
||||
"quote_vault": token_info.quote_vault,
|
||||
"base_vault": additional_accounts["base_vault"],
|
||||
"quote_vault": additional_accounts["quote_vault"],
|
||||
"base_token_mint": token_info.mint,
|
||||
"quote_token_mint": SystemAddresses.SOL_MINT,
|
||||
"base_token_program": SystemAddresses.TOKEN_PROGRAM,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
LetsBonk-specific PumpPortal event processor.
|
||||
File: src/platforms/letsbonk/pumpportal_processor.py
|
||||
"""
|
||||
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
from platforms.letsbonk.address_provider import LetsBonkAddressProvider
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class LetsBonkPumpPortalProcessor:
|
||||
"""PumpPortal processor for LetsBonk tokens."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the processor with address provider."""
|
||||
self.address_provider = LetsBonkAddressProvider()
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
"""Get the platform this processor handles."""
|
||||
return Platform.LETS_BONK
|
||||
|
||||
@property
|
||||
def supported_pool_names(self) -> list[str]:
|
||||
"""Get the pool names this processor supports from PumpPortal."""
|
||||
return ["bonk"] # PumpPortal pool name for LetsBonk/bonk pools
|
||||
|
||||
def can_process(self, token_data: dict) -> bool:
|
||||
"""Check if this processor can handle the given token data.
|
||||
|
||||
Args:
|
||||
token_data: Token data from PumpPortal
|
||||
|
||||
Returns:
|
||||
True if this processor can handle the token data
|
||||
"""
|
||||
pool = token_data.get("pool", "").lower()
|
||||
return pool in self.supported_pool_names
|
||||
|
||||
def process_token_data(self, token_data: dict) -> TokenInfo | None:
|
||||
"""Process LetsBonk token data from PumpPortal.
|
||||
|
||||
Args:
|
||||
token_data: Token data from PumpPortal WebSocket
|
||||
|
||||
Returns:
|
||||
TokenInfo if token creation found, None otherwise
|
||||
"""
|
||||
try:
|
||||
# Extract required fields for LetsBonk
|
||||
name = token_data.get("name", "")
|
||||
symbol = token_data.get("symbol", "")
|
||||
mint_str = token_data.get("mint")
|
||||
creator_str = token_data.get("traderPublicKey")
|
||||
uri = token_data.get("uri", "")
|
||||
|
||||
# Note: LetsBonk tokens from PumpPortal might have different field mappings
|
||||
# This would need to be adjusted based on actual PumpPortal data for LetsBonk tokens
|
||||
|
||||
if not all([name, symbol, mint_str, creator_str]):
|
||||
logger.warning("Missing required fields in PumpPortal LetsBonk token data")
|
||||
return None
|
||||
|
||||
# Convert string addresses to Pubkey objects
|
||||
mint = Pubkey.from_string(mint_str)
|
||||
user = Pubkey.from_string(creator_str)
|
||||
creator = user
|
||||
|
||||
# Derive LetsBonk-specific addresses
|
||||
pool_state = self.address_provider.derive_pool_address(mint)
|
||||
|
||||
# For LetsBonk, vault addresses might need to be derived differently
|
||||
# or provided in the PumpPortal data. For now, we'll derive them
|
||||
# using the standard pattern, but this might need adjustment
|
||||
additional_accounts = self.address_provider.get_additional_accounts(
|
||||
# Create a minimal TokenInfo to get additional accounts
|
||||
TokenInfo(
|
||||
name=name,
|
||||
symbol=symbol,
|
||||
uri=uri,
|
||||
mint=mint,
|
||||
platform=Platform.LETS_BONK,
|
||||
pool_state=pool_state,
|
||||
user=user,
|
||||
creator=creator,
|
||||
base_vault=None, # Will be filled from additional_accounts
|
||||
quote_vault=None, # Will be filled from additional_accounts
|
||||
)
|
||||
)
|
||||
|
||||
# Extract vault addresses if available
|
||||
base_vault = additional_accounts.get("base_vault")
|
||||
quote_vault = additional_accounts.get("quote_vault")
|
||||
|
||||
# If vaults aren't available from additional_accounts,
|
||||
# we might need to derive them or leave them None
|
||||
# and let the trading logic handle the derivation
|
||||
|
||||
return TokenInfo(
|
||||
name=name,
|
||||
symbol=symbol,
|
||||
uri=uri,
|
||||
mint=mint,
|
||||
platform=Platform.LETS_BONK,
|
||||
pool_state=pool_state,
|
||||
base_vault=base_vault,
|
||||
quote_vault=quote_vault,
|
||||
user=user,
|
||||
creator=creator,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process PumpPortal LetsBonk token data: {e}")
|
||||
return None
|
||||
@@ -9,11 +9,13 @@ from .address_provider import PumpFunAddressProvider
|
||||
from .curve_manager import PumpFunCurveManager
|
||||
from .event_parser import PumpFunEventParser
|
||||
from .instruction_builder import PumpFunInstructionBuilder
|
||||
from .pumpportal_processor import PumpFunPumpPortalProcessor
|
||||
|
||||
# Export implementations for direct use if needed
|
||||
__all__ = [
|
||||
'PumpFunAddressProvider',
|
||||
'PumpFunCurveManager',
|
||||
'PumpFunCurveManager',
|
||||
'PumpFunEventParser',
|
||||
'PumpFunInstructionBuilder'
|
||||
'PumpFunInstructionBuilder',
|
||||
'PumpFunPumpPortalProcessor'
|
||||
]
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
PumpFun-specific PumpPortal event processor.
|
||||
File: src/platforms/pumpfun/pumpportal_processor.py
|
||||
"""
|
||||
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
from platforms.pumpfun.address_provider import PumpFunAddressProvider
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PumpFunPumpPortalProcessor:
|
||||
"""PumpPortal processor for pump.fun tokens."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the processor with address provider."""
|
||||
self.address_provider = PumpFunAddressProvider()
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
"""Get the platform this processor handles."""
|
||||
return Platform.PUMP_FUN
|
||||
|
||||
@property
|
||||
def supported_pool_names(self) -> list[str]:
|
||||
"""Get the pool names this processor supports from PumpPortal."""
|
||||
return ["pump"] # PumpPortal pool name for pump.fun
|
||||
|
||||
def can_process(self, token_data: dict) -> bool:
|
||||
"""Check if this processor can handle the given token data.
|
||||
|
||||
Args:
|
||||
token_data: Token data from PumpPortal
|
||||
|
||||
Returns:
|
||||
True if this processor can handle the token data
|
||||
"""
|
||||
pool = token_data.get("pool", "").lower()
|
||||
return pool in self.supported_pool_names
|
||||
|
||||
def process_token_data(self, token_data: dict) -> TokenInfo | None:
|
||||
"""Process pump.fun token data from PumpPortal.
|
||||
|
||||
Args:
|
||||
token_data: Token data from PumpPortal WebSocket
|
||||
|
||||
Returns:
|
||||
TokenInfo if token creation found, None otherwise
|
||||
"""
|
||||
try:
|
||||
# Extract required fields
|
||||
name = token_data.get("name", "")
|
||||
symbol = token_data.get("symbol", "")
|
||||
mint_str = token_data.get("mint")
|
||||
bonding_curve_str = token_data.get("bondingCurveKey")
|
||||
creator_str = token_data.get("traderPublicKey") # Maps to user field
|
||||
uri = token_data.get("uri", "")
|
||||
|
||||
# Additional fields available from PumpPortal but not currently used:
|
||||
# - initialBuy: Initial buy amount in tokens
|
||||
# - solAmount: SOL amount spent on initial buy
|
||||
# - vSolInBondingCurve: Virtual SOL in bonding curve
|
||||
# - vTokensInBondingCurve: Virtual tokens in bonding curve
|
||||
# - marketCapSol: Market cap in SOL
|
||||
# - signature: Transaction signature
|
||||
|
||||
if not all([name, symbol, mint_str, bonding_curve_str, creator_str]):
|
||||
logger.warning("Missing required fields in PumpPortal token data")
|
||||
return None
|
||||
|
||||
# Convert string addresses to Pubkey objects
|
||||
mint = Pubkey.from_string(mint_str)
|
||||
bonding_curve = Pubkey.from_string(bonding_curve_str)
|
||||
user = Pubkey.from_string(creator_str)
|
||||
|
||||
# For PumpPortal, we assume the creator is the same as the user
|
||||
# since PumpPortal doesn't distinguish between them
|
||||
creator = user
|
||||
|
||||
# Derive additional addresses using platform provider
|
||||
associated_bonding_curve = self.address_provider.derive_associated_bonding_curve(
|
||||
mint, bonding_curve
|
||||
)
|
||||
creator_vault = self.address_provider.derive_creator_vault(creator)
|
||||
|
||||
return TokenInfo(
|
||||
name=name,
|
||||
symbol=symbol,
|
||||
uri=uri,
|
||||
mint=mint,
|
||||
platform=Platform.PUMP_FUN,
|
||||
bonding_curve=bonding_curve,
|
||||
associated_bonding_curve=associated_bonding_curve,
|
||||
user=user,
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process PumpPortal token data: {e}")
|
||||
return None
|
||||
Reference in New Issue
Block a user