fix(letsbonk): buy/sell instructions

This commit is contained in:
smypmsa
2025-08-02 15:38:53 +00:00
parent 72a3d23e6b
commit e7107751f5
7 changed files with 306 additions and 131 deletions
+2 -2
View File
@@ -8,11 +8,11 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
private_key: "${SOLANA_PRIVATE_KEY}"
enabled: false # You can turn off the bot w/o removing its config
enabled: true # You can turn off the bot w/o removing its config
separate_process: true
# Options: "pump_fun" (default), "lets_bonk"
platform: "pump_fun"
platform: "lets_bonk"
# Geyser configuration (fastest method for getting updates)
geyser:
+1 -1
View File
@@ -8,7 +8,7 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
private_key: "${SOLANA_PRIVATE_KEY}"
enabled: true # You can turn off the bot w/o removing its config
enabled: false # You can turn off the bot w/o removing its config
separate_process: true
# Options: "pump_fun" (default), "lets_bonk"
+18 -12
View File
@@ -102,20 +102,16 @@ class LetsBonkAddressProvider(AddressProvider):
"""
accounts = {}
# Add pool state if available
# Add pool state - must be present in token_info
if token_info.pool_state:
accounts["pool_state"] = token_info.pool_state
# Add vault addresses if available
# Add vault addresses - must be present in token_info
if token_info.base_vault:
accounts["base_vault"] = token_info.base_vault
if token_info.quote_vault:
accounts["quote_vault"] = token_info.quote_vault
# Derive pool state if not provided
if not token_info.pool_state:
accounts["pool_state"] = self.derive_pool_address(token_info.mint)
# Derive authority PDA
accounts["authority"] = self.derive_authority_pda()
@@ -178,15 +174,20 @@ class LetsBonkAddressProvider(AddressProvider):
"""
additional_accounts = self.get_additional_accounts(token_info)
# Vault addresses must be present in token_info
if not token_info.base_vault or not token_info.quote_vault:
raise ValueError(f"Missing required vault addresses for token {token_info.mint}. "
f"base_vault: {token_info.base_vault}, quote_vault: {token_info.quote_vault}")
return {
"payer": user,
"authority": additional_accounts["authority"],
"global_config": LetsBonkAddresses.GLOBAL_CONFIG,
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
"pool_state": additional_accounts["pool_state"],
"pool_state": token_info.pool_state,
"user_base_token": self.derive_user_token_account(user, token_info.mint),
"base_vault": additional_accounts.get("base_vault", token_info.base_vault),
"quote_vault": additional_accounts.get("quote_vault", token_info.quote_vault),
"base_vault": token_info.base_vault,
"quote_vault": token_info.quote_vault,
"base_token_mint": token_info.mint,
"quote_token_mint": SystemAddresses.SOL_MINT,
"base_token_program": SystemAddresses.TOKEN_PROGRAM,
@@ -207,15 +208,20 @@ class LetsBonkAddressProvider(AddressProvider):
"""
additional_accounts = self.get_additional_accounts(token_info)
# Vault addresses must be present in token_info
if not token_info.base_vault or not token_info.quote_vault:
raise ValueError(f"Missing required vault addresses for token {token_info.mint}. "
f"base_vault: {token_info.base_vault}, quote_vault: {token_info.quote_vault}")
return {
"payer": user,
"authority": additional_accounts["authority"],
"global_config": LetsBonkAddresses.GLOBAL_CONFIG,
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
"pool_state": additional_accounts["pool_state"],
"pool_state": token_info.pool_state,
"user_base_token": self.derive_user_token_account(user, token_info.mint),
"base_vault": additional_accounts.get("base_vault", token_info.base_vault),
"quote_vault": additional_accounts.get("quote_vault", token_info.quote_vault),
"base_vault": token_info.base_vault,
"quote_vault": token_info.quote_vault,
"base_token_mint": token_info.mint,
"quote_token_mint": SystemAddresses.SOL_MINT,
"base_token_program": SystemAddresses.TOKEN_PROGRAM,
+17 -2
View File
@@ -104,14 +104,29 @@ class LetsBonkEventParser(EventParser):
if not base_mint_param:
return None
# Extract account information based on IDL account order
creator = get_account_key(1) # creator account
# Extract account information based on IDL account order for initialize instruction
# From the manual example, the account order is:
# 0: creator (signer)
# 1: creator_ata (not needed for TokenInfo)
# 2: global_config
# 3: platform_config
# 4: creator
# 5: pool_state
# 6: base_mint
# 7: quote_mint (WSOL)
# 8: base_vault
# 9: quote_vault
# ... other accounts
creator = get_account_key(0) # First signer account (creator)
pool_state = get_account_key(5) # pool_state account
base_mint = get_account_key(6) # base_mint account
base_vault = get_account_key(8) # base_vault account
quote_vault = get_account_key(9) # quote_vault account
if not all([creator, pool_state, base_mint, base_vault, quote_vault]):
logger.debug(f"Missing required accounts: creator={creator}, pool_state={pool_state}, "
f"base_mint={base_mint}, base_vault={base_vault}, quote_vault={quote_vault}")
return None
return TokenInfo(
+43 -50
View File
@@ -104,28 +104,28 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
initialize_wsol_ix = self._create_initialize_account_instruction(
wsol_account,
SystemAddresses.SOL_MINT,
user,
address_provider
user
)
instructions.append(initialize_wsol_ix)
# 4. Build buy_exact_in instruction
# 4. Build buy_exact_in instruction with correct account ordering
# Based on the IDL and manual examples, the account order should be:
buy_accounts = [
AccountMeta(pubkey=accounts_info["payer"], is_signer=True, is_writable=True),
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True),
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True),
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True),
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True),
AccountMeta(pubkey=accounts_info["base_token_mint"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["quote_token_mint"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["base_token_program"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["quote_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),
AccountMeta(pubkey=user, is_signer=True, is_writable=True), # payer
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False), # authority
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False), # global_config
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False), # platform_config
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True), # pool_state
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True), # user_base_token
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token (WSOL account)
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True), # base_vault
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True), # quote_vault
AccountMeta(pubkey=token_info.mint, is_signer=False, is_writable=False), # base_token_mint
AccountMeta(pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False), # quote_token_mint
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # base_token_program
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # quote_token_program
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False), # event_authority
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False), # program
]
# Build instruction data: discriminator + amount_in + minimum_amount_out + share_fee_rate
@@ -148,8 +148,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
close_wsol_ix = self._create_close_account_instruction(
wsol_account,
user,
user,
address_provider
user
)
instructions.append(close_wsol_ix)
@@ -204,28 +203,27 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
initialize_wsol_ix = self._create_initialize_account_instruction(
wsol_account,
SystemAddresses.SOL_MINT,
user,
address_provider
user
)
instructions.append(initialize_wsol_ix)
# 3. Build sell_exact_in instruction
# 3. Build sell_exact_in instruction with correct account ordering
sell_accounts = [
AccountMeta(pubkey=accounts_info["payer"], is_signer=True, is_writable=True),
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True),
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True),
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True),
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True),
AccountMeta(pubkey=accounts_info["base_token_mint"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["quote_token_mint"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["base_token_program"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["quote_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),
AccountMeta(pubkey=user, is_signer=True, is_writable=True), # payer
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False), # authority
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False), # global_config
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False), # platform_config
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True), # pool_state
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True), # user_base_token (tokens being sold)
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token (WSOL received)
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True), # base_vault (receives tokens)
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True), # quote_vault (sends WSOL)
AccountMeta(pubkey=token_info.mint, is_signer=False, is_writable=False), # base_token_mint
AccountMeta(pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False), # quote_token_mint
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # base_token_program
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # quote_token_program
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False), # event_authority
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False), # program
]
# Build instruction data: discriminator + amount_in + minimum_amount_out + share_fee_rate
@@ -248,8 +246,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
close_wsol_ix = self._create_close_account_instruction(
wsol_account,
user,
user,
address_provider
user
)
instructions.append(close_wsol_ix)
@@ -278,8 +275,8 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
accounts_info["user_base_token"],
accounts_info["base_vault"],
accounts_info["quote_vault"],
accounts_info["base_token_mint"],
accounts_info["quote_token_mint"],
token_info.mint,
SystemAddresses.SOL_MINT,
accounts_info["program"],
]
@@ -306,8 +303,8 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
accounts_info["user_base_token"],
accounts_info["base_vault"],
accounts_info["quote_vault"],
accounts_info["base_token_mint"],
accounts_info["quote_token_mint"],
token_info.mint,
SystemAddresses.SOL_MINT,
accounts_info["program"],
]
@@ -328,8 +325,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
self,
account: Pubkey,
mint: Pubkey,
owner: Pubkey,
address_provider: AddressProvider
owner: Pubkey
) -> Instruction:
"""Create an InitializeAccount instruction for the Token Program.
@@ -337,7 +333,6 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
account: The account to initialize
mint: The token mint
owner: The account owner
address_provider: Platform address provider
Returns:
Instruction for initializing the account
@@ -362,8 +357,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
self,
account: Pubkey,
destination: Pubkey,
owner: Pubkey,
address_provider: AddressProvider
owner: Pubkey
) -> Instruction:
"""Create a CloseAccount instruction for the Token Program.
@@ -371,7 +365,6 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
account: The account to close
destination: Where to send the remaining lamports
owner: The account owner (must sign)
address_provider: Platform address provider
Returns:
Instruction for closing the account
+222 -63
View File
@@ -1,14 +1,17 @@
"""
Pump.Fun implementation of InstructionBuilder interface.
LetsBonk implementation of InstructionBuilder interface.
This module builds pump.fun-specific buy and sell instructions
This module builds LetsBonk (Raydium LaunchLab) specific buy and sell instructions
by implementing the InstructionBuilder interface with IDL-based discriminators.
"""
import hashlib
import struct
import time
from solders.instruction import AccountMeta, Instruction
from solders.pubkey import Pubkey
from solders.system_program import CreateAccountWithSeedParams, create_account_with_seed
from spl.token.instructions import create_idempotent_associated_token_account
from core.pubkeys import TOKEN_DECIMALS, SystemAddresses
@@ -19,28 +22,28 @@ from utils.logger import get_logger
logger = get_logger(__name__)
class PumpFunInstructionBuilder(InstructionBuilder):
"""Pump.Fun implementation of InstructionBuilder interface with IDL-based discriminators."""
class LetsBonkInstructionBuilder(InstructionBuilder):
"""LetsBonk (Raydium LaunchLab) implementation of InstructionBuilder interface with IDL-based discriminators."""
def __init__(self, idl_parser: IDLParser):
"""Initialize pump.fun instruction builder with injected IDL parser.
"""Initialize LetsBonk instruction builder with injected IDL parser.
Args:
idl_parser: Pre-loaded IDL parser for pump.fun platform
idl_parser: Pre-loaded IDL parser for LetsBonk platform
"""
self._idl_parser = idl_parser
# Get discriminators from injected IDL parser
discriminators = self._idl_parser.get_instruction_discriminators()
self._buy_discriminator = discriminators["buy"]
self._sell_discriminator = discriminators["sell"]
self._buy_exact_in_discriminator = discriminators["buy_exact_in"]
self._sell_exact_in_discriminator = discriminators["sell_exact_in"]
logger.info("Pump.Fun instruction builder initialized with injected IDL parser")
logger.info("LetsBonk instruction builder initialized with injected IDL parser")
@property
def platform(self) -> Platform:
"""Get the platform this builder serves."""
return Platform.PUMP_FUN
return Platform.LETS_BONK
async def build_buy_instruction(
self,
@@ -50,7 +53,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
minimum_amount_out: int,
address_provider: AddressProvider
) -> list[Instruction]:
"""Build buy instruction(s) for pump.fun.
"""Build buy instruction(s) for LetsBonk using buy_exact_in.
Args:
token_info: Token information
@@ -67,7 +70,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
# Get all required accounts
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
# 1. Create idempotent ATA instruction (won't fail if ATA already exists)
# 1. Create idempotent ATA for base token
ata_instruction = create_idempotent_associated_token_account(
user, # payer
user, # owner
@@ -76,29 +79,62 @@ class PumpFunInstructionBuilder(InstructionBuilder):
)
instructions.append(ata_instruction)
# 2. Build buy instruction
# 2. Create WSOL account with seed (temporary account for the transaction)
wsol_seed = self._generate_wsol_seed(user)
wsol_account = address_provider.create_wsol_account_with_seed(user, wsol_seed)
# Account creation cost + amount to spend
account_creation_lamports = 2_039_280 # Standard account creation cost
total_lamports = amount_in + account_creation_lamports
create_wsol_ix = create_account_with_seed(
CreateAccountWithSeedParams(
from_pubkey=user,
to_pubkey=wsol_account,
base=user,
seed=wsol_seed,
lamports=total_lamports,
space=165, # Size of a token account
owner=SystemAddresses.TOKEN_PROGRAM
)
)
instructions.append(create_wsol_ix)
# 3. Initialize WSOL account
initialize_wsol_ix = self._create_initialize_account_instruction(
wsol_account,
SystemAddresses.SOL_MINT,
user
)
instructions.append(initialize_wsol_ix)
# 4. Build buy_exact_in instruction with correct account ordering
# Based on the IDL and manual examples, the account order should be:
buy_accounts = [
AccountMeta(pubkey=accounts_info["global"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["fee"], is_signer=False, is_writable=True),
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),
AccountMeta(pubkey=accounts_info["user"], is_signer=True, is_writable=True),
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),
AccountMeta(pubkey=user, is_signer=True, is_writable=True), # payer
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False), # authority
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False), # global_config
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False), # platform_config
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True), # pool_state
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True), # user_base_token
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token (WSOL account)
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True), # base_vault
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True), # quote_vault
AccountMeta(pubkey=token_info.mint, is_signer=False, is_writable=False), # base_token_mint
AccountMeta(pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False), # quote_token_mint
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # base_token_program
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # quote_token_program
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False), # event_authority
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False), # program
]
# Build instruction data: discriminator + token_amount + max_sol_cost
# Build instruction data: discriminator + amount_in + minimum_amount_out + share_fee_rate
SHARE_FEE_RATE = 0 # No sharing fee
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
self._buy_exact_in_discriminator +
struct.pack("<Q", amount_in) + # amount_in (u64) - SOL to spend
struct.pack("<Q", minimum_amount_out) + # minimum_amount_out (u64) - min tokens
struct.pack("<Q", SHARE_FEE_RATE) # share_fee_rate (u64): 0
)
buy_instruction = Instruction(
@@ -108,6 +144,14 @@ class PumpFunInstructionBuilder(InstructionBuilder):
)
instructions.append(buy_instruction)
# 5. Close WSOL account to reclaim SOL
close_wsol_ix = self._create_close_account_instruction(
wsol_account,
user,
user
)
instructions.append(close_wsol_ix)
return instructions
async def build_sell_instruction(
@@ -118,7 +162,7 @@ class PumpFunInstructionBuilder(InstructionBuilder):
minimum_amount_out: int,
address_provider: AddressProvider
) -> list[Instruction]:
"""Build sell instruction(s) for pump.fun.
"""Build sell instruction(s) for LetsBonk using sell_exact_in.
Args:
token_info: Token information
@@ -135,27 +179,60 @@ class PumpFunInstructionBuilder(InstructionBuilder):
# Get all required accounts
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
# Build sell instruction accounts
# 1. Create WSOL account with seed (to receive SOL)
wsol_seed = self._generate_wsol_seed(user)
wsol_account = address_provider.create_wsol_account_with_seed(user, wsol_seed)
# Minimal account creation cost
account_creation_lamports = 2_039_280
create_wsol_ix = create_account_with_seed(
CreateAccountWithSeedParams(
from_pubkey=user,
to_pubkey=wsol_account,
base=user,
seed=wsol_seed,
lamports=account_creation_lamports,
space=165, # Size of a token account
owner=SystemAddresses.TOKEN_PROGRAM
)
)
instructions.append(create_wsol_ix)
# 2. Initialize WSOL account
initialize_wsol_ix = self._create_initialize_account_instruction(
wsol_account,
SystemAddresses.SOL_MINT,
user
)
instructions.append(initialize_wsol_ix)
# 3. Build sell_exact_in instruction with correct account ordering
sell_accounts = [
AccountMeta(pubkey=accounts_info["global"], is_signer=False, is_writable=False),
AccountMeta(pubkey=accounts_info["fee"], is_signer=False, is_writable=True),
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),
AccountMeta(pubkey=accounts_info["user"], is_signer=True, is_writable=True),
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),
AccountMeta(pubkey=user, is_signer=True, is_writable=True), # payer
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False), # authority
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False), # global_config
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False), # platform_config
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True), # pool_state
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True), # user_base_token (tokens being sold)
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token (WSOL received)
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True), # base_vault (receives tokens)
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True), # quote_vault (sends WSOL)
AccountMeta(pubkey=token_info.mint, is_signer=False, is_writable=False), # base_token_mint
AccountMeta(pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False), # quote_token_mint
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # base_token_program
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # quote_token_program
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False), # event_authority
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False), # program
]
# Build instruction data: discriminator + token_amount + min_sol_output
# Build instruction data: discriminator + amount_in + minimum_amount_out + share_fee_rate
SHARE_FEE_RATE = 0 # No sharing fee
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
self._sell_exact_in_discriminator +
struct.pack("<Q", amount_in) + # amount_in (u64) - tokens to sell
struct.pack("<Q", minimum_amount_out) + # minimum_amount_out (u64) - min SOL
struct.pack("<Q", SHARE_FEE_RATE) # share_fee_rate (u64): 0
)
sell_instruction = Instruction(
@@ -165,6 +242,14 @@ class PumpFunInstructionBuilder(InstructionBuilder):
)
instructions.append(sell_instruction)
# 4. Close WSOL account to reclaim SOL
close_wsol_ix = self._create_close_account_instruction(
wsol_account,
user,
user
)
instructions.append(close_wsol_ix)
return instructions
def get_required_accounts_for_buy(
@@ -186,14 +271,12 @@ class PumpFunInstructionBuilder(InstructionBuilder):
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
return [
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"],
accounts_info["pool_state"],
accounts_info["user_base_token"],
accounts_info["base_vault"],
accounts_info["quote_vault"],
token_info.mint,
SystemAddresses.SOL_MINT,
accounts_info["program"],
]
@@ -216,15 +299,91 @@ class PumpFunInstructionBuilder(InstructionBuilder):
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
return [
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["pool_state"],
accounts_info["user_base_token"],
accounts_info["base_vault"],
accounts_info["quote_vault"],
token_info.mint,
SystemAddresses.SOL_MINT,
accounts_info["program"],
]
def _generate_wsol_seed(self, user: Pubkey) -> str:
"""Generate a unique seed for WSOL account creation.
Args:
user: User's wallet address
Returns:
Unique seed string for WSOL account
"""
# Generate a unique seed based on timestamp and user pubkey
seed_data = f"{int(time.time())}{user!s}"
return hashlib.sha256(seed_data.encode()).hexdigest()[:32]
def _create_initialize_account_instruction(
self,
account: Pubkey,
mint: Pubkey,
owner: Pubkey
) -> Instruction:
"""Create an InitializeAccount instruction for the Token Program.
Args:
account: The account to initialize
mint: The token mint
owner: The account owner
Returns:
Instruction for initializing the account
"""
accounts = [
AccountMeta(pubkey=account, is_signer=False, is_writable=True),
AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
AccountMeta(pubkey=owner, is_signer=False, is_writable=False),
AccountMeta(pubkey=SystemAddresses.RENT, is_signer=False, is_writable=False),
]
# InitializeAccount instruction discriminator (instruction 1 in Token Program)
data = bytes([1])
return Instruction(
program_id=SystemAddresses.TOKEN_PROGRAM,
data=data,
accounts=accounts
)
def _create_close_account_instruction(
self,
account: Pubkey,
destination: Pubkey,
owner: Pubkey
) -> Instruction:
"""Create a CloseAccount instruction for the Token Program.
Args:
account: The account to close
destination: Where to send the remaining lamports
owner: The account owner (must sign)
Returns:
Instruction for closing the account
"""
accounts = [
AccountMeta(pubkey=account, is_signer=False, is_writable=True),
AccountMeta(pubkey=destination, is_signer=False, is_writable=True),
AccountMeta(pubkey=owner, is_signer=True, is_writable=False),
]
# CloseAccount instruction discriminator (instruction 9 in Token Program)
data = bytes([9])
return Instruction(
program_id=SystemAddresses.TOKEN_PROGRAM,
data=data,
accounts=accounts
)
def calculate_token_amount_raw(self, token_amount_decimal: float) -> int:
"""Convert decimal token amount to raw token units.
+2
View File
@@ -1,2 +1,4 @@
{"timestamp": "2025-04-24T20:30:13.087092", "action": "buy", "token_address": "DWMUmRQUZPCBA1gwdDxTJuz6JHnQkREiWMyQpsKWGp9v", "symbol": "U8", "price": 5e-06, "amount": 20, "tx_hash": "3JvdfCep45PUB6rCcH4dB2NuwvFP8n67SCUxqJMt4MuN5ekHYc6J27aCUfwNUK3hh5rSyKNYAWXya5vQAT2qQivB"}
{"timestamp": "2025-04-24T20:30:32.759177", "action": "sell", "token_address": "DWMUmRQUZPCBA1gwdDxTJuz6JHnQkREiWMyQpsKWGp9v", "symbol": "U8", "price": 3.805530050663904e-08, "amount": 20.0, "tx_hash": "5cveLfU7XhPNCPMCZfTXyugJpmAQNmi7zr81PSqs8DsP1T2swYFjJwaB5hNSf3kFPfRzgzd7QZBVaZLd5MqsJevB"}
{"timestamp": "2025-08-02T15:37:04.403139", "action": "buy", "platform": "lets_bonk", "token_address": "7o5FtYXxpX6sqtcJJ3ES4DiWt4C9HnHzKuHrZPpjbonk", "symbol": "pants", "price": 5e-06, "amount": 20, "tx_hash": "26Uu4rZ1PcnzioHwWAh3Reca4MMNZjgiQbmihjisiurpv4xcHFtvSptZoV3zUkS5bdqZ4zpLeGb46J4bWyczu6FY"}
{"timestamp": "2025-08-02T15:37:21.984944", "action": "sell", "platform": "lets_bonk", "token_address": "7o5FtYXxpX6sqtcJJ3ES4DiWt4C9HnHzKuHrZPpjbonk", "symbol": "pants", "price": 2.7959121193874663e-08, "amount": 3712.914779, "tx_hash": "5zXxjxHZuWXxGih1Aca6HoFkybiyHT4jPghhXCg3NbkyD3nuzzaJtmjg4mnoEQDeTn64b1cUZMawhmYbeq3cwjfj"}