diff --git a/cli.py b/cli.py index 8a0feaf..2701092 100644 --- a/cli.py +++ b/cli.py @@ -72,10 +72,19 @@ async def main() -> None: args = parse_args() # Get configuration values, preferring command line args over config.py - rpc_endpoint: str | None = args.rpc or os.environ.get("SOLANA_NODE_RPC_ENDPOINT") - wss_endpoint: str | None = args.wss or os.environ.get("SOLANA_NODE_WSS_ENDPOINT") + rpc_endpoint: str | None = ( + args.rpc + or os.environ.get("SOLANA_NODE_RPC_ENDPOINT") + or config.PUBLIC_RPC_ENDPOINT + ) + wss_endpoint: str | None = ( + args.wss + or os.environ.get("SOLANA_NODE_WSS_ENDPOINT") + or config.PUBLIC_WSS_ENDPOINT + ) private_key: str | None = args.key or os.environ.get("SOLANA_PRIVATE_KEY") + # Validate configuration values if not rpc_endpoint or rpc_endpoint.startswith(("http://", "https://")): logger.error("Invalid RPC endpoint. Must start with http:// or https://") sys.exit(1) @@ -88,6 +97,7 @@ async def main() -> None: logger.error("Invalid private key. Key appears to be missing or too short") sys.exit(1) + # Get trading parameters buy_amount: float = args.amount if args.amount is not None else config.BUY_AMOUNT buy_slippage: float = ( args.buy_slippage if args.buy_slippage is not None else config.BUY_SLIPPAGE @@ -96,6 +106,12 @@ async def main() -> None: args.sell_slippage if args.sell_slippage is not None else config.SELL_SLIPPAGE ) + # Not implemented parameters + enable_dynamic_prior__fee = ( + config.ENABLE_DYNAMIC_PRIORITY_FEE + ) # TODO: to be implemented + prior_fee_multiplier = config.EXTRA_PRIORITY_FEE # TODO: to be implemented + trader: PumpTrader = PumpTrader( rpc_endpoint=rpc_endpoint, # type: ignore wss_endpoint=wss_endpoint, # type: ignore diff --git a/config.py b/config.py index 518f442..36c273d 100644 --- a/config.py +++ b/config.py @@ -6,20 +6,17 @@ Configuration for the pump.fun trading bot. BUY_AMOUNT = 0.0001 # Amount of SOL to spend when buying BUY_SLIPPAGE = 0.2 # 20% slippage tolerance for buying SELL_SLIPPAGE = 0.2 # 20% slippage tolerance for selling -ENABLE_DYNAMIC_PRIORITY_FEE = ( - True # TODO: getRecentPriorityFee is used to get current priority fee -) -EXTRA_PRIORITY_FEE = 0.1 # TODO: 10% increase in dynamic priority fee +ENABLE_DYNAMIC_PRIORITY_FEE = True # TODO: not implemented. getRecentPriorityFee is used to get current priority fee +EXTRA_PRIORITY_FEE = 0.1 # TODO: not implemented. 10% increase in dynamic priority fee -TOKEN_DECIMALS: int = 6 +# Retries and timeouts MAX_RETRIES: int = 5 -WAIT_TIME_AFTER_BUY: int = 20 +WAIT_TIME_AFTER_BUY: int = 5 WAIT_TIME_BEFORE_NEW_TOKEN: int = 5 -WAIT_TIME_AFTER_CREATION: int = 15 +WAIT_TIME_AFTER_CREATION: int = 5 -# TODO: RPS of your node to avoid rate limit errors +# Node provier configuration # You can also get a trader node https://docs.chainstack.com/docs/solana-trader-nodes -MAX_RPS = 25 - +MAX_RPS = 25 # TODO: not implemented. Max RPS to avoid rate limit errors PUBLIC_RPC_ENDPOINT = "https://api.mainnet-beta.solana.com" PUBLIC_WSS_ENDPOINT = "wss://api.mainnet-beta.solana.com" diff --git a/src/core/client.py b/src/core/client.py index 9a7b51c..7f9ea2c 100644 --- a/src/core/client.py +++ b/src/core/client.py @@ -3,12 +3,11 @@ Solana client abstraction for blockchain operations. """ import asyncio -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts -from solders.instruction import Instruction from solders.keypair import Keypair from solders.pubkey import Pubkey from solders.transaction import Transaction diff --git a/src/trading/trader.py b/src/trading/trader.py index 485f866..d56ae61 100644 --- a/src/trading/trader.py +++ b/src/trading/trader.py @@ -6,10 +6,8 @@ import asyncio import json import os from datetime import datetime -from typing import Any, Dict, List, Optional - -from solders.pubkey import Pubkey +import config from src.core.client import SolanaClient from src.core.curve import BondingCurveManager from src.core.pubkeys import PumpAddresses @@ -120,26 +118,25 @@ class PumpTrader: try: await self._save_token_info(token_info) - logger.info("Waiting for 15 seconds for the bonding curve to stabilize...") - await asyncio.sleep(15) - - try: - token_price = await self.curve_manager.calculate_price( - token_info.bonding_curve - ) - logger.info(f"Token price: {token_price:.10f} SOL") - except Exception as e: - logger.error(f"Failed to get token price: {str(e)}") - token_price = 0 + logger.info( + f"Waiting for {config.WAIT_TIME_AFTER_CREATION} seconds for the bonding curve to stabilize..." + ) + await asyncio.sleep(config.WAIT_TIME_AFTER_CREATION) logger.info( f"Buying {self.buy_amount:.6f} SOL worth of {token_info.symbol}..." ) - buy_result = await self.buyer.execute(token_info) + buy_result: TradeResult = await self.buyer.execute(token_info) if buy_result.success: logger.info(f"Successfully bought {token_info.symbol}") - self._log_trade("buy", token_info, token_price, buy_result.tx_signature) + self._log_trade( + "buy", + token_info, + buy_result.price, # type: ignore + buy_result.amount, # type: ignore + buy_result.tx_signature, + ) else: logger.error( f"Failed to buy {token_info.symbol}: {buy_result.error_message}" @@ -147,16 +144,22 @@ class PumpTrader: # Sell token if not in marry mode if not marry_mode and buy_result.success: - logger.info("Waiting for 20 seconds before selling...") - await asyncio.sleep(20) + logger.info( + f"Waiting for {config.WAIT_TIME_AFTER_BUY} seconds before selling..." + ) + await asyncio.sleep(config.WAIT_TIME_AFTER_BUY) logger.info(f"Selling {token_info.symbol}...") - sell_result = await self.seller.execute(token_info) + sell_result: TradeResult = await self.seller.execute(token_info) if sell_result.success: logger.info(f"Successfully sold {token_info.symbol}") self._log_trade( - "sell", token_info, token_price, sell_result.tx_signature + "sell", + token_info, + sell_result.price, # type: ignore + sell_result.amount, # type: ignore + sell_result.tx_signature, ) else: logger.error( @@ -168,9 +171,9 @@ class PumpTrader: # Wait before looking for the next token if yolo_mode: logger.info( - "YOLO mode enabled. Waiting 5 seconds before looking for next token..." + f"YOLO mode enabled. Waiting {config.WAIT_TIME_BEFORE_NEW_TOKEN} seconds before looking for next token..." ) - await asyncio.sleep(5) + await asyncio.sleep(config.WAIT_TIME_BEFORE_NEW_TOKEN) except Exception as e: logger.error(f"Error handling token {token_info.symbol}: {str(e)}") @@ -190,7 +193,12 @@ class PumpTrader: logger.info(f"Token information saved to {file_name}") def _log_trade( - self, action: str, token_info: TokenInfo, price: float, tx_hash: str | None + self, + action: str, + token_info: TokenInfo, + price: float, + amount: float, + tx_hash: str | None, ) -> None: """Log trade information. @@ -198,6 +206,7 @@ class PumpTrader: action: Trade action (buy/sell) token_info: Token information price: Token price in SOL + amount: Trade amount in SOL tx_hash: Transaction hash """ os.makedirs("trades", exist_ok=True) @@ -208,6 +217,7 @@ class PumpTrader: "token_address": str(token_info.mint), "symbol": token_info.symbol, "price": price, + "amount": amount, "tx_hash": tx_hash, } diff --git a/tests/test_listener.py b/tests/test_listener.py index e729a6e..13a40d8 100644 --- a/tests/test_listener.py +++ b/tests/test_listener.py @@ -6,6 +6,8 @@ Tests websocket monitoring for new pump.fun tokens import asyncio import logging import os + +import config from core.pubkeys import PumpAddresses from monitoring.listener import PumpTokenListener from trading.base import TokenInfo @@ -43,7 +45,7 @@ async def test_pump_token_listener( ): """Test the token listener functionality""" wss_endpoint = os.environ.get( - "SOLANA_NODE_WSS_ENDPOINT", "wss://api.mainnet-beta.solana.com" + "SOLANA_NODE_WSS_ENDPOINT", config.PUBLIC_WSS_ENDPOINT ) logger.info(f"Connecting to WebSocket: {wss_endpoint}") listener = PumpTokenListener(wss_endpoint, PumpAddresses.PROGRAM) @@ -76,7 +78,7 @@ async def test_pump_token_listener( logger.info(f"Detected {len(callback.detected_tokens)} tokens") for token in callback.detected_tokens: - logger.info(f" - 🪙 {token.name} ({token.symbol}): {token.mint}") + logger.info(f" - {token.name} ({token.symbol}): {token.mint}") return callback.detected_tokens @@ -84,6 +86,7 @@ async def test_pump_token_listener( if __name__ == "__main__": match_string = None # Update if you want to filter tokens by name/symbol creator_address = None # Update if you want to filter tokens by creator address - test_duration = 60 + test_duration = 15 + logger.info("Starting token listener test") asyncio.run(test_pump_token_listener(match_string, creator_address, test_duration))