mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 23:37:45 +00:00
68cac87aad
* feat(core): support multiple initialize instruction variants in LetsBonkEventParser * feat(core): add creator and platform fee vault derivation methods and update account handling in buy/sell instructions * feat(letsbonk): add global_config and platform_config to TokenInfo and update address provider logic
416 lines
15 KiB
Python
416 lines
15 KiB
Python
"""
|
|
LetsBonk implementation of AddressProvider interface.
|
|
|
|
This module provides all LetsBonk (Raydium LaunchLab) specific addresses and PDA derivations
|
|
by implementing the AddressProvider interface.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Final
|
|
|
|
from solders.pubkey import Pubkey
|
|
from spl.token.instructions import get_associated_token_address
|
|
|
|
from core.pubkeys import SystemAddresses
|
|
from interfaces.core import AddressProvider, Platform, TokenInfo
|
|
|
|
|
|
@dataclass
|
|
class LetsBonkAddresses:
|
|
"""LetsBonk (Raydium LaunchLab) program addresses."""
|
|
|
|
# Raydium LaunchLab program addresses
|
|
PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
|
"LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"
|
|
)
|
|
# NOTE: GLOBAL_CONFIG is NOT constant across all pools!
|
|
# Each pool can be initialized with different global_config values. Different global_configs
|
|
# may define different program-wide settings, versions, or operational parameters.
|
|
# The correct global_config for each pool is extracted during pool initialization
|
|
# and stored in TokenInfo.global_config. This value below is a default, used as a fallback.
|
|
GLOBAL_CONFIG: Final[Pubkey] = Pubkey.from_string(
|
|
"6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX"
|
|
)
|
|
# NOTE: PLATFORM_CONFIG is NOT constant across all pools!
|
|
# Each pool is initialized with a specific platform_config that defines its fee structure,
|
|
# launch restrictions, and other settings. Different pools may use different platform_configs
|
|
# (e.g., standard launches vs partner launches). The correct platform_config for each pool
|
|
# is extracted during pool initialization and stored in TokenInfo.platform_config.
|
|
# This value below is the default/most common platform_config, used as a fallback.
|
|
PLATFORM_CONFIG: Final[Pubkey] = Pubkey.from_string(
|
|
"5thqcDwKp5QQ8US4XRMoseGeGbmLKMmoKZmS6zHrQAsA"
|
|
)
|
|
|
|
|
|
class LetsBonkAddressProvider(AddressProvider):
|
|
"""LetsBonk (Raydium LaunchLab) implementation of AddressProvider interface."""
|
|
|
|
@property
|
|
def platform(self) -> Platform:
|
|
"""Get the platform this provider serves."""
|
|
return Platform.LETS_BONK
|
|
|
|
@property
|
|
def program_id(self) -> Pubkey:
|
|
"""Get the main program ID for this platform."""
|
|
return LetsBonkAddresses.PROGRAM
|
|
|
|
def get_system_addresses(self) -> dict[str, Pubkey]:
|
|
"""Get all system addresses required for LetsBonk.
|
|
|
|
Returns:
|
|
Dictionary mapping address names to Pubkey objects
|
|
"""
|
|
# Get system addresses from the single source of truth
|
|
system_addresses = SystemAddresses.get_all_system_addresses()
|
|
|
|
# Add LetsBonk specific addresses
|
|
letsbonk_addresses = {
|
|
# Raydium LaunchLab specific addresses
|
|
"program": LetsBonkAddresses.PROGRAM,
|
|
"global_config": LetsBonkAddresses.GLOBAL_CONFIG,
|
|
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
|
|
}
|
|
|
|
# Combine system and platform-specific addresses
|
|
return {**system_addresses, **letsbonk_addresses}
|
|
|
|
def derive_pool_address(
|
|
self, base_mint: Pubkey, quote_mint: Pubkey | None = None
|
|
) -> Pubkey:
|
|
"""Derive the pool state address for a token pair.
|
|
|
|
For LetsBonk, this derives the pool state PDA using base_mint and WSOL.
|
|
|
|
Args:
|
|
base_mint: Base token mint address
|
|
quote_mint: Quote token mint (defaults to WSOL)
|
|
|
|
Returns:
|
|
Pool state address
|
|
"""
|
|
if quote_mint is None:
|
|
quote_mint = SystemAddresses.SOL_MINT
|
|
|
|
pool_state, _ = Pubkey.find_program_address(
|
|
[b"pool", bytes(base_mint), bytes(quote_mint)], LetsBonkAddresses.PROGRAM
|
|
)
|
|
return pool_state
|
|
|
|
def derive_base_vault(
|
|
self, base_mint: Pubkey, quote_mint: Pubkey | None = None
|
|
) -> Pubkey:
|
|
"""Derive the base vault address for a token pair.
|
|
|
|
Args:
|
|
base_mint: Base token mint address
|
|
quote_mint: Quote token mint (defaults to WSOL)
|
|
|
|
Returns:
|
|
Base vault address
|
|
"""
|
|
if quote_mint is None:
|
|
quote_mint = SystemAddresses.SOL_MINT
|
|
|
|
# First derive the pool state address
|
|
pool_state = self.derive_pool_address(base_mint, quote_mint)
|
|
|
|
# Then derive the base vault using pool_vault seed
|
|
base_vault, _ = Pubkey.find_program_address(
|
|
[b"pool_vault", bytes(pool_state), bytes(base_mint)],
|
|
LetsBonkAddresses.PROGRAM,
|
|
)
|
|
return base_vault
|
|
|
|
def derive_quote_vault(
|
|
self, base_mint: Pubkey, quote_mint: Pubkey | None = None
|
|
) -> Pubkey:
|
|
"""Derive the quote vault address for a token pair.
|
|
|
|
Args:
|
|
base_mint: Base token mint address
|
|
quote_mint: Quote token mint (defaults to WSOL)
|
|
|
|
Returns:
|
|
Quote vault address
|
|
"""
|
|
if quote_mint is None:
|
|
quote_mint = SystemAddresses.SOL_MINT
|
|
|
|
# First derive the pool state address
|
|
pool_state = self.derive_pool_address(base_mint, quote_mint)
|
|
|
|
# Then derive the quote vault using pool_vault seed
|
|
quote_vault, _ = Pubkey.find_program_address(
|
|
[b"pool_vault", bytes(pool_state), bytes(quote_mint)],
|
|
LetsBonkAddresses.PROGRAM,
|
|
)
|
|
return quote_vault
|
|
|
|
def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey:
|
|
"""Derive user's associated token account address.
|
|
|
|
Args:
|
|
user: User's wallet address
|
|
mint: Token mint address
|
|
|
|
Returns:
|
|
User's associated token account address
|
|
"""
|
|
return get_associated_token_address(user, mint)
|
|
|
|
def get_additional_accounts(self, token_info: TokenInfo) -> dict[str, Pubkey]:
|
|
"""Get LetsBonk-specific additional accounts needed for trading.
|
|
|
|
Args:
|
|
token_info: Token information
|
|
|
|
Returns:
|
|
Dictionary of additional account addresses
|
|
"""
|
|
accounts = {}
|
|
|
|
# Add pool state - derive if not present or use existing
|
|
if token_info.pool_state:
|
|
accounts["pool_state"] = token_info.pool_state
|
|
else:
|
|
accounts["pool_state"] = self.derive_pool_address(token_info.mint)
|
|
|
|
# Add vault addresses - derive if not present or use existing
|
|
if token_info.base_vault:
|
|
accounts["base_vault"] = token_info.base_vault
|
|
else:
|
|
accounts["base_vault"] = self.derive_base_vault(token_info.mint)
|
|
|
|
if token_info.quote_vault:
|
|
accounts["quote_vault"] = token_info.quote_vault
|
|
else:
|
|
accounts["quote_vault"] = self.derive_quote_vault(token_info.mint)
|
|
|
|
# Derive authority PDA
|
|
accounts["authority"] = self.derive_authority_pda()
|
|
|
|
# Derive event authority PDA
|
|
accounts["event_authority"] = self.derive_event_authority_pda()
|
|
|
|
return accounts
|
|
|
|
def derive_authority_pda(self) -> Pubkey:
|
|
"""Derive the authority PDA for Raydium LaunchLab.
|
|
|
|
This PDA acts as the authority for pool vault operations.
|
|
|
|
Returns:
|
|
Authority PDA address
|
|
"""
|
|
AUTH_SEED = b"vault_auth_seed"
|
|
authority_pda, _ = Pubkey.find_program_address(
|
|
[AUTH_SEED], LetsBonkAddresses.PROGRAM
|
|
)
|
|
return authority_pda
|
|
|
|
def derive_event_authority_pda(self) -> Pubkey:
|
|
"""Derive the event authority PDA for Raydium LaunchLab.
|
|
|
|
This PDA is used for emitting program events during swaps.
|
|
|
|
Returns:
|
|
Event authority PDA address
|
|
"""
|
|
EVENT_AUTHORITY_SEED = b"__event_authority"
|
|
event_authority_pda, _ = Pubkey.find_program_address(
|
|
[EVENT_AUTHORITY_SEED], LetsBonkAddresses.PROGRAM
|
|
)
|
|
return event_authority_pda
|
|
|
|
def derive_creator_fee_vault(
|
|
self, creator: Pubkey, quote_mint: Pubkey | None = None
|
|
) -> Pubkey:
|
|
"""Derive the creator fee vault PDA.
|
|
|
|
This vault accumulates creator fees from trades.
|
|
|
|
Args:
|
|
creator: The pool creator's pubkey
|
|
quote_mint: The quote token mint (defaults to WSOL)
|
|
|
|
Returns:
|
|
Creator fee vault address
|
|
"""
|
|
if quote_mint is None:
|
|
quote_mint = SystemAddresses.SOL_MINT
|
|
|
|
creator_fee_vault, _ = Pubkey.find_program_address(
|
|
[bytes(creator), bytes(quote_mint)], LetsBonkAddresses.PROGRAM
|
|
)
|
|
return creator_fee_vault
|
|
|
|
def derive_platform_fee_vault(
|
|
self, platform_config: Pubkey | None = None, quote_mint: Pubkey | None = None
|
|
) -> Pubkey:
|
|
"""Derive the platform fee vault PDA.
|
|
|
|
This vault accumulates platform fees from trades.
|
|
|
|
Args:
|
|
platform_config: The platform config account (defaults to LetsBonk config)
|
|
quote_mint: The quote token mint (defaults to WSOL)
|
|
|
|
Returns:
|
|
Platform fee vault address
|
|
"""
|
|
if platform_config is None:
|
|
platform_config = LetsBonkAddresses.PLATFORM_CONFIG
|
|
|
|
if quote_mint is None:
|
|
quote_mint = SystemAddresses.SOL_MINT
|
|
|
|
platform_fee_vault, _ = Pubkey.find_program_address(
|
|
[bytes(platform_config), bytes(quote_mint)], LetsBonkAddresses.PROGRAM
|
|
)
|
|
return platform_fee_vault
|
|
|
|
def create_wsol_account_with_seed(self, payer: Pubkey, seed: str) -> Pubkey:
|
|
"""Create a WSOL account address using createAccountWithSeed pattern.
|
|
|
|
Args:
|
|
payer: The account that will pay for and own the new account
|
|
seed: String seed for deterministic account generation
|
|
|
|
Returns:
|
|
New WSOL account address
|
|
"""
|
|
return Pubkey.create_with_seed(payer, seed, SystemAddresses.TOKEN_PROGRAM)
|
|
|
|
def get_buy_instruction_accounts(
|
|
self, token_info: TokenInfo, user: Pubkey
|
|
) -> dict[str, Pubkey]:
|
|
"""Get all accounts needed for a buy instruction.
|
|
|
|
Args:
|
|
token_info: Token information
|
|
user: User's wallet address
|
|
|
|
Returns:
|
|
Dictionary of account addresses for buy instruction
|
|
"""
|
|
additional_accounts = self.get_additional_accounts(token_info)
|
|
|
|
# Use global_config from TokenInfo if available, otherwise use default
|
|
global_config = (
|
|
token_info.global_config
|
|
if token_info.global_config
|
|
else LetsBonkAddresses.GLOBAL_CONFIG
|
|
)
|
|
|
|
# Use platform_config from TokenInfo if available, otherwise use default
|
|
platform_config = (
|
|
token_info.platform_config
|
|
if token_info.platform_config
|
|
else LetsBonkAddresses.PLATFORM_CONFIG
|
|
)
|
|
|
|
accounts = {
|
|
"payer": user,
|
|
"authority": additional_accounts["authority"],
|
|
"global_config": global_config,
|
|
"platform_config": platform_config,
|
|
"pool_state": additional_accounts["pool_state"],
|
|
"user_base_token": self.derive_user_token_account(user, token_info.mint),
|
|
"base_vault": additional_accounts["base_vault"],
|
|
"quote_vault": additional_accounts["quote_vault"],
|
|
"base_token_mint": token_info.mint,
|
|
"quote_token_mint": SystemAddresses.SOL_MINT,
|
|
"base_token_program": SystemAddresses.TOKEN_PROGRAM,
|
|
"quote_token_program": SystemAddresses.TOKEN_PROGRAM,
|
|
"event_authority": additional_accounts["event_authority"],
|
|
"program": LetsBonkAddresses.PROGRAM,
|
|
"system_program": SystemAddresses.SYSTEM_PROGRAM,
|
|
"platform_fee_vault": self.derive_platform_fee_vault(platform_config),
|
|
}
|
|
|
|
# Add creator fee vault if creator is known
|
|
if token_info.creator:
|
|
accounts["creator_fee_vault"] = self.derive_creator_fee_vault(
|
|
token_info.creator
|
|
)
|
|
|
|
return accounts
|
|
|
|
def get_sell_instruction_accounts(
|
|
self, token_info: TokenInfo, user: Pubkey
|
|
) -> dict[str, Pubkey]:
|
|
"""Get all accounts needed for a sell instruction.
|
|
|
|
Args:
|
|
token_info: Token information
|
|
user: User's wallet address
|
|
|
|
Returns:
|
|
Dictionary of account addresses for sell instruction
|
|
"""
|
|
additional_accounts = self.get_additional_accounts(token_info)
|
|
|
|
# Use global_config from TokenInfo if available, otherwise use default
|
|
global_config = (
|
|
token_info.global_config
|
|
if token_info.global_config
|
|
else LetsBonkAddresses.GLOBAL_CONFIG
|
|
)
|
|
|
|
# Use platform_config from TokenInfo if available, otherwise use default
|
|
platform_config = (
|
|
token_info.platform_config
|
|
if token_info.platform_config
|
|
else LetsBonkAddresses.PLATFORM_CONFIG
|
|
)
|
|
|
|
accounts = {
|
|
"payer": user,
|
|
"authority": additional_accounts["authority"],
|
|
"global_config": global_config,
|
|
"platform_config": platform_config,
|
|
"pool_state": additional_accounts["pool_state"],
|
|
"user_base_token": self.derive_user_token_account(user, token_info.mint),
|
|
"base_vault": additional_accounts["base_vault"],
|
|
"quote_vault": additional_accounts["quote_vault"],
|
|
"base_token_mint": token_info.mint,
|
|
"quote_token_mint": SystemAddresses.SOL_MINT,
|
|
"base_token_program": SystemAddresses.TOKEN_PROGRAM,
|
|
"quote_token_program": SystemAddresses.TOKEN_PROGRAM,
|
|
"event_authority": additional_accounts["event_authority"],
|
|
"program": LetsBonkAddresses.PROGRAM,
|
|
"system_program": SystemAddresses.SYSTEM_PROGRAM,
|
|
"platform_fee_vault": self.derive_platform_fee_vault(platform_config),
|
|
}
|
|
|
|
# Add creator fee vault if creator is known
|
|
if token_info.creator:
|
|
accounts["creator_fee_vault"] = self.derive_creator_fee_vault(
|
|
token_info.creator
|
|
)
|
|
|
|
return accounts
|
|
|
|
def get_wsol_account_creation_accounts(
|
|
self, user: Pubkey, wsol_account: Pubkey
|
|
) -> dict[str, Pubkey]:
|
|
"""Get accounts needed for WSOL account creation and initialization.
|
|
|
|
Args:
|
|
user: User's wallet address
|
|
wsol_account: WSOL account to be created
|
|
|
|
Returns:
|
|
Dictionary of account addresses for WSOL operations
|
|
"""
|
|
return {
|
|
"payer": user,
|
|
"wsol_account": wsol_account,
|
|
"wsol_mint": SystemAddresses.SOL_MINT,
|
|
"owner": user,
|
|
"system_program": SystemAddresses.SYSTEM_PROGRAM,
|
|
"token_program": SystemAddresses.TOKEN_PROGRAM,
|
|
"rent": SystemAddresses.RENT,
|
|
}
|