mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-17 17:28:05 +00:00
fix(core): platform listener check
This commit is contained in:
@@ -8,11 +8,11 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
|
|||||||
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
|
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
|
||||||
private_key: "${SOLANA_PRIVATE_KEY}"
|
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
|
separate_process: true
|
||||||
|
|
||||||
# Options: "pump_fun" (default), "lets_bonk"
|
# Options: "pump_fun" (default), "lets_bonk"
|
||||||
platform: "lets_bonk"
|
platform: "pump_fun"
|
||||||
|
|
||||||
# Geyser configuration (fastest method for getting updates)
|
# Geyser configuration (fastest method for getting updates)
|
||||||
geyser:
|
geyser:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
|
|||||||
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
|
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
|
||||||
private_key: "${SOLANA_PRIVATE_KEY}"
|
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
|
separate_process: true
|
||||||
|
|
||||||
# Options: "pump_fun" (default), "lets_bonk"
|
# Options: "pump_fun" (default), "lets_bonk"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ VALID_VALUES = {
|
|||||||
# Platform-specific listener compatibility
|
# Platform-specific listener compatibility
|
||||||
PLATFORM_LISTENER_COMPATIBILITY = {
|
PLATFORM_LISTENER_COMPATIBILITY = {
|
||||||
Platform.PUMP_FUN: ["logs", "blocks", "geyser", "pumpportal"],
|
Platform.PUMP_FUN: ["logs", "blocks", "geyser", "pumpportal"],
|
||||||
Platform.LETS_BONK: ["logs", "blocks", "geyser"], # PumpPortal is pump.fun only
|
Platform.LETS_BONK: ["blocks", "geyser"], # LetsBonk only supports geyser and blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,6 @@ class ListenerFactory:
|
|||||||
if platform == Platform.PUMP_FUN:
|
if platform == Platform.PUMP_FUN:
|
||||||
return ["logs", "blocks", "geyser", "pumpportal"]
|
return ["logs", "blocks", "geyser", "pumpportal"]
|
||||||
elif platform == Platform.LETS_BONK:
|
elif platform == Platform.LETS_BONK:
|
||||||
return ["logs", "blocks", "geyser"] # PumpPortal is pump.fun only
|
return ["blocks", "geyser"] # LetsBonk only supports geyser and blocks
|
||||||
else:
|
else:
|
||||||
return ["logs", "blocks", "geyser"] # Default universal listeners
|
return ["blocks", "geyser"] # Default universal listeners
|
||||||
@@ -49,9 +49,20 @@ class UniversalBlockListener(BaseTokenListener):
|
|||||||
|
|
||||||
for platform in self.platforms:
|
for platform in self.platforms:
|
||||||
try:
|
try:
|
||||||
# We'll need a dummy client for getting the parser
|
# Create a simple dummy client that doesn't start blockhash updater
|
||||||
from core.client import SolanaClient
|
from core.client import SolanaClient
|
||||||
dummy_client = SolanaClient("http://localhost") # Won't be used for parsing
|
|
||||||
|
# Create a mock client class to avoid network operations
|
||||||
|
class DummyClient(SolanaClient):
|
||||||
|
def __init__(self):
|
||||||
|
# Skip SolanaClient.__init__ to avoid starting blockhash updater
|
||||||
|
self.rpc_endpoint = "http://dummy"
|
||||||
|
self._client = None
|
||||||
|
self._cached_blockhash = None
|
||||||
|
self._blockhash_lock = None
|
||||||
|
self._blockhash_updater_task = None
|
||||||
|
|
||||||
|
dummy_client = DummyClient()
|
||||||
|
|
||||||
implementations = get_platform_implementations(platform, dummy_client)
|
implementations = get_platform_implementations(platform, dummy_client)
|
||||||
parser = implementations.event_parser
|
parser = implementations.event_parser
|
||||||
|
|||||||
@@ -50,13 +50,24 @@ class UniversalGeyserListener(BaseTokenListener):
|
|||||||
self.platform_parsers = {}
|
self.platform_parsers = {}
|
||||||
self.platform_program_ids = set()
|
self.platform_program_ids = set()
|
||||||
|
|
||||||
# Create a temporary client for getting parsers
|
|
||||||
from core.client import SolanaClient
|
|
||||||
temp_client = SolanaClient("http://temp")
|
|
||||||
|
|
||||||
for platform in self.platforms:
|
for platform in self.platforms:
|
||||||
try:
|
try:
|
||||||
implementations = platform_factory.create_for_platform(platform, temp_client)
|
# Create a simple dummy client that doesn't start blockhash updater
|
||||||
|
from core.client import SolanaClient
|
||||||
|
|
||||||
|
# Create a mock client class to avoid network operations
|
||||||
|
class DummyClient(SolanaClient):
|
||||||
|
def __init__(self):
|
||||||
|
# Skip SolanaClient.__init__ to avoid starting blockhash updater
|
||||||
|
self.rpc_endpoint = "http://dummy"
|
||||||
|
self._client = None
|
||||||
|
self._cached_blockhash = None
|
||||||
|
self._blockhash_lock = None
|
||||||
|
self._blockhash_updater_task = None
|
||||||
|
|
||||||
|
dummy_client = DummyClient()
|
||||||
|
|
||||||
|
implementations = platform_factory.create_for_platform(platform, dummy_client)
|
||||||
parser = implementations.event_parser
|
parser = implementations.event_parser
|
||||||
self.platform_parsers[platform] = parser
|
self.platform_parsers[platform] = parser
|
||||||
self.platform_program_ids.add(parser.get_program_id())
|
self.platform_program_ids.add(parser.get_program_id())
|
||||||
|
|||||||
@@ -45,13 +45,24 @@ class UniversalLogsListener(BaseTokenListener):
|
|||||||
self.platform_parsers = {}
|
self.platform_parsers = {}
|
||||||
self.platform_program_ids = []
|
self.platform_program_ids = []
|
||||||
|
|
||||||
# Create a temporary client for getting parsers (stateless parsers don't use it)
|
|
||||||
from core.client import SolanaClient
|
|
||||||
temp_client = SolanaClient("http://temp")
|
|
||||||
|
|
||||||
for platform in self.platforms:
|
for platform in self.platforms:
|
||||||
try:
|
try:
|
||||||
implementations = platform_factory.create_for_platform(platform, temp_client)
|
# Create a simple dummy client that doesn't start blockhash updater
|
||||||
|
from core.client import SolanaClient
|
||||||
|
|
||||||
|
# Create a mock client class to avoid network operations
|
||||||
|
class DummyClient(SolanaClient):
|
||||||
|
def __init__(self):
|
||||||
|
# Skip SolanaClient.__init__ to avoid starting blockhash updater
|
||||||
|
self.rpc_endpoint = "http://dummy"
|
||||||
|
self._client = None
|
||||||
|
self._cached_blockhash = None
|
||||||
|
self._blockhash_lock = None
|
||||||
|
self._blockhash_updater_task = None
|
||||||
|
|
||||||
|
dummy_client = DummyClient()
|
||||||
|
|
||||||
|
implementations = platform_factory.create_for_platform(platform, dummy_client)
|
||||||
parser = implementations.event_parser
|
parser = implementations.event_parser
|
||||||
self.platform_parsers[platform] = parser
|
self.platform_parsers[platform] = parser
|
||||||
self.platform_program_ids.append(str(parser.get_program_id()))
|
self.platform_program_ids.append(str(parser.get_program_id()))
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
"""
|
"""
|
||||||
LetsBonk implementation of InstructionBuilder interface.
|
Pump.Fun implementation of InstructionBuilder interface.
|
||||||
|
|
||||||
This module builds LetsBonk (Raydium LaunchLab) specific buy and sell instructions
|
This module builds pump.fun-specific buy and sell instructions
|
||||||
by implementing the InstructionBuilder interface with IDL-based discriminators.
|
by implementing the InstructionBuilder interface with IDL-based discriminators.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import struct
|
import struct
|
||||||
import time
|
|
||||||
|
|
||||||
from solders.instruction import AccountMeta, Instruction
|
from solders.instruction import AccountMeta, Instruction
|
||||||
from solders.pubkey import Pubkey
|
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 spl.token.instructions import create_idempotent_associated_token_account
|
||||||
|
|
||||||
from core.pubkeys import TOKEN_DECIMALS, SystemAddresses
|
from core.pubkeys import TOKEN_DECIMALS, SystemAddresses
|
||||||
@@ -22,28 +19,28 @@ from utils.logger import get_logger
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LetsBonkInstructionBuilder(InstructionBuilder):
|
class PumpFunInstructionBuilder(InstructionBuilder):
|
||||||
"""LetsBonk (Raydium LaunchLab) implementation of InstructionBuilder interface with IDL-based discriminators."""
|
"""Pump.Fun implementation of InstructionBuilder interface with IDL-based discriminators."""
|
||||||
|
|
||||||
def __init__(self, idl_parser: IDLParser):
|
def __init__(self, idl_parser: IDLParser):
|
||||||
"""Initialize LetsBonk instruction builder with injected IDL parser.
|
"""Initialize pump.fun instruction builder with injected IDL parser.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
idl_parser: Pre-loaded IDL parser for LetsBonk platform
|
idl_parser: Pre-loaded IDL parser for pump.fun platform
|
||||||
"""
|
"""
|
||||||
self._idl_parser = idl_parser
|
self._idl_parser = idl_parser
|
||||||
|
|
||||||
# Get discriminators from injected IDL parser
|
# Get discriminators from injected IDL parser
|
||||||
discriminators = self._idl_parser.get_instruction_discriminators()
|
discriminators = self._idl_parser.get_instruction_discriminators()
|
||||||
self._buy_exact_in_discriminator = discriminators["buy_exact_in"]
|
self._buy_discriminator = discriminators["buy"]
|
||||||
self._sell_exact_in_discriminator = discriminators["sell_exact_in"]
|
self._sell_discriminator = discriminators["sell"]
|
||||||
|
|
||||||
logger.info("LetsBonk instruction builder initialized with injected IDL parser")
|
logger.info("Pump.Fun instruction builder initialized with injected IDL parser")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platform(self) -> Platform:
|
def platform(self) -> Platform:
|
||||||
"""Get the platform this builder serves."""
|
"""Get the platform this builder serves."""
|
||||||
return Platform.LETS_BONK
|
return Platform.PUMP_FUN
|
||||||
|
|
||||||
async def build_buy_instruction(
|
async def build_buy_instruction(
|
||||||
self,
|
self,
|
||||||
@@ -53,7 +50,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
minimum_amount_out: int,
|
minimum_amount_out: int,
|
||||||
address_provider: AddressProvider
|
address_provider: AddressProvider
|
||||||
) -> list[Instruction]:
|
) -> list[Instruction]:
|
||||||
"""Build buy instruction(s) for LetsBonk using buy_exact_in.
|
"""Build buy instruction(s) for pump.fun.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
token_info: Token information
|
token_info: Token information
|
||||||
@@ -70,7 +67,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
# Get all required accounts
|
# Get all required accounts
|
||||||
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
|
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
|
||||||
|
|
||||||
# 1. Create idempotent ATA for base token
|
# 1. Create idempotent ATA instruction (won't fail if ATA already exists)
|
||||||
ata_instruction = create_idempotent_associated_token_account(
|
ata_instruction = create_idempotent_associated_token_account(
|
||||||
user, # payer
|
user, # payer
|
||||||
user, # owner
|
user, # owner
|
||||||
@@ -79,62 +76,29 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
)
|
)
|
||||||
instructions.append(ata_instruction)
|
instructions.append(ata_instruction)
|
||||||
|
|
||||||
# 2. Create WSOL account with seed (temporary account for the transaction)
|
# 2. Build buy instruction
|
||||||
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 = [
|
buy_accounts = [
|
||||||
AccountMeta(pubkey=user, is_signer=True, is_writable=True), # payer
|
AccountMeta(pubkey=accounts_info["global"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False), # authority
|
AccountMeta(pubkey=accounts_info["fee"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False), # global_config
|
AccountMeta(pubkey=accounts_info["mint"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False), # platform_config
|
AccountMeta(pubkey=accounts_info["bonding_curve"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True), # pool_state
|
AccountMeta(pubkey=accounts_info["associated_bonding_curve"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True), # user_base_token
|
AccountMeta(pubkey=accounts_info["user_token_account"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token (WSOL account)
|
AccountMeta(pubkey=accounts_info["user"], is_signer=True, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True), # base_vault
|
AccountMeta(pubkey=accounts_info["system_program"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True), # quote_vault
|
AccountMeta(pubkey=accounts_info["token_program"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=token_info.mint, is_signer=False, is_writable=False), # base_token_mint
|
AccountMeta(pubkey=accounts_info["creator_vault"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False), # quote_token_mint
|
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # base_token_program
|
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # quote_token_program
|
AccountMeta(pubkey=accounts_info["global_volume_accumulator"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False), # event_authority
|
AccountMeta(pubkey=accounts_info["user_volume_accumulator"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False), # program
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Build instruction data: discriminator + amount_in + minimum_amount_out + share_fee_rate
|
# Build instruction data: discriminator + token_amount + max_sol_cost
|
||||||
SHARE_FEE_RATE = 0 # No sharing fee
|
|
||||||
instruction_data = (
|
instruction_data = (
|
||||||
self._buy_exact_in_discriminator +
|
self._buy_discriminator +
|
||||||
struct.pack("<Q", amount_in) + # amount_in (u64) - SOL to spend
|
struct.pack("<Q", minimum_amount_out) + # token amount in raw units
|
||||||
struct.pack("<Q", minimum_amount_out) + # minimum_amount_out (u64) - min tokens
|
struct.pack("<Q", amount_in) # max SOL cost in lamports
|
||||||
struct.pack("<Q", SHARE_FEE_RATE) # share_fee_rate (u64): 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
buy_instruction = Instruction(
|
buy_instruction = Instruction(
|
||||||
@@ -144,14 +108,6 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
)
|
)
|
||||||
instructions.append(buy_instruction)
|
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
|
return instructions
|
||||||
|
|
||||||
async def build_sell_instruction(
|
async def build_sell_instruction(
|
||||||
@@ -162,7 +118,7 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
minimum_amount_out: int,
|
minimum_amount_out: int,
|
||||||
address_provider: AddressProvider
|
address_provider: AddressProvider
|
||||||
) -> list[Instruction]:
|
) -> list[Instruction]:
|
||||||
"""Build sell instruction(s) for LetsBonk using sell_exact_in.
|
"""Build sell instruction(s) for pump.fun.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
token_info: Token information
|
token_info: Token information
|
||||||
@@ -179,60 +135,27 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
# Get all required accounts
|
# Get all required accounts
|
||||||
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
|
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
|
||||||
|
|
||||||
# 1. Create WSOL account with seed (to receive SOL)
|
# Build sell instruction accounts
|
||||||
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 = [
|
sell_accounts = [
|
||||||
AccountMeta(pubkey=user, is_signer=True, is_writable=True), # payer
|
AccountMeta(pubkey=accounts_info["global"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=accounts_info["authority"], is_signer=False, is_writable=False), # authority
|
AccountMeta(pubkey=accounts_info["fee"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["global_config"], is_signer=False, is_writable=False), # global_config
|
AccountMeta(pubkey=accounts_info["mint"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=accounts_info["platform_config"], is_signer=False, is_writable=False), # platform_config
|
AccountMeta(pubkey=accounts_info["bonding_curve"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["pool_state"], is_signer=False, is_writable=True), # pool_state
|
AccountMeta(pubkey=accounts_info["associated_bonding_curve"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["user_base_token"], is_signer=False, is_writable=True), # user_base_token (tokens being sold)
|
AccountMeta(pubkey=accounts_info["user_token_account"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=wsol_account, is_signer=False, is_writable=True), # user_quote_token (WSOL received)
|
AccountMeta(pubkey=accounts_info["user"], is_signer=True, is_writable=True),
|
||||||
AccountMeta(pubkey=accounts_info["base_vault"], is_signer=False, is_writable=True), # base_vault (receives tokens)
|
AccountMeta(pubkey=accounts_info["system_program"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=accounts_info["quote_vault"], is_signer=False, is_writable=True), # quote_vault (sends WSOL)
|
AccountMeta(pubkey=accounts_info["creator_vault"], is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=token_info.mint, is_signer=False, is_writable=False), # base_token_mint
|
AccountMeta(pubkey=accounts_info["token_program"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SystemAddresses.SOL_MINT, is_signer=False, is_writable=False), # quote_token_mint
|
AccountMeta(pubkey=accounts_info["event_authority"], is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False), # base_token_program
|
AccountMeta(pubkey=accounts_info["program"], is_signer=False, is_writable=False),
|
||||||
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
|
# Build instruction data: discriminator + token_amount + min_sol_output
|
||||||
SHARE_FEE_RATE = 0 # No sharing fee
|
|
||||||
instruction_data = (
|
instruction_data = (
|
||||||
self._sell_exact_in_discriminator +
|
self._sell_discriminator +
|
||||||
struct.pack("<Q", amount_in) + # amount_in (u64) - tokens to sell
|
struct.pack("<Q", amount_in) + # token amount in raw units
|
||||||
struct.pack("<Q", minimum_amount_out) + # minimum_amount_out (u64) - min SOL
|
struct.pack("<Q", minimum_amount_out) # min SOL output in lamports
|
||||||
struct.pack("<Q", SHARE_FEE_RATE) # share_fee_rate (u64): 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sell_instruction = Instruction(
|
sell_instruction = Instruction(
|
||||||
@@ -242,14 +165,6 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
)
|
)
|
||||||
instructions.append(sell_instruction)
|
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
|
return instructions
|
||||||
|
|
||||||
def get_required_accounts_for_buy(
|
def get_required_accounts_for_buy(
|
||||||
@@ -271,12 +186,14 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
|
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
accounts_info["pool_state"],
|
accounts_info["mint"],
|
||||||
accounts_info["user_base_token"],
|
accounts_info["bonding_curve"],
|
||||||
accounts_info["base_vault"],
|
accounts_info["associated_bonding_curve"],
|
||||||
accounts_info["quote_vault"],
|
accounts_info["user_token_account"],
|
||||||
token_info.mint,
|
accounts_info["fee"],
|
||||||
SystemAddresses.SOL_MINT,
|
accounts_info["creator_vault"],
|
||||||
|
accounts_info["global_volume_accumulator"],
|
||||||
|
accounts_info["user_volume_accumulator"],
|
||||||
accounts_info["program"],
|
accounts_info["program"],
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -299,91 +216,15 @@ class LetsBonkInstructionBuilder(InstructionBuilder):
|
|||||||
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
|
accounts_info = address_provider.get_sell_instruction_accounts(token_info, user)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
accounts_info["pool_state"],
|
accounts_info["mint"],
|
||||||
accounts_info["user_base_token"],
|
accounts_info["bonding_curve"],
|
||||||
accounts_info["base_vault"],
|
accounts_info["associated_bonding_curve"],
|
||||||
accounts_info["quote_vault"],
|
accounts_info["user_token_account"],
|
||||||
token_info.mint,
|
accounts_info["fee"],
|
||||||
SystemAddresses.SOL_MINT,
|
accounts_info["creator_vault"],
|
||||||
accounts_info["program"],
|
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:
|
def calculate_token_amount_raw(self, token_amount_decimal: float) -> int:
|
||||||
"""Convert decimal token amount to raw token units.
|
"""Convert decimal token amount to raw token units.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user