mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-01 01:37:44 +00:00
Add mayhem mode support and Token2022 integration (#149)
* feat: mayhem update in idl * feat(examples): update bonding curve scripts * feat(example): update listen_blocksubscribe * feat(examples): update geyser listener * feat(examples): update all new token listeners * feat(examples): add comments, fix printing, formatting * feat(examples): pumpswap buy and sell update with mayhem mode * fix(examples): sell pump amm fee recipient * feat(examples): update decode scripts * feat(examples): update fetch price * feat(examples): buy and sell bonding curve scripts * feat(examples): add mint with mayhem mode enabled * feat(examples): improve listening to wallet txs * feat(examples): migration listener improvements * feat(examples): global vol accumulator is not writable * feat(examples): support token/token2022 programs in buy instructions * feat(examples): token/token2022 for pumpswap buy * feat(examples): token/token2022 supprot for sell instructions * feat(bot): support create_v2 with token2022, mayhem mode, other fixes * fix(bot): support only token2022 in logs and pumportal listeners * feat(bot): token2022 support in cleanup flow * fix(bot): update token program handling and improve price validation in trading logic * feat(bot): enhance token program handling for LetsBonk integration
This commit is contained in:
+11
-4
@@ -34,12 +34,19 @@ class AccountCleanupManager:
|
||||
self.use_priority_fee = use_priority_fee
|
||||
self.close_with_force_burn = force_burn
|
||||
|
||||
async def cleanup_ata(self, mint: Pubkey) -> None:
|
||||
async def cleanup_ata(self, mint: Pubkey, token_program_id: Pubkey | None = None) -> None:
|
||||
"""
|
||||
Attempt to burn any remaining tokens and close the ATA.
|
||||
Skips if account doesn't exist or is already empty/closed.
|
||||
|
||||
Args:
|
||||
mint: Token mint address
|
||||
token_program_id: Token program (TOKEN or TOKEN_2022). Defaults to TOKEN_2022_PROGRAM
|
||||
"""
|
||||
ata = self.wallet.get_associated_token_address(mint)
|
||||
if token_program_id is None:
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
|
||||
ata = self.wallet.get_associated_token_address(mint, token_program_id)
|
||||
solana_client = await self.client.get_client()
|
||||
|
||||
priority_fee = (
|
||||
@@ -70,7 +77,7 @@ class AccountCleanupManager:
|
||||
mint=mint,
|
||||
owner=self.wallet.pubkey,
|
||||
amount=balance,
|
||||
program_id=SystemAddresses.TOKEN_PROGRAM,
|
||||
program_id=token_program_id,
|
||||
)
|
||||
)
|
||||
instructions.append(burn_ix)
|
||||
@@ -89,7 +96,7 @@ class AccountCleanupManager:
|
||||
account=ata,
|
||||
dest=self.wallet.pubkey,
|
||||
owner=self.wallet.pubkey,
|
||||
program_id=SystemAddresses.TOKEN_PROGRAM,
|
||||
program_id=token_program_id,
|
||||
)
|
||||
)
|
||||
instructions.append(close_ix)
|
||||
|
||||
@@ -20,6 +20,7 @@ async def handle_cleanup_after_failure(
|
||||
client,
|
||||
wallet,
|
||||
mint,
|
||||
token_program_id,
|
||||
priority_fee_manager,
|
||||
cleanup_mode,
|
||||
cleanup_with_prior_fee,
|
||||
@@ -30,13 +31,14 @@ async def handle_cleanup_after_failure(
|
||||
manager = AccountCleanupManager(
|
||||
client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn
|
||||
)
|
||||
await manager.cleanup_ata(mint)
|
||||
await manager.cleanup_ata(mint, token_program_id)
|
||||
|
||||
|
||||
async def handle_cleanup_after_sell(
|
||||
client,
|
||||
wallet,
|
||||
mint,
|
||||
token_program_id,
|
||||
priority_fee_manager,
|
||||
cleanup_mode,
|
||||
cleanup_with_prior_fee,
|
||||
@@ -47,13 +49,14 @@ async def handle_cleanup_after_sell(
|
||||
manager = AccountCleanupManager(
|
||||
client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn
|
||||
)
|
||||
await manager.cleanup_ata(mint)
|
||||
await manager.cleanup_ata(mint, token_program_id)
|
||||
|
||||
|
||||
async def handle_cleanup_post_session(
|
||||
client,
|
||||
wallet,
|
||||
mints,
|
||||
token_program_ids,
|
||||
priority_fee_manager,
|
||||
cleanup_mode,
|
||||
cleanup_with_prior_fee,
|
||||
@@ -64,5 +67,5 @@ async def handle_cleanup_post_session(
|
||||
manager = AccountCleanupManager(
|
||||
client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn
|
||||
)
|
||||
for mint in mints:
|
||||
await manager.cleanup_ata(mint)
|
||||
for mint, token_program_id in zip(mints, token_program_ids):
|
||||
await manager.cleanup_ata(mint, token_program_id)
|
||||
|
||||
@@ -23,6 +23,9 @@ SYSTEM_PROGRAM: Final[Pubkey] = Pubkey.from_string("1111111111111111111111111111
|
||||
TOKEN_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
)
|
||||
TOKEN_2022_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
||||
)
|
||||
ASSOCIATED_TOKEN_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
)
|
||||
@@ -42,6 +45,7 @@ class SystemAddresses:
|
||||
# Reference the module-level constants
|
||||
SYSTEM_PROGRAM = SYSTEM_PROGRAM
|
||||
TOKEN_PROGRAM = TOKEN_PROGRAM
|
||||
TOKEN_2022_PROGRAM = TOKEN_2022_PROGRAM
|
||||
ASSOCIATED_TOKEN_PROGRAM = ASSOCIATED_TOKEN_PROGRAM
|
||||
RENT = RENT
|
||||
SOL_MINT = SOL_MINT
|
||||
@@ -56,6 +60,7 @@ class SystemAddresses:
|
||||
return {
|
||||
"system_program": cls.SYSTEM_PROGRAM,
|
||||
"token_program": cls.TOKEN_PROGRAM,
|
||||
"token_2022_program": cls.TOKEN_2022_PROGRAM,
|
||||
"associated_token_program": cls.ASSOCIATED_TOKEN_PROGRAM,
|
||||
"rent": cls.RENT,
|
||||
"sol_mint": cls.SOL_MINT,
|
||||
|
||||
+9
-2
@@ -7,6 +7,8 @@ from solders.keypair import Keypair
|
||||
from solders.pubkey import Pubkey
|
||||
from spl.token.instructions import get_associated_token_address
|
||||
|
||||
from core.pubkeys import SystemAddresses
|
||||
|
||||
|
||||
class Wallet:
|
||||
"""Manages a Solana wallet for trading operations."""
|
||||
@@ -30,16 +32,21 @@ class Wallet:
|
||||
"""Get the keypair for signing transactions."""
|
||||
return self._keypair
|
||||
|
||||
def get_associated_token_address(self, mint: Pubkey) -> Pubkey:
|
||||
def get_associated_token_address(
|
||||
self, mint: Pubkey, token_program_id: Pubkey | None = None
|
||||
) -> Pubkey:
|
||||
"""Get the associated token account address for a mint.
|
||||
|
||||
Args:
|
||||
mint: Token mint address
|
||||
token_program_id: Token program (TOKEN or TOKEN_2022). Defaults to TOKEN_2022_PROGRAM
|
||||
|
||||
Returns:
|
||||
Associated token account address
|
||||
"""
|
||||
return get_associated_token_address(self.pubkey, mint)
|
||||
if token_program_id is None:
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
return get_associated_token_address(self.pubkey, mint, token_program_id)
|
||||
|
||||
@staticmethod
|
||||
def _load_keypair(private_key: str) -> Keypair:
|
||||
|
||||
@@ -45,6 +45,8 @@ class TokenInfo:
|
||||
user: Pubkey | None = None
|
||||
creator: Pubkey | None = None
|
||||
creator_vault: Pubkey | None = None
|
||||
token_program_id: Pubkey | None = None # Token or Token2022 program
|
||||
is_mayhem_mode: bool = False # pump.fun mayhem mode flag
|
||||
|
||||
# Metadata
|
||||
creation_timestamp: float | None = None
|
||||
|
||||
@@ -147,17 +147,22 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
)
|
||||
return quote_vault
|
||||
|
||||
def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey:
|
||||
def derive_user_token_account(
|
||||
self, user: Pubkey, mint: Pubkey, token_program_id: Pubkey | None = None
|
||||
) -> Pubkey:
|
||||
"""Derive user's associated token account address.
|
||||
|
||||
Args:
|
||||
user: User's wallet address
|
||||
mint: Token mint address
|
||||
token_program_id: Token program (TOKEN or TOKEN_2022). Defaults to TOKEN_2022_PROGRAM
|
||||
|
||||
Returns:
|
||||
User's associated token account address
|
||||
"""
|
||||
return get_associated_token_address(user, mint)
|
||||
if token_program_id is None:
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
return get_associated_token_address(user, mint, token_program_id)
|
||||
|
||||
def get_additional_accounts(self, token_info: TokenInfo) -> dict[str, Pubkey]:
|
||||
"""Get LetsBonk-specific additional accounts needed for trading.
|
||||
@@ -296,6 +301,13 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"""
|
||||
additional_accounts = self.get_additional_accounts(token_info)
|
||||
|
||||
# Determine token program to use
|
||||
token_program_id = (
|
||||
token_info.token_program_id
|
||||
if token_info.token_program_id
|
||||
else SystemAddresses.TOKEN_2022_PROGRAM
|
||||
)
|
||||
|
||||
# Use global_config from TokenInfo if available, otherwise use default
|
||||
global_config = (
|
||||
token_info.global_config
|
||||
@@ -316,12 +328,12 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"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),
|
||||
"user_base_token": self.derive_user_token_account(user, token_info.mint, token_program_id),
|
||||
"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,
|
||||
"base_token_program": token_program_id,
|
||||
"quote_token_program": SystemAddresses.TOKEN_PROGRAM,
|
||||
"event_authority": additional_accounts["event_authority"],
|
||||
"program": LetsBonkAddresses.PROGRAM,
|
||||
@@ -351,6 +363,13 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"""
|
||||
additional_accounts = self.get_additional_accounts(token_info)
|
||||
|
||||
# Determine token program to use
|
||||
token_program_id = (
|
||||
token_info.token_program_id
|
||||
if token_info.token_program_id
|
||||
else SystemAddresses.TOKEN_2022_PROGRAM
|
||||
)
|
||||
|
||||
# Use global_config from TokenInfo if available, otherwise use default
|
||||
global_config = (
|
||||
token_info.global_config
|
||||
@@ -371,12 +390,12 @@ class LetsBonkAddressProvider(AddressProvider):
|
||||
"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),
|
||||
"user_base_token": self.derive_user_token_account(user, token_info.mint, token_program_id),
|
||||
"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,
|
||||
"base_token_program": token_program_id,
|
||||
"quote_token_program": SystemAddresses.TOKEN_PROGRAM,
|
||||
"event_authority": additional_accounts["event_authority"],
|
||||
"program": LetsBonkAddresses.PROGRAM,
|
||||
|
||||
@@ -194,14 +194,21 @@ class LetsBonkCurveManager(CurveManager):
|
||||
}
|
||||
|
||||
# Calculate additional metrics
|
||||
if pool_data["virtual_base"] > 0:
|
||||
pool_data["price_per_token"] = (
|
||||
(pool_data["virtual_quote"] / pool_data["virtual_base"])
|
||||
* (10**TOKEN_DECIMALS)
|
||||
/ LAMPORTS_PER_SOL
|
||||
# Validate reserves are positive before calculating price
|
||||
if pool_data["virtual_base"] <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid virtual_base: {pool_data['virtual_base']} - cannot calculate price"
|
||||
)
|
||||
else:
|
||||
pool_data["price_per_token"] = 0
|
||||
if pool_data["virtual_quote"] <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid virtual_quote: {pool_data['virtual_quote']} - cannot calculate price"
|
||||
)
|
||||
|
||||
pool_data["price_per_token"] = (
|
||||
(pool_data["virtual_quote"] / pool_data["virtual_base"])
|
||||
* (10**TOKEN_DECIMALS)
|
||||
/ LAMPORTS_PER_SOL
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"Decoded pool state: virtual_base={pool_data['virtual_base']}, "
|
||||
|
||||
@@ -13,6 +13,7 @@ from typing import Any
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.transaction import VersionedTransaction
|
||||
|
||||
from core.pubkeys import SystemAddresses
|
||||
from interfaces.core import EventParser, Platform, TokenInfo
|
||||
from platforms.letsbonk.address_provider import LetsBonkAddressProvider
|
||||
from utils.idl_parser import IDLParser
|
||||
@@ -114,6 +115,15 @@ class LetsBonkEventParser(EventParser):
|
||||
}:
|
||||
return None
|
||||
|
||||
# Determine token program based on instruction variant
|
||||
instruction_name = decoded["instruction_name"]
|
||||
is_token_2022 = instruction_name == "initialize_with_token_2022"
|
||||
token_program_id = (
|
||||
SystemAddresses.TOKEN_2022_PROGRAM
|
||||
if is_token_2022
|
||||
else SystemAddresses.TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
args = decoded.get("args", {})
|
||||
|
||||
# Extract MintParams from the decoded arguments
|
||||
@@ -174,6 +184,7 @@ class LetsBonkEventParser(EventParser):
|
||||
platform_config=platform_config,
|
||||
user=creator,
|
||||
creator=creator,
|
||||
token_program_id=token_program_id,
|
||||
creation_timestamp=monotonic(),
|
||||
)
|
||||
|
||||
|
||||
@@ -75,12 +75,19 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
||||
# Get all required accounts
|
||||
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
|
||||
|
||||
# Determine token program to use
|
||||
token_program_id = (
|
||||
token_info.token_program_id
|
||||
if token_info.token_program_id
|
||||
else SystemAddresses.TOKEN_2022_PROGRAM
|
||||
)
|
||||
|
||||
# 1. Create idempotent ATA for base token
|
||||
ata_instruction = create_idempotent_associated_token_account(
|
||||
user, # payer
|
||||
user, # owner
|
||||
token_info.mint, # mint
|
||||
SystemAddresses.TOKEN_PROGRAM, # token program
|
||||
token_program_id, # token program (dynamic for token2022 support)
|
||||
)
|
||||
instructions.append(ata_instruction)
|
||||
|
||||
@@ -151,10 +158,10 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
||||
pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False
|
||||
), # quote_token_mint
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
pubkey=accounts_info["base_token_program"], is_signer=False, is_writable=False
|
||||
), # base_token_program
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
pubkey=accounts_info["quote_token_program"], is_signer=False, is_writable=False
|
||||
), # quote_token_program
|
||||
AccountMeta(
|
||||
pubkey=accounts_info["event_authority"],
|
||||
@@ -306,10 +313,10 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
||||
pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False
|
||||
), # quote_token_mint
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
pubkey=accounts_info["base_token_program"], is_signer=False, is_writable=False
|
||||
), # base_token_program
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
pubkey=accounts_info["quote_token_program"], is_signer=False, is_writable=False
|
||||
), # quote_token_program
|
||||
AccountMeta(
|
||||
pubkey=accounts_info["event_authority"],
|
||||
|
||||
@@ -31,6 +31,12 @@ class PumpFunAddresses:
|
||||
FEE: Final[Pubkey] = Pubkey.from_string(
|
||||
"CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"
|
||||
)
|
||||
# Mayhem mode fee recipient (hardcoded to avoid RPC calls)
|
||||
# To check if this address is up-to-date, fetch Global account data at offset 483
|
||||
# from the pump.fun Global account: 4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf
|
||||
MAYHEM_FEE: Final[Pubkey] = Pubkey.from_string(
|
||||
"GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"
|
||||
)
|
||||
LIQUIDITY_MIGRATOR: Final[Pubkey] = Pubkey.from_string(
|
||||
"39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg"
|
||||
)
|
||||
@@ -139,17 +145,22 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
)
|
||||
return bonding_curve
|
||||
|
||||
def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey:
|
||||
def derive_user_token_account(
|
||||
self, user: Pubkey, mint: Pubkey, token_program_id: Pubkey | None = None
|
||||
) -> Pubkey:
|
||||
"""Derive user's associated token account address.
|
||||
|
||||
Args:
|
||||
user: User's wallet address
|
||||
mint: Token mint address
|
||||
token_program_id: Token program (TOKEN or TOKEN_2022). Defaults to TOKEN_2022_PROGRAM
|
||||
|
||||
Returns:
|
||||
User's associated token account address
|
||||
"""
|
||||
return get_associated_token_address(user, mint)
|
||||
if token_program_id is None:
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
return get_associated_token_address(user, mint, token_program_id)
|
||||
|
||||
def get_additional_accounts(self, token_info: TokenInfo) -> dict[str, Pubkey]:
|
||||
"""Get pump.fun-specific additional accounts needed for trading.
|
||||
@@ -177,7 +188,7 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
# Derive associated bonding curve if not provided
|
||||
if not token_info.associated_bonding_curve and token_info.bonding_curve:
|
||||
accounts["associated_bonding_curve"] = self.derive_associated_bonding_curve(
|
||||
token_info.mint, token_info.bonding_curve
|
||||
token_info.mint, token_info.bonding_curve, token_info.token_program_id
|
||||
)
|
||||
|
||||
# Derive creator vault if not provided but creator is available
|
||||
@@ -187,21 +198,25 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
return accounts
|
||||
|
||||
def derive_associated_bonding_curve(
|
||||
self, mint: Pubkey, bonding_curve: Pubkey
|
||||
self, mint: Pubkey, bonding_curve: Pubkey, token_program_id: Pubkey | None = None
|
||||
) -> Pubkey:
|
||||
"""Derive the associated bonding curve (ATA of bonding curve for the token).
|
||||
|
||||
Args:
|
||||
mint: Token mint address
|
||||
bonding_curve: Bonding curve address
|
||||
token_program_id: Token program (TOKEN or TOKEN_2022). Defaults to TOKEN_2022_PROGRAM
|
||||
|
||||
Returns:
|
||||
Associated bonding curve address
|
||||
"""
|
||||
if token_program_id is None:
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
bytes(bonding_curve),
|
||||
bytes(SystemAddresses.TOKEN_PROGRAM),
|
||||
bytes(token_program_id),
|
||||
bytes(mint),
|
||||
],
|
||||
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
|
||||
@@ -249,6 +264,19 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
"""
|
||||
return PumpFunAddresses.find_fee_config()
|
||||
|
||||
def get_fee_recipient(self, token_info: TokenInfo) -> Pubkey:
|
||||
"""Get the correct fee recipient based on mayhem mode.
|
||||
|
||||
Args:
|
||||
token_info: Token information with is_mayhem_mode flag
|
||||
|
||||
Returns:
|
||||
Fee recipient address (mayhem or standard)
|
||||
"""
|
||||
if token_info.is_mayhem_mode:
|
||||
return PumpFunAddresses.MAYHEM_FEE
|
||||
return PumpFunAddresses.FEE
|
||||
|
||||
def get_buy_instruction_accounts(
|
||||
self, token_info: TokenInfo, user: Pubkey
|
||||
) -> dict[str, Pubkey]:
|
||||
@@ -263,9 +291,19 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
"""
|
||||
additional_accounts = self.get_additional_accounts(token_info)
|
||||
|
||||
# Determine token program to use
|
||||
token_program_id = (
|
||||
token_info.token_program_id
|
||||
if token_info.token_program_id
|
||||
else SystemAddresses.TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
# Determine fee recipient based on mayhem mode
|
||||
fee_recipient = self.get_fee_recipient(token_info)
|
||||
|
||||
return {
|
||||
"global": PumpFunAddresses.GLOBAL,
|
||||
"fee": PumpFunAddresses.FEE,
|
||||
"fee": fee_recipient,
|
||||
"mint": token_info.mint,
|
||||
"bonding_curve": additional_accounts.get(
|
||||
"bonding_curve", token_info.bonding_curve
|
||||
@@ -273,10 +311,12 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
"associated_bonding_curve": additional_accounts.get(
|
||||
"associated_bonding_curve", token_info.associated_bonding_curve
|
||||
),
|
||||
"user_token_account": self.derive_user_token_account(user, token_info.mint),
|
||||
"user_token_account": self.derive_user_token_account(
|
||||
user, token_info.mint, token_program_id
|
||||
),
|
||||
"user": user,
|
||||
"system_program": SystemAddresses.SYSTEM_PROGRAM,
|
||||
"token_program": SystemAddresses.TOKEN_PROGRAM,
|
||||
"token_program": token_program_id,
|
||||
"creator_vault": additional_accounts.get(
|
||||
"creator_vault", token_info.creator_vault
|
||||
),
|
||||
@@ -302,9 +342,19 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
"""
|
||||
additional_accounts = self.get_additional_accounts(token_info)
|
||||
|
||||
# Determine token program to use
|
||||
token_program_id = (
|
||||
token_info.token_program_id
|
||||
if token_info.token_program_id
|
||||
else SystemAddresses.TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
# Determine fee recipient based on mayhem mode
|
||||
fee_recipient = self.get_fee_recipient(token_info)
|
||||
|
||||
return {
|
||||
"global": PumpFunAddresses.GLOBAL,
|
||||
"fee": PumpFunAddresses.FEE,
|
||||
"fee": fee_recipient,
|
||||
"mint": token_info.mint,
|
||||
"bonding_curve": additional_accounts.get(
|
||||
"bonding_curve", token_info.bonding_curve
|
||||
@@ -312,13 +362,15 @@ class PumpFunAddressProvider(AddressProvider):
|
||||
"associated_bonding_curve": additional_accounts.get(
|
||||
"associated_bonding_curve", token_info.associated_bonding_curve
|
||||
),
|
||||
"user_token_account": self.derive_user_token_account(user, token_info.mint),
|
||||
"user_token_account": self.derive_user_token_account(
|
||||
user, token_info.mint, token_program_id
|
||||
),
|
||||
"user": user,
|
||||
"system_program": SystemAddresses.SYSTEM_PROGRAM,
|
||||
"creator_vault": additional_accounts.get(
|
||||
"creator_vault", token_info.creator_vault
|
||||
),
|
||||
"token_program": SystemAddresses.TOKEN_PROGRAM,
|
||||
"token_program": token_program_id,
|
||||
"event_authority": PumpFunAddresses.EVENT_AUTHORITY,
|
||||
"program": PumpFunAddresses.PROGRAM,
|
||||
"fee_config": self.derive_fee_config(),
|
||||
|
||||
@@ -190,20 +190,28 @@ class PumpFunCurveManager(CurveManager):
|
||||
"token_total_supply": decoded_curve_state.get("token_total_supply", 0),
|
||||
"complete": decoded_curve_state.get("complete", False),
|
||||
"creator": decoded_curve_state.get("creator", ""),
|
||||
"is_mayhem_mode": decoded_curve_state.get("is_mayhem_mode", False),
|
||||
}
|
||||
|
||||
# Calculate additional metrics
|
||||
if curve_data["virtual_token_reserves"] > 0:
|
||||
curve_data["price_per_token"] = (
|
||||
(
|
||||
curve_data["virtual_sol_reserves"]
|
||||
/ curve_data["virtual_token_reserves"]
|
||||
)
|
||||
* (10**TOKEN_DECIMALS)
|
||||
/ LAMPORTS_PER_SOL
|
||||
# Validate reserves are positive before calculating price
|
||||
if curve_data["virtual_token_reserves"] <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid virtual_token_reserves: {curve_data['virtual_token_reserves']} - cannot calculate price"
|
||||
)
|
||||
else:
|
||||
curve_data["price_per_token"] = 0
|
||||
if curve_data["virtual_sol_reserves"] <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid virtual_sol_reserves: {curve_data['virtual_sol_reserves']} - cannot calculate price"
|
||||
)
|
||||
|
||||
curve_data["price_per_token"] = (
|
||||
(
|
||||
curve_data["virtual_sol_reserves"]
|
||||
/ curve_data["virtual_token_reserves"]
|
||||
)
|
||||
* (10**TOKEN_DECIMALS)
|
||||
/ LAMPORTS_PER_SOL
|
||||
)
|
||||
|
||||
# Add convenience decimal fields
|
||||
curve_data["token_reserves_decimal"] = (
|
||||
|
||||
@@ -47,6 +47,16 @@ class PumpFunEventParser(EventParser):
|
||||
"<Q", self._create_instruction_discriminator_bytes
|
||||
)[0]
|
||||
|
||||
# Support for token2022 (create_v2 instruction)
|
||||
self._create_v2_instruction_discriminator_bytes = instruction_discriminators.get(
|
||||
"create_v2"
|
||||
)
|
||||
self._create_v2_instruction_discriminator = (
|
||||
struct.unpack("<Q", self._create_v2_instruction_discriminator_bytes)[0]
|
||||
if self._create_v2_instruction_discriminator_bytes
|
||||
else None
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Pump.Fun event parser initialized with IDL-based event and instruction parsing"
|
||||
)
|
||||
@@ -56,6 +66,10 @@ class PumpFunEventParser(EventParser):
|
||||
logger.info(
|
||||
f"create instruction discriminator: {self._create_instruction_discriminator_bytes.hex()}"
|
||||
)
|
||||
if self._create_v2_instruction_discriminator_bytes:
|
||||
logger.info(
|
||||
f"create_v2 instruction discriminator: {self._create_v2_instruction_discriminator_bytes.hex()}"
|
||||
)
|
||||
|
||||
@property
|
||||
def platform(self) -> Platform:
|
||||
@@ -74,8 +88,12 @@ class PumpFunEventParser(EventParser):
|
||||
Returns:
|
||||
TokenInfo if token creation found, None otherwise
|
||||
"""
|
||||
# Check if this is a token creation transaction
|
||||
if not any("Program log: Instruction: Create" in log for log in logs):
|
||||
# Check if this is a token creation transaction (create or create_v2 for token2022)
|
||||
if not any(
|
||||
"Program log: Instruction: Create" in log
|
||||
or "Program log: Instruction: Create_v2" in log
|
||||
for log in logs
|
||||
):
|
||||
return None
|
||||
|
||||
# Skip swaps as the first condition may pass them
|
||||
@@ -92,9 +110,10 @@ class PumpFunEventParser(EventParser):
|
||||
|
||||
# First, collect all Program data entries and note when Create instruction happens
|
||||
for i, log in enumerate(logs):
|
||||
if "Program log: Instruction: Create" in log:
|
||||
if "Program log: Instruction: Create" in log or "Program log: Instruction: Create_v2" in log:
|
||||
create_instruction_found = True
|
||||
logger.info(f"📝 Found Create instruction at log index {i}")
|
||||
instruction_type = "Create_v2" if "Create_v2" in log else "Create"
|
||||
logger.info(f"📝 Found {instruction_type} instruction at log index {i}")
|
||||
elif "Program data:" in log:
|
||||
# Extract base64 encoded event data
|
||||
encoded_data = log.split("Program data: ")[1].strip()
|
||||
@@ -104,7 +123,7 @@ class PumpFunEventParser(EventParser):
|
||||
)
|
||||
|
||||
if not create_instruction_found:
|
||||
logger.info("❌ No Create instruction found in logs")
|
||||
logger.info("❌ No Create or Create_v2 instruction found in logs")
|
||||
return None
|
||||
|
||||
if not program_data_entries:
|
||||
@@ -224,9 +243,13 @@ class PumpFunEventParser(EventParser):
|
||||
logger.info(f"❌ Failed to convert pubkey fields: {e}")
|
||||
continue
|
||||
|
||||
# Derive additional addresses
|
||||
# Derive additional addresses (default to TOKEN_2022_PROGRAM as per pump.fun's migration to create_v2)
|
||||
# Note: As of recent pump.fun updates, all tokens are created via create_v2 instruction
|
||||
# This is a technical limitation of logs listener - cannot distinguish create vs create_v2
|
||||
# Risk is low since pump.fun now defaults to Token2022 for all new tokens
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
associated_bonding_curve = self._derive_associated_bonding_curve(
|
||||
mint, bonding_curve
|
||||
mint, bonding_curve, token_program_id
|
||||
)
|
||||
creator_vault = self._derive_creator_vault(creator)
|
||||
|
||||
@@ -245,6 +268,7 @@ class PumpFunEventParser(EventParser):
|
||||
user=user,
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
token_program_id=token_program_id,
|
||||
creation_timestamp=monotonic(),
|
||||
)
|
||||
|
||||
@@ -274,9 +298,16 @@ class PumpFunEventParser(EventParser):
|
||||
Returns:
|
||||
TokenInfo if token creation found, None otherwise
|
||||
"""
|
||||
if not instruction_data.startswith(
|
||||
self._create_instruction_discriminator_bytes
|
||||
# Determine which create instruction (standard or v2 for token2022)
|
||||
is_create_v2 = False
|
||||
if instruction_data.startswith(self._create_instruction_discriminator_bytes):
|
||||
is_create_v2 = False
|
||||
elif (
|
||||
self._create_v2_instruction_discriminator_bytes
|
||||
and instruction_data.startswith(self._create_v2_instruction_discriminator_bytes)
|
||||
):
|
||||
is_create_v2 = True
|
||||
else:
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -293,7 +324,8 @@ class PumpFunEventParser(EventParser):
|
||||
decoded = self._idl_parser.decode_instruction(
|
||||
instruction_data, account_keys, accounts
|
||||
)
|
||||
if not decoded or decoded["instruction_name"] != "create":
|
||||
expected_instruction_name = "create_v2" if is_create_v2 else "create"
|
||||
if not decoded or decoded["instruction_name"] != expected_instruction_name:
|
||||
return None
|
||||
|
||||
args = decoded.get("args", {})
|
||||
@@ -315,6 +347,13 @@ class PumpFunEventParser(EventParser):
|
||||
)
|
||||
creator_vault = self._derive_creator_vault(creator)
|
||||
|
||||
# Determine token program based on instruction type
|
||||
token_program_id = (
|
||||
SystemAddresses.TOKEN_2022_PROGRAM
|
||||
if is_create_v2
|
||||
else SystemAddresses.TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
return TokenInfo(
|
||||
name=args.get("name", ""),
|
||||
symbol=args.get("symbol", ""),
|
||||
@@ -326,6 +365,7 @@ class PumpFunEventParser(EventParser):
|
||||
user=user,
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
token_program_id=token_program_id,
|
||||
creation_timestamp=monotonic(),
|
||||
)
|
||||
|
||||
@@ -435,14 +475,19 @@ class PumpFunEventParser(EventParser):
|
||||
|
||||
ix_data = bytes(ix.data)
|
||||
|
||||
# Check for create discriminator
|
||||
# Check for create or create_v2 discriminator
|
||||
if len(ix_data) >= 8:
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
|
||||
if (
|
||||
discriminator
|
||||
== self._create_instruction_discriminator
|
||||
):
|
||||
is_create = (
|
||||
discriminator == self._create_instruction_discriminator
|
||||
)
|
||||
is_create_v2 = (
|
||||
self._create_v2_instruction_discriminator
|
||||
and discriminator == self._create_v2_instruction_discriminator
|
||||
)
|
||||
|
||||
if is_create or is_create_v2:
|
||||
# Token creation should have substantial data and many accounts
|
||||
if len(ix_data) <= 8 or len(ix.accounts) < 10:
|
||||
continue
|
||||
@@ -497,10 +542,15 @@ class PumpFunEventParser(EventParser):
|
||||
if len(ix_data) >= 8:
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
|
||||
if (
|
||||
discriminator
|
||||
== self._create_instruction_discriminator
|
||||
):
|
||||
is_create = (
|
||||
discriminator == self._create_instruction_discriminator
|
||||
)
|
||||
is_create_v2 = (
|
||||
self._create_v2_instruction_discriminator
|
||||
and discriminator == self._create_v2_instruction_discriminator
|
||||
)
|
||||
|
||||
if is_create or is_create_v2:
|
||||
if len(ix_data) <= 8 or len(ix["accounts"]) < 10:
|
||||
continue
|
||||
|
||||
@@ -544,27 +594,70 @@ class PumpFunEventParser(EventParser):
|
||||
return derived_address
|
||||
|
||||
def _derive_associated_bonding_curve(
|
||||
self, mint: Pubkey, bonding_curve: Pubkey
|
||||
self, mint: Pubkey, bonding_curve: Pubkey, token_program_id: Pubkey | None = None
|
||||
) -> Pubkey:
|
||||
"""Derive the associated bonding curve (ATA of bonding curve for the token).
|
||||
|
||||
Args:
|
||||
mint: Token mint address
|
||||
bonding_curve: Bonding curve address
|
||||
token_program_id: Token program (TOKEN or TOKEN_2022). Defaults to TOKEN_PROGRAM
|
||||
|
||||
Returns:
|
||||
Associated bonding curve address
|
||||
"""
|
||||
if token_program_id is None:
|
||||
token_program_id = SystemAddresses.TOKEN_PROGRAM
|
||||
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
bytes(bonding_curve),
|
||||
bytes(SystemAddresses.TOKEN_PROGRAM),
|
||||
bytes(token_program_id),
|
||||
bytes(mint),
|
||||
],
|
||||
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
|
||||
def _parse_bonding_curve_state(self, data: bytes) -> dict[str, Any] | None:
|
||||
"""Parse bonding curve state from raw account data using IDL parser.
|
||||
|
||||
Args:
|
||||
data: Raw bonding curve account data
|
||||
|
||||
Returns:
|
||||
Dictionary with parsed bonding curve state or None if parsing fails
|
||||
"""
|
||||
try:
|
||||
decoded = self._idl_parser.decode_account_data(
|
||||
data, "BondingCurve", skip_discriminator=True
|
||||
)
|
||||
if not decoded:
|
||||
return None
|
||||
return decoded
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to parse bonding curve state: {e}")
|
||||
return None
|
||||
|
||||
def _get_is_mayhem_mode_from_curve(self, bonding_curve_address: Pubkey) -> bool:
|
||||
"""Determine if a token is in mayhem mode based on bonding curve state.
|
||||
|
||||
Note: This would require an RPC call to fetch the bonding curve account.
|
||||
For now, we return False as a default since the event parser doesn't have
|
||||
access to an RPC client. The mayhem mode flag will be set by traders
|
||||
when they fetch bonding curve state for other operations.
|
||||
|
||||
Args:
|
||||
bonding_curve_address: Address of the bonding curve
|
||||
|
||||
Returns:
|
||||
True if mayhem mode, False otherwise (or if parsing fails)
|
||||
"""
|
||||
# Since event parser doesn't have RPC client access, we cannot fetch
|
||||
# and parse bonding curve state here. Mayhem mode will be set later
|
||||
# when traders fetch the bonding curve state.
|
||||
return False
|
||||
|
||||
@property
|
||||
def verbose(self) -> bool:
|
||||
"""Check if verbose logging is enabled."""
|
||||
|
||||
@@ -64,15 +64,16 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
"""
|
||||
instructions = []
|
||||
|
||||
# Get all required accounts
|
||||
# Get all required accounts (includes mayhem-mode-aware fee recipient)
|
||||
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
|
||||
|
||||
# 1. Create idempotent ATA instruction (won't fail if ATA already exists)
|
||||
# Use token_program from accounts_info to ensure AddressProvider controls program selection
|
||||
ata_instruction = create_idempotent_associated_token_account(
|
||||
user, # payer
|
||||
user, # owner
|
||||
token_info.mint, # mint
|
||||
SystemAddresses.TOKEN_PROGRAM, # token program
|
||||
accounts_info["token_program"], # token program from AddressProvider
|
||||
)
|
||||
instructions.append(ata_instruction)
|
||||
|
||||
@@ -123,7 +124,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
AccountMeta(
|
||||
pubkey=accounts_info["global_volume_accumulator"],
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
is_writable=False,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=accounts_info["user_volume_accumulator"],
|
||||
@@ -144,11 +145,14 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
),
|
||||
]
|
||||
|
||||
# Build instruction data: discriminator + token_amount + max_sol_cost
|
||||
# Build instruction data: discriminator + token_amount + max_sol_cost + track_volume
|
||||
# Encode OptionBool for track_volume: [1, 1] = Some(true)
|
||||
track_volume_bytes = bytes([1, 1])
|
||||
instruction_data = (
|
||||
self._buy_discriminator
|
||||
+ struct.pack("<Q", minimum_amount_out) # token amount in raw units
|
||||
+ struct.pack("<Q", amount_in) # max SOL cost in lamports
|
||||
+ track_volume_bytes # enable volume tracking
|
||||
)
|
||||
|
||||
buy_instruction = Instruction(
|
||||
@@ -182,7 +186,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
"""
|
||||
instructions = []
|
||||
|
||||
# Get all required accounts
|
||||
# Get all required accounts (includes mayhem-mode-aware fee recipient)
|
||||
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
|
||||
|
||||
# Build sell instruction accounts
|
||||
@@ -243,11 +247,14 @@ class PumpFunInstructionBuilder(InstructionBuilder):
|
||||
),
|
||||
]
|
||||
|
||||
# Build instruction data: discriminator + token_amount + min_sol_output
|
||||
# Build instruction data: discriminator + token_amount + min_sol_output + track_volume
|
||||
# Encode OptionBool for track_volume: [1, 1] = Some(true)
|
||||
track_volume_bytes = bytes([1, 1])
|
||||
instruction_data = (
|
||||
self._sell_discriminator
|
||||
+ struct.pack("<Q", amount_in) # token amount in raw units
|
||||
+ struct.pack("<Q", minimum_amount_out) # min SOL output in lamports
|
||||
+ track_volume_bytes # enable volume tracking
|
||||
)
|
||||
|
||||
sell_instruction = Instruction(
|
||||
|
||||
@@ -5,6 +5,7 @@ File: src/platforms/pumpfun/pumpportal_processor.py
|
||||
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from core.pubkeys import SystemAddresses
|
||||
from interfaces.core import Platform, TokenInfo
|
||||
from platforms.pumpfun.address_provider import PumpFunAddressProvider
|
||||
from utils.logger import get_logger
|
||||
@@ -81,9 +82,15 @@ class PumpFunPumpPortalProcessor:
|
||||
creator = user
|
||||
|
||||
# Derive additional addresses using platform provider
|
||||
# PumpPortal doesn't distinguish between Token and Token2022.
|
||||
# Default to TOKEN_2022_PROGRAM as per pump.fun's migration to create_v2.
|
||||
# Technical limitation: Cannot distinguish from pre-parsed data, but risk is low
|
||||
# since pump.fun now defaults to Token2022 for all new tokens.
|
||||
token_program_id = SystemAddresses.TOKEN_2022_PROGRAM
|
||||
|
||||
associated_bonding_curve = (
|
||||
self.address_provider.derive_associated_bonding_curve(
|
||||
mint, bonding_curve
|
||||
mint, bonding_curve, token_program_id
|
||||
)
|
||||
)
|
||||
creator_vault = self.address_provider.derive_creator_vault(creator)
|
||||
@@ -99,6 +106,7 @@ class PumpFunPumpPortalProcessor:
|
||||
user=user,
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
token_program_id=token_program_id,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
|
||||
@@ -66,10 +66,20 @@ class PlatformAwareBuyer(Trader):
|
||||
pool_address = self._get_pool_address(token_info, address_provider)
|
||||
|
||||
# Regular behavior with RPC call
|
||||
token_price_sol = await curve_manager.calculate_price(pool_address)
|
||||
token_amount = (
|
||||
self.amount / token_price_sol if token_price_sol > 0 else 0
|
||||
)
|
||||
# Fetch pool state to get price and mayhem mode status
|
||||
pool_state = await curve_manager.get_pool_state(pool_address)
|
||||
token_price_sol = pool_state.get("price_per_token")
|
||||
|
||||
# Validate price_per_token is present and positive
|
||||
if token_price_sol is None or token_price_sol <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid price_per_token: {token_price_sol} for pool {pool_address} "
|
||||
f"(mint: {token_info.mint}) - cannot execute buy with zero/invalid price"
|
||||
)
|
||||
|
||||
# Set is_mayhem_mode from bonding curve state
|
||||
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
|
||||
token_amount = self.amount / token_price_sol
|
||||
|
||||
# Calculate minimum token amount with slippage
|
||||
minimum_token_amount = token_amount * (1 - self.slippage)
|
||||
@@ -225,7 +235,19 @@ class PlatformAwareSeller(Trader):
|
||||
|
||||
# Get pool address and current price using platform-agnostic method
|
||||
pool_address = self._get_pool_address(token_info, address_provider)
|
||||
token_price_sol = await curve_manager.calculate_price(pool_address)
|
||||
# Fetch pool state to get price and mayhem mode status
|
||||
pool_state = await curve_manager.get_pool_state(pool_address)
|
||||
token_price_sol = pool_state.get("price_per_token")
|
||||
|
||||
# Validate price_per_token is present and positive
|
||||
if token_price_sol is None or token_price_sol <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid price_per_token: {token_price_sol} for pool {pool_address} "
|
||||
f"(mint: {token_info.mint}) - cannot execute sell with zero/invalid price"
|
||||
)
|
||||
|
||||
# Set is_mayhem_mode from bonding curve state
|
||||
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
|
||||
|
||||
logger.info(f"Price per Token: {token_price_sol:.8f} SOL")
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ class UniversalTrader:
|
||||
|
||||
# State tracking
|
||||
self.traded_mints: set[Pubkey] = set()
|
||||
self.traded_token_programs: dict[str, Pubkey] = {} # Maps mint (as string) to token_program_id
|
||||
self.token_queue: asyncio.Queue = asyncio.Queue()
|
||||
self.processing: bool = False
|
||||
self.processed_tokens: set[str] = set()
|
||||
@@ -325,10 +326,17 @@ class UniversalTrader:
|
||||
if self.traded_mints:
|
||||
try:
|
||||
logger.info(f"Cleaning up {len(self.traded_mints)} traded token(s)...")
|
||||
# Build parallel lists of mints and token_program_ids
|
||||
mints_list = list(self.traded_mints)
|
||||
token_program_ids = [
|
||||
self.traded_token_programs.get(str(mint))
|
||||
for mint in mints_list
|
||||
]
|
||||
await handle_cleanup_post_session(
|
||||
self.solana_client,
|
||||
self.wallet,
|
||||
list(self.traded_mints),
|
||||
mints_list,
|
||||
token_program_ids,
|
||||
self.priority_fee_manager,
|
||||
self.cleanup_mode,
|
||||
self.cleanup_with_priority_fee,
|
||||
@@ -447,6 +455,10 @@ class UniversalTrader:
|
||||
buy_result.tx_signature,
|
||||
)
|
||||
self.traded_mints.add(token_info.mint)
|
||||
# Track token program for cleanup
|
||||
mint_str = str(token_info.mint)
|
||||
if token_info.token_program_id:
|
||||
self.traded_token_programs[mint_str] = token_info.token_program_id
|
||||
|
||||
# Choose exit strategy
|
||||
if not self.marry_mode:
|
||||
@@ -469,6 +481,7 @@ class UniversalTrader:
|
||||
self.solana_client,
|
||||
self.wallet,
|
||||
token_info.mint,
|
||||
token_info.token_program_id,
|
||||
self.priority_fee_manager,
|
||||
self.cleanup_mode,
|
||||
self.cleanup_with_priority_fee,
|
||||
@@ -521,6 +534,7 @@ class UniversalTrader:
|
||||
self.solana_client,
|
||||
self.wallet,
|
||||
token_info.mint,
|
||||
token_info.token_program_id,
|
||||
self.priority_fee_manager,
|
||||
self.cleanup_mode,
|
||||
self.cleanup_with_priority_fee,
|
||||
@@ -590,6 +604,7 @@ class UniversalTrader:
|
||||
self.solana_client,
|
||||
self.wallet,
|
||||
token_info.mint,
|
||||
token_info.token_program_id,
|
||||
self.priority_fee_manager,
|
||||
self.cleanup_mode,
|
||||
self.cleanup_with_priority_fee,
|
||||
|
||||
Reference in New Issue
Block a user