feat: add priority fee support with dynamic/fixed options, hard cap, and extra fee

This commit is contained in:
smypmsa
2025-03-12 22:11:31 +00:00
parent 3fb5d15104
commit ac3d463d20
10 changed files with 266 additions and 40 deletions
+1
View File
@@ -1,5 +1,6 @@
trades/*
.vscode
.pylintrc
.ruff_cache
+31 -6
View File
@@ -3,11 +3,18 @@ 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: float = 0.000001 # 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 = True # Enable dynamic priority fee calculation
ENABLE_FIXED_PRIORITY_FEE: bool = True # Enable fixed priority fee
FIXED_PRIORITY_FEE: int = 200000 # Fixed priority fee in lamports (0 means no fee)
EXTRA_PRIORITY_FEE: float = (
0.1 # Percentage increase applied to priority fee (0.1 = 10%)
)
HARD_CAP_PRIOR_FEE: int = 1000000 # Maximum allowed priority fee in lamports (hard cap)
# Retries and timeouts
MAX_RETRIES: int = 2
@@ -22,6 +29,24 @@ MAX_TOKEN_AGE: 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
MAX_RPS: int = 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"
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()
+28 -12
View File
@@ -8,7 +8,11 @@ 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
@@ -88,34 +92,46 @@ 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 lamports.
Returns:
Transaction signature
Raises:
Exception: If transaction fails after all retries
Transaction signature.
"""
client = await self.get_client()
# Attempt to send with retries
# 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, opts=tx_opts)
response = await client.send_transaction(transaction, tx_opts)
return response.value
except Exception as e:
+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
+38
View File
@@ -0,0 +1,38 @@
from src.core.client import SolanaClient
from src.utils.logger import get_logger
from . import PriorityFeePlugin
logger = get_logger(__name__)
class DynamicPriorityFee(PriorityFeePlugin):
"""Default dynamic priority fee plugin using getRecentPriorityFee."""
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) -> int | None:
"""
Fetch the recent priority fee from the Solana network.
Returns:
Optional[int]: Recent priority fee in lamports, or None if the request fails.
"""
try:
client = await self.client.get_client()
response = await client.get_recent_prioritization_fees()
if response and response.value:
return response.value[
0
].prioritization_fee # Use the first fee from the list
return None
except Exception as e:
logger.error(f"Failed to fetch recent priority fee: {str(e)}")
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 lamports.
"""
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 lamports, or None if fixed_fee is 0.
"""
if self.fixed_fee == 0:
return None
return self.fixed_fee
+84
View File
@@ -0,0 +1,84 @@
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 lamports.
extra_fee: Percentage increase to apply to the base fee.
hard_cap: Maximum allowed priority fee in lamports.
"""
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) -> int | None:
"""
Calculate the priority fee based on the configuration.
Returns:
Optional[int]: Calculated priority fee in lamports, or None if no fee should be applied.
"""
base_fee = await self._get_base_fee()
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) -> int | None:
"""
Determine the base fee based on the configuration.
Returns:
Optional[int]: Base fee in lamports, 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()
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 fee if both are disabled or return None
return None
+22 -14
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
@@ -147,13 +147,19 @@ 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
)
# 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)
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(), # Get priority fee from manager
)
await self.client.confirm_transaction(tx_sig)
logger.info(
@@ -234,15 +240,17 @@ 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)
# 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(), # Get priority fee from manager
)
except Exception as e:
logger.error(f"Buy transaction failed: {str(e)}")
+10 -8
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
@@ -202,15 +202,17 @@ 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)
# 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(), # Get priority fee from manager
)
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,
)