wip(core): platform aware trading

This commit is contained in:
smypmsa
2025-08-02 09:25:39 +00:00
parent 60c452da0f
commit bb35101a8f
31 changed files with 3907 additions and 1094 deletions
+341 -21
View File
@@ -1,5 +1,9 @@
"""
Base interfaces for trading operations.
Enhanced base interfaces for trading operations with platform support.
This module provides the complete enhanced base classes that replace the existing
trading/base.py while maintaining full backward compatibility. It integrates the
new interface system with the existing trading infrastructure.
"""
from abc import ABC, abstractmethod
@@ -8,13 +12,16 @@ from typing import Any
from solders.pubkey import Pubkey
from core.pubkeys import PumpAddresses
from interfaces.core import Platform
# Import the new enhanced TokenInfo and Platform from interfaces
from interfaces.core import TokenInfo as EnhancedTokenInfo
# Keep the original TokenInfo structure for backward compatibility
@dataclass
class TokenInfo:
"""Token information."""
class TokenInfo_Legacy:
"""Legacy token information structure for backward compatibility."""
name: str
symbol: str
uri: str
@@ -26,14 +33,14 @@ class TokenInfo:
creator_vault: Pubkey
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "TokenInfo":
def from_dict(cls, data: dict[str, Any]) -> "TokenInfo_Legacy":
"""Create TokenInfo from dictionary.
Args:
data: Dictionary with token data
Returns:
TokenInfo instance
TokenInfo_Legacy instance
"""
return cls(
name=data["name"],
@@ -68,40 +75,353 @@ class TokenInfo:
@dataclass
class TradeResult:
"""Result of a trading operation."""
"""Enhanced result of a trading operation with platform support."""
success: bool
platform: Platform = Platform.PUMP_FUN # Add platform tracking
tx_signature: str | None = None
error_message: str | None = None
amount: float | None = None
price: float | None = None
def to_dict(self) -> dict[str, Any]:
"""Convert to dictionary for logging/serialization.
Returns:
Dictionary representation of the trade result
"""
return {
"success": self.success,
"platform": self.platform.value,
"tx_signature": self.tx_signature,
"error_message": self.error_message,
"amount": self.amount,
"price": self.price,
}
class Trader(ABC):
"""Base interface for trading operations."""
"""Enhanced base interface for trading operations with platform support."""
@abstractmethod
async def execute(self, *args, **kwargs) -> TradeResult:
async def execute(self, token_info: "TokenInfo", *args, **kwargs) -> TradeResult:
"""Execute trading operation.
Args:
token_info: Enhanced token information with platform support
Returns:
TradeResult with operation outcome
TradeResult with operation outcome including platform info
"""
pass
def _get_relevant_accounts(self, token_info: TokenInfo) -> list[Pubkey]:
def _get_relevant_accounts(self, token_info: "TokenInfo") -> list[Pubkey]:
"""
Get the list of accounts relevant for calculating the priority fee.
This is now platform-agnostic and should be overridden by platform-specific traders.
Args:
token_info: Token information for the buy/sell operation.
token_info: Enhanced token information
Returns:
list[Pubkey]: List of relevant accounts.
List of relevant accounts (default implementation for pump.fun compatibility)
"""
return [
token_info.mint, # Token mint address
token_info.bonding_curve, # Bonding curve address
PumpAddresses.PROGRAM, # Pump.fun program address
PumpAddresses.FEE, # Pump.fun fee account
]
# Default implementation maintains pump.fun compatibility
from core.pubkeys import PumpAddresses
accounts = [token_info.mint]
if token_info.bonding_curve:
accounts.append(token_info.bonding_curve)
if token_info.pool_state: # For other platforms
accounts.append(token_info.pool_state)
# Add platform program
if token_info.platform == Platform.PUMP_FUN:
accounts.extend([
PumpAddresses.PROGRAM,
PumpAddresses.FEE,
])
# Other platforms would add their specific accounts here
return accounts
# Use the enhanced TokenInfo as the main TokenInfo class
# This provides the new functionality while maintaining the same import path
TokenInfo = EnhancedTokenInfo
def upgrade_token_info(legacy_token_info: TokenInfo_Legacy) -> TokenInfo:
"""Convert legacy TokenInfo to enhanced TokenInfo.
This function allows existing code that creates legacy TokenInfo objects
to be upgraded to the new enhanced format.
Args:
legacy_token_info: Legacy TokenInfo instance
Returns:
Enhanced TokenInfo with platform information
"""
return TokenInfo(
name=legacy_token_info.name,
symbol=legacy_token_info.symbol,
uri=legacy_token_info.uri,
mint=legacy_token_info.mint,
platform=Platform.PUMP_FUN, # Default to pump.fun for legacy tokens
bonding_curve=legacy_token_info.bonding_curve,
associated_bonding_curve=legacy_token_info.associated_bonding_curve,
user=legacy_token_info.user,
creator=legacy_token_info.creator,
creator_vault=legacy_token_info.creator_vault,
)
def create_legacy_token_info(enhanced_token_info: TokenInfo) -> TokenInfo_Legacy:
"""Convert enhanced TokenInfo back to legacy TokenInfo if needed.
This function allows the enhanced TokenInfo to be used with existing
code that expects the legacy format.
Args:
enhanced_token_info: Enhanced TokenInfo instance
Returns:
Legacy TokenInfo instance
Raises:
ValueError: If enhanced TokenInfo doesn't have required pump.fun fields
"""
if enhanced_token_info.platform != Platform.PUMP_FUN:
raise ValueError("Can only convert pump.fun tokens to legacy format")
if not all([
enhanced_token_info.bonding_curve,
enhanced_token_info.associated_bonding_curve,
enhanced_token_info.creator_vault
]):
raise ValueError("Enhanced TokenInfo missing required pump.fun fields")
return TokenInfo_Legacy(
name=enhanced_token_info.name,
symbol=enhanced_token_info.symbol,
uri=enhanced_token_info.uri,
mint=enhanced_token_info.mint,
bonding_curve=enhanced_token_info.bonding_curve,
associated_bonding_curve=enhanced_token_info.associated_bonding_curve,
user=enhanced_token_info.user or enhanced_token_info.creator,
creator=enhanced_token_info.creator or enhanced_token_info.user,
creator_vault=enhanced_token_info.creator_vault,
)
def create_pump_fun_token_info(
name: str,
symbol: str,
uri: str,
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
user: Pubkey,
creator: Pubkey | None = None,
creator_vault: Pubkey | None = None,
**kwargs
) -> TokenInfo:
"""Convenience function to create pump.fun TokenInfo with proper platform setting.
Args:
name: Token name
symbol: Token symbol
uri: Token metadata URI
mint: Token mint address
bonding_curve: Bonding curve address
associated_bonding_curve: Associated bonding curve address
user: User/trader address
creator: Creator address (defaults to user if not provided)
creator_vault: Creator vault address (will be derived if not provided)
**kwargs: Additional fields for TokenInfo
Returns:
Enhanced TokenInfo configured for pump.fun
"""
from core.pubkeys import PumpAddresses
# Default creator to user if not provided
if creator is None:
creator = user
# Derive creator vault if not provided
if creator_vault is None:
creator_vault, _ = Pubkey.find_program_address(
[b"creator-vault", bytes(creator)],
PumpAddresses.PROGRAM,
)
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,
**kwargs
)
def create_lets_bonk_token_info(
name: str,
symbol: str,
uri: str,
mint: Pubkey,
pool_state: Pubkey,
base_vault: Pubkey,
quote_vault: Pubkey,
user: Pubkey,
creator: Pubkey | None = None,
**kwargs
) -> TokenInfo:
"""Convenience function to create LetsBonk TokenInfo with proper platform setting.
Args:
name: Token name
symbol: Token symbol
uri: Token metadata URI
mint: Token mint address
pool_state: Pool state address
base_vault: Base token vault address
quote_vault: Quote token vault address
user: User/trader address
creator: Creator address (defaults to user if not provided)
**kwargs: Additional fields for TokenInfo
Returns:
Enhanced TokenInfo configured for LetsBonk
"""
# Default creator to user if not provided
if creator is None:
creator = user
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,
**kwargs
)
def is_pump_fun_token(token_info: TokenInfo) -> bool:
"""Check if a TokenInfo is for pump.fun platform.
Args:
token_info: Token information to check
Returns:
True if token is for pump.fun platform
"""
return token_info.platform == Platform.PUMP_FUN
def is_lets_bonk_token(token_info: TokenInfo) -> bool:
"""Check if a TokenInfo is for LetsBonk platform.
Args:
token_info: Token information to check
Returns:
True if token is for LetsBonk platform
"""
return token_info.platform == Platform.LETS_BONK
def get_platform_specific_fields(token_info: TokenInfo) -> dict[str, Any]:
"""Get platform-specific fields from TokenInfo.
Args:
token_info: Token information
Returns:
Dictionary of platform-specific fields
"""
if token_info.platform == Platform.PUMP_FUN:
return {
"bonding_curve": token_info.bonding_curve,
"associated_bonding_curve": token_info.associated_bonding_curve,
"creator_vault": token_info.creator_vault,
}
elif token_info.platform == Platform.LETS_BONK:
return {
"pool_state": token_info.pool_state,
"base_vault": token_info.base_vault,
"quote_vault": token_info.quote_vault,
}
else:
return {}
def validate_token_info(token_info: TokenInfo) -> bool:
"""Validate that TokenInfo has required fields for its platform.
Args:
token_info: Token information to validate
Returns:
True if TokenInfo is valid for its platform
"""
# Check common required fields
if not all([
token_info.name,
token_info.symbol,
token_info.mint,
token_info.platform,
]):
return False
# Check platform-specific required fields
if token_info.platform == Platform.PUMP_FUN:
return all([
token_info.bonding_curve,
token_info.associated_bonding_curve,
])
elif token_info.platform == Platform.LETS_BONK:
return all([
token_info.pool_state,
token_info.base_vault,
token_info.quote_vault,
])
return True
# Backward compatibility exports
# This allows existing imports to continue working
__all__ = [
'Platform', # Platform enum
'TokenInfo', # Enhanced TokenInfo (main export)
'TokenInfo_Legacy', # Legacy TokenInfo for compatibility
'TradeResult', # Enhanced TradeResult
'Trader', # Enhanced Trader base class
'create_legacy_token_info',
'create_lets_bonk_token_info',
# Convenience functions
'create_pump_fun_token_info',
'get_platform_specific_fields',
'is_lets_bonk_token',
# Utility functions
'is_pump_fun_token',
# Conversion functions
'upgrade_token_info',
'validate_token_info',
]
+315
View File
@@ -0,0 +1,315 @@
"""
Platform-aware trader implementations that use the interface system.
This module provides new trader classes that work with any platform
through the interface system, while maintaining compatibility with existing code.
"""
from solders.pubkey import Pubkey
from core.client import SolanaClient
from core.priority_fee.manager import PriorityFeeManager
from core.pubkeys import LAMPORTS_PER_SOL, TOKEN_DECIMALS
from core.wallet import Wallet
from interfaces.core import AddressProvider, Platform, TokenInfo
from platforms import get_platform_implementations
from trading.base import Trader, TradeResult
from utils.logger import get_logger
logger = get_logger(__name__)
class PlatformAwareBuyer(Trader):
"""Platform-aware token buyer that works with any supported platform."""
def __init__(
self,
client: SolanaClient,
wallet: Wallet,
priority_fee_manager: PriorityFeeManager,
amount: float,
slippage: float = 0.01,
max_retries: int = 5,
extreme_fast_token_amount: int = 0,
extreme_fast_mode: bool = False,
):
"""Initialize platform-aware token buyer.
Args:
client: Solana client for RPC calls
wallet: Wallet for signing transactions
priority_fee_manager: Priority fee manager
amount: Amount of SOL to spend
slippage: Slippage tolerance (0.01 = 1%)
max_retries: Maximum number of retry attempts
extreme_fast_token_amount: Amount of token to buy if extreme fast mode is enabled
extreme_fast_mode: If enabled, avoid fetching pool state for price estimation
"""
self.client = client
self.wallet = wallet
self.priority_fee_manager = priority_fee_manager
self.amount = amount
self.slippage = slippage
self.max_retries = max_retries
self.extreme_fast_mode = extreme_fast_mode
self.extreme_fast_token_amount = extreme_fast_token_amount
async def execute(self, token_info: TokenInfo, *args, **kwargs) -> TradeResult:
"""Execute buy operation using platform-specific implementations.
Args:
token_info: Enhanced token information with platform
Returns:
TradeResult with buy outcome
"""
try:
# Get platform-specific implementations
implementations = get_platform_implementations(token_info.platform, self.client)
address_provider = implementations.address_provider
instruction_builder = implementations.instruction_builder
curve_manager = implementations.curve_manager
# Convert amount to lamports
amount_lamports = int(self.amount * LAMPORTS_PER_SOL)
if self.extreme_fast_mode:
# Skip the wait and directly calculate the amount
token_amount = self.extreme_fast_token_amount
token_price_sol = self.amount / token_amount if token_amount > 0 else 0
else:
# Get pool address based on platform
pool_address = self._get_pool_address(token_info, address_provider)
# Regular behavior with RPC call
token_price_sol = await curve_manager.calculate_price(pool_address)
token_amount = self.amount / token_price_sol if token_price_sol > 0 else 0
# Calculate minimum token amount with slippage
minimum_token_amount = token_amount * (1 - self.slippage)
minimum_token_amount_raw = int(minimum_token_amount * 10**TOKEN_DECIMALS)
# Calculate maximum SOL to spend with slippage
max_amount_lamports = int(amount_lamports * (1 + self.slippage))
# Build buy instructions
instructions = await instruction_builder.build_buy_instruction(
token_info,
self.wallet.pubkey,
max_amount_lamports, # amount_in (SOL)
minimum_token_amount_raw, # minimum_amount_out (tokens)
address_provider
)
# Get accounts for priority fee calculation
priority_accounts = instruction_builder.get_required_accounts_for_buy(
token_info, self.wallet.pubkey, address_provider
)
logger.info(
f"Buying {token_amount:.6f} tokens at {token_price_sol:.8f} SOL per token"
)
logger.info(
f"Total cost: {self.amount:.6f} SOL (max: {max_amount_lamports / LAMPORTS_PER_SOL:.6f} SOL)"
)
# Send transaction
tx_signature = await self.client.build_and_send_transaction(
instructions,
self.wallet.keypair,
skip_preflight=True,
max_retries=self.max_retries,
priority_fee=await self.priority_fee_manager.calculate_priority_fee(
priority_accounts
),
)
success = await self.client.confirm_transaction(tx_signature)
if success:
logger.info(f"Buy transaction confirmed: {tx_signature}")
return TradeResult(
success=True,
platform=token_info.platform,
tx_signature=tx_signature,
amount=token_amount,
price=token_price_sol,
)
else:
return TradeResult(
success=False,
platform=token_info.platform,
error_message=f"Transaction failed to confirm: {tx_signature}",
)
except Exception as e:
logger.error(f"Buy operation failed: {e!s}")
return TradeResult(
success=False,
platform=token_info.platform,
error_message=str(e)
)
def _get_pool_address(self, token_info: TokenInfo, address_provider: AddressProvider) -> Pubkey:
"""Get the pool/curve address for price calculations.
Args:
token_info: Token information
address_provider: Platform address provider
Returns:
Pool/curve address
"""
if token_info.platform == Platform.PUMP_FUN:
return token_info.bonding_curve or address_provider.derive_pool_address(token_info.mint)
elif token_info.platform == Platform.LETS_BONK:
return token_info.pool_state or address_provider.derive_pool_address(token_info.mint)
else:
# Fallback to deriving the address
return address_provider.derive_pool_address(token_info.mint)
class PlatformAwareSeller(Trader):
"""Platform-aware token seller that works with any supported platform."""
def __init__(
self,
client: SolanaClient,
wallet: Wallet,
priority_fee_manager: PriorityFeeManager,
slippage: float = 0.25,
max_retries: int = 5,
):
"""Initialize platform-aware token seller.
Args:
client: Solana client for RPC calls
wallet: Wallet for signing transactions
priority_fee_manager: Priority fee manager
slippage: Slippage tolerance (0.25 = 25%)
max_retries: Maximum number of retry attempts
"""
self.client = client
self.wallet = wallet
self.priority_fee_manager = priority_fee_manager
self.slippage = slippage
self.max_retries = max_retries
async def execute(self, token_info: TokenInfo, *args, **kwargs) -> TradeResult:
"""Execute sell operation using platform-specific implementations.
Args:
token_info: Enhanced token information with platform
Returns:
TradeResult with sell outcome
"""
try:
# Get platform-specific implementations
implementations = get_platform_implementations(token_info.platform, self.client)
address_provider = implementations.address_provider
instruction_builder = implementations.instruction_builder
curve_manager = implementations.curve_manager
# Get user's token account and balance
user_token_account = address_provider.derive_user_token_account(
self.wallet.pubkey, token_info.mint
)
token_balance = await self.client.get_token_account_balance(user_token_account)
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
logger.info(f"Token balance: {token_balance_decimal}")
if token_balance == 0:
logger.info("No tokens to sell.")
return TradeResult(
success=False,
platform=token_info.platform,
error_message="No tokens to sell"
)
# Get pool address and current price
pool_address = self._get_pool_address(token_info, address_provider)
token_price_sol = await curve_manager.calculate_price(pool_address)
logger.info(f"Price per Token: {token_price_sol:.8f} SOL")
# Calculate minimum SOL output with slippage
expected_sol_output = float(token_balance_decimal) * float(token_price_sol)
min_sol_output = int((expected_sol_output * (1 - self.slippage)) * LAMPORTS_PER_SOL)
logger.info(f"Selling {token_balance_decimal} tokens")
logger.info(f"Expected SOL output: {expected_sol_output:.8f} SOL")
logger.info(
f"Minimum SOL output (with {self.slippage * 100}% slippage): {min_sol_output / LAMPORTS_PER_SOL:.8f} SOL"
)
# Build sell instructions
instructions = await instruction_builder.build_sell_instruction(
token_info,
self.wallet.pubkey,
token_balance, # amount_in (tokens)
min_sol_output, # minimum_amount_out (SOL)
address_provider
)
# Get accounts for priority fee calculation
priority_accounts = instruction_builder.get_required_accounts_for_sell(
token_info, self.wallet.pubkey, address_provider
)
# Send transaction
tx_signature = await self.client.build_and_send_transaction(
instructions,
self.wallet.keypair,
skip_preflight=True,
max_retries=self.max_retries,
priority_fee=await self.priority_fee_manager.calculate_priority_fee(
priority_accounts
),
)
success = await self.client.confirm_transaction(tx_signature)
if success:
logger.info(f"Sell transaction confirmed: {tx_signature}")
return TradeResult(
success=True,
platform=token_info.platform,
tx_signature=tx_signature,
amount=token_balance_decimal,
price=token_price_sol,
)
else:
return TradeResult(
success=False,
platform=token_info.platform,
error_message=f"Transaction failed to confirm: {tx_signature}",
)
except Exception as e:
logger.error(f"Sell operation failed: {e!s}")
return TradeResult(
success=False,
platform=token_info.platform,
error_message=str(e)
)
def _get_pool_address(self, token_info: TokenInfo, address_provider: AddressProvider) -> Pubkey:
"""Get the pool/curve address for price calculations.
Args:
token_info: Token information
address_provider: Platform address provider
Returns:
Pool/curve address
"""
if token_info.platform == Platform.PUMP_FUN:
return token_info.bonding_curve or address_provider.derive_pool_address(token_info.mint)
elif token_info.platform == Platform.LETS_BONK:
return token_info.pool_state or address_provider.derive_pool_address(token_info.mint)
else:
# Fallback to deriving the address
return address_provider.derive_pool_address(token_info.mint)