Skip to content

Commit 97fd3f7

Browse files
committed
fix(bot): update token program handling and improve price validation in trading logic
1 parent b4840db commit 97fd3f7

File tree

7 files changed

+65
-37
lines changed

7 files changed

+65
-37
lines changed

learning-examples/cleanup_accounts.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
# Update this address to MINT address of a token you want to close
2020
MINT_ADDRESS = Pubkey.from_string("9WHpYbqG6LJvfCYfMjvGbyo1wHXgroCrixPb33s2pump")
2121

22+
# Token program for the mint - use TOKEN_PROGRAM for legacy SPL tokens, TOKEN_2022_PROGRAM for Token-2022
23+
# This must match the actual token's program to derive the correct ATA address
24+
TOKEN_PROGRAM = SystemAddresses.TOKEN_PROGRAM
25+
2226

2327
async def close_account_if_exists(
2428
client: SolanaClient, wallet: Wallet, account: Pubkey, mint: Pubkey
@@ -41,7 +45,7 @@ async def close_account_if_exists(
4145
mint=mint,
4246
owner=wallet.pubkey,
4347
amount=balance,
44-
program_id=SystemAddresses.TOKEN_PROGRAM,
48+
program_id=TOKEN_PROGRAM,
4549
)
4650
)
4751
await client.build_and_send_transaction([burn_ix], wallet.keypair)
@@ -54,7 +58,7 @@ async def close_account_if_exists(
5458
account=account,
5559
dest=wallet.pubkey,
5660
owner=wallet.pubkey,
57-
program_id=SystemAddresses.TOKEN_PROGRAM,
61+
program_id=TOKEN_PROGRAM,
5862
)
5963
ix = close_account(close_params)
6064

@@ -78,7 +82,7 @@ async def main():
7882
wallet = Wallet(PRIVATE_KEY)
7983

8084
# Get user's ATA for the token
81-
ata = wallet.get_associated_token_address(MINT_ADDRESS)
85+
ata = wallet.get_associated_token_address(MINT_ADDRESS, TOKEN_PROGRAM)
8286
await close_account_if_exists(client, wallet, ata, MINT_ADDRESS)
8387

8488
except Exception as e:

src/platforms/letsbonk/address_provider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey:
157157
Returns:
158158
User's associated token account address
159159
"""
160-
return get_associated_token_address(user, mint)
160+
# Explicitly pass TOKEN_PROGRAM since LetsBonk uses standard SPL tokens
161+
return get_associated_token_address(user, mint, SystemAddresses.TOKEN_PROGRAM)
161162

162163
def get_additional_accounts(self, token_info: TokenInfo) -> dict[str, Pubkey]:
163164
"""Get LetsBonk-specific additional accounts needed for trading.

src/platforms/letsbonk/curve_manager.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,21 @@ def _decode_pool_state_with_idl(self, data: bytes) -> dict[str, Any]:
194194
}
195195

