mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-08 04:57:45 +00:00
wip(core): platform aware trading
This commit is contained in:
+341
-21
@@ -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',
|
||||
]
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user