Merge pull request #64 from chainstacklabs/feature/dynamic-prior-fees

Add dynamic/fixed priority fee
This commit is contained in:
Anton
2025-03-15 08:22:12 +00:00
committed by GitHub
13 changed files with 415 additions and 100 deletions
+1
View File
@@ -1,5 +1,6 @@
trades/*
.vscode
.pylintrc
.ruff_cache
+2 -6
View File
@@ -62,12 +62,8 @@ async def main() -> None:
args = parse_args()
# Get configuration values, preferring command line args over config.py
rpc_endpoint: str | None = (
os.environ.get("SOLANA_NODE_RPC_ENDPOINT") or config.PUBLIC_RPC_ENDPOINT
)
wss_endpoint: str | None = (
os.environ.get("SOLANA_NODE_WSS_ENDPOINT") or config.PUBLIC_WSS_ENDPOINT
)
rpc_endpoint: str | None = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
wss_endpoint: str | None = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
private_key: str | None = os.environ.get("SOLANA_PRIVATE_KEY")
# Validate configuration values
+56 -15
View File
@@ -3,25 +3,66 @@ Configuration for the pump.fun trading bot.
"""
# Trading parameters
BUY_AMOUNT = 0.000001 # Amount of SOL to spend when buying
BUY_SLIPPAGE = 0.4 # 40% slippage tolerance for buying
SELL_SLIPPAGE = 0.4 # 40% slippage tolerance for selling
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
BUY_AMOUNT: int | float = 0.000_001 # Amount of SOL to spend when buying
BUY_SLIPPAGE: float = 0.4 # 40% slippage tolerance for buying
SELL_SLIPPAGE: float = 0.4 # 40% slippage tolerance for selling
# Configuration for priority fee settings
ENABLE_DYNAMIC_PRIORITY_FEE: bool = False # Enable dynamic priority fee calculation
ENABLE_FIXED_PRIORITY_FEE: bool = True # Enable fixed priority fee
FIXED_PRIORITY_FEE: int = 2_000 # Fixed priority fee in microlamports
EXTRA_PRIORITY_FEE: float = (
0.0 # Percentage increase applied to priority fee (0.1 = 10%)
)
HARD_CAP_PRIOR_FEE: int = (
200_000 # Maximum allowed priority fee in microlamports (hard cap)
)
# Retries and timeouts
MAX_RETRIES: int = 2
WAIT_TIME_AFTER_BUY: int = 15
WAIT_TIME_BEFORE_NEW_TOKEN: int = 30
WAIT_TIME_AFTER_CREATION: int = 15
MAX_RETRIES: int = 10 # Number of retries for transaction sending
# TODO: waiting times will be replaced with retries to shorten delays
WAIT_TIME_AFTER_CREATION: int | float = (
15 # Time to wait after token creation (in seconds)
# Too short a delay may cause the RPC node to be unaware of the bonding curve account
)
WAIT_TIME_AFTER_BUY: int | float = (
15 # Time to wait after a buy transaction is confirmed (in seconds)
# Acts as a simple holding period
# Too short delay may cause the RPC node to be unaware of account balance
)
WAIT_TIME_BEFORE_NEW_TOKEN: int | float = (
5 # Time to wait after a sell transaction is confirmed (in seconds)
# Provides a pause between completed trades, can be set to 0
)
# Maximum age (in seconds) for a token to be considered "fresh" and eligible for processing.
# This threshold is checked before processing starts - tokens older than this are skipped
# since they likely contain outdated information from the websocket stream
MAX_TOKEN_AGE: float = 0.1
MAX_TOKEN_AGE: int | float = 0.1
# Node provier configuration
# You can also get a trader node https://docs.chainstack.com/docs/solana-trader-nodes
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"
# Node provider configuration
# Tested with Chainstack nodes (https://console.chainstack.com), but you can use any node provider
# You can get a trader node https://docs.chainstack.com/docs/solana-trader-nodes
MAX_RPS: int = 25 # TODO: not implemented. Max RPS to avoid rate limit errors
def validate_priority_fee_config() -> None:
"""Validate priority fee configuration values."""
if not isinstance(ENABLE_DYNAMIC_PRIORITY_FEE, bool):
raise ValueError("ENABLE_DYNAMIC_PRIORITY_FEE must be a boolean")
if not isinstance(ENABLE_FIXED_PRIORITY_FEE, bool):
raise ValueError("ENABLE_FIXED_PRIORITY_FEE must be a boolean")
if not isinstance(FIXED_PRIORITY_FEE, int) or FIXED_PRIORITY_FEE < 0:
raise ValueError("FIXED_PRIORITY_FEE must be a non-negative integer")
if not isinstance(EXTRA_PRIORITY_FEE, float) or EXTRA_PRIORITY_FEE < 0:
raise ValueError("EXTRA_PRIORITY_FEE must be a non-negative float")
if not isinstance(HARD_CAP_PRIOR_FEE, int) or HARD_CAP_PRIOR_FEE < 0:
raise ValueError("HARD_CAP_PRIOR_FEE must be a non-negative integer")
# Validate config on import
validate_priority_fee_config()
+2 -1
View File
@@ -5,4 +5,5 @@ construct-typing>=0.5.2
solana==0.36.6
solders>=0.26.0
websockets>=15.0
python-dotenv>=1.0.1
python-dotenv>=1.0.1
aiohttp==3.11.13
+60 -12
View File
@@ -3,12 +3,18 @@ Solana client abstraction for blockchain operations.
"""
import asyncio
import json
from typing import Any
import aiohttp
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
@@ -88,34 +94,50 @@ class SolanaClient:
response = await client.get_latest_blockhash()
return response.value.blockhash
async def send_transaction(
async def build_and_send_transaction(
self,
transaction: Transaction,
instructions: list[Instruction],
signer_keypair: Keypair,
skip_preflight: bool = True,
max_retries: int = 3,
priority_fee: int | None = None,
) -> str:
"""Send a transaction to the network.
"""
Send a transaction with optional priority fee.
Args:
transaction: Prepared transaction
skip_preflight: Whether to skip preflight checks
max_retries: Maximum number of sending attempts
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 microlamports.
Returns:
Transaction signature
Raises:
Exception: If transaction fails after all retries
Transaction signature.
"""
client = await self.get_client()
# Attempt to send with retries
logger.info(
f"Priority fee in microlamports: {priority_fee if priority_fee else 0}"
)
# Add priority fee instructions if applicable
if priority_fee is not None:
fee_instructions = [
set_compute_unit_limit(100_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, opts=tx_opts)
response = await client.send_transaction(transaction, tx_opts)
return response.value
except Exception as e:
@@ -150,3 +172,29 @@ class SolanaClient:
except Exception as e:
logger.error(f"Failed to confirm transaction {signature}: {str(e)}")
return False
async def post_rpc(self, body: dict[str, Any]) -> dict[str, Any] | None:
"""
Send a raw RPC request to the Solana node.
Args:
body: JSON-RPC request body.
Returns:
Optional[Dict[str, Any]]: Parsed JSON response, or None if the request fails.
"""
try:
async with aiohttp.ClientSession() as session:
async with session.post(
self.rpc_endpoint,
json=body,
timeout=aiohttp.ClientTimeout(10), # 10-second timeout
) as response:
response.raise_for_status()
return await response.json()
except aiohttp.ClientError as e:
logger.error(f"RPC request failed: {str(e)}", exc_info=True)
return None
except json.JSONDecodeError as e:
logger.error(f"Failed to decode RPC response: {str(e)}", exc_info=True)
return None
+15
View File
@@ -0,0 +1,15 @@
from abc import ABC, abstractmethod
class PriorityFeePlugin(ABC):
"""Base class for priority fee calculation plugins."""
@abstractmethod
async def get_priority_fee(self) -> int | None:
"""
Calculate the priority fee.
Returns:
Optional[int]: Priority fee in lamports, or None if no fee should be applied.
"""
pass
+69
View File
@@ -0,0 +1,69 @@
import statistics
from solders.pubkey import Pubkey
from core.priority_fee import PriorityFeePlugin
from src.core.client import SolanaClient
from src.utils.logger import get_logger
logger = get_logger(__name__)
class DynamicPriorityFee(PriorityFeePlugin):
"""Dynamic priority fee plugin using getRecentPrioritizationFees."""
def __init__(self, client: SolanaClient):
"""
Initialize the dynamic fee plugin.
Args:
client: Solana RPC client for network requests.
"""
self.client = client
async def get_priority_fee(
self, accounts: list[Pubkey] | None = None
) -> int | None:
"""
Fetch the recent priority fee using getRecentPrioritizationFees.
Args:
accounts: List of accounts to consider for the fee calculation.
If None, the fee is calculated without specific account constraints.
Returns:
Optional[int]: Median priority fee in microlamports, or None if the request fails.
"""
try:
body = {
"jsonrpc": "2.0",
"id": 1,
"method": "getRecentPrioritizationFees",
"params": [[str(account) for account in accounts]] if accounts else [],
}
response = await self.client.post_rpc(body)
if not response or "result" not in response:
logger.error(
"Failed to fetch recent prioritization fees: invalid response"
)
return None
fees = [fee["prioritizationFee"] for fee in response["result"]]
if not fees:
logger.warning("No prioritization fees found in the response")
return None
# Get the 70th percentile of fees for faster processing
# It means you're paying a fee that's higher than 70% of other transactions
# Higher percentile = faster transactions but more expensive
# Lower percentile = cheaper but slower transactions
prior_fee = int(statistics.quantiles(fees, n=10)[-3]) # 70th percentile
return prior_fee
except Exception as e:
logger.error(
f"Failed to fetch recent priority fee: {str(e)}", exc_info=True
)
return None
+25
View File
@@ -0,0 +1,25 @@
from . import PriorityFeePlugin
class FixedPriorityFee(PriorityFeePlugin):
"""Fixed priority fee plugin."""
def __init__(self, fixed_fee: int):
"""
Initialize the fixed fee plugin.
Args:
fixed_fee: Fixed priority fee in microlamports.
"""
self.fixed_fee = fixed_fee
async def get_priority_fee(self) -> int | None:
"""
Return the fixed priority fee.
Returns:
Optional[int]: Fixed priority fee in microlamports, or None if fixed_fee is 0.
"""
if self.fixed_fee == 0:
return None
return self.fixed_fee
+92
View File
@@ -0,0 +1,92 @@
from solders.pubkey import Pubkey
from src.core.client import SolanaClient
from src.core.priority_fee.dynamic_fee import DynamicPriorityFee
from src.core.priority_fee.fixed_fee import FixedPriorityFee
from src.utils.logger import get_logger
logger = get_logger(__name__)
class PriorityFeeManager:
"""Manager for priority fee calculation and validation."""
def __init__(
self,
client: SolanaClient,
enable_dynamic_fee: bool,
enable_fixed_fee: bool,
fixed_fee: int,
extra_fee: float,
hard_cap: int,
):
"""
Initialize the priority fee manager.
Args:
client: Solana RPC client for dynamic fee calculation.
enable_dynamic_fee: Whether to enable dynamic fee calculation.
enable_fixed_fee: Whether to enable fixed fee.
fixed_fee: Fixed priority fee in microlamports.
extra_fee: Percentage increase to apply to the base fee.
hard_cap: Maximum allowed priority fee in microlamports.
"""
self.client = client
self.enable_dynamic_fee = enable_dynamic_fee
self.enable_fixed_fee = enable_fixed_fee
self.fixed_fee = fixed_fee
self.extra_fee = extra_fee
self.hard_cap = hard_cap
# Initialize plugins
self.dynamic_fee_plugin = DynamicPriorityFee(client)
self.fixed_fee_plugin = FixedPriorityFee(fixed_fee)
async def calculate_priority_fee(
self, accounts: list[Pubkey] | None = None
) -> int | None:
"""
Calculate the priority fee based on the configuration.
Args:
accounts: List of accounts to consider for dynamic fee calculation.
If None, the fee is calculated without specific account constraints.
Returns:
Optional[int]: Calculated priority fee in microlamports, or None if no fee should be applied.
"""
base_fee = await self._get_base_fee(accounts)
if base_fee is None:
return None
# Apply extra fee (percentage increase)
final_fee = int(base_fee * (1 + self.extra_fee))
# Enforce hard cap
if final_fee > self.hard_cap:
logger.warning(
f"Calculated priority fee {final_fee} exceeds hard cap {self.hard_cap}. Applying hard cap."
)
final_fee = self.hard_cap
return final_fee
async def _get_base_fee(self, accounts: list[Pubkey] | None = None) -> int | None:
"""
Determine the base fee based on the configuration.
Returns:
Optional[int]: Base fee in microlamports, or None if no fee should be applied.
"""
# Prefer dynamic fee if both are enabled
if self.enable_dynamic_fee:
dynamic_fee = await self.dynamic_fee_plugin.get_priority_fee(accounts)
if dynamic_fee is not None:
return dynamic_fee
# Fall back to fixed fee if enabled
if self.enable_fixed_fee:
return await self.fixed_fee_plugin.get_priority_fee()
# No priority fee if both are disabled
return None
+19
View File
@@ -8,6 +8,8 @@ from typing import Any
from solders.pubkey import Pubkey
from core.pubkeys import PumpAddresses
@dataclass
class TokenInfo:
@@ -80,3 +82,20 @@ class Trader(ABC):
TradeResult with operation outcome
"""
pass
def _get_relevant_accounts(self, token_info: TokenInfo) -> list[Pubkey]:
"""
Get the list of accounts relevant for calculating the priority fee.
Args:
token_info: Token information for the buy/sell operation.
Returns:
list[Pubkey]: List of relevant accounts.
"""
return [
token_info.mint, # Token mint address
token_info.bonding_curve, # Bonding curve address
PumpAddresses.PROGRAM, # Pump.fun program address
PumpAddresses.FEE, # Pump.fun fee account
]
+37 -37
View File
@@ -5,13 +5,11 @@ Buy operations for pump.fun tokens.
import struct
from typing import Final
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
from spl.token.instructions import create_associated_token_account
from core.priority_fee.manager import PriorityFeeManager
from src.core.client import SolanaClient
from src.core.curve import BondingCurveManager
from src.core.pubkeys import (
@@ -38,6 +36,7 @@ class TokenBuyer(Trader):
client: SolanaClient,
wallet: Wallet,
curve_manager: BondingCurveManager,
priority_fee_manager: PriorityFeeManager,
amount: float,
slippage: float = 0.01,
max_retries: int = 5,
@@ -55,6 +54,7 @@ class TokenBuyer(Trader):
self.client = client
self.wallet = wallet
self.curve_manager = curve_manager
self.priority_fee_manager = priority_fee_manager
self.amount = amount
self.slippage = slippage
self.max_retries = max_retries
@@ -69,16 +69,13 @@ class TokenBuyer(Trader):
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)
curve_state = await self.curve_manager.get_curve_state(
token_info.bonding_curve
)
token_price_sol = curve_state.calculate_price()
token_amount = self.amount / token_price_sol
@@ -92,14 +89,16 @@ class TokenBuyer(Trader):
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)
associated_token_account = self.wallet.get_associated_token_address(
token_info.mint
)
await self._ensure_associated_token_account(mint, associated_token_account)
await self._ensure_associated_token_account(
token_info.mint, associated_token_account
)
tx_signature = await self._send_buy_transaction(
mint,
bonding_curve,
associated_bonding_curve,
token_info,
associated_token_account,
token_amount,
max_amount_lamports,
@@ -128,7 +127,7 @@ class TokenBuyer(Trader):
async def _ensure_associated_token_account(
self, mint: Pubkey, associated_token_account: Pubkey
) -> None:
"""Ensure associated token account exists.
"""Ensure associated token account exists, else create it.
Args:
mint: Token mint
@@ -147,14 +146,16 @@ class TokenBuyer(Trader):
payer=self.wallet.pubkey, owner=self.wallet.pubkey, mint=mint
)
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.build_and_send_transaction(
[create_ata_ix],
self.wallet.keypair,
skip_preflight=True,
max_retries=self.max_retries,
priority_fee=await self.priority_fee_manager.calculate_priority_fee(
[mint, SystemAddresses.PROGRAM, SystemAddresses.TOKEN_PROGRAM]
),
)
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}"
@@ -170,9 +171,7 @@ class TokenBuyer(Trader):
async def _send_buy_transaction(
self,
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
token_info: TokenInfo,
associated_token_account: Pubkey,
token_amount: float,
max_amount_lamports: int,
@@ -180,9 +179,7 @@ class TokenBuyer(Trader):
"""Send buy transaction.
Args:
mint: Token mint
bonding_curve: Bonding curve address
associated_bonding_curve: Associated bonding curve address
token_info: Token information
associated_token_account: User's token account
token_amount: Amount of tokens to buy
max_amount_lamports: Maximum SOL to spend in lamports
@@ -198,10 +195,14 @@ class TokenBuyer(Trader):
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=token_info.mint, is_signer=False, is_writable=False),
AccountMeta(
pubkey=associated_bonding_curve, is_signer=False, is_writable=True
pubkey=token_info.bonding_curve, is_signer=False, is_writable=True
),
AccountMeta(
pubkey=token_info.associated_bonding_curve,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=associated_token_account, is_signer=False, is_writable=True
@@ -233,16 +234,15 @@ class TokenBuyer(Trader):
)
buy_ix = Instruction(PumpAddresses.PROGRAM, data, accounts)
# 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(
buy_tx,
return await self.client.build_and_send_transaction(
[buy_ix],
self.wallet.keypair,
skip_preflight=True,
max_retries=self.max_retries,
priority_fee=await self.priority_fee_manager.calculate_priority_fee(
self._get_relevant_accounts(token_info)
),
)
except Exception as e:
logger.error(f"Buy transaction failed: {str(e)}")
+25 -29
View File
@@ -5,12 +5,10 @@ Sell operations for pump.fun tokens.
import struct
from typing import Final
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
from core.priority_fee.manager import PriorityFeeManager
from src.core.client import SolanaClient
from src.core.curve import BondingCurveManager
from src.core.pubkeys import (
@@ -37,6 +35,7 @@ class TokenSeller(Trader):
client: SolanaClient,
wallet: Wallet,
curve_manager: BondingCurveManager,
priority_fee_manager: PriorityFeeManager,
slippage: float = 0.25,
max_retries: int = 5,
):
@@ -52,6 +51,7 @@ class TokenSeller(Trader):
self.client = client
self.wallet = wallet
self.curve_manager = curve_manager
self.priority_fee_manager = priority_fee_manager
self.slippage = slippage
self.max_retries = max_retries
@@ -65,13 +65,10 @@ class TokenSeller(Trader):
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)
associated_token_account = self.wallet.get_associated_token_address(
token_info.mint
)
# Get token balance
token_balance = await self.client.get_token_account_balance(
@@ -86,7 +83,9 @@ class TokenSeller(Trader):
return TradeResult(success=False, error_message="No tokens to sell")
# Fetch token price
curve_state = await self.curve_manager.get_curve_state(bonding_curve)
curve_state = await self.curve_manager.get_curve_state(
token_info.bonding_curve
)
token_price_sol = curve_state.calculate_price()
logger.info(f"Price per Token: {token_price_sol:.8f} SOL")
@@ -106,9 +105,7 @@ class TokenSeller(Trader):
)
tx_signature = await self._send_sell_transaction(
mint,
bonding_curve,
associated_bonding_curve,
token_info,
associated_token_account,
amount,
min_sol_output,
@@ -136,9 +133,7 @@ class TokenSeller(Trader):
async def _send_sell_transaction(
self,
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
token_info: TokenInfo,
associated_token_account: Pubkey,
token_amount: int,
min_sol_output: int,
@@ -146,9 +141,7 @@ class TokenSeller(Trader):
"""Send sell transaction.
Args:
mint: Token mint
bonding_curve: Bonding curve address
associated_bonding_curve: Associated bonding curve address
mint: Token information
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
@@ -165,10 +158,14 @@ class TokenSeller(Trader):
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=token_info.mint, is_signer=False, is_writable=False),
AccountMeta(
pubkey=associated_bonding_curve, is_signer=False, is_writable=True
pubkey=token_info.bonding_curve, is_signer=False, is_writable=True
),
AccountMeta(
pubkey=token_info.associated_bonding_curve,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=associated_token_account, is_signer=False, is_writable=True
@@ -201,16 +198,15 @@ class TokenSeller(Trader):
)
sell_ix = Instruction(PumpAddresses.PROGRAM, data, accounts)
# 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(
sell_tx,
return await self.client.build_and_send_transaction(
[sell_ix],
self.wallet.keypair,
skip_preflight=True,
max_retries=self.max_retries,
priority_fee=await self.priority_fee_manager.calculate_priority_fee(
self._get_relevant_accounts(token_info)
),
)
except Exception as e:
logger.error(f"Sell transaction failed: {str(e)}")
+12
View File
@@ -9,6 +9,7 @@ import os
from datetime import datetime
import config
from core.priority_fee.manager import PriorityFeeManager
from src.core.client import SolanaClient
from src.core.curve import BondingCurveManager
from src.core.pubkeys import PumpAddresses
@@ -50,10 +51,20 @@ class PumpTrader:
self.wallet = Wallet(private_key)
self.curve_manager = BondingCurveManager(self.solana_client)
self.priority_fee_manager = PriorityFeeManager(
client=self.solana_client,
enable_dynamic_fee=config.ENABLE_DYNAMIC_PRIORITY_FEE,
enable_fixed_fee=config.ENABLE_FIXED_PRIORITY_FEE,
fixed_fee=config.FIXED_PRIORITY_FEE,
extra_fee=config.EXTRA_PRIORITY_FEE,
hard_cap=config.HARD_CAP_PRIOR_FEE,
)
self.buyer = TokenBuyer(
self.solana_client,
self.wallet,
self.curve_manager,
self.priority_fee_manager,
buy_amount,
buy_slippage,
max_retries,
@@ -63,6 +74,7 @@ class PumpTrader:
self.solana_client,
self.wallet,
self.curve_manager,
self.priority_fee_manager,
sell_slippage,
max_retries,
)