updated code structure
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Base interfaces for trading operations.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.signature import Signature
|
||||
|
||||
|
||||
@dataclass
|
||||
class TokenInfo:
|
||||
"""Token information."""
|
||||
|
||||
name: str
|
||||
symbol: str
|
||||
uri: str
|
||||
mint: Pubkey
|
||||
bonding_curve: Pubkey
|
||||
associated_bonding_curve: Pubkey
|
||||
user: Pubkey
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "TokenInfo":
|
||||
"""Create TokenInfo from dictionary.
|
||||
|
||||
Args:
|
||||
data: Dictionary with token data
|
||||
|
||||
Returns:
|
||||
TokenInfo instance
|
||||
"""
|
||||
return cls(
|
||||
name=data["name"],
|
||||
symbol=data["symbol"],
|
||||
uri=data["uri"],
|
||||
mint=Pubkey.from_string(data["mint"]),
|
||||
bonding_curve=Pubkey.from_string(data["bondingCurve"]),
|
||||
associated_bonding_curve=Pubkey.from_string(data["associatedBondingCurve"]),
|
||||
user=Pubkey.from_string(data["user"]),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, str]:
|
||||
"""Convert to dictionary.
|
||||
|
||||
Returns:
|
||||
Dictionary representation
|
||||
"""
|
||||
return {
|
||||
"name": self.name,
|
||||
"symbol": self.symbol,
|
||||
"uri": self.uri,
|
||||
"mint": str(self.mint),
|
||||
"bondingCurve": str(self.bonding_curve),
|
||||
"associatedBondingCurve": str(self.associated_bonding_curve),
|
||||
"user": str(self.user),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradeResult:
|
||||
"""Result of a trading operation."""
|
||||
|
||||
success: bool
|
||||
tx_signature: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
amount: Optional[float] = None
|
||||
price: Optional[float] = None
|
||||
|
||||
|
||||
class Trader(ABC):
|
||||
"""Base interface for trading operations."""
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, *args, **kwargs) -> TradeResult:
|
||||
"""Execute trading operation.
|
||||
|
||||
Returns:
|
||||
TradeResult with operation outcome
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Buy operations for pump.fun tokens.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import struct
|
||||
from typing import 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 solana.transaction import Transaction
|
||||
from solders.instruction import AccountMeta, Instruction
|
||||
from solders.keypair import Keypair
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.system_program import TransferParams, transfer
|
||||
from spl.token.instructions import get_associated_token_address
|
||||
|
||||
from src.core.client import SolanaClient
|
||||
from src.core.curve import BondingCurveManager
|
||||
from src.core.pubkeys import (
|
||||
LAMPORTS_PER_SOL,
|
||||
TOKEN_DECIMALS,
|
||||
PumpAddresses,
|
||||
SystemAddresses,
|
||||
)
|
||||
from src.core.wallet import Wallet
|
||||
from src.trading.base import TokenInfo, Trader, TradeResult
|
||||
from src.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class TokenBuyer(Trader):
|
||||
"""Handles buying tokens on pump.fun."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: SolanaClient,
|
||||
wallet: Wallet,
|
||||
curve_manager: BondingCurveManager,
|
||||
amount: float,
|
||||
slippage: float = 0.01,
|
||||
max_retries: int = 5,
|
||||
):
|
||||
"""Initialize token buyer.
|
||||
|
||||
Args:
|
||||
client: Solana client for RPC calls
|
||||
wallet: Wallet for signing transactions
|
||||
curve_manager: Bonding curve manager
|
||||
amount: Amount of SOL to spend
|
||||
slippage: Slippage tolerance (0.01 = 1%)
|
||||
max_retries: Maximum number of retry attempts
|
||||
"""
|
||||
self.client = client
|
||||
self.wallet = wallet
|
||||
self.curve_manager = curve_manager
|
||||
self.amount = amount
|
||||
self.slippage = slippage
|
||||
self.max_retries = max_retries
|
||||
|
||||
async def execute(self, token_info: TokenInfo, *args, **kwargs) -> TradeResult:
|
||||
"""Execute buy operation.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
|
||||
Returns:
|
||||
TradeResult with buy outcome
|
||||
"""
|
||||
try:
|
||||
# Extract token info
|
||||
mint = token_info.mint
|
||||
bonding_curve = token_info.bonding_curve
|
||||
associated_bonding_curve = token_info.associated_bonding_curve
|
||||
|
||||
# Convert amount to lamports
|
||||
amount_lamports = int(self.amount * LAMPORTS_PER_SOL)
|
||||
|
||||
# Fetch token price
|
||||
curve_state = await self.curve_manager.get_curve_state(bonding_curve)
|
||||
token_price_sol = curve_state.calculate_price()
|
||||
token_amount = self.amount / token_price_sol
|
||||
|
||||
# Calculate maximum SOL to spend with slippage
|
||||
max_amount_lamports = int(amount_lamports * (1 + self.slippage))
|
||||
|
||||
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)"
|
||||
)
|
||||
|
||||
associated_token_account = self.wallet.get_associated_token_address(mint)
|
||||
|
||||
await self._ensure_associated_token_account(mint, associated_token_account)
|
||||
|
||||
tx_signature = await self._send_buy_transaction(
|
||||
mint,
|
||||
bonding_curve,
|
||||
associated_bonding_curve,
|
||||
associated_token_account,
|
||||
token_amount,
|
||||
max_amount_lamports,
|
||||
)
|
||||
|
||||
success = await self.client.confirm_transaction(tx_signature)
|
||||
|
||||
if success:
|
||||
logger.info(f"Buy transaction confirmed: {tx_signature}")
|
||||
return TradeResult(
|
||||
success=True,
|
||||
tx_signature=tx_signature,
|
||||
amount=token_amount,
|
||||
price=token_price_sol,
|
||||
)
|
||||
else:
|
||||
return TradeResult(
|
||||
success=False,
|
||||
error_message=f"Transaction failed to confirm: {tx_signature}",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Buy operation failed: {str(e)}")
|
||||
return TradeResult(success=False, error_message=str(e))
|
||||
|
||||
async def _ensure_associated_token_account(
|
||||
self, mint: Pubkey, associated_token_account: Pubkey
|
||||
) -> None:
|
||||
"""Ensure associated token account exists.
|
||||
|
||||
Args:
|
||||
mint: Token mint
|
||||
associated_token_account: Associated token account address
|
||||
"""
|
||||
try:
|
||||
solana_client = await self.client.get_client()
|
||||
account_info = await solana_client.get_account_info(
|
||||
associated_token_account
|
||||
)
|
||||
|
||||
if account_info.value is None:
|
||||
logger.info(f"Creating associated token account for {mint}...")
|
||||
|
||||
create_ata_ix = spl_token.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
|
||||
)
|
||||
|
||||
await self.client.confirm_transaction(tx_sig)
|
||||
logger.info(
|
||||
f"Associated token account created: {associated_token_account}"
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Associated token account already exists: {associated_token_account}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating associated token account: {str(e)}")
|
||||
raise
|
||||
|
||||
async def _send_buy_transaction(
|
||||
self,
|
||||
mint: Pubkey,
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
associated_token_account: Pubkey,
|
||||
token_amount: float,
|
||||
max_amount_lamports: int,
|
||||
) -> str:
|
||||
"""Send buy transaction.
|
||||
|
||||
Args:
|
||||
mint: Token mint
|
||||
bonding_curve: Bonding curve address
|
||||
associated_bonding_curve: Associated bonding curve address
|
||||
associated_token_account: User's token account
|
||||
token_amount: Amount of tokens to buy
|
||||
max_amount_lamports: Maximum SOL to spend in lamports
|
||||
|
||||
Returns:
|
||||
Transaction signature
|
||||
|
||||
Raises:
|
||||
Exception: If transaction fails after all retries
|
||||
"""
|
||||
accounts = [
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.GLOBAL, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=PumpAddresses.FEE, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=associated_bonding_curve, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=associated_token_account, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(pubkey=self.wallet.pubkey, is_signer=True, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.RENT, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
]
|
||||
|
||||
# Prepare buy instruction data
|
||||
# Discriminator for buy instruction
|
||||
discriminator = struct.pack("<Q", 16927863322537952870)
|
||||
token_amount_raw = int(token_amount * 10**TOKEN_DECIMALS)
|
||||
data = (
|
||||
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)
|
||||
|
||||
try:
|
||||
return await self.client.send_transaction(
|
||||
transaction,
|
||||
self.wallet.keypair,
|
||||
skip_preflight=True,
|
||||
max_retries=self.max_retries,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Buy transaction failed: {str(e)}")
|
||||
raise
|
||||
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
Sell operations for pump.fun tokens.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import struct
|
||||
from typing import Optional
|
||||
|
||||
from solana.transaction import Transaction
|
||||
from solders.instruction import AccountMeta, Instruction
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from src.core.client import SolanaClient
|
||||
from src.core.curve import BondingCurveManager
|
||||
from src.core.pubkeys import (
|
||||
LAMPORTS_PER_SOL,
|
||||
TOKEN_DECIMALS,
|
||||
PumpAddresses,
|
||||
SystemAddresses,
|
||||
)
|
||||
from src.core.wallet import Wallet
|
||||
from src.trading.base import TokenInfo, Trader, TradeResult
|
||||
from src.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class TokenSeller(Trader):
|
||||
"""Handles selling tokens on pump.fun."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: SolanaClient,
|
||||
wallet: Wallet,
|
||||
curve_manager: BondingCurveManager,
|
||||
slippage: float = 0.25,
|
||||
max_retries: int = 5,
|
||||
):
|
||||
"""Initialize token seller.
|
||||
|
||||
Args:
|
||||
client: Solana client for RPC calls
|
||||
wallet: Wallet for signing transactions
|
||||
curve_manager: Bonding curve manager
|
||||
slippage: Slippage tolerance (0.25 = 25%)
|
||||
max_retries: Maximum number of retry attempts
|
||||
"""
|
||||
self.client = client
|
||||
self.wallet = wallet
|
||||
self.curve_manager = curve_manager
|
||||
self.slippage = slippage
|
||||
self.max_retries = max_retries
|
||||
|
||||
async def execute(self, token_info: TokenInfo, *args, **kwargs) -> TradeResult:
|
||||
"""Execute sell operation.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
|
||||
Returns:
|
||||
TradeResult with sell outcome
|
||||
"""
|
||||
try:
|
||||
# Extract token info
|
||||
mint = token_info.mint
|
||||
bonding_curve = token_info.bonding_curve
|
||||
associated_bonding_curve = token_info.associated_bonding_curve
|
||||
|
||||
# Get associated token account
|
||||
associated_token_account = self.wallet.get_associated_token_address(mint)
|
||||
|
||||
# Get token balance
|
||||
token_balance = await self.client.get_token_account_balance(
|
||||
associated_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, error_message="No tokens to sell")
|
||||
|
||||
# Fetch token price
|
||||
curve_state = await self.curve_manager.get_curve_state(bonding_curve)
|
||||
token_price_sol = curve_state.calculate_price()
|
||||
|
||||
logger.info(f"Price per Token: {token_price_sol:.8f} SOL")
|
||||
|
||||
# Calculate minimum SOL output with slippage
|
||||
amount = token_balance
|
||||
expected_sol_output = float(token_balance_decimal) * float(token_price_sol)
|
||||
slippage_factor = 1 - self.slippage
|
||||
min_sol_output = int(
|
||||
(expected_sol_output * slippage_factor) * 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"
|
||||
)
|
||||
|
||||
tx_signature = await self._send_sell_transaction(
|
||||
mint,
|
||||
bonding_curve,
|
||||
associated_bonding_curve,
|
||||
associated_token_account,
|
||||
amount,
|
||||
min_sol_output,
|
||||
)
|
||||
|
||||
success = await self.client.confirm_transaction(tx_signature)
|
||||
|
||||
if success:
|
||||
logger.info(f"Sell transaction confirmed: {tx_signature}")
|
||||
return TradeResult(
|
||||
success=True,
|
||||
tx_signature=tx_signature,
|
||||
amount=token_balance_decimal,
|
||||
price=token_price_sol,
|
||||
)
|
||||
else:
|
||||
return TradeResult(
|
||||
success=False,
|
||||
error_message=f"Transaction failed to confirm: {tx_signature}",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Sell operation failed: {str(e)}")
|
||||
return TradeResult(success=False, error_message=str(e))
|
||||
|
||||
async def _send_sell_transaction(
|
||||
self,
|
||||
mint: Pubkey,
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
associated_token_account: Pubkey,
|
||||
token_amount: int,
|
||||
min_sol_output: int,
|
||||
) -> str:
|
||||
"""Send sell transaction.
|
||||
|
||||
Args:
|
||||
mint: Token mint
|
||||
bonding_curve: Bonding curve address
|
||||
associated_bonding_curve: Associated bonding curve address
|
||||
associated_token_account: User's token account
|
||||
token_amount: Amount of tokens to sell in raw units
|
||||
min_sol_output: Minimum SOL to receive in lamports
|
||||
|
||||
Returns:
|
||||
Transaction signature
|
||||
|
||||
Raises:
|
||||
Exception: If transaction fails after all retries
|
||||
"""
|
||||
# Prepare sell instruction accounts
|
||||
accounts = [
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.GLOBAL, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=PumpAddresses.FEE, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=associated_bonding_curve, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=associated_token_account, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(pubkey=self.wallet.pubkey, is_signer=True, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
|
||||
is_signer=False,
|
||||
is_writable=False,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
]
|
||||
|
||||
# Prepare sell instruction data
|
||||
# Discriminator for sell instruction
|
||||
discriminator = struct.pack("<Q", 12502976635542562355)
|
||||
data = (
|
||||
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)
|
||||
|
||||
try:
|
||||
return await self.client.send_transaction(
|
||||
transaction,
|
||||
self.wallet.keypair,
|
||||
skip_preflight=True,
|
||||
max_retries=self.max_retries,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Sell transaction failed: {str(e)}")
|
||||
raise
|
||||
@@ -0,0 +1,215 @@
|
||||
"""
|
||||
Main trading coordinator for pump.fun tokens.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from src.core.client import SolanaClient
|
||||
from src.core.curve import BondingCurveManager
|
||||
from src.core.pubkeys import PumpAddresses
|
||||
from src.core.wallet import Wallet
|
||||
from src.monitoring.listener import PumpTokenListener
|
||||
from src.trading.base import TokenInfo, TradeResult
|
||||
from src.trading.buyer import TokenBuyer
|
||||
from src.trading.seller import TokenSeller
|
||||
from src.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class PumpTrader:
|
||||
"""Coordinates trading operations for pump.fun tokens."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rpc_endpoint: str,
|
||||
wss_endpoint: str,
|
||||
private_key: str,
|
||||
buy_amount: float,
|
||||
buy_slippage: float,
|
||||
sell_slippage: float,
|
||||
max_retries: int = 5,
|
||||
):
|
||||
"""Initialize the pump trader.
|
||||
|
||||
Args:
|
||||
rpc_endpoint: RPC endpoint URL
|
||||
wss_endpoint: WebSocket endpoint URL
|
||||
private_key: Wallet private key
|
||||
buy_amount: Amount of SOL to spend on buys
|
||||
buy_slippage: Slippage tolerance for buys
|
||||
sell_slippage: Slippage tolerance for sells
|
||||
max_retries: Maximum number of retry attempts
|
||||
"""
|
||||
self.solana_client = SolanaClient(rpc_endpoint)
|
||||
self.wallet = Wallet(private_key)
|
||||
self.curve_manager = BondingCurveManager(self.solana_client)
|
||||
|
||||
self.buyer = TokenBuyer(
|
||||
self.solana_client,
|
||||
self.wallet,
|
||||
self.curve_manager,
|
||||
buy_amount,
|
||||
buy_slippage,
|
||||
max_retries,
|
||||
)
|
||||
|
||||
self.seller = TokenSeller(
|
||||
self.solana_client,
|
||||
self.wallet,
|
||||
self.curve_manager,
|
||||
sell_slippage,
|
||||
max_retries,
|
||||
)
|
||||
|
||||
self.token_listener = PumpTokenListener(wss_endpoint, PumpAddresses.PROGRAM)
|
||||
|
||||
self.buy_amount = buy_amount
|
||||
self.buy_slippage = buy_slippage
|
||||
self.sell_slippage = sell_slippage
|
||||
self.max_retries = max_retries
|
||||
|
||||
async def start(
|
||||
self,
|
||||
match_string: str | None = None,
|
||||
bro_address: str | None = None,
|
||||
marry_mode: bool = False,
|
||||
yolo_mode: bool = False,
|
||||
) -> None:
|
||||
"""Start the trading bot.
|
||||
|
||||
Args:
|
||||
match_string: Optional string to match in token name/symbol
|
||||
bro_address: Optional creator address to filter by
|
||||
marry_mode: If True, only buy tokens and skip selling
|
||||
yolo_mode: If True, trade continuously
|
||||
"""
|
||||
logger.info("Starting pump.fun trader")
|
||||
logger.info(f"Match filter: {match_string if match_string else 'None'}")
|
||||
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}")
|
||||
|
||||
try:
|
||||
await self.token_listener.listen_for_tokens(
|
||||
lambda token: self._handle_new_token(token, marry_mode, yolo_mode),
|
||||
match_string,
|
||||
bro_address,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Trading stopped due to error: {str(e)}")
|
||||
await self.solana_client.close()
|
||||
|
||||
async def _handle_new_token(
|
||||
self, token_info: TokenInfo, marry_mode: bool, yolo_mode: bool
|
||||
) -> None:
|
||||
"""Handle a new token creation event.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
marry_mode: If True, only buy tokens and skip selling
|
||||
yolo_mode: If True, continue trading after this token
|
||||
"""
|
||||
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"Buying {self.buy_amount:.6f} SOL worth of {token_info.symbol}..."
|
||||
)
|
||||
buy_result = 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)
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to buy {token_info.symbol}: {buy_result.error_message}"
|
||||
)
|
||||
|
||||
# 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"Selling {token_info.symbol}...")
|
||||
sell_result = 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
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to sell {token_info.symbol}: {sell_result.error_message}"
|
||||
)
|
||||
elif marry_mode:
|
||||
logger.info("Marry mode enabled. Skipping sell operation.")
|
||||
|
||||
# Wait before looking for the next token
|
||||
if yolo_mode:
|
||||
logger.info(
|
||||
"YOLO mode enabled. Waiting 5 seconds before looking for next token..."
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling token {token_info.symbol}: {str(e)}")
|
||||
|
||||
async def _save_token_info(self, token_info: TokenInfo) -> None:
|
||||
"""Save token information to a file.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
"""
|
||||
os.makedirs("trades", exist_ok=True)
|
||||
file_name = os.path.join("trades", f"{token_info.mint}.txt")
|
||||
|
||||
with open(file_name, "w") as file:
|
||||
file.write(json.dumps(token_info.to_dict(), indent=2))
|
||||
|
||||
logger.info(f"Token information saved to {file_name}")
|
||||
|
||||
def _log_trade(
|
||||
self, action: str, token_info: TokenInfo, price: float, tx_hash: str | None
|
||||
) -> None:
|
||||
"""Log trade information.
|
||||
|
||||
Args:
|
||||
action: Trade action (buy/sell)
|
||||
token_info: Token information
|
||||
price: Token price in SOL
|
||||
tx_hash: Transaction hash
|
||||
"""
|
||||
os.makedirs("trades", exist_ok=True)
|
||||
|
||||
log_entry = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"action": action,
|
||||
"token_address": str(token_info.mint),
|
||||
"symbol": token_info.symbol,
|
||||
"price": price,
|
||||
"tx_hash": tx_hash,
|
||||
}
|
||||
|
||||
with open("trades/trades.log", "a") as log_file:
|
||||
log_file.write(json.dumps(log_entry) + "\n")
|
||||
Reference in New Issue
Block a user