mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 23:37:45 +00:00
169 lines
5.2 KiB
Python
169 lines
5.2 KiB
Python
"""
|
|
Solana client abstraction for blockchain operations.
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
|
|
from solana.rpc.async_api import AsyncClient
|
|
from solana.rpc.commitment import Confirmed
|
|
from solana.rpc.types import TxOpts
|
|
from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
|
|
from solders.hash import Hash
|
|
from solders.instruction import Instruction
|
|
from solders.keypair import Keypair
|
|
from solders.message import Message
|
|
from solders.pubkey import Pubkey
|
|
from solders.transaction import Transaction
|
|
|
|
from src.utils.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class SolanaClient:
|
|
"""Abstraction for Solana RPC client operations."""
|
|
|
|
def __init__(self, rpc_endpoint: str):
|
|
"""Initialize Solana client with RPC endpoint.
|
|
|
|
Args:
|
|
rpc_endpoint: URL of the Solana RPC endpoint
|
|
"""
|
|
self.rpc_endpoint = rpc_endpoint
|
|
self._client = None
|
|
|
|
async def get_client(self) -> AsyncClient:
|
|
"""Get or create the AsyncClient instance.
|
|
|
|
Returns:
|
|
AsyncClient instance
|
|
"""
|
|
if self._client is None:
|
|
self._client = AsyncClient(self.rpc_endpoint)
|
|
return self._client
|
|
|
|
async def close(self):
|
|
"""Close the client connection if open."""
|
|
if self._client:
|
|
await self._client.close()
|
|
self._client = None
|
|
|
|
async def get_account_info(self, pubkey: Pubkey) -> dict[str, Any]:
|
|
"""Get account info from the blockchain.
|
|
|
|
Args:
|
|
pubkey: Public key of the account
|
|
|
|
Returns:
|
|
Account info response
|
|
|
|
Raises:
|
|
ValueError: If account doesn't exist or has no data
|
|
"""
|
|
client = await self.get_client()
|
|
response = await client.get_account_info(pubkey)
|
|
if not response.value:
|
|
raise ValueError(f"Account {pubkey} not found")
|
|
return response.value
|
|
|
|
async def get_token_account_balance(self, token_account: Pubkey) -> int:
|
|
"""Get token balance for an account.
|
|
|
|
Args:
|
|
token_account: Token account address
|
|
|
|
Returns:
|
|
Token balance as integer
|
|
"""
|
|
client = await self.get_client()
|
|
response = await client.get_token_account_balance(token_account)
|
|
if response.value:
|
|
return int(response.value.amount)
|
|
return 0
|
|
|
|
async def get_latest_blockhash(self) -> Hash:
|
|
"""Get the latest blockhash.
|
|
|
|
Returns:
|
|
Recent blockhash as string
|
|
"""
|
|
client = await self.get_client()
|
|
response = await client.get_latest_blockhash()
|
|
return response.value.blockhash
|
|
|
|
async def build_and_send_transaction(
|
|
self,
|
|
instructions: list[Instruction],
|
|
signer_keypair: Keypair,
|
|
skip_preflight: bool = True,
|
|
max_retries: int = 3,
|
|
priority_fee: int | None = None,
|
|
) -> str:
|
|
"""
|
|
Send a transaction with optional priority fee.
|
|
|
|
Args:
|
|
instructions: List of instructions to include in the transaction.
|
|
skip_preflight: Whether to skip preflight checks.
|
|
max_retries: Maximum number of retry attempts.
|
|
priority_fee: Optional priority fee in lamports.
|
|
|
|
Returns:
|
|
Transaction signature.
|
|
"""
|
|
client = await self.get_client()
|
|
|
|
# Add priority fee instructions if applicable
|
|
if priority_fee is not None:
|
|
fee_instructions = [
|
|
set_compute_unit_limit(200_000), # Default compute unit limit
|
|
set_compute_unit_price(priority_fee),
|
|
]
|
|
instructions = fee_instructions + instructions
|
|
|
|
recent_blockhash = await self.get_latest_blockhash()
|
|
message = Message(instructions, signer_keypair.pubkey())
|
|
transaction = Transaction([signer_keypair], message, recent_blockhash)
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
tx_opts = TxOpts(
|
|
skip_preflight=skip_preflight, preflight_commitment=Confirmed
|
|
)
|
|
response = await client.send_transaction(transaction, tx_opts)
|
|
return response.value
|
|
|
|
except Exception as e:
|
|
if attempt == max_retries - 1:
|
|
logger.error(
|
|
f"Failed to send transaction after {max_retries} attempts"
|
|
)
|
|
raise
|
|
|
|
wait_time = 2**attempt
|
|
logger.warning(
|
|
f"Transaction attempt {attempt + 1} failed: {str(e)}, retrying in {wait_time}s"
|
|
)
|
|
await asyncio.sleep(wait_time)
|
|
|
|
async def confirm_transaction(
|
|
self, signature: str, commitment: str = "confirmed"
|
|
) -> bool:
|
|
"""Wait for transaction confirmation.
|
|
|
|
Args:
|
|
signature: Transaction signature
|
|
commitment: Confirmation commitment level
|
|
|
|
Returns:
|
|
Whether transaction was confirmed
|
|
"""
|
|
client = await self.get_client()
|
|
try:
|
|
await client.confirm_transaction(signature, commitment=commitment)
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to confirm transaction {signature}: {str(e)}")
|
|
return False
|