196196
# Calculate additional metrics
197-
if pool_data["virtual_base"] > 0:
198-
pool_data["price_per_token"] = (
199-
(pool_data["virtual_quote"] / pool_data["virtual_base"])
200-
* (10**TOKEN_DECIMALS)
201-
/ LAMPORTS_PER_SOL
197+
# Validate reserves are positive before calculating price
198+
if pool_data["virtual_base"] <= 0:
199+
raise ValueError(
200+
f"Invalid virtual_base: {pool_data['virtual_base']} - cannot calculate price"
202201
)
203-
else:
204-
pool_data["price_per_token"] = 0
202+
if pool_data["virtual_quote"] <= 0:
203+
raise ValueError(
204+
f"Invalid virtual_quote: {pool_data['virtual_quote']} - cannot calculate price"
205+
)
206+
207+
pool_data["price_per_token"] = (
208+
(pool_data["virtual_quote"] / pool_data["virtual_base"])
209+
* (10**TOKEN_DECIMALS)
210+
/ LAMPORTS_PER_SOL
211+
)
205212

206213
logger.debug(
207214
f"Decoded pool state: virtual_base={pool_data['virtual_base']}, "

src/platforms/pumpfun/curve_manager.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,24 @@ def _decode_curve_state_with_idl(self, data: bytes) -> dict[str, Any]:
194194
}
195195

196196
# Calculate additional metrics
197-
if curve_data["virtual_token_reserves"] > 0:
198-
curve_data["price_per_token"] = (
199-
(
200-
curve_data["virtual_sol_reserves"]
201-
/ curve_data["virtual_token_reserves"]
202-
)
203-
* (10**TOKEN_DECIMALS)
204-
/ LAMPORTS_PER_SOL
197+
# Validate reserves are positive before calculating price
198+
if curve_data["virtual_token_reserves"] <= 0:
199+
raise ValueError(
200+
f"Invalid virtual_token_reserves: {curve_data['virtual_token_reserves']} - cannot calculate price"
205201
)
206-
else:
207-
curve_data["price_per_token"] = 0
202+
if curve_data["virtual_sol_reserves"] <= 0:
203+
raise ValueError(
204+
f"Invalid virtual_sol_reserves: {curve_data['virtual_sol_reserves']} - cannot calculate price"
205+
)
206+
207+
curve_data["price_per_token"] = (
208+
(
209+
curve_data["virtual_sol_reserves"]
210+
/ curve_data["virtual_token_reserves"]
211+
)
212+
* (10**TOKEN_DECIMALS)
213+
/ LAMPORTS_PER_SOL
214+
)
208215

209216
# Add convenience decimal fields
210217
curve_data["token_reserves_decimal"] = (

src/platforms/pumpfun/event_parser.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ def parse_token_creation_from_logs(
110110

111111
# First, collect all Program data entries and note when Create instruction happens
112112
for i, log in enumerate(logs):
113-
if "Program log: Instruction: Create" in log:
113+
if "Program log: Instruction: Create" in log or "Program log: Instruction: Create_v2" in log:
114114
create_instruction_found = True
115-
logger.info(f"📝 Found Create instruction at log index {i}")
115+
instruction_type = "Create_v2" if "Create_v2" in log else "Create"
116+
logger.info(f"📝 Found {instruction_type} instruction at log index {i}")
116117
elif "Program data:" in log:
117118
# Extract base64 encoded event data
118119
encoded_data = log.split("Program data: ")[1].strip()
@@ -122,7 +123,7 @@ def parse_token_creation_from_logs(
122123
)
123124

124125
if not create_instruction_found:
125-
logger.info("❌ No Create instruction found in logs")
126+
logger.info("❌ No Create or Create_v2 instruction found in logs")
126127
return None
127128

128129
if not program_data_entries:

src/platforms/pumpfun/instruction_builder.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,13 @@ async def build_buy_instruction(
6767
# Get all required accounts (includes mayhem-mode-aware fee recipient)
6868
accounts_info = address_provider.get_buy_instruction_accounts(token_info, user)
6969

70-
# Determine token program to use (default to TOKEN_2022_PROGRAM per pump.fun's migration to create_v2)
71-
token_program_id = (
72-
token_info.token_program_id
73-
if token_info.token_program_id
74-
else SystemAddresses.TOKEN_2022_PROGRAM
75-
)
76-
7770
# 1. Create idempotent ATA instruction (won't fail if ATA already exists)
71+
# Use token_program from accounts_info to ensure AddressProvider controls program selection
7872
ata_instruction = create_idempotent_associated_token_account(
7973
user, # payer
8074
user, # owner
8175
token_info.mint, # mint
82-
token_program_id, # token program (dynamic for token2022 support)
76+
accounts_info["token_program"], # token program from AddressProvider
8377
)
8478
instructions.append(ata_instruction)
8579

src/trading/platform_aware.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,18 @@ async def execute(self, token_info: TokenInfo) -> TradeResult:
6868
# Regular behavior with RPC call
6969
# Fetch pool state to get price and mayhem mode status
7070
pool_state = await curve_manager.get_pool_state(pool_address)
71-
token_price_sol = pool_state.get("price_per_token", 0)
71+
token_price_sol = pool_state.get("price_per_token")
72+
73+
# Validate price_per_token is present and positive
74+
if token_price_sol is None or token_price_sol <= 0:
75+
raise ValueError(
76+
f"Invalid price_per_token: {token_price_sol} for pool {pool_address} "
77+
f"(mint: {token_info.mint}) - cannot execute buy with zero/invalid price"
78+
)
79+
7280
# Set is_mayhem_mode from bonding curve state
7381
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
74-
token_amount = (
75-
self.amount / token_price_sol if token_price_sol > 0 else 0
76-
)
82+
token_amount = self.amount / token_price_sol
7783

7884
# Calculate minimum token amount with slippage
7985
minimum_token_amount = token_amount * (1 - self.slippage)
@@ -231,7 +237,15 @@ async def execute(self, token_info: TokenInfo) -> TradeResult:
231237
pool_address = self._get_pool_address(token_info, address_provider)
232238
# Fetch pool state to get price and mayhem mode status
233239
pool_state = await curve_manager.get_pool_state(pool_address)
234-
token_price_sol = pool_state.get("price_per_token", 0)
240+
token_price_sol = pool_state.get("price_per_token")
241+
242+
# Validate price_per_token is present and positive
243+
if token_price_sol is None or token_price_sol <= 0:
244+
raise ValueError(
245+
f"Invalid price_per_token: {token_price_sol} for pool {pool_address} "
246+
f"(mint: {token_info.mint}) - cannot execute sell with zero/invalid price"
247+
)
248+
235249
# Set is_mayhem_mode from bonding curve state
236250
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
237251

0 commit comments

Comments
 (0)