Files
pumpfun-bonkfun-bot/src/platforms/pumpfun/instruction_builder.py
T

367 lines
13 KiB
Python
Raw Normal View History

2025-08-02 09:25:39 +00:00
"""
2025-08-02 16:12:45 +00:00
Pump.Fun implementation of InstructionBuilder interface.
2025-08-02 09:25:39 +00:00
2025-08-02 16:12:45 +00:00
This module builds pump.fun-specific buy and sell instructions
2025-08-02 15:10:24 +00:00
by implementing the InstructionBuilder interface with IDL-based discriminators.
2025-08-02 09:25:39 +00:00
"""
import struct
from solders.instruction import AccountMeta, Instruction
from solders.pubkey import Pubkey
from spl.token.instructions import create_idempotent_associated_token_account
from core.pubkeys import TOKEN_DECIMALS, SystemAddresses
from interfaces.core import AddressProvider, InstructionBuilder, Platform, TokenInfo
2025-08-02 15:10:24 +00:00
from utils.idl_parser import IDLParser
from utils.logger import get_logger
2025-08-02 09:25:39 +00:00
2025-08-02 15:10:24 +00:00
logger = get_logger(__name__)
2025-08-02 09:25:39 +00:00
2025-08-02 16:12:45 +00:00
class PumpFunInstructionBuilder(InstructionBuilder):
"""Pump.Fun implementation of InstructionBuilder interface with IDL-based discriminators."""
2025-08-11 05:35:25 +00:00
2025-08-02 15:10:24 +00:00
def __init__(self, idl_parser: IDLParser):
2025-08-02 16:12:45 +00:00
"""Initialize pump.fun instruction builder with injected IDL parser.
2025-08-11 05:35:25 +00:00
2025-08-02 15:10:24 +00:00
Args:
2025-08-02 16:12:45 +00:00
idl_parser: Pre-loaded IDL parser for pump.fun platform
2025-08-02 15:10:24 +00:00
"""
self._idl_parser = idl_parser
2025-08-11 05:35:25 +00:00
2025-08-02 15:10:24 +00:00
# Get discriminators from injected IDL parser
discriminators = self._idl_parser.get_instruction_discriminators()
2025-08-02 16:12:45 +00:00
self._buy_discriminator = discriminators["buy"]
self._sell_discriminator = discriminators["sell"]
2025-08-11 05:35:25 +00:00
2025-08-02 16:12:45 +00:00
logger.info("Pump.Fun instruction builder initialized with injected IDL parser")
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
@property
def platform(self) -> Platform:
"""Get the platform this builder serves."""
2025-08-02 16:12:45 +00:00
return Platform.PUMP_FUN
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
async def build_buy_instruction(
self,
token_info: TokenInfo,
user: Pubkey,
amount_in: int,
minimum_amount_out: int,
2025-08-11 05:35:25 +00:00
address_provider: AddressProvider,
2025-08-02 09:25:39 +00:00
) -> list[Instruction]:
2025-08-02 16:12:45 +00:00
"""Build buy instruction(s) for pump.fun.
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Args:
token_info: Token information
user: User's wallet address
amount_in: Amount of SOL to spend (in lamports)
minimum_amount_out: Minimum tokens expected (raw token units)
address_provider: Platform address provider
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Returns:
List of instructions needed for the buy operation
"""
instructions = []
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
# Get all required accounts
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
2025-08-11 05:35:25 +00:00
2025-08-02 16:12:45 +00:00
# 1. Create idempotent ATA instruction (won't fail if ATA already exists)
2025-08-02 09:25:39 +00:00
ata_instruction = create_idempotent_associated_token_account(
user, # payer
2025-08-02 16:12:45 +00:00
user, # owner
2025-08-02 09:25:39 +00:00
token_info.mint, # mint
SystemAddresses.TOKEN_PROGRAM, # token program
)
instructions.append(ata_instruction)
2025-08-11 05:35:25 +00:00
2025-08-02 16:12:45 +00:00
# 2. Build buy instruction
2025-08-02 09:25:39 +00:00
buy_accounts = [
2025-08-11 05:35:25 +00:00
AccountMeta(
pubkey=accounts_info["global"], is_signer=False, is_writable=False
),
2025-08-02 16:12:45 +00:00
AccountMeta(pubkey=accounts_info["fee"], is_signer=False, is_writable=True),
2025-08-11 05:35:25 +00:00
AccountMeta(
pubkey=accounts_info["mint"], is_signer=False, is_writable=False
),
AccountMeta(
pubkey=accounts_info["bonding_curve"], is_signer=False, is_writable=True
),
AccountMeta(
pubkey=accounts_info["associated_bonding_curve"],
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=accounts_info["user_token_account"],
is_signer=False,
is_writable=True,
),
2025-08-02 16:12:45 +00:00
AccountMeta(pubkey=accounts_info["user"], is_signer=True, is_writable=True),
2025-08-11 05:35:25 +00:00
AccountMeta(
pubkey=accounts_info["system_program"],
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=accounts_info["token_program"],
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=accounts_info["creator_vault"], is_signer=False, is_writable=True
),
AccountMeta(
pubkey=accounts_info["event_authority"],
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=accounts_info["program"], is_signer=False, is_writable=False
),
AccountMeta(
pubkey=accounts_info["global_volume_accumulator"],
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=accounts_info["user_volume_accumulator"],
is_signer=False,
is_writable=True,
),
# Index 14: fee_config (readonly)
AccountMeta(
pubkey=accounts_info["fee_config"],
is_signer=False,
is_writable=False,
),
# Index 15: fee_program (readonly)
AccountMeta(
pubkey=accounts_info["fee_program"],
is_signer=False,
is_writable=False,
),
2025-08-02 09:25:39 +00:00
]
2025-08-11 05:35:25 +00:00
2025-08-02 16:12:45 +00:00
# Build instruction data: discriminator + token_amount + max_sol_cost
2025-08-02 09:25:39 +00:00
instruction_data = (
2025-08-11 05:35:25 +00:00
self._buy_discriminator
+ struct.pack("<Q", minimum_amount_out) # token amount in raw units
+ struct.pack("<Q", amount_in) # max SOL cost in lamports
2025-08-02 09:25:39 +00:00
)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
buy_instruction = Instruction(
program_id=accounts_info["program"],
data=instruction_data,
2025-08-11 05:35:25 +00:00
accounts=buy_accounts,
2025-08-02 09:25:39 +00:00
)
instructions.append(buy_instruction)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
return instructions
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
async def build_sell_instruction(
self,
token_info: TokenInfo,
user: Pubkey,
amount_in: int,
minimum_amount_out: int,
2025-08-11 05:35:25 +00:00
address_provider: AddressProvider,
2025-08-02 09:25:39 +00:00
) -> list[Instruction]:
2025-08-02 16:12:45 +00:00
"""Build sell instruction(s) for pump.fun.
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Args:
token_info: Token information
user: User's wallet address
amount_in: Amount of tokens to sell (raw token units)
minimum_amount_out: Minimum SOL expected (in lamports)
address_provider: Platform address provider
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Returns:
List of instructions needed for the sell operation
"""
instructions = []
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
# Get all required accounts
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
2025-08-11 05:35:25 +00:00
2025-08-02 16:12:45 +00:00
# Build sell instruction accounts
2025-08-02 09:25:39 +00:00
sell_accounts = [
2025-08-11 05:35:25 +00:00
AccountMeta(
pubkey=accounts_info["global"], is_signer=False, is_writable=False
),
2025-08-02 16:12:45 +00:00
AccountMeta(pubkey=accounts_info["fee"], is_signer=False, is_writable=True),
2025-08-11 05:35:25 +00:00
AccountMeta(
pubkey=accounts_info["mint"], is_signer=False, is_writable=False
),
AccountMeta(
pubkey=accounts_info["bonding_curve"], is_signer=False, is_writable=True
),
AccountMeta(
pubkey=accounts_info["associated_bonding_curve"],
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=accounts_info["user_token_account"],
is_signer=False,
is_writable=True,
),
2025-08-02 16:12:45 +00:00
AccountMeta(pubkey=accounts_info["user"], is_signer=True, is_writable=True),
2025-08-11 05:35:25 +00:00
AccountMeta(
pubkey=accounts_info["system_program"],
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=accounts_info["creator_vault"], is_signer=False, is_writable=True
),
AccountMeta(
pubkey=accounts_info["token_program"],
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=accounts_info["event_authority"],
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=accounts_info["program"], is_signer=False, is_writable=False
),
# Index 12: fee_config (readonly)
AccountMeta(
pubkey=accounts_info["fee_config"],
is_signer=False,
is_writable=False,
),
# Index 13: fee_program (readonly)
AccountMeta(
pubkey=accounts_info["fee_program"],
is_signer=False,
is_writable=False,
),
2025-08-02 09:25:39 +00:00
]
2025-08-11 05:35:25 +00:00
2025-08-02 16:12:45 +00:00
# Build instruction data: discriminator + token_amount + min_sol_output
2025-08-02 09:25:39 +00:00
instruction_data = (
2025-08-11 05:35:25 +00:00
self._sell_discriminator
+ struct.pack("<Q", amount_in) # token amount in raw units
+ struct.pack("<Q", minimum_amount_out) # min SOL output in lamports
2025-08-02 09:25:39 +00:00
)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
sell_instruction = Instruction(
program_id=accounts_info["program"],
data=instruction_data,
2025-08-11 05:35:25 +00:00
accounts=sell_accounts,
2025-08-02 09:25:39 +00:00
)
instructions.append(sell_instruction)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
return instructions
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
def get_required_accounts_for_buy(
2025-08-11 05:35:25 +00:00
self, token_info: TokenInfo, user: Pubkey, address_provider: AddressProvider
2025-08-02 09:25:39 +00:00
) -> list[Pubkey]:
"""Get list of accounts required for buy operation (for priority fee calculation).
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Args:
token_info: Token information
user: User's wallet address
address_provider: Platform address provider
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Returns:
List of account addresses that will be accessed
"""
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
return [
2025-08-02 16:12:45 +00:00
accounts_info["mint"],
accounts_info["bonding_curve"],
accounts_info["associated_bonding_curve"],
accounts_info["user_token_account"],
accounts_info["fee"],
accounts_info["creator_vault"],
accounts_info["global_volume_accumulator"],
accounts_info["user_volume_accumulator"],
2025-08-02 09:25:39 +00:00
accounts_info["program"],
accounts_info["fee_config"],
accounts_info["fee_program"],
2025-08-02 09:25:39 +00:00
]
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
def get_required_accounts_for_sell(
2025-08-11 05:35:25 +00:00
self, token_info: TokenInfo, user: Pubkey, address_provider: AddressProvider
2025-08-02 09:25:39 +00:00
) -> list[Pubkey]:
"""Get list of accounts required for sell operation (for priority fee calculation).
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Args:
token_info: Token information
user: User's wallet address
address_provider: Platform address provider
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Returns:
List of account addresses that will be accessed
"""
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
return [
2025-08-02 16:12:45 +00:00
accounts_info["mint"],
accounts_info["bonding_curve"],
accounts_info["associated_bonding_curve"],
accounts_info["user_token_account"],
accounts_info["fee"],
accounts_info["creator_vault"],
2025-08-02 09:25:39 +00:00
accounts_info["program"],
accounts_info["fee_config"],
accounts_info["fee_program"],
2025-08-02 09:25:39 +00:00
]
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
def calculate_token_amount_raw(self, token_amount_decimal: float) -> int:
"""Convert decimal token amount to raw token units.
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Args:
token_amount_decimal: Token amount in decimal form
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Returns:
Token amount in raw units (adjusted for decimals)
"""
return int(token_amount_decimal * 10**TOKEN_DECIMALS)
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
def calculate_token_amount_decimal(self, token_amount_raw: int) -> float:
"""Convert raw token amount to decimal form.
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Args:
token_amount_raw: Token amount in raw units
2025-08-11 05:35:25 +00:00
2025-08-02 09:25:39 +00:00
Returns:
Token amount in decimal form
"""
2025-08-11 05:35:25 +00:00
return token_amount_raw / 10**TOKEN_DECIMALS
2025-09-03 15:05:12 +00:00
def get_buy_compute_unit_limit(self, config_override: int | None = None) -> int:
"""Get the recommended compute unit limit for pump.fun buy operations.
Args:
config_override: Optional override from configuration
Returns:
Compute unit limit appropriate for buy operations
"""
if config_override is not None:
return config_override
# Buy operations: ATA creation + buy instruction
return 100_000
def get_sell_compute_unit_limit(self, config_override: int | None = None) -> int:
"""Get the recommended compute unit limit for pump.fun sell operations.
Args:
config_override: Optional override from configuration
Returns:
Compute unit limit appropriate for sell operations
"""
if config_override is not None:
return config_override
# Sell operations: typically just sell instruction (ATA exists)
return 60_000