mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-07 12:37:47 +00:00
Reduce RPC delays when selling (#150)
* feat(trading): enhance sell execution with token amount and price parameters to reduce RPC delays * feat(trading): add method to fetch actual token balance after transaction to improve accuracy * feat(trading): update account data size limit and enhance price calculations
This commit is contained in:
+107
-39
@@ -130,6 +130,35 @@ class PlatformAwareBuyer(Trader):
|
||||
|
||||
if success:
|
||||
logger.info(f"Buy transaction confirmed: {tx_signature}")
|
||||
|
||||
# Fetch actual tokens and SOL spent from transaction
|
||||
# Uses preBalances/postBalances to get exact amounts
|
||||
sol_destination = self._get_sol_destination(
|
||||
token_info, address_provider
|
||||
)
|
||||
tokens_raw, sol_spent = await self.client.get_buy_transaction_details(
|
||||
str(tx_signature), token_info.mint, sol_destination
|
||||
)
|
||||
|
||||
if tokens_raw is not None and sol_spent is not None:
|
||||
actual_amount = tokens_raw / 10**TOKEN_DECIMALS
|
||||
actual_price = (sol_spent / LAMPORTS_PER_SOL) / actual_amount
|
||||
logger.info(
|
||||
f"Actual tokens received: {actual_amount:.6f} "
|
||||
f"(expected: {token_amount:.6f})"
|
||||
)
|
||||
logger.info(
|
||||
f"Actual SOL spent: {sol_spent / LAMPORTS_PER_SOL:.10f} SOL"
|
||||
)
|
||||
logger.info(f"Actual price: {actual_price:.10f} SOL/token")
|
||||
token_amount = actual_amount
|
||||
token_price_sol = actual_price
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Failed to parse transaction details: tokens={tokens_raw}, "
|
||||
f"sol_spent={sol_spent}"
|
||||
)
|
||||
|
||||
return TradeResult(
|
||||
success=True,
|
||||
platform=token_info.platform,
|
||||
@@ -165,6 +194,42 @@ class PlatformAwareBuyer(Trader):
|
||||
# Fallback to deriving the address using platform provider
|
||||
return address_provider.derive_pool_address(token_info.mint)
|
||||
|
||||
def _get_sol_destination(
|
||||
self, token_info: TokenInfo, address_provider: AddressProvider
|
||||
) -> Pubkey:
|
||||
"""Get the address where SOL is sent during a buy transaction.
|
||||
|
||||
For pump.fun: SOL goes to the bonding curve
|
||||
For letsbonk: SOL goes to the quote_vault (WSOL vault)
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
address_provider: Platform-specific address provider
|
||||
|
||||
Returns:
|
||||
Address where SOL is transferred during buy
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If platform SOL destination is not implemented
|
||||
"""
|
||||
if token_info.platform == Platform.PUMP_FUN:
|
||||
# For pump.fun, SOL goes directly to bonding curve
|
||||
if hasattr(token_info, "bonding_curve") and token_info.bonding_curve:
|
||||
return token_info.bonding_curve
|
||||
return address_provider.derive_pool_address(token_info.mint)
|
||||
elif token_info.platform == Platform.LETS_BONK:
|
||||
# For letsbonk, SOL goes to quote_vault (WSOL vault)
|
||||
if hasattr(token_info, "quote_vault") and token_info.quote_vault:
|
||||
return token_info.quote_vault
|
||||
# Derive quote_vault if not available
|
||||
return address_provider.derive_quote_vault(token_info.mint)
|
||||
|
||||
raise NotImplementedError(
|
||||
f"SOL destination not implemented for platform {token_info.platform.value}. "
|
||||
f"Add platform-specific logic to _get_sol_destination() to specify where "
|
||||
f"SOL is transferred during buy transactions for this platform."
|
||||
)
|
||||
|
||||
def _get_cu_override(self, operation: str, platform: Platform) -> int | None:
|
||||
"""Get compute unit override from configuration.
|
||||
|
||||
@@ -202,8 +267,35 @@ class PlatformAwareSeller(Trader):
|
||||
self.max_retries = max_retries
|
||||
self.compute_units = compute_units or {}
|
||||
|
||||
async def execute(self, token_info: TokenInfo) -> TradeResult:
|
||||
"""Execute sell operation using platform-specific implementations."""
|
||||
async def execute(
|
||||
self, token_info: TokenInfo, token_amount: float, token_price: float
|
||||
) -> TradeResult:
|
||||
"""Execute sell operation using platform-specific implementations.
|
||||
|
||||
Args:
|
||||
token_info: Token information for the sell operation
|
||||
token_amount: Token amount to sell (from buy result). Required to avoid
|
||||
RPC balance query delays.
|
||||
token_price: Token price in SOL (from buy result). Required to avoid
|
||||
RPC pool state query delays.
|
||||
|
||||
Returns:
|
||||
TradeResult with operation outcome
|
||||
|
||||
Raises:
|
||||
ValueError: If required parameters are not provided
|
||||
"""
|
||||
if token_amount is None:
|
||||
raise ValueError(
|
||||
"token_amount is required for sell operation. "
|
||||
"Pass the amount from buy result to avoid RPC delays."
|
||||
)
|
||||
if token_price is None or token_price <= 0:
|
||||
raise ValueError(
|
||||
"token_price is required for sell operation and must be positive. "
|
||||
"Pass the price from buy result to avoid RPC delays."
|
||||
)
|
||||
|
||||
try:
|
||||
# Get platform-specific implementations
|
||||
implementations = get_platform_implementations(
|
||||
@@ -211,19 +303,14 @@ class PlatformAwareSeller(Trader):
|
||||
)
|
||||
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
|
||||
)
|
||||
# Use pre-known amount and price (no RPC delay)
|
||||
token_balance_decimal = token_amount
|
||||
token_balance = int(token_amount * 10**TOKEN_DECIMALS)
|
||||
token_price_sol = token_price
|
||||
|
||||
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}")
|
||||
logger.info(f"Token balance: {token_balance_decimal:.6f}")
|
||||
logger.info(f"Price per Token (from buy): {token_price_sol:.8f} SOL")
|
||||
|
||||
if token_balance == 0:
|
||||
logger.info("No tokens to sell.")
|
||||
@@ -233,38 +320,19 @@ class PlatformAwareSeller(Trader):
|
||||
error_message="No tokens to sell",
|
||||
)
|
||||
|
||||
# Get pool address and current price using platform-agnostic method
|
||||
pool_address = self._get_pool_address(token_info, address_provider)
|
||||
# Fetch pool state to get price and mayhem mode status
|
||||
pool_state = await curve_manager.get_pool_state(pool_address)
|
||||
token_price_sol = pool_state.get("price_per_token")
|
||||
|
||||
# Validate price_per_token is present and positive
|
||||
if token_price_sol is None or token_price_sol <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid price_per_token: {token_price_sol} for pool {pool_address} "
|
||||
f"(mint: {token_info.mint}) - cannot execute sell with zero/invalid price"
|
||||
)
|
||||
|
||||
# Set is_mayhem_mode from bonding curve state
|
||||
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
|
||||
|
||||
logger.info(f"Price per Token: {token_price_sol:.8f} SOL")
|
||||
|
||||
# Calculate expected SOL output
|
||||
# Calculate expected SOL output with slippage protection
|
||||
expected_sol_output = token_balance_decimal * token_price_sol
|
||||
|
||||
# Calculate minimum SOL output with slippage protection
|
||||
min_sol_output = int(
|
||||
(expected_sol_output * (1 - self.slippage)) * LAMPORTS_PER_SOL
|
||||
min_sol_output = max(
|
||||
1,
|
||||
int((expected_sol_output * (1 - self.slippage)) * LAMPORTS_PER_SOL),
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Selling {token_balance_decimal} tokens on {token_info.platform.value}"
|
||||
)
|
||||
logger.info(f"Expected SOL output: {expected_sol_output:.8f} SOL")
|
||||
logger.info(f"Expected SOL output: {expected_sol_output:.10f} SOL")
|
||||
logger.info(
|
||||
f"Minimum SOL output (with {self.slippage * 100:.1f}% slippage): {min_sol_output / LAMPORTS_PER_SOL:.8f} SOL"
|
||||
f"Minimum SOL output (with {self.slippage * 100:.1f}% slippage): "
|
||||
f"{min_sol_output / LAMPORTS_PER_SOL:.10f} SOL ({min_sol_output} lamports)"
|
||||
)
|
||||
|
||||
# Build sell instructions using platform-specific builder
|
||||
|
||||
@@ -193,7 +193,9 @@ class UniversalTrader:
|
||||
|
||||
# State tracking
|
||||
self.traded_mints: set[Pubkey] = set()
|
||||
self.traded_token_programs: dict[str, Pubkey] = {} # Maps mint (as string) to token_program_id
|
||||
self.traded_token_programs: dict[
|
||||
str, Pubkey
|
||||
] = {} # Maps mint (as string) to token_program_id
|
||||
self.token_queue: asyncio.Queue = asyncio.Queue()
|
||||
self.processing: bool = False
|
||||
self.processed_tokens: set[str] = set()
|
||||
@@ -329,8 +331,7 @@ class UniversalTrader:
|
||||
# Build parallel lists of mints and token_program_ids
|
||||
mints_list = list(self.traded_mints)
|
||||
token_program_ids = [
|
||||
self.traded_token_programs.get(str(mint))
|
||||
for mint in mints_list
|
||||
self.traded_token_programs.get(str(mint)) for mint in mints_list
|
||||
]
|
||||
await handle_cleanup_post_session(
|
||||
self.solana_client,
|
||||
@@ -465,7 +466,7 @@ class UniversalTrader:
|
||||
if self.exit_strategy == "tp_sl":
|
||||
await self._handle_tp_sl_exit(token_info, buy_result)
|
||||
elif self.exit_strategy == "time_based":
|
||||
await self._handle_time_based_exit(token_info)
|
||||
await self._handle_time_based_exit(token_info, buy_result)
|
||||
elif self.exit_strategy == "manual":
|
||||
logger.info("Manual exit strategy - position will remain open")
|
||||
else:
|
||||
@@ -512,13 +513,23 @@ class UniversalTrader:
|
||||
# Monitor position until exit condition is met
|
||||
await self._monitor_position_until_exit(token_info, position)
|
||||
|
||||
async def _handle_time_based_exit(self, token_info: TokenInfo) -> None:
|
||||
"""Handle legacy time-based exit strategy."""
|
||||
async def _handle_time_based_exit(
|
||||
self, token_info: TokenInfo, buy_result: TradeResult
|
||||
) -> None:
|
||||
"""Handle legacy time-based exit strategy.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
buy_result: Result from the buy operation (contains token amount)
|
||||
"""
|
||||
logger.info(f"Waiting for {self.wait_time_after_buy} seconds before selling...")
|
||||
await asyncio.sleep(self.wait_time_after_buy)
|
||||
|
||||
logger.info(f"Selling {token_info.symbol}...")
|
||||
sell_result: TradeResult = await self.seller.execute(token_info)
|
||||
# Pass token amount and price from buy result to avoid RPC delays
|
||||
sell_result: TradeResult = await self.seller.execute(
|
||||
token_info, token_amount=buy_result.amount, token_price=buy_result.price
|
||||
)
|
||||
|
||||
if sell_result.success:
|
||||
logger.info(f"Successfully sold {token_info.symbol}")
|
||||
@@ -575,8 +586,12 @@ class UniversalTrader:
|
||||
f"Position PnL: {pnl['price_change_pct']:.2f}% ({pnl['unrealized_pnl_sol']:.6f} SOL)"
|
||||
)
|
||||
|
||||
# Execute sell
|
||||
sell_result = await self.seller.execute(token_info)
|
||||
# Execute sell with position quantity and entry price to avoid RPC delays
|
||||
sell_result = await self.seller.execute(
|
||||
token_info,
|
||||
token_amount=position.quantity,
|
||||
token_price=position.entry_price,
|
||||
)
|
||||
|
||||
if sell_result.success:
|
||||
# Close position with actual exit price
|
||||
|
||||
Reference in New Issue
Block a user