wip(core): platform aware trading

This commit is contained in:
smypmsa
2025-08-02 13:27:07 +00:00
parent 9ec9eeb793
commit 227ef0b3bd
9 changed files with 699 additions and 506 deletions
+19 -33
View File
@@ -6,12 +6,10 @@ import asyncio
from collections.abc import Awaitable, Callable
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 platforms import get_platform_implementations
from utils.logger import get_logger
logger = get_logger(__name__)
@@ -27,14 +25,7 @@ class UniversalGeyserListener(BaseTokenListener):
geyser_auth_type: str,
platforms: list[Platform] | None = None,
):
"""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')
platforms: List of platforms to monitor (if None, monitor all supported platforms)
"""
"""Initialize universal Geyser listener."""
super().__init__()
self.geyser_endpoint = geyser_endpoint
self.geyser_api_token = geyser_api_token
@@ -51,7 +42,6 @@ class UniversalGeyserListener(BaseTokenListener):
from platforms import platform_factory
if platforms is None:
# Monitor all supported platforms
self.platforms = platform_factory.get_supported_platforms()
else:
self.platforms = platforms
@@ -60,13 +50,13 @@ class UniversalGeyserListener(BaseTokenListener):
self.platform_parsers = {}
self.platform_program_ids = set()
# Create a temporary client for getting parsers
from core.client import SolanaClient
temp_client = SolanaClient("http://temp")
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)
implementations = platform_factory.create_for_platform(platform, temp_client)
parser = implementations.event_parser
self.platform_parsers[platform] = parser
self.platform_program_ids.add(parser.get_program_id())
@@ -78,6 +68,7 @@ class UniversalGeyserListener(BaseTokenListener):
async def _create_geyser_connection(self):
"""Establish a secure connection to the Geyser endpoint."""
if self.auth_type == "x-token":
auth = grpc.metadata_call_credentials(
lambda _, callback: callback(
@@ -92,10 +83,12 @@ class UniversalGeyserListener(BaseTokenListener):
)
creds = grpc.composite_channel_credentials(grpc.ssl_channel_credentials(), auth)
channel = grpc.aio.secure_channel(self.geyser_endpoint, creds)
return geyser_pb2_grpc.GeyserStub(channel), channel
def _create_subscription_request(self):
"""Create a subscription request for all monitored platforms."""
request = geyser_pb2.SubscribeRequest()
# Add all platform program IDs to the filter
@@ -113,13 +106,7 @@ class UniversalGeyserListener(BaseTokenListener):
match_string: str | None = None,
creator_address: str | None = None,
) -> None:
"""Listen for new token creations using Geyser subscription.
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
"""
"""Listen for new token creations using Geyser subscription."""
if not self.platform_parsers:
logger.error("No platform parsers available. Cannot listen for tokens.")
return
@@ -161,8 +148,12 @@ class UniversalGeyserListener(BaseTokenListener):
await token_callback(token_info)
except grpc.aio.AioRpcError as e:
logger.error(f"gRPC error: {e.details()}")
except Exception as e:
import grpc
if isinstance(e, grpc.aio.AioRpcError):
logger.error(f"gRPC error: {e.details()}")
else:
logger.error(f"Geyser error: {e}")
await asyncio.sleep(5)
finally:
@@ -174,14 +165,7 @@ class UniversalGeyserListener(BaseTokenListener):
await asyncio.sleep(10)
async def _process_update(self, update) -> TokenInfo | None:
"""Process a Geyser update and extract token creation info.
Args:
update: Geyser update from the subscription
Returns:
TokenInfo if a token creation is found, None otherwise
"""
"""Process a Geyser update and extract token creation info."""
try:
if not update.HasField("transaction"):
return None
@@ -191,6 +175,8 @@ class UniversalGeyserListener(BaseTokenListener):
if msg is None:
return None
from solders.pubkey import Pubkey
for ix in msg.instructions:
# Check which platform this instruction belongs to
program_idx = ix.program_id_index
+9 -23
View File
@@ -1,7 +1,6 @@
"""
Universal logs listener that works with any platform through the interface system.
"""
import asyncio
import json
from collections.abc import Awaitable, Callable
@@ -10,7 +9,6 @@ import websockets
from interfaces.core import Platform, TokenInfo
from monitoring.base_listener import BaseTokenListener
from platforms import get_platform_implementations
from utils.logger import get_logger
logger = get_logger(__name__)
@@ -47,13 +45,13 @@ class UniversalLogsListener(BaseTokenListener):
self.platform_parsers = {}
self.platform_program_ids = []
# Create a temporary client for getting parsers (stateless parsers don't use it)
from core.client import SolanaClient
temp_client = SolanaClient("http://temp")
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)
implementations = platform_factory.create_for_platform(platform, temp_client)
parser = implementations.event_parser
self.platform_parsers[platform] = parser
self.platform_program_ids.append(str(parser.get_program_id()))
@@ -130,11 +128,11 @@ class UniversalLogsListener(BaseTokenListener):
websocket: Active WebSocket connection
"""
# Subscribe to logs for all monitored platforms
for program_id in self.platform_program_ids:
for i, program_id in enumerate(self.platform_program_ids):
subscription_message = json.dumps(
{
"jsonrpc": "2.0",
"id": len(self.platform_program_ids), # Use different IDs
"id": i + 1,
"method": "logsSubscribe",
"params": [
{"mentions": [program_id]},
@@ -155,11 +153,7 @@ class UniversalLogsListener(BaseTokenListener):
logger.warning(f"Unexpected subscription response: {response}")
async def _ping_loop(self, websocket) -> None:
"""Keep connection alive with pings.
Args:
websocket: Active WebSocket connection
"""
"""Keep connection alive with pings."""
try:
while True:
await asyncio.sleep(self.ping_interval)
@@ -168,7 +162,6 @@ class UniversalLogsListener(BaseTokenListener):
await asyncio.wait_for(pong_waiter, timeout=10)
except TimeoutError:
logger.warning("Ping timeout - server not responding")
# Force reconnection
await websocket.close()
return
except asyncio.CancelledError:
@@ -177,14 +170,7 @@ class UniversalLogsListener(BaseTokenListener):
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
"""
"""Wait for token creation events from any platform."""
try:
response = await asyncio.wait_for(websocket.recv(), timeout=30)
data = json.loads(response)