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
+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