315 lines
12 KiB
Python
315 lines
12 KiB
Python
"""
|
|
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) |