Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions bots/bot-sniper-1-geyser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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"
Expand All @@ -31,7 +31,7 @@ trade:
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
#take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
#stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
max_hold_time: 15 # Maximum hold time in seconds
max_hold_time: 5 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
#price_check_interval: 2 # Check price every 2 seconds

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

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

# Token and account management
Expand Down
7 changes: 4 additions & 3 deletions bots/bot-sniper-2-logs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ geyser:
# Control trade execution: amount of SOL per trade and acceptable price deviation
trade:
buy_amount: 0.0001 # Amount of SOL to spend when buying (in SOL)
buy_slippage: 0.2 # Maximum acceptable price deviation (0.2 = 20%)
buy_slippage: 0.3 # Maximum acceptable price deviation (0.3 = 30%)
sell_slippage: 0.3

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

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

# Filters for token selection
filters:
Expand Down
7 changes: 4 additions & 3 deletions bots/bot-sniper-3-blocks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -31,7 +31,7 @@ trade:
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
#take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
#stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
max_hold_time: 15 # Maximum hold time in seconds
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
#price_check_interval: 2 # Check price every 2 seconds

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

# Filters for token selection
filters:
Expand Down
5 changes: 3 additions & 2 deletions bots/bot-sniper-4-pp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ trade:
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
max_hold_time: 600 # Maximum hold time in seconds (600 = 10 minutes)
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
price_check_interval: 2 # Check price every 2 seconds

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

# Filters for token selection
filters:
Expand Down
146 changes: 146 additions & 0 deletions src/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,152 @@ async def confirm_transaction(
logger.exception(f"Failed to confirm transaction {signature}")
return False

async def get_transaction_token_balance(
self, signature: str, user_pubkey: Pubkey, mint: Pubkey
) -> int | None:
"""Get the user's token balance after a transaction from postTokenBalances.

Args:
signature: Transaction signature
user_pubkey: User's wallet public key
mint: Token mint address

Returns:
Token balance (raw amount) after transaction, or None if not found
"""
result = await self._get_transaction_result(signature)
if not result:
return None

meta = result.get("meta", {})
post_token_balances = meta.get("postTokenBalances", [])

user_str = str(user_pubkey)
mint_str = str(mint)

for balance in post_token_balances:
if balance.get("owner") == user_str and balance.get("mint") == mint_str:
ui_amount = balance.get("uiTokenAmount", {})
amount_str = ui_amount.get("amount")
if amount_str:
return int(amount_str)

return None

async def get_buy_transaction_details(
self, signature: str, mint: Pubkey, sol_destination: Pubkey
) -> tuple[int | None, int | None]:
"""Get actual tokens received and SOL spent from a buy transaction.

Uses preBalances/postBalances to find exact SOL transferred to the
pool/curve and pre/post token balance diff to find tokens received.

Args:
signature: Transaction signature
mint: Token mint address
sol_destination: Address where SOL is sent (bonding curve for pump.fun,
quote_vault for letsbonk)

Returns:
Tuple of (tokens_received_raw, sol_spent_lamports), or (None, None)
"""
result = await self._get_transaction_result(signature)
if not result:
return None, None

meta = result.get("meta", {})
mint_str = str(mint)

# Get tokens received from pre/post token balance diff
# This works for Token2022 where owner might be different
tokens_received = None
pre_token_balances = meta.get("preTokenBalances", [])
post_token_balances = meta.get("postTokenBalances", [])

# Build lookup by account index
pre_by_idx = {b.get("accountIndex"): b for b in pre_token_balances}
post_by_idx = {b.get("accountIndex"): b for b in post_token_balances}

# Find positive token diff for our mint (user receiving tokens)
all_indices = set(pre_by_idx.keys()) | set(post_by_idx.keys())
for idx in all_indices:
pre = pre_by_idx.get(idx)
post = post_by_idx.get(idx)

# Check if this is our mint
balance_mint = (post or pre).get("mint", "")
if balance_mint != mint_str:
continue

pre_amount = (
int(pre.get("uiTokenAmount", {}).get("amount", 0)) if pre else 0
)
post_amount = (
int(post.get("uiTokenAmount", {}).get("amount", 0)) if post else 0
)
diff = post_amount - pre_amount

# Positive diff means tokens received (not the bonding curve's negative)
if diff > 0:
tokens_received = diff
logger.info(f"Tokens received from tx: {tokens_received}")
break

# Get SOL spent from preBalances/postBalances at sol_destination
sol_destination_str = str(sol_destination)
sol_spent = None
pre_balances = meta.get("preBalances", [])
post_balances = meta.get("postBalances", [])
account_keys = (
result.get("transaction", {}).get("message", {}).get("accountKeys", [])
)

for i, key in enumerate(account_keys):
key_str = key if isinstance(key, str) else key.get("pubkey", "")
if key_str == sol_destination_str:
if i < len(pre_balances) and i < len(post_balances):
sol_spent = post_balances[i] - pre_balances[i]
if sol_spent > 0:
logger.info(f"SOL to pool/curve: {sol_spent} lamports")
else:
logger.warning(
f"SOL destination balance change not positive: {sol_spent}"
)
sol_spent = None
break

return tokens_received, sol_spent

async def _get_transaction_result(self, signature: str) -> dict | None:
"""Fetch transaction result from RPC.

Args:
signature: Transaction signature

Returns:
Transaction result dict or None
"""
body = {
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
signature,
{"encoding": "jsonParsed", "commitment": "confirmed"},
],
}

response = await self.post_rpc(body)
if not response or "result" not in response:
logger.warning(f"Failed to get transaction {signature}")
return None

result = response["result"]
if not result or "meta" not in result:
return None

return result

async def post_rpc(self, body: dict[str, Any]) -> dict[str, Any] | None:
"""
Send a raw RPC request to the Solana node.
Expand Down
Loading