fixed and buy transactions

This commit is contained in:
smypmsa
2025-03-06 16:14:17 +00:00
parent a7b2c135f9
commit fce547ec9e
8 changed files with 133 additions and 67 deletions
+3 -11
View File
@@ -8,6 +8,7 @@ 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.hash import Hash
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.transaction import Transaction
@@ -78,7 +79,7 @@ class SolanaClient:
return int(response.value.amount)
return 0
async def get_latest_blockhash(self) -> str:
async def get_latest_blockhash(self) -> Hash:
"""Get the latest blockhash.
Returns:
@@ -91,7 +92,6 @@ class SolanaClient:
async def send_transaction(
self,
transaction: Transaction,
signer: Keypair,
skip_preflight: bool = True,
max_retries: int = 3,
) -> str:
@@ -99,7 +99,6 @@ class SolanaClient:
Args:
transaction: Prepared transaction
signer: Transaction signer
skip_preflight: Whether to skip preflight checks
max_retries: Maximum number of sending attempts
@@ -111,20 +110,13 @@ class SolanaClient:
"""
client = await self.get_client()
# Ensure transaction has a recent blockhash
if not transaction.recent_blockhash:
blockhash = await self.get_latest_blockhash()
transaction.recent_blockhash = blockhash
# Attempt to send with retries
for attempt in range(max_retries):
try:
tx_opts = TxOpts(
skip_preflight=skip_preflight, preflight_commitment=Confirmed
)
response = await client.send_transaction(
transaction, signer, opts=tx_opts
)
response = await client.send_transaction(transaction, opts=tx_opts)
return response.value
except Exception as e:
+8 -1
View File
@@ -125,7 +125,14 @@ class PumpTokenListener:
try:
while True:
await asyncio.sleep(self.ping_interval)
await websocket.ping()
try:
pong_waiter = await websocket.ping()
await asyncio.wait_for(pong_waiter, timeout=10)
except asyncio.TimeoutError:
logger.warning("Ping timeout - server not responding")
# Force reconnection
await websocket.close()
return
except asyncio.CancelledError:
pass
except Exception as e:
+20 -17
View File
@@ -4,18 +4,20 @@ Buy operations for pump.fun tokens.
import asyncio
import struct
from typing import List, Optional
from typing import Final, List, Optional
import spl.token.instructions as spl_token
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts
from solders.hash import Hash
from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.message import Message
from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer
from solders.transaction import Transaction
from spl.token.instructions import get_associated_token_address
from spl.token.instructions import create_associated_token_account
from src.core.client import SolanaClient
from src.core.curve import BondingCurveManager
@@ -31,6 +33,9 @@ from src.utils.logger import get_logger
logger = get_logger(__name__)
# Discriminator for the buy instruction
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 16927863322537952870)
class TokenBuyer(Trader):
"""Handles buying tokens on pump.fun."""
@@ -145,19 +150,18 @@ class TokenBuyer(Trader):
if account_info.value is None:
logger.info(f"Creating associated token account for {mint}...")
create_ata_ix = spl_token.create_associated_token_account(
create_ata_ix = create_associated_token_account(
payer=self.wallet.pubkey, owner=self.wallet.pubkey, mint=mint
)
create_ata_tx = Transaction()
create_ata_tx.add(create_ata_ix)
blockhash = await self.client.get_latest_blockhash()
create_ata_tx.recent_blockhash = blockhash
tx_sig = await self.client.send_transaction(
create_ata_tx, self.wallet.keypair
recent_blockhash: Hash = await self.client.get_latest_blockhash()
create_ata_msg = Message([create_ata_ix], self.wallet.keypair.pubkey())
create_ata_tx = Transaction(
[self.wallet.keypair], create_ata_msg, recent_blockhash
)
tx_sig = await self.client.send_transaction(create_ata_tx)
await self.client.confirm_transaction(tx_sig)
logger.info(
f"Associated token account created: {associated_token_account}"
@@ -228,23 +232,22 @@ class TokenBuyer(Trader):
]
# Prepare buy instruction data
# Discriminator for buy instruction
discriminator = struct.pack("<Q", 16927863322537952870)
token_amount_raw = int(token_amount * 10**TOKEN_DECIMALS)
data = (
discriminator
EXPECTED_DISCRIMINATOR
+ struct.pack("<Q", token_amount_raw)
+ struct.pack("<Q", max_amount_lamports)
)
buy_ix = Instruction(PumpAddresses.PROGRAM, data, accounts)
transaction = Transaction()
transaction.add(buy_ix)
# Prepare buy transaction data
recent_blockhash: Hash = await self.client.get_latest_blockhash()
buy_message = Message([buy_ix], self.wallet.keypair.pubkey())
buy_tx = Transaction([self.wallet.keypair], buy_message, recent_blockhash)
try:
return await self.client.send_transaction(
transaction,
self.wallet.keypair,
buy_tx,
skip_preflight=True,
max_retries=self.max_retries,
)
+12 -8
View File
@@ -4,9 +4,11 @@ Sell operations for pump.fun tokens.
import asyncio
import struct
from typing import Optional
from typing import Final, Optional
from solders.hash import Hash
from solders.instruction import AccountMeta, Instruction
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import Transaction
@@ -24,6 +26,9 @@ from src.utils.logger import get_logger
logger = get_logger(__name__)
# Discriminator for the sell instruction
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 12502976635542562355)
class TokenSeller(Trader):
"""Handles selling tokens on pump.fun."""
@@ -190,22 +195,21 @@ class TokenSeller(Trader):
]
# Prepare sell instruction data
# Discriminator for sell instruction
discriminator = struct.pack("<Q", 12502976635542562355)
data = (
discriminator
EXPECTED_DISCRIMINATOR
+ struct.pack("<Q", token_amount)
+ struct.pack("<Q", min_sol_output)
)
sell_ix = Instruction(PumpAddresses.PROGRAM, data, accounts)
transaction = Transaction()
transaction.add(sell_ix)
# Prepare sell transaction data
recent_blockhash: Hash = await self.client.get_latest_blockhash()
sell_message = Message([sell_ix], self.wallet.keypair.pubkey())
sell_tx = Transaction([self.wallet.keypair], sell_message, recent_blockhash)
try:
return await self.client.send_transaction(
transaction,
self.wallet.keypair,
sell_tx,
skip_preflight=True,
max_retries=self.max_retries,
)
+74 -4
View File
@@ -1,11 +1,13 @@
"""
Main trading coordinator for pump.fun tokens.
Refactored PumpTrader to only process fresh tokens from WebSocket.
"""
import asyncio
import json
import os
from datetime import datetime
from typing import Dict, Optional, Set
import config
from src.core.client import SolanaClient
@@ -22,7 +24,7 @@ logger = get_logger(__name__)
class PumpTrader:
"""Coordinates trading operations for pump.fun tokens."""
"""Coordinates trading operations for pump.fun tokens with focus on freshness."""
def __init__(
self,
@@ -72,6 +74,13 @@ class PumpTrader:
self.buy_slippage = buy_slippage
self.sell_slippage = sell_slippage
self.max_retries = max_retries
self.max_token_age = max_token_age
# Token processing state
self.token_queue = asyncio.Queue()
self.processing = False
self.processed_tokens: Set[str] = set()
self.token_timestamps: Dict[str, float] = {}
async def start(
self,
@@ -93,19 +102,73 @@ class PumpTrader:
logger.info(f"Creator filter: {bro_address if bro_address else 'None'}")
logger.info(f"Marry mode: {marry_mode}")
logger.info(f"YOLO mode: {yolo_mode}")
logger.info(f"Max token age: {self.max_token_age} seconds")
# Start processor task
processor_task = asyncio.create_task(
self._process_token_queue(marry_mode, yolo_mode)
)
try:
await self.token_listener.listen_for_tokens(
lambda token: self._handle_new_token(token, marry_mode, yolo_mode),
lambda token: self._queue_token(token),
match_string,
bro_address,
)
except Exception as e:
logger.error(f"Trading stopped due to error: {str(e)}")
processor_task.cancel()
await self.solana_client.close()
async def _handle_new_token(
async def _queue_token(self, token_info: TokenInfo) -> None:
"""Queue a token for processing if not already processed."""
token_key = str(token_info.mint)
if token_key in self.processed_tokens:
logger.debug(f"Token {token_info.symbol} already processed. Skipping...")
return
# Record timestamp when token was discovered
self.token_timestamps[token_key] = asyncio.get_event_loop().time()
# Add to queue
await self.token_queue.put(token_info)
logger.info(f"Queued new token: {token_info.symbol} ({token_info.mint})")
async def _process_token_queue(self, marry_mode: bool, yolo_mode: bool) -> None:
"""Continuously process tokens from the queue, only if they're fresh."""
while True:
# Get next token
token_info = await self.token_queue.get()
token_key = str(token_info.mint)
# Check if token is still fresh
current_time = asyncio.get_event_loop().time()
token_age = current_time - self.token_timestamps.get(
token_key, current_time
)
if token_age > self.max_token_age:
logger.info(
f"Skipping token {token_info.symbol} - too old ({token_age:.1f}s > {self.max_token_age}s)"
)
self.token_queue.task_done()
continue
# Mark as processing
self.processed_tokens.add(token_key)
# Process token
logger.info(
f"Processing fresh token: {token_info.symbol} (age: {token_age:.1f}s)"
)
await self._handle_token(token_info, marry_mode, yolo_mode)
# Mark queue task as done
self.token_queue.task_done()
async def _handle_token(
self, token_info: TokenInfo, marry_mode: bool, yolo_mode: bool
) -> None:
"""Handle a new token creation event.
@@ -168,6 +231,13 @@ class PumpTrader:
elif marry_mode:
logger.info("Marry mode enabled. Skipping sell operation.")
# Wait before looking for the next token
if yolo_mode:
logger.info(
f"YOLO mode enabled. Waiting {config.WAIT_TIME_BEFORE_NEW_TOKEN} seconds before looking for next token..."
)
await asyncio.sleep(config.WAIT_TIME_BEFORE_NEW_TOKEN)
except Exception as e:
logger.error(f"Error handling token {token_info.symbol}: {str(e)}")
@@ -211,7 +281,7 @@ class PumpTrader:
"symbol": token_info.symbol,
"price": price,
"amount": amount,
"tx_hash": tx_hash,
"tx_hash": str(tx_hash),
}
with open("trades/trades.log", "a") as log_file: