Skip to content

Commit 9d93d85

Browse files
authored
Reduce RPC delays when selling (#150)
* feat(trading): enhance sell execution with token amount and price parameters to reduce RPC delays * feat(trading): add method to fetch actual token balance after transaction to improve accuracy * feat(trading): update account data size limit and enhance price calculations
1 parent 03a4e7b commit 9d93d85

File tree

7 files changed

+293
-60
lines changed

7 files changed

+293
-60
lines changed

bots/bot-sniper-1-geyser.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
88
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
99
private_key: "${SOLANA_PRIVATE_KEY}"
1010

11-
enabled: false # You can turn off the bot w/o removing its config
11+
enabled: true # You can turn off the bot w/o removing its config
1212
separate_process: true
1313

1414
# Options: "pump_fun" (default), "lets_bonk"
@@ -31,7 +31,7 @@ trade:
3131
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
3232
#take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
3333
#stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
34-
max_hold_time: 15 # Maximum hold time in seconds
34+
max_hold_time: 5 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
3535
#price_check_interval: 2 # Check price every 2 seconds
3636

3737
# EXTREME FAST mode configuration
@@ -61,8 +61,9 @@ compute_units:
6161
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
6262
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
6363
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
64+
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
6465
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
65-
account_data_size: 512_000 # 512KB limit
66+
account_data_size: 12_500_000
6667

6768
# Filters for token selection
6869
filters:
@@ -77,7 +78,7 @@ filters:
7778
retries:
7879
max_attempts: 1 # Number of attempts for transaction submission
7980
wait_after_creation: 15 # Seconds to wait after token creation (only if EXTREME FAST is disabled)
80-
wait_after_buy: 15 # Holding period after buy transaction
81+
wait_after_buy: 5 # Holding period after buy transaction
8182
wait_before_new_token: 15 # Pause between token trades
8283

8384
# Token and account management

bots/bot-sniper-2-logs.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ geyser:
2424
# Control trade execution: amount of SOL per trade and acceptable price deviation
2525
trade:
2626
buy_amount: 0.0001 # Amount of SOL to spend when buying (in SOL)
27-
buy_slippage: 0.2 # Maximum acceptable price deviation (0.2 = 20%)
27+
buy_slippage: 0.3 # Maximum acceptable price deviation (0.3 = 30%)
2828
sell_slippage: 0.3
2929

3030
# Exit strategy configuration
3131
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
3232
take_profit_percentage: 0.4 # Take profit at 40% gain (0.4 = 40%)
3333
stop_loss_percentage: 0.4 # Stop loss at 40% loss (0.4 = 40%)
34-
max_hold_time: 60 # Maximum hold time in seconds
34+
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
3535
price_check_interval: 2 # Check price every 2 seconds
3636

3737
# EXTREME FAST mode configuration
@@ -61,8 +61,9 @@ compute_units:
6161
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
6262
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
6363
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
64+
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
6465
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
65-
account_data_size: 512_000 # 512KB limit
66+
account_data_size: 12_500_000
6667

6768
# Filters for token selection
6869
filters:

bots/bot-sniper-3-blocks.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
88
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
99
private_key: "${SOLANA_PRIVATE_KEY}"
1010

11-
enabled: true # You can turn off the bot w/o removing its config
11+
enabled: false # You can turn off the bot w/o removing its config
1212
separate_process: true
1313

1414
# Options: "pump_fun" (default), "lets_bonk"
@@ -31,7 +31,7 @@ trade:
3131
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
3232
#take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
3333
#stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
34-
max_hold_time: 15 # Maximum hold time in seconds
34+
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
3535
#price_check_interval: 2 # Check price every 2 seconds
3636

3737
# EXTREME FAST mode configuration
@@ -61,8 +61,9 @@ compute_units:
6161
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
6262
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
6363
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
64+
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
6465
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
65-
account_data_size: 512_000 # 512KB limit
66+
account_data_size: 12_500_000
6667

6768
# Filters for token selection
6869
filters:

bots/bot-sniper-4-pp.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ trade:
2929
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
3030
take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
3131
stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
32-
max_hold_time: 600 # Maximum hold time in seconds (600 = 10 minutes)
32+
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
3333
price_check_interval: 2 # Check price every 2 seconds
3434

3535
# EXTREME FAST mode configuration
@@ -59,8 +59,9 @@ compute_units:
5959
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
6060
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
6161
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
62+
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
6263
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
63-
account_data_size: 512_000 # 512KB limit
64+
account_data_size: 12_500_000
6465

6566
# Filters for token selection
6667
filters:

src/core/client.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,152 @@ async def confirm_transaction(
271271
logger.exception(f"Failed to confirm transaction {signature}")
272272
return False
273273

274+
async def get_transaction_token_balance(
275+
self, signature: str, user_pubkey: Pubkey, mint: Pubkey
276+
) -> int | None:
277+
"""Get the user's token balance after a transaction from postTokenBalances.
278+
279+
Args:
280+
signature: Transaction signature
281+
user_pubkey: User's wallet public key
282+
mint: Token mint address
283+
284+
Returns:
285+
Token balance (raw amount) after transaction, or None if not found
286+
"""
287+
result = await self._get_transaction_result(signature)
288+
if not result:
289+
return None
290+
291+
meta = result.get("meta", {})
292+
post_token_balances = meta.get("postTokenBalances", [])
293+
294+
user_str = str(user_pubkey)
295+
mint_str = str(mint)
296+
297+
for balance in post_token_balances:
298+
if balance.get("owner") == user_str and balance.get("mint") == mint_str:
299+
ui_amount = balance.get("uiTokenAmount", {})
300+
amount_str = ui_amount.get("amount")
301+
if amount_str:
302+
return int(amount_str)
303+
304+
return None
305+
306+
async def get_buy_transaction_details(
307+
self, signature: str, mint: Pubkey, sol_destination: Pubkey
308+
) -> tuple[int | None, int | None]:
309+
"""Get actual tokens received and SOL spent from a buy transaction.
310+
311+
Uses preBalances/postBalances to find exact SOL transferred to the
312+
pool/curve and pre/post token balance diff to find tokens received.
313+
314+
Args:
315+
signature: Transaction signature
316+
mint: Token mint address
317+
sol_destination: Address where SOL is sent (bonding curve for pump.fun,
318+
quote_vault for letsbonk)
319+
320+
Returns:
321+
Tuple of (tokens_received_raw, sol_spent_lamports), or (None, None)
322+
"""
323+
result = await self._get_transaction_result(signature)
324+
if not result:
325+
return None, None
326+
327+
meta = result.get("meta", {})
328+
mint_str = str(mint)
329+
330+
# Get tokens received from pre/post token balance diff
331+
# This works for Token2022 where owner might be different
332+
tokens_received = None
333+
pre_token_balances = meta.get("preTokenBalances", [])
334+
post_token_balances = meta.get("postTokenBalances", [])
335+
336+
# Build lookup by account index
337+
pre_by_idx = {b.get("accountIndex"): b for b in pre_token_balances}
338+
post_by_idx = {b.get("accountIndex"): b for b in post_token_balances}
339+
340+
# Find positive token diff for our mint (user receiving tokens)
341+
all_indices = set(pre_by_idx.keys()) | set(post_by_idx.keys())
342+
for idx in all_indices:
343+
pre = pre_by_idx.get(idx)
344+
post = post_by_idx.get(idx)
345+
346+
# Check if this is our mint
347+
balance_mint = (post or pre).get("mint", "")
348+
if balance_mint != mint_str:
349+
continue
350+
351+
pre_amount = (
352+
int(pre.get("uiTokenAmount", {}).get("amount", 0)) if pre else 0
353+
)
354+
post_amount = (
355+
int(post.get("uiTokenAmount", {}).get("amount", 0)) if post else 0
356+
)
357+
diff = post_amount - pre_amount
358+
359+
# Positive diff means tokens received (not the bonding curve's negative)
360+
if diff > 0:
361+
tokens_received = diff
362+
logger.info(f"Tokens received from tx: {tokens_received}")
363+
break
364+
365+
# Get SOL spent from preBalances/postBalances at sol_destination
366+
sol_destination_str = str(sol_destination)
367+
sol_spent = None
368+
pre_balances = meta.get("preBalances", [])
369+
post_balances = meta.get("postBalances", [])
370+
account_keys = (
371+
result.get("transaction", {}).get("message", {}).get("accountKeys", [])
372+
)
373+
374+
for i, key in enumerate(account_keys):
375+
key_str = key if isinstance(key, str) else key.get("pubkey", "")
376+
if key_str == sol_destination_str:
377+
if i < len(pre_balances) and i < len(post_balances):
378+
sol_spent = post_balances[i] - pre_balances[i]
379+
if sol_spent > 0:
380+
logger.info(f"SOL to pool/curve: {sol_spent} lamports")
381+
else:
382+
logger.warning(
383+
f"SOL destination balance change not positive: {sol_spent}"
384+
)
385+
sol_spent = None
386+
break
387+
388+
return tokens_received, sol_spent
389+
390+
async def _get_transaction_result(self, signature: str) -> dict | None:
391+
"""Fetch transaction result from RPC.
392+
393+
Args:
394+
signature: Transaction signature
395+
396+
Returns:
397+
Transaction result dict or None
398+
"""
399+
body = {
400+
"jsonrpc": "2.0",
401+
"id": 1,
402+
"method": "getTransaction",
403+
"params": [
404+
signature,
405+
{"encoding": "jsonParsed", "commitment": "confirmed"},
406+
],
407+
}
408+
409+
response = await self.post_rpc(body)
410+
if not response or "result" not in response:
411+
logger.warning(f"Failed to get transaction {signature}")
412+
return None
413+
414+
result = response["result"]
415+
if not result or "meta" not in result:
416+
return None
417+
418+
return result
419+
274420
async def post_rpc(self, body: dict[str, Any]) -> dict[str, Any] | None:
275421
"""
276422
Send a raw RPC request to the Solana node.

0 commit comments

Comments
 (0)