mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-03 18:57:44 +00:00
wip(core): platform aware trading
This commit is contained in:
@@ -1,15 +1,23 @@
|
||||
"""
|
||||
Base class for WebSocket token listeners.
|
||||
Base class for WebSocket token listeners - now platform-agnostic.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from trading.base import TokenInfo
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
|
||||
|
||||
class BaseTokenListener(ABC):
|
||||
"""Base abstract class for token listeners."""
|
||||
"""Base abstract class for token listeners - now platform-agnostic."""
|
||||
|
||||
def __init__(self, platform: Platform | None = None):
|
||||
"""Initialize the listener with optional platform specification.
|
||||
|
||||
Args:
|
||||
platform: Platform to monitor (if None, monitor all platforms)
|
||||
"""
|
||||
self.platform = platform
|
||||
|
||||
@abstractmethod
|
||||
async def listen_for_tokens(
|
||||
@@ -27,3 +35,16 @@ class BaseTokenListener(ABC):
|
||||
creator_address: Optional creator address to filter by
|
||||
"""
|
||||
pass
|
||||
|
||||
def should_process_token(self, token_info: TokenInfo) -> bool:
|
||||
"""Check if a token should be processed based on platform filter.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
|
||||
Returns:
|
||||
True if token should be processed
|
||||
"""
|
||||
if self.platform is None:
|
||||
return True # Process all platforms
|
||||
return token_info.platform == self.platform
|
||||
@@ -1,190 +0,0 @@
|
||||
"""
|
||||
Event processing for pump.fun tokens.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import struct
|
||||
from typing import Any
|
||||
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.transaction import VersionedTransaction
|
||||
|
||||
from core.pubkeys import PumpAddresses
|
||||
from trading.base import TokenInfo
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PumpEventProcessor:
|
||||
"""Processes events from pump.fun program."""
|
||||
|
||||
# Discriminator for create instruction
|
||||
CREATE_DISCRIMINATOR = 8576854823835016728
|
||||
|
||||
def __init__(self, pump_program: Pubkey):
|
||||
"""Initialize event processor.
|
||||
|
||||
Args:
|
||||
pump_program: Pump.fun program address
|
||||
"""
|
||||
self.pump_program = pump_program
|
||||
self._idl = self._load_idl()
|
||||
|
||||
def _load_idl(self) -> dict[str, Any]:
|
||||
"""Load IDL from file.
|
||||
|
||||
Returns:
|
||||
IDL as dictionary
|
||||
"""
|
||||
try:
|
||||
with open("idl/pump_fun_idl.json") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load IDL: {e!s}")
|
||||
# Create a minimal IDL with just what we need
|
||||
return {
|
||||
"instructions": [
|
||||
{
|
||||
"name": "create",
|
||||
"args": [
|
||||
{"name": "name", "type": "string"},
|
||||
{"name": "symbol", "type": "string"},
|
||||
{"name": "uri", "type": "string"},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def process_transaction(self, tx_data: str) -> TokenInfo | None:
|
||||
"""Process a transaction and extract token info.
|
||||
|
||||
Args:
|
||||
tx_data: Base64 encoded transaction data
|
||||
|
||||
Returns:
|
||||
TokenInfo if a token creation is found, None otherwise
|
||||
"""
|
||||
try:
|
||||
tx_data_decoded = base64.b64decode(tx_data)
|
||||
transaction = VersionedTransaction.from_bytes(tx_data_decoded)
|
||||
|
||||
for ix in transaction.message.instructions:
|
||||
# Check if instruction is from pump.fun program
|
||||
program_id_index = ix.program_id_index
|
||||
if program_id_index >= len(transaction.message.account_keys):
|
||||
continue
|
||||
|
||||
program_id = transaction.message.account_keys[program_id_index]
|
||||
|
||||
if str(program_id) != str(self.pump_program):
|
||||
continue
|
||||
|
||||
ix_data = bytes(ix.data)
|
||||
|
||||
# Check if it's a create instruction
|
||||
if len(ix_data) < 8:
|
||||
continue
|
||||
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
if discriminator != self.CREATE_DISCRIMINATOR:
|
||||
continue
|
||||
|
||||
# Found a create instruction, decode it
|
||||
create_ix = next(
|
||||
(
|
||||
instr
|
||||
for instr in self._idl["instructions"]
|
||||
if instr["name"] == "create"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not create_ix:
|
||||
continue
|
||||
|
||||
# Get account keys for this instruction
|
||||
account_keys = [
|
||||
transaction.message.account_keys[index] for index in ix.accounts
|
||||
]
|
||||
|
||||
# Decode instruction arguments
|
||||
decoded_args = self._decode_create_instruction(
|
||||
ix_data, create_ix, account_keys
|
||||
)
|
||||
creator = Pubkey.from_string(decoded_args["creator"])
|
||||
creator_vault = self._find_creator_vault(creator)
|
||||
|
||||
return TokenInfo(
|
||||
name=decoded_args["name"],
|
||||
symbol=decoded_args["symbol"],
|
||||
uri=decoded_args["uri"],
|
||||
mint=Pubkey.from_string(decoded_args["mint"]),
|
||||
bonding_curve=Pubkey.from_string(decoded_args["bondingCurve"]),
|
||||
associated_bonding_curve=Pubkey.from_string(
|
||||
decoded_args["associatedBondingCurve"]
|
||||
),
|
||||
user=Pubkey.from_string(decoded_args["user"]),
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing transaction: {e!s}")
|
||||
|
||||
return None
|
||||
|
||||
def _decode_create_instruction(
|
||||
self, ix_data: bytes, ix_def: dict[str, Any], accounts: list[Pubkey]
|
||||
) -> dict[str, Any]:
|
||||
"""Decode create instruction data.
|
||||
|
||||
Args:
|
||||
ix_data: Instruction data bytes
|
||||
ix_def: Instruction definition from IDL
|
||||
accounts: List of account pubkeys
|
||||
|
||||
Returns:
|
||||
Decoded instruction arguments
|
||||
"""
|
||||
args = {}
|
||||
offset = 8 # Skip 8-byte discriminator
|
||||
|
||||
for arg in ix_def["args"]:
|
||||
if arg["type"] == "string":
|
||||
length = struct.unpack_from("<I", ix_data, offset)[0]
|
||||
offset += 4
|
||||
value = ix_data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
elif arg["type"] == "pubkey":
|
||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||
offset += 32
|
||||
else:
|
||||
logger.warning(f"Unsupported type: {arg['type']}")
|
||||
value = None
|
||||
|
||||
args[arg["name"]] = value
|
||||
|
||||
args["mint"] = str(accounts[0])
|
||||
args["bondingCurve"] = str(accounts[2])
|
||||
args["associatedBondingCurve"] = str(accounts[3])
|
||||
args["user"] = str(accounts[7])
|
||||
|
||||
return args
|
||||
|
||||
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
|
||||
"""
|
||||
Find the creator vault for a creator.
|
||||
|
||||
Args:
|
||||
creator: Creator address
|
||||
|
||||
Returns:
|
||||
Creator vault address
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[b"creator-vault", bytes(creator)],
|
||||
PumpAddresses.PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
@@ -1,125 +0,0 @@
|
||||
"""
|
||||
Event processing for pump.fun tokens using Geyser data.
|
||||
"""
|
||||
|
||||
import struct
|
||||
from typing import Final
|
||||
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from core.pubkeys import PumpAddresses
|
||||
from trading.base import TokenInfo
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class GeyserEventProcessor:
|
||||
"""Processes token creation events from Geyser stream."""
|
||||
|
||||
CREATE_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 8576854823835016728)
|
||||
|
||||
def __init__(self, pump_program: Pubkey):
|
||||
"""Initialize event processor.
|
||||
|
||||
Args:
|
||||
pump_program: Pump.fun program address
|
||||
"""
|
||||
self.pump_program = pump_program
|
||||
|
||||
def process_transaction_data(
|
||||
self, instruction_data: bytes, accounts: list, keys: list
|
||||
) -> TokenInfo | None:
|
||||
"""Process transaction data and extract token creation info.
|
||||
|
||||
Args:
|
||||
instruction_data: Raw instruction data
|
||||
accounts: List of account indices
|
||||
keys: List of account public keys
|
||||
|
||||
Returns:
|
||||
TokenInfo if token creation found, None otherwise
|
||||
"""
|
||||
if not instruction_data.startswith(self.CREATE_DISCRIMINATOR):
|
||||
return None
|
||||
|
||||
try:
|
||||
# Skip past the 8-byte discriminator
|
||||
offset = 8
|
||||
|
||||
# Helper to read strings (prefixed with length)
|
||||
def read_string():
|
||||
nonlocal offset
|
||||
# Get string length (4-byte uint)
|
||||
length = struct.unpack_from("<I", instruction_data, offset)[0]
|
||||
offset += 4
|
||||
# Extract and decode the string
|
||||
value = instruction_data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
return value
|
||||
|
||||
def read_pubkey():
|
||||
nonlocal offset
|
||||
value = base58.b58encode(instruction_data[offset : offset + 32]).decode(
|
||||
"utf-8"
|
||||
)
|
||||
offset += 32
|
||||
return Pubkey.from_string(value)
|
||||
|
||||
# Helper to get account key
|
||||
def get_account_key(index):
|
||||
if index >= len(accounts):
|
||||
return None
|
||||
account_index = accounts[index]
|
||||
if account_index >= len(keys):
|
||||
return None
|
||||
return Pubkey.from_bytes(keys[account_index])
|
||||
|
||||
name = read_string()
|
||||
symbol = read_string()
|
||||
uri = read_string()
|
||||
creator = read_pubkey()
|
||||
|
||||
mint = get_account_key(0)
|
||||
bonding_curve = get_account_key(2)
|
||||
associated_bonding_curve = get_account_key(3)
|
||||
user = get_account_key(7)
|
||||
|
||||
creator_vault = self._find_creator_vault(creator)
|
||||
|
||||
if not all([mint, bonding_curve, associated_bonding_curve, user]):
|
||||
logger.warning("Missing required account keys in token creation")
|
||||
return None
|
||||
|
||||
return TokenInfo(
|
||||
name=name,
|
||||
symbol=symbol,
|
||||
uri=uri,
|
||||
mint=mint,
|
||||
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 transaction data: {e}")
|
||||
return None
|
||||
|
||||
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
|
||||
"""
|
||||
Find the creator vault for a creator.
|
||||
|
||||
Args:
|
||||
creator: Creator address
|
||||
|
||||
Returns:
|
||||
Creator vault address
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[b"creator-vault", bytes(creator)],
|
||||
PumpAddresses.PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Factory for creating platform-aware token listeners.
|
||||
"""
|
||||
|
||||
from interfaces.core import Platform
|
||||
from monitoring.base_listener import BaseTokenListener
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ListenerFactory:
|
||||
"""Factory for creating appropriate token listeners based on configuration."""
|
||||
|
||||
@staticmethod
|
||||
def create_listener(
|
||||
listener_type: str,
|
||||
wss_endpoint: str | None = None,
|
||||
geyser_endpoint: str | None = None,
|
||||
geyser_api_token: str | None = None,
|
||||
geyser_auth_type: str = "x-token",
|
||||
pumpportal_url: str = "wss://pumpportal.fun/api/data",
|
||||
platforms: list[Platform] | None = None,
|
||||
**kwargs
|
||||
) -> BaseTokenListener:
|
||||
"""Create a token listener based on the specified type.
|
||||
|
||||
Args:
|
||||
listener_type: Type of listener ('logs', 'blocks', 'geyser', or 'pumpportal')
|
||||
wss_endpoint: WebSocket endpoint URL (for logs/blocks listeners)
|
||||
geyser_endpoint: Geyser gRPC endpoint URL (for geyser listener)
|
||||
geyser_api_token: Geyser API token (for geyser listener)
|
||||
geyser_auth_type: Geyser authentication type
|
||||
pumpportal_url: PumpPortal WebSocket URL (for pumpportal listener)
|
||||
platforms: List of platforms to monitor (if None, monitor all)
|
||||
**kwargs: Additional arguments
|
||||
|
||||
Returns:
|
||||
Configured token listener
|
||||
|
||||
Raises:
|
||||
ValueError: If listener type is invalid or required parameters are missing
|
||||
"""
|
||||
listener_type = listener_type.lower()
|
||||
|
||||
if listener_type == "geyser":
|
||||
if not geyser_endpoint or not geyser_api_token:
|
||||
raise ValueError(
|
||||
"Geyser endpoint and API token are required for geyser listener"
|
||||
)
|
||||
|
||||
from monitoring.universal_geyser_listener import UniversalGeyserListener
|
||||
|
||||
listener = UniversalGeyserListener(
|
||||
geyser_endpoint=geyser_endpoint,
|
||||
geyser_api_token=geyser_api_token,
|
||||
geyser_auth_type=geyser_auth_type,
|
||||
platforms=platforms,
|
||||
)
|
||||
logger.info("Created Universal Geyser listener for token monitoring")
|
||||
return listener
|
||||
|
||||
elif listener_type == "logs":
|
||||
if not wss_endpoint:
|
||||
raise ValueError("WebSocket endpoint is required for logs listener")
|
||||
|
||||
from monitoring.universal_logs_listener import UniversalLogsListener
|
||||
|
||||
listener = UniversalLogsListener(
|
||||
wss_endpoint=wss_endpoint,
|
||||
platforms=platforms,
|
||||
)
|
||||
logger.info("Created Universal Logs listener for token monitoring")
|
||||
return listener
|
||||
|
||||
elif listener_type == "blocks":
|
||||
if not wss_endpoint:
|
||||
raise ValueError("WebSocket endpoint is required for blocks listener")
|
||||
|
||||
from monitoring.universal_block_listener import UniversalBlockListener
|
||||
|
||||
listener = UniversalBlockListener(
|
||||
wss_endpoint=wss_endpoint,
|
||||
platforms=platforms,
|
||||
)
|
||||
logger.info("Created Universal Block listener for token monitoring")
|
||||
return listener
|
||||
|
||||
elif listener_type == "pumpportal":
|
||||
# PumpPortal is pump.fun specific, so filter platforms
|
||||
pumpfun_platforms = [Platform.PUMP_FUN]
|
||||
if platforms:
|
||||
pumpfun_platforms = [p for p in platforms if p == Platform.PUMP_FUN]
|
||||
|
||||
if not pumpfun_platforms:
|
||||
raise ValueError("PumpPortal listener only supports pump.fun platform")
|
||||
|
||||
from monitoring.pumpportal_listener import PumpPortalListener
|
||||
|
||||
listener = PumpPortalListener(
|
||||
pump_program=None, # Will be determined from platform
|
||||
pumpportal_url=pumpportal_url,
|
||||
)
|
||||
logger.info("Created PumpPortal listener for token monitoring")
|
||||
return listener
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid listener type '{listener_type}'. "
|
||||
f"Must be one of: 'logs', 'blocks', 'geyser', 'pumpportal'"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_supported_listener_types() -> list[str]:
|
||||
"""Get list of supported listener types.
|
||||
|
||||
Returns:
|
||||
List of supported listener type strings
|
||||
"""
|
||||
return ["logs", "blocks", "geyser", "pumpportal"]
|
||||
|
||||
@staticmethod
|
||||
def get_platform_compatible_listeners(platform: Platform) -> list[str]:
|
||||
"""Get list of listener types compatible with a specific platform.
|
||||
|
||||
Args:
|
||||
platform: Platform to check compatibility for
|
||||
|
||||
Returns:
|
||||
List of compatible listener types
|
||||
"""
|
||||
if platform == Platform.PUMP_FUN:
|
||||
return ["logs", "blocks", "geyser", "pumpportal"]
|
||||
elif platform == Platform.LETS_BONK:
|
||||
return ["logs", "blocks", "geyser"] # PumpPortal is pump.fun only
|
||||
else:
|
||||
return ["logs", "blocks", "geyser"] # Default universal listeners
|
||||
@@ -1,174 +0,0 @@
|
||||
"""
|
||||
Event processing for pump.fun tokens using logsSubscribe data.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import struct
|
||||
from typing import Final
|
||||
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from core.pubkeys import PumpAddresses, SystemAddresses
|
||||
from trading.base import TokenInfo
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class LogsEventProcessor:
|
||||
"""Processes events from pump.fun program logs."""
|
||||
|
||||
# Discriminator for create instruction to avoid non-create transactions
|
||||
CREATE_DISCRIMINATOR: Final[int] = 8530921459188068891
|
||||
|
||||
def __init__(self, pump_program: Pubkey):
|
||||
"""Initialize event processor.
|
||||
|
||||
Args:
|
||||
pump_program: Pump.fun program address
|
||||
"""
|
||||
self.pump_program = pump_program
|
||||
|
||||
def process_program_logs(self, logs: list[str], signature: str) -> TokenInfo | None:
|
||||
"""Process program logs and extract token info.
|
||||
|
||||
Args:
|
||||
logs: List of log strings from the notification
|
||||
signature: Transaction signature
|
||||
|
||||
Returns:
|
||||
TokenInfo if a token creation is found, None otherwise
|
||||
"""
|
||||
# Check if this is a token creation
|
||||
if not any("Program log: Instruction: Create" in log for log in logs):
|
||||
return None
|
||||
|
||||
# Skip swaps as the first condition may pass them
|
||||
if any("Program log: Instruction: CreateTokenAccount" in log for log in logs):
|
||||
return None
|
||||
|
||||
# Find and process program data
|
||||
for log in logs:
|
||||
if "Program data:" in log:
|
||||
try:
|
||||
encoded_data = log.split(": ")[1]
|
||||
decoded_data = base64.b64decode(encoded_data)
|
||||
parsed_data = self._parse_create_instruction(decoded_data)
|
||||
|
||||
if parsed_data and "name" in parsed_data:
|
||||
mint = Pubkey.from_string(parsed_data["mint"])
|
||||
bonding_curve = Pubkey.from_string(parsed_data["bondingCurve"])
|
||||
associated_curve = self._find_associated_bonding_curve(
|
||||
mint, bonding_curve
|
||||
)
|
||||
creator = Pubkey.from_string(parsed_data["creator"])
|
||||
creator_vault = self._find_creator_vault(creator)
|
||||
|
||||
return TokenInfo(
|
||||
name=parsed_data["name"],
|
||||
symbol=parsed_data["symbol"],
|
||||
uri=parsed_data["uri"],
|
||||
mint=mint,
|
||||
bonding_curve=bonding_curve,
|
||||
associated_bonding_curve=associated_curve,
|
||||
user=Pubkey.from_string(parsed_data["user"]),
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process log data: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def _parse_create_instruction(self, data: bytes) -> dict | None:
|
||||
"""Parse the create instruction data.
|
||||
|
||||
Args:
|
||||
data: Raw instruction data
|
||||
|
||||
Returns:
|
||||
Dictionary of parsed data or None if parsing fails
|
||||
"""
|
||||
if len(data) < 8:
|
||||
return None
|
||||
|
||||
# Check for the correct instruction discriminator
|
||||
discriminator = struct.unpack("<Q", data[:8])[0]
|
||||
if discriminator != self.CREATE_DISCRIMINATOR:
|
||||
logger.info(
|
||||
f"Skipping non-Create instruction with discriminator: {discriminator}"
|
||||
)
|
||||
return None
|
||||
|
||||
offset = 8
|
||||
parsed_data = {}
|
||||
|
||||
# Parse fields based on CreateEvent structure
|
||||
fields = [
|
||||
("name", "string"),
|
||||
("symbol", "string"),
|
||||
("uri", "string"),
|
||||
("mint", "publicKey"),
|
||||
("bondingCurve", "publicKey"),
|
||||
("user", "publicKey"),
|
||||
("creator", "publicKey"),
|
||||
]
|
||||
|
||||
try:
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "string":
|
||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
||||
offset += 4
|
||||
value = data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
elif field_type == "publicKey":
|
||||
value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
||||
offset += 32
|
||||
|
||||
parsed_data[field_name] = value
|
||||
|
||||
return parsed_data
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to parse create instruction: {e}")
|
||||
return None
|
||||
|
||||
def _find_associated_bonding_curve(
|
||||
self, mint: Pubkey, bonding_curve: Pubkey
|
||||
) -> Pubkey:
|
||||
"""
|
||||
Find the associated bonding curve for a given mint and bonding curve.
|
||||
This uses the standard ATA derivation.
|
||||
|
||||
Args:
|
||||
mint: Token mint address
|
||||
bonding_curve: Bonding curve address
|
||||
|
||||
Returns:
|
||||
Associated bonding curve address
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
bytes(bonding_curve),
|
||||
bytes(SystemAddresses.TOKEN_PROGRAM),
|
||||
bytes(mint),
|
||||
],
|
||||
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
|
||||
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
|
||||
"""
|
||||
Find the creator vault for a creator.
|
||||
|
||||
Args:
|
||||
creator: Creator address
|
||||
|
||||
Returns:
|
||||
Creator vault address
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[b"creator-vault", bytes(creator)],
|
||||
PumpAddresses.PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
WebSocket monitoring for pump.fun tokens.
|
||||
Universal block listener that works with any platform through the interface system.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -7,30 +7,61 @@ import json
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
import websockets
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
from monitoring.base_listener import BaseTokenListener
|
||||
from monitoring.block_event_processor import PumpEventProcessor
|
||||
from trading.base import TokenInfo
|
||||
from platforms import get_platform_implementations
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class BlockListener(BaseTokenListener):
|
||||
"""WebSocket listener for pump.fun token creation events using blockSubscribe."""
|
||||
class UniversalBlockListener(BaseTokenListener):
|
||||
"""Universal block listener that works with any platform."""
|
||||
|
||||
def __init__(self, wss_endpoint: str, pump_program: Pubkey):
|
||||
"""Initialize token listener.
|
||||
def __init__(
|
||||
self,
|
||||
wss_endpoint: str,
|
||||
platforms: list[Platform] | None = None,
|
||||
):
|
||||
"""Initialize universal block listener.
|
||||
|
||||
Args:
|
||||
wss_endpoint: WebSocket endpoint URL
|
||||
pump_program: Pump.fun program address
|
||||
platforms: List of platforms to monitor (if None, monitor all supported platforms)
|
||||
"""
|
||||
super().__init__()
|
||||
self.wss_endpoint = wss_endpoint
|
||||
self.pump_program = pump_program
|
||||
self.event_processor = PumpEventProcessor(pump_program)
|
||||
self.ping_interval = 20 # seconds
|
||||
|
||||
# Import platform factory and get supported platforms
|
||||
from platforms import platform_factory
|
||||
|
||||
if platforms is None:
|
||||
# Monitor all supported platforms
|
||||
self.platforms = platform_factory.get_supported_platforms()
|
||||
else:
|
||||
self.platforms = platforms
|
||||
|
||||
# Get event parsers for all platforms
|
||||
self.platform_parsers = {}
|
||||
self.platform_program_ids = []
|
||||
|
||||
for platform in self.platforms:
|
||||
try:
|
||||
# We'll need a dummy client for getting the parser
|
||||
from core.client import SolanaClient
|
||||
dummy_client = SolanaClient("http://localhost") # Won't be used for parsing
|
||||
|
||||
implementations = get_platform_implementations(platform, dummy_client)
|
||||
parser = implementations.event_parser
|
||||
self.platform_parsers[platform] = parser
|
||||
self.platform_program_ids.append(str(parser.get_program_id()))
|
||||
|
||||
logger.info(f"Registered platform {platform.value} with program ID {parser.get_program_id()}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not register platform {platform.value}: {e}")
|
||||
|
||||
async def listen_for_tokens(
|
||||
self,
|
||||
@@ -38,17 +69,21 @@ class BlockListener(BaseTokenListener):
|
||||
match_string: str | None = None,
|
||||
creator_address: str | None = None,
|
||||
) -> None:
|
||||
"""Listen for new token creations.
|
||||
"""Listen for new token creations using blockSubscribe.
|
||||
|
||||
Args:
|
||||
token_callback: Callback function for new tokens
|
||||
match_string: Optional string to match in token name/symbol
|
||||
creator_address: Optional creator address to filter by
|
||||
"""
|
||||
if not self.platform_parsers:
|
||||
logger.error("No platform parsers available. Cannot listen for tokens.")
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
async with websockets.connect(self.wss_endpoint) as websocket:
|
||||
await self._subscribe_to_program(websocket)
|
||||
await self._subscribe_to_programs(websocket)
|
||||
ping_task = asyncio.create_task(self._ping_loop(websocket))
|
||||
|
||||
try:
|
||||
@@ -58,9 +93,10 @@ class BlockListener(BaseTokenListener):
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
f"New token detected: {token_info.name} ({token_info.symbol})"
|
||||
f"New token detected: {token_info.name} ({token_info.symbol}) on {token_info.platform.value}"
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
if match_string and not (
|
||||
match_string.lower() in token_info.name.lower()
|
||||
or match_string.lower() in token_info.symbol.lower()
|
||||
@@ -70,10 +106,7 @@ class BlockListener(BaseTokenListener):
|
||||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
creator_address
|
||||
and str(token_info.user) != creator_address
|
||||
):
|
||||
if creator_address and str(token_info.user) != creator_address:
|
||||
logger.info(
|
||||
f"Token not created by {creator_address}. Skipping..."
|
||||
)
|
||||
@@ -90,32 +123,35 @@ class BlockListener(BaseTokenListener):
|
||||
logger.info("Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def _subscribe_to_program(self, websocket) -> None:
|
||||
"""Subscribe to blocks mentioning the pump.fun program.
|
||||
async def _subscribe_to_programs(self, websocket) -> None:
|
||||
"""Subscribe to blocks mentioning any of the monitored program IDs.
|
||||
|
||||
Args:
|
||||
websocket: Active WebSocket connection
|
||||
"""
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "blockSubscribe",
|
||||
"params": [
|
||||
{"mentionsAccountOrProgram": str(self.pump_program)},
|
||||
{
|
||||
"commitment": "confirmed",
|
||||
"encoding": "base64", # base64 is faster than other encoding options
|
||||
"showRewards": False,
|
||||
"transactionDetails": "full",
|
||||
"maxSupportedTransactionVersion": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
# For block subscriptions, we can use mentionsAccountOrProgram to monitor multiple programs
|
||||
# We'll create separate subscriptions for each program to be more specific
|
||||
for i, program_id in enumerate(self.platform_program_ids):
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": i + 1,
|
||||
"method": "blockSubscribe",
|
||||
"params": [
|
||||
{"mentionsAccountOrProgram": program_id},
|
||||
{
|
||||
"commitment": "confirmed",
|
||||
"encoding": "base64",
|
||||
"showRewards": False,
|
||||
"transactionDetails": "full",
|
||||
"maxSupportedTransactionVersion": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
await websocket.send(subscription_message)
|
||||
logger.info(f"Subscribed to blocks mentioning program: {self.pump_program}")
|
||||
await websocket.send(subscription_message)
|
||||
logger.info(f"Subscribed to blocks mentioning program: {program_id}")
|
||||
|
||||
async def _ping_loop(self, websocket) -> None:
|
||||
"""Keep connection alive with pings.
|
||||
@@ -140,7 +176,7 @@ class BlockListener(BaseTokenListener):
|
||||
logger.error(f"Ping error: {e!s}")
|
||||
|
||||
async def _wait_for_token_creation(self, websocket) -> TokenInfo | None:
|
||||
"""Wait for token creation event.
|
||||
"""Wait for token creation event from any platform.
|
||||
|
||||
Args:
|
||||
websocket: Active WebSocket connection
|
||||
@@ -166,15 +202,21 @@ class BlockListener(BaseTokenListener):
|
||||
if "transactions" not in block:
|
||||
return None
|
||||
|
||||
# Try each platform's event parser on each transaction
|
||||
for tx in block["transactions"]:
|
||||
if not isinstance(tx, dict) or "transaction" not in tx:
|
||||
continue
|
||||
|
||||
token_info = self.event_processor.process_transaction(
|
||||
tx["transaction"][0]
|
||||
)
|
||||
if token_info:
|
||||
return token_info
|
||||
for platform, parser in self.platform_parsers.items():
|
||||
# Check if the parser has a block parsing method
|
||||
if hasattr(parser, 'parse_token_creation_from_block'):
|
||||
token_info = parser.parse_token_creation_from_block({
|
||||
"transactions": [tx]
|
||||
})
|
||||
if token_info:
|
||||
return token_info
|
||||
|
||||
return None
|
||||
|
||||
except TimeoutError:
|
||||
logger.debug("No data received for 30 seconds")
|
||||
@@ -184,4 +226,4 @@ class BlockListener(BaseTokenListener):
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing WebSocket message: {e!s}")
|
||||
|
||||
return None
|
||||
return None
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Geyser monitoring for pump.fun tokens.
|
||||
Universal Geyser listener that works with any platform through the interface system.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -9,34 +9,36 @@ import grpc
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from geyser.generated import geyser_pb2, geyser_pb2_grpc
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
from monitoring.base_listener import BaseTokenListener
|
||||
from monitoring.geyser_event_processor import GeyserEventProcessor
|
||||
from trading.base import TokenInfo
|
||||
from platforms import get_platform_implementations
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class GeyserListener(BaseTokenListener):
|
||||
"""Geyser listener for pump.fun token creation events."""
|
||||
class UniversalGeyserListener(BaseTokenListener):
|
||||
"""Universal Geyser listener that works with any platform."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
geyser_endpoint: str,
|
||||
geyser_api_token: str,
|
||||
geyser_auth_type: str,
|
||||
pump_program: Pubkey,
|
||||
platforms: list[Platform] | None = None,
|
||||
):
|
||||
"""Initialize token listener.
|
||||
"""Initialize universal Geyser listener.
|
||||
|
||||
Args:
|
||||
geyser_endpoint: Geyser gRPC endpoint URL
|
||||
geyser_api_token: API token for authentication
|
||||
geyser_auth_type: authentication type ('x-token' or 'basic')
|
||||
pump_program: Pump.fun program address
|
||||
platforms: List of platforms to monitor (if None, monitor all supported platforms)
|
||||
"""
|
||||
super().__init__()
|
||||
self.geyser_endpoint = geyser_endpoint
|
||||
self.geyser_api_token = geyser_api_token
|
||||
|
||||
valid_auth_types = {"x-token", "basic"}
|
||||
self.auth_type: str = (geyser_auth_type or "x-token").lower()
|
||||
if self.auth_type not in valid_auth_types:
|
||||
@@ -44,8 +46,35 @@ class GeyserListener(BaseTokenListener):
|
||||
f"Unsupported auth_type={self.auth_type!r}. "
|
||||
f"Expected one of {valid_auth_types}"
|
||||
)
|
||||
self.pump_program = pump_program
|
||||
self.event_processor = GeyserEventProcessor(pump_program)
|
||||
|
||||
# Import platform factory and get supported platforms
|
||||
from platforms import platform_factory
|
||||
|
||||
if platforms is None:
|
||||
# Monitor all supported platforms
|
||||
self.platforms = platform_factory.get_supported_platforms()
|
||||
else:
|
||||
self.platforms = platforms
|
||||
|
||||
# Get event parsers for all platforms
|
||||
self.platform_parsers = {}
|
||||
self.platform_program_ids = set()
|
||||
|
||||
for platform in self.platforms:
|
||||
try:
|
||||
# We'll need a dummy client for getting the parser - this is a design issue we should fix
|
||||
from core.client import SolanaClient
|
||||
dummy_client = SolanaClient("http://localhost") # Won't be used for parsing
|
||||
|
||||
implementations = get_platform_implementations(platform, dummy_client)
|
||||
parser = implementations.event_parser
|
||||
self.platform_parsers[platform] = parser
|
||||
self.platform_program_ids.add(parser.get_program_id())
|
||||
|
||||
logger.info(f"Registered platform {platform.value} with program ID {parser.get_program_id()}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not register platform {platform.value}: {e}")
|
||||
|
||||
async def _create_geyser_connection(self):
|
||||
"""Establish a secure connection to the Geyser endpoint."""
|
||||
@@ -66,12 +95,15 @@ class GeyserListener(BaseTokenListener):
|
||||
return geyser_pb2_grpc.GeyserStub(channel), channel
|
||||
|
||||
def _create_subscription_request(self):
|
||||
"""Create a subscription request for Pump.fun transactions."""
|
||||
"""Create a subscription request for all monitored platforms."""
|
||||
request = geyser_pb2.SubscribeRequest()
|
||||
request.transactions["pump_filter"].account_include.append(
|
||||
str(self.pump_program)
|
||||
)
|
||||
request.transactions["pump_filter"].failed = False
|
||||
|
||||
# Add all platform program IDs to the filter
|
||||
for program_id in self.platform_program_ids:
|
||||
filter_name = f"platform_filter_{program_id}"
|
||||
request.transactions[filter_name].account_include.append(str(program_id))
|
||||
request.transactions[filter_name].failed = False
|
||||
|
||||
request.commitment = geyser_pb2.CommitmentLevel.PROCESSED
|
||||
return request
|
||||
|
||||
@@ -88,15 +120,18 @@ class GeyserListener(BaseTokenListener):
|
||||
match_string: Optional string to match in token name/symbol
|
||||
creator_address: Optional creator address to filter by
|
||||
"""
|
||||
if not self.platform_parsers:
|
||||
logger.error("No platform parsers available. Cannot listen for tokens.")
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
stub, channel = await self._create_geyser_connection()
|
||||
request = self._create_subscription_request()
|
||||
|
||||
logger.info(f"Connected to Geyser endpoint: {self.geyser_endpoint}")
|
||||
logger.info(
|
||||
f"Monitoring for transactions involving program: {self.pump_program}"
|
||||
)
|
||||
logger.info(f"Monitoring platforms: {[p.value for p in self.platforms]}")
|
||||
logger.info(f"Monitoring program IDs: {[str(pid) for pid in self.platform_program_ids]}")
|
||||
|
||||
try:
|
||||
async for update in stub.Subscribe(iter([request])):
|
||||
@@ -105,9 +140,10 @@ class GeyserListener(BaseTokenListener):
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
f"New token detected: {token_info.name} ({token_info.symbol})"
|
||||
f"New token detected: {token_info.name} ({token_info.symbol}) on {token_info.platform.value}"
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
if match_string and not (
|
||||
match_string.lower() in token_info.name.lower()
|
||||
or match_string.lower() in token_info.symbol.lower()
|
||||
@@ -156,24 +192,25 @@ class GeyserListener(BaseTokenListener):
|
||||
return None
|
||||
|
||||
for ix in msg.instructions:
|
||||
# Skip non-Pump.fun program instructions
|
||||
# Check which platform this instruction belongs to
|
||||
program_idx = ix.program_id_index
|
||||
if program_idx >= len(msg.account_keys):
|
||||
continue
|
||||
|
||||
program_id = msg.account_keys[program_idx]
|
||||
if bytes(program_id) != bytes(self.pump_program):
|
||||
continue
|
||||
|
||||
# Process instruction data
|
||||
token_info = self.event_processor.process_transaction_data(
|
||||
ix.data, ix.accounts, msg.account_keys
|
||||
)
|
||||
if token_info:
|
||||
return token_info
|
||||
program_id = Pubkey.from_bytes(msg.account_keys[program_idx])
|
||||
|
||||
# Find the matching platform parser
|
||||
for platform, parser in self.platform_parsers.items():
|
||||
if program_id == parser.get_program_id():
|
||||
# Use the platform's event parser
|
||||
token_info = parser.parse_token_creation_from_instruction(
|
||||
ix.data, ix.accounts, msg.account_keys
|
||||
)
|
||||
if token_info:
|
||||
return token_info
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing Geyser update: {e}")
|
||||
return None
|
||||
return None
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
WebSocket monitoring for pump.fun tokens using logsSubscribe.
|
||||
Universal logs listener that works with any platform through the interface system.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -7,30 +7,61 @@ import json
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
import websockets
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
from monitoring.base_listener import BaseTokenListener
|
||||
from monitoring.logs_event_processor import LogsEventProcessor
|
||||
from trading.base import TokenInfo
|
||||
from platforms import get_platform_implementations
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class LogsListener(BaseTokenListener):
|
||||
"""WebSocket listener for pump.fun token creation events using logsSubscribe."""
|
||||
class UniversalLogsListener(BaseTokenListener):
|
||||
"""Universal logs listener that works with any platform."""
|
||||
|
||||
def __init__(self, wss_endpoint: str, pump_program: Pubkey):
|
||||
"""Initialize token listener.
|
||||
def __init__(
|
||||
self,
|
||||
wss_endpoint: str,
|
||||
platforms: list[Platform] | None = None,
|
||||
):
|
||||
"""Initialize universal logs listener.
|
||||
|
||||
Args:
|
||||
wss_endpoint: WebSocket endpoint URL
|
||||
pump_program: Pump.fun program address
|
||||
platforms: List of platforms to monitor (if None, monitor all supported platforms)
|
||||
"""
|
||||
super().__init__()
|
||||
self.wss_endpoint = wss_endpoint
|
||||
self.pump_program = pump_program
|
||||
self.event_processor = LogsEventProcessor(pump_program)
|
||||
self.ping_interval = 20 # seconds
|
||||
|
||||
# Import platform factory and get supported platforms
|
||||
from platforms import platform_factory
|
||||
|
||||
if platforms is None:
|
||||
# Monitor all supported platforms
|
||||
self.platforms = platform_factory.get_supported_platforms()
|
||||
else:
|
||||
self.platforms = platforms
|
||||
|
||||
# Get event parsers for all platforms
|
||||
self.platform_parsers = {}
|
||||
self.platform_program_ids = []
|
||||
|
||||
for platform in self.platforms:
|
||||
try:
|
||||
# We'll need a dummy client for getting the parser
|
||||
from core.client import SolanaClient
|
||||
dummy_client = SolanaClient("http://localhost") # Won't be used for parsing
|
||||
|
||||
implementations = get_platform_implementations(platform, dummy_client)
|
||||
parser = implementations.event_parser
|
||||
self.platform_parsers[platform] = parser
|
||||
self.platform_program_ids.append(str(parser.get_program_id()))
|
||||
|
||||
logger.info(f"Registered platform {platform.value} with program ID {parser.get_program_id()}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not register platform {platform.value}: {e}")
|
||||
|
||||
async def listen_for_tokens(
|
||||
self,
|
||||
@@ -45,6 +76,10 @@ class LogsListener(BaseTokenListener):
|
||||
match_string: Optional string to match in token name/symbol
|
||||
creator_address: Optional creator address to filter by
|
||||
"""
|
||||
if not self.platform_parsers:
|
||||
logger.error("No platform parsers available. Cannot listen for tokens.")
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
async with websockets.connect(self.wss_endpoint) as websocket:
|
||||
@@ -58,9 +93,10 @@ class LogsListener(BaseTokenListener):
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
f"New token detected: {token_info.name} ({token_info.symbol})"
|
||||
f"New token detected: {token_info.name} ({token_info.symbol}) on {token_info.platform.value}"
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
if match_string and not (
|
||||
match_string.lower() in token_info.name.lower()
|
||||
or match_string.lower() in token_info.symbol.lower()
|
||||
@@ -70,10 +106,7 @@ class LogsListener(BaseTokenListener):
|
||||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
creator_address
|
||||
and str(token_info.user) != creator_address
|
||||
):
|
||||
if creator_address and str(token_info.user) != creator_address:
|
||||
logger.info(
|
||||
f"Token not created by {creator_address}. Skipping..."
|
||||
)
|
||||
@@ -86,38 +119,40 @@ class LogsListener(BaseTokenListener):
|
||||
ping_task.cancel()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket connection error: {str(e)}")
|
||||
logger.error(f"WebSocket connection error: {e!s}")
|
||||
logger.info("Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def _subscribe_to_logs(self, websocket) -> None:
|
||||
"""Subscribe to logs mentioning the pump.fun program.
|
||||
"""Subscribe to logs mentioning any of the monitored program IDs.
|
||||
|
||||
Args:
|
||||
websocket: Active WebSocket connection
|
||||
"""
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "logsSubscribe",
|
||||
"params": [
|
||||
{"mentions": [str(self.pump_program)]},
|
||||
{"commitment": "processed"},
|
||||
],
|
||||
}
|
||||
)
|
||||
# Subscribe to logs for all monitored platforms
|
||||
for program_id in self.platform_program_ids:
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": len(self.platform_program_ids), # Use different IDs
|
||||
"method": "logsSubscribe",
|
||||
"params": [
|
||||
{"mentions": [program_id]},
|
||||
{"commitment": "processed"},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
await websocket.send(subscription_message)
|
||||
logger.info(f"Subscribed to logs mentioning program: {self.pump_program}")
|
||||
await websocket.send(subscription_message)
|
||||
logger.info(f"Subscribed to logs mentioning program: {program_id}")
|
||||
|
||||
# Wait for subscription confirmation
|
||||
response = await websocket.recv()
|
||||
response_data = json.loads(response)
|
||||
if "result" in response_data:
|
||||
logger.info(f"Subscription confirmed with ID: {response_data['result']}")
|
||||
else:
|
||||
logger.warning(f"Unexpected subscription response: {response}")
|
||||
# Wait for subscription confirmation
|
||||
response = await websocket.recv()
|
||||
response_data = json.loads(response)
|
||||
if "result" in response_data:
|
||||
logger.info(f"Subscription confirmed with ID: {response_data['result']}")
|
||||
else:
|
||||
logger.warning(f"Unexpected subscription response: {response}")
|
||||
|
||||
async def _ping_loop(self, websocket) -> None:
|
||||
"""Keep connection alive with pings.
|
||||
@@ -131,7 +166,7 @@ class LogsListener(BaseTokenListener):
|
||||
try:
|
||||
pong_waiter = await websocket.ping()
|
||||
await asyncio.wait_for(pong_waiter, timeout=10)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning("Ping timeout - server not responding")
|
||||
# Force reconnection
|
||||
await websocket.close()
|
||||
@@ -139,9 +174,17 @@ class LogsListener(BaseTokenListener):
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Ping error: {str(e)}")
|
||||
logger.error(f"Ping error: {e!s}")
|
||||
|
||||
async def _wait_for_token_creation(self, websocket) -> TokenInfo | None:
|
||||
"""Wait for token creation events from any platform.
|
||||
|
||||
Args:
|
||||
websocket: Active WebSocket connection
|
||||
|
||||
Returns:
|
||||
TokenInfo if a token creation is found, None otherwise
|
||||
"""
|
||||
try:
|
||||
response = await asyncio.wait_for(websocket.recv(), timeout=30)
|
||||
data = json.loads(response)
|
||||
@@ -153,15 +196,20 @@ class LogsListener(BaseTokenListener):
|
||||
logs = log_data.get("logs", [])
|
||||
signature = log_data.get("signature", "unknown")
|
||||
|
||||
# Use the processor to extract token info
|
||||
return self.event_processor.process_program_logs(logs, signature)
|
||||
# Try each platform's event parser
|
||||
for platform, parser in self.platform_parsers.items():
|
||||
token_info = parser.parse_token_creation_from_logs(logs, signature)
|
||||
if token_info:
|
||||
return token_info
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return None
|
||||
|
||||
except TimeoutError:
|
||||
logger.debug("No data received for 30 seconds")
|
||||
except websockets.exceptions.ConnectionClosed:
|
||||
logger.warning("WebSocket connection closed")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing WebSocket message: {str(e)}")
|
||||
logger.error(f"Error processing WebSocket message: {e!s}")
|
||||
|
||||
return None
|
||||
return None
|
||||
Reference in New Issue
Block a user