feat(core): add support for creator fee update

This commit is contained in:
smypmsa
2025-05-12 20:07:51 +00:00
parent d979427662
commit e41a5f4f4f
18 changed files with 5951 additions and 441 deletions
+1 -1
View File
@@ -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: 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
# Geyser configuration (fastest method for getting updates) # Geyser configuration (fastest method for getting updates)
+72
View File
@@ -0,0 +1,72 @@
# This file defines comprehensive parameters and settings for the trading bot.
# Carefully review and adjust values to match your trading strategy and risk tolerance.
# Bot identification and connection settings
name: "bot-sniper-2"
env_file: ".env"
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
separate_process: true
# Geyser configuration (fastest method for getting updates)
geyser:
endpoint: "${GEYSER_ENDPOINT}"
api_token: "${GEYSER_API_TOKEN}"
auth_type: "basic" # or "x-token"
# Trading parameters
# 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.3 # Maximum acceptable price deviation (0.3 = 30%)
sell_slippage: 0.3
# EXTREME FAST mode configuration
# When enabled, skips waiting for the bonding curve to stabilize and RPC price check.
# The bot buys the specified number of tokens directly, making the process faster but less precise.
extreme_fast_mode: true
extreme_fast_token_amount: 20 # Amount of tokens to buy
# Priority fee configuration
# Manage transaction speed and cost on the Solana network.
# Note: dynamic mode requires an additional RPC call, which slows down the buying process.
priority_fees:
enable_dynamic: false # Use latest transactions to estimate required fee (getRecentPrioritizationFees)
enable_fixed: true # Use fixed amount below
fixed_amount: 200_000 # Base fee in microlamports
extra_percentage: 0.0 # Percentage increase on riority fee regardless of the calculation method (0.1 = 10%)
hard_cap: 200_000 # Maximum allowable fee in microlamports to prevent excessive spending
# Filters for token selection
filters:
match_string: null # Only process tokens with this string in name/symbol
bro_address: null # Only trade tokens created by this user address
listener_type: "blocks" # Method for detecting new tokens: "logs", "blocks", or "geyser"
max_token_age: 0.001 # Maximum token age in seconds for processing
marry_mode: false # Only buy tokens, skip selling
yolo_mode: false # Continuously trade tokens
# Retry and timeout settings
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_before_new_token: 15 # Pause between token trades
# Token and account management
cleanup:
# Cleanup mode determines when to manage token accounts. Options:
# "disabled": no cleanup will occur.
# "on_fail": only clean up if a buy transaction fails.
# "after_sell": clean up after selling.
# "post_session": clean up all empty accounts after a trading session ends.
mode: "post_session"
force_close_with_burn: false # Force burning remaining tokens before closing account
with_priority_fee: false # Use priority fees for cleanup transactions
# Node provider configuration (not implemented)
node:
max_rps: 25 # Maximum requests per second
+2699 -402
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -11,6 +11,7 @@ import json
import os import os
import struct import struct
import base58
import websockets import websockets
from dotenv import load_dotenv from dotenv import load_dotenv
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
@@ -37,8 +38,8 @@ def decode_create_instruction(ix_data, ix_def, accounts):
offset += 4 offset += 4
value = ix_data[offset : offset + length].decode("utf-8") value = ix_data[offset : offset + length].decode("utf-8")
offset += length offset += length
elif arg["type"] == "publicKey": elif arg["type"] == "pubkey":
value = base64.b64encode(ix_data[offset : offset + 32]).decode("utf-8") value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
offset += 32 offset += 32
else: else:
raise ValueError(f"Unsupported type: {arg['type']}") raise ValueError(f"Unsupported type: {arg['type']}")
@@ -77,14 +77,22 @@ def decode_create_instruction(ix_data: bytes, keys, accounts) -> dict:
offset += length offset += length
return value return value
def read_pubkey():
nonlocal offset
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
offset += 32
return value
name = read_string() name = read_string()
symbol = read_string() symbol = read_string()
uri = read_string() uri = read_string()
creator = read_pubkey()
token_info = { token_info = {
"name": name, "name": name,
"symbol": symbol, "symbol": symbol,
"uri": uri, "uri": uri,
"creator": creator,
"mint": get_account_key(0), "mint": get_account_key(0),
"metadata": get_account_key(1), "metadata": get_account_key(1),
"bonding_curve": get_account_key(2), "bonding_curve": get_account_key(2),
@@ -105,6 +113,7 @@ def print_token_info(info, signature):
print(f"Mint: {info['mint']}") print(f"Mint: {info['mint']}")
print(f"Bonding curve: {info['bonding_curve']}") print(f"Bonding curve: {info['bonding_curve']}")
print(f"Associated bonding curve: {info['associated_bonding_curve']}") print(f"Associated bonding curve: {info['associated_bonding_curve']}")
print(f"Creator: {info['creator']}")
print(f"Signature: {signature}") print(f"Signature: {signature}")
@@ -54,6 +54,7 @@ def parse_create_instruction(data):
("mint", "publicKey"), ("mint", "publicKey"),
("bondingCurve", "publicKey"), ("bondingCurve", "publicKey"),
("user", "publicKey"), ("user", "publicKey"),
("creator", "publicKey"),
] ]
try: try:
@@ -36,6 +36,7 @@ def parse_create_instruction(data):
("mint", "publicKey"), ("mint", "publicKey"),
("bondingCurve", "publicKey"), ("bondingCurve", "publicKey"),
("user", "publicKey"), ("user", "publicKey"),
("creator", "publicKey"),
] ]
try: try:
+8 -3
View File
@@ -5,7 +5,7 @@ Bonding curve operations for pump.fun tokens.
import struct import struct
from typing import Final from typing import Final
from construct import Flag, Int64ul, Struct from construct import Bytes, Flag, Int64ul, Struct
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
from core.client import SolanaClient from core.client import SolanaClient
@@ -28,6 +28,7 @@ class BondingCurveState:
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag, "complete" / Flag,
"creator" / Bytes(32), # Added new creator field - 32 bytes for Pubkey
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
@@ -45,6 +46,10 @@ class BondingCurveState:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
# Convert raw bytes to Pubkey for creator field
if hasattr(self, 'creator') and isinstance(self.creator, bytes):
self.creator = Pubkey.from_bytes(self.creator)
def calculate_price(self) -> float: def calculate_price(self) -> float:
"""Calculate token price in SOL. """Calculate token price in SOL.
@@ -103,8 +108,8 @@ class BondingCurveManager:
return BondingCurveState(account.data) return BondingCurveState(account.data)
except Exception as e: except Exception as e:
logger.error(f"Failed to get curve state: {str(e)}") logger.error(f"Failed to get curve state: {e!s}")
raise ValueError(f"Invalid curve state: {str(e)}") raise ValueError(f"Invalid curve state: {e!s}")
async def calculate_price(self, curve_address: Pubkey) -> float: async def calculate_price(self, curve_address: Pubkey) -> float:
"""Calculate the current price of a token. """Calculate the current price of a token.
+29 -4
View File
@@ -7,9 +7,11 @@ import json
import struct import struct
from typing import Any from typing import Any
import base58
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction from solders.transaction import VersionedTransaction
from core.pubkeys import PumpAddresses
from trading.base import TokenInfo from trading.base import TokenInfo
from utils.logger import get_logger from utils.logger import get_logger
@@ -41,7 +43,7 @@ class PumpEventProcessor:
with open("idl/pump_fun_idl.json") as f: with open("idl/pump_fun_idl.json") as f:
return json.load(f) return json.load(f)
except Exception as e: except Exception as e:
logger.error(f"Failed to load IDL: {str(e)}") logger.error(f"Failed to load IDL: {e!s}")
# Create a minimal IDL with just what we need # Create a minimal IDL with just what we need
return { return {
"instructions": [ "instructions": [
@@ -111,6 +113,8 @@ class PumpEventProcessor:
decoded_args = self._decode_create_instruction( decoded_args = self._decode_create_instruction(
ix_data, create_ix, account_keys ix_data, create_ix, account_keys
) )
creator = Pubkey.from_string(decoded_args["creator"])
creator_vault = self._find_creator_vault(creator)
return TokenInfo( return TokenInfo(
name=decoded_args["name"], name=decoded_args["name"],
@@ -122,10 +126,12 @@ class PumpEventProcessor:
decoded_args["associatedBondingCurve"] decoded_args["associatedBondingCurve"]
), ),
user=Pubkey.from_string(decoded_args["user"]), user=Pubkey.from_string(decoded_args["user"]),
creator=creator,
creator_vault=creator_vault,
) )
except Exception as e: except Exception as e:
logger.error(f"Error processing transaction: {str(e)}") logger.error(f"Error processing transaction: {e!s}")
return None return None
@@ -151,8 +157,8 @@ class PumpEventProcessor:
offset += 4 offset += 4
value = ix_data[offset : offset + length].decode("utf-8") value = ix_data[offset : offset + length].decode("utf-8")
offset += length offset += length
elif arg["type"] == "publicKey": elif arg["type"] == "pubkey":
value = base64.b64encode(ix_data[offset : offset + 32]).decode("utf-8") value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
offset += 32 offset += 32
else: else:
logger.warning(f"Unsupported type: {arg['type']}") logger.warning(f"Unsupported type: {arg['type']}")
@@ -166,3 +172,22 @@ class PumpEventProcessor:
args["user"] = str(accounts[7]) args["user"] = str(accounts[7])
return args return args
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
Args:
creator: Creator address
Returns:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
PumpAddresses.PROGRAM,
)
return derived_address
+32
View File
@@ -5,8 +5,10 @@ Event processing for pump.fun tokens using Geyser data.
import struct import struct
from typing import Final from typing import Final
import base58
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
from core.pubkeys import PumpAddresses
from trading.base import TokenInfo from trading.base import TokenInfo
from utils.logger import get_logger from utils.logger import get_logger
@@ -55,6 +57,12 @@ class GeyserEventProcessor:
offset += length offset += length
return value return value
def read_pubkey():
nonlocal offset
value = base58.b58encode(instruction_data[offset : offset + 32]).decode("utf-8")
offset += 32
return Pubkey.from_string(value)
# Helper to get account key # Helper to get account key
def get_account_key(index): def get_account_key(index):
if index >= len(accounts): if index >= len(accounts):
@@ -67,12 +75,15 @@ class GeyserEventProcessor:
name = read_string() name = read_string()
symbol = read_string() symbol = read_string()
uri = read_string() uri = read_string()
creator = read_pubkey()
mint = get_account_key(0) mint = get_account_key(0)
bonding_curve = get_account_key(2) bonding_curve = get_account_key(2)
associated_bonding_curve = get_account_key(3) associated_bonding_curve = get_account_key(3)
user = get_account_key(7) user = get_account_key(7)
creator_vault = self._find_creator_vault(creator)
if not all([mint, bonding_curve, associated_bonding_curve, user]): if not all([mint, bonding_curve, associated_bonding_curve, user]):
logger.warning("Missing required account keys in token creation") logger.warning("Missing required account keys in token creation")
return None return None
@@ -85,8 +96,29 @@ class GeyserEventProcessor:
bonding_curve=bonding_curve, bonding_curve=bonding_curve,
associated_bonding_curve=associated_bonding_curve, associated_bonding_curve=associated_bonding_curve,
user=user, user=user,
creator=creator,
creator_vault=creator_vault,
) )
except Exception as e: except Exception as e:
logger.error(f"Failed to process transaction data: {e}") logger.error(f"Failed to process transaction data: {e}")
return None return None
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
Args:
creator: Creator address
Returns:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
PumpAddresses.PROGRAM,
)
return derived_address
+25 -1
View File
@@ -9,7 +9,7 @@ from typing import Final
import base58 import base58
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
from core.pubkeys import SystemAddresses from core.pubkeys import PumpAddresses, SystemAddresses
from trading.base import TokenInfo from trading.base import TokenInfo
from utils.logger import get_logger from utils.logger import get_logger
@@ -62,6 +62,8 @@ class LogsEventProcessor:
associated_curve = self._find_associated_bonding_curve( associated_curve = self._find_associated_bonding_curve(
mint, bonding_curve mint, bonding_curve
) )
creator = Pubkey.from_string(parsed_data["creator"])
creator_vault = self._find_creator_vault(creator)
return TokenInfo( return TokenInfo(
name=parsed_data["name"], name=parsed_data["name"],
@@ -71,6 +73,8 @@ class LogsEventProcessor:
bonding_curve=bonding_curve, bonding_curve=bonding_curve,
associated_bonding_curve=associated_curve, associated_bonding_curve=associated_curve,
user=Pubkey.from_string(parsed_data["user"]), user=Pubkey.from_string(parsed_data["user"]),
creator=creator,
creator_vault=creator_vault,
) )
except Exception as e: except Exception as e:
logger.error(f"Failed to process log data: {e}") logger.error(f"Failed to process log data: {e}")
@@ -106,6 +110,7 @@ class LogsEventProcessor:
("mint", "publicKey"), ("mint", "publicKey"),
("bondingCurve", "publicKey"), ("bondingCurve", "publicKey"),
("user", "publicKey"), ("user", "publicKey"),
("creator", "publicKey"),
] ]
try: try:
@@ -149,3 +154,22 @@ class LogsEventProcessor:
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM, SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
) )
return derived_address return derived_address
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
Args:
creator: Creator address
Returns:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
PumpAddresses.PROGRAM,
)
return derived_address
+6
View File
@@ -22,6 +22,8 @@ class TokenInfo:
bonding_curve: Pubkey bonding_curve: Pubkey
associated_bonding_curve: Pubkey associated_bonding_curve: Pubkey
user: Pubkey user: Pubkey
creator: Pubkey
creator_vault: Pubkey
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> "TokenInfo": def from_dict(cls, data: dict[str, Any]) -> "TokenInfo":
@@ -41,6 +43,8 @@ class TokenInfo:
bonding_curve=Pubkey.from_string(data["bondingCurve"]), bonding_curve=Pubkey.from_string(data["bondingCurve"]),
associated_bonding_curve=Pubkey.from_string(data["associatedBondingCurve"]), associated_bonding_curve=Pubkey.from_string(data["associatedBondingCurve"]),
user=Pubkey.from_string(data["user"]), user=Pubkey.from_string(data["user"]),
creator=Pubkey.from_string(data["creator"]),
creator_vault=Pubkey.from_string(data["creator_vault"]),
) )
def to_dict(self) -> dict[str, str]: def to_dict(self) -> dict[str, str]:
@@ -57,6 +61,8 @@ class TokenInfo:
"bondingCurve": str(self.bonding_curve), "bondingCurve": str(self.bonding_curve),
"associatedBondingCurve": str(self.associated_bonding_curve), "associatedBondingCurve": str(self.associated_bonding_curve),
"user": str(self.user), "user": str(self.user),
"creator": str(self.creator),
"creatorVault": str(self.creator_vault),
} }
+1 -1
View File
@@ -176,7 +176,7 @@ class TokenBuyer(Trader):
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
), ),
AccountMeta( AccountMeta(
pubkey=SystemAddresses.RENT, is_signer=False, is_writable=False pubkey=token_info.creator_vault, is_signer=False, is_writable=True
), ),
AccountMeta( AccountMeta(
pubkey=PumpAddresses.EVENT_AUTHORITY, is_signer=False, is_writable=False pubkey=PumpAddresses.EVENT_AUTHORITY, is_signer=False, is_writable=False
+3 -5
View File
@@ -128,7 +128,7 @@ class TokenSeller(Trader):
) )
except Exception as e: except Exception as e:
logger.error(f"Sell operation failed: {str(e)}") logger.error(f"Sell operation failed: {e!s}")
return TradeResult(success=False, error_message=str(e)) return TradeResult(success=False, error_message=str(e))
async def _send_sell_transaction( async def _send_sell_transaction(
@@ -175,9 +175,7 @@ class TokenSeller(Trader):
pubkey=SystemAddresses.PROGRAM, is_signer=False, is_writable=False pubkey=SystemAddresses.PROGRAM, is_signer=False, is_writable=False
), ),
AccountMeta( AccountMeta(
pubkey=SystemAddresses.ASSOCIATED_TOKEN_PROGRAM, pubkey=token_info.creator_vault, is_signer=False, is_writable=True,
is_signer=False,
is_writable=False,
), ),
AccountMeta( AccountMeta(
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
@@ -209,5 +207,5 @@ class TokenSeller(Trader):
), ),
) )
except Exception as e: except Exception as e:
logger.error(f"Sell transaction failed: {str(e)}") logger.error(f"Sell transaction failed: {e!s}")
raise raise
+2 -1
View File
@@ -39,7 +39,8 @@ class TestTokenCallback:
print(f"Symbol: {token_info.symbol}") print(f"Symbol: {token_info.symbol}")
print(f"Mint: {token_info.mint}") print(f"Mint: {token_info.mint}")
print(f"URI: {token_info.uri}") print(f"URI: {token_info.uri}")
print(f"Creator: {token_info.user}") print(f"User: {token_info.user}")
print(f"Creator: {token_info.creator}")
print(f"Bonding Curve: {token_info.bonding_curve}") print(f"Bonding Curve: {token_info.bonding_curve}")
print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}") print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}")
print(f"{'=' * 50}\n") print(f"{'=' * 50}\n")
+2 -1
View File
@@ -39,7 +39,8 @@ class TestTokenCallback:
print(f"Symbol: {token_info.symbol}") print(f"Symbol: {token_info.symbol}")
print(f"Mint: {token_info.mint}") print(f"Mint: {token_info.mint}")
print(f"URI: {token_info.uri}") print(f"URI: {token_info.uri}")
print(f"Creator: {token_info.user}") print(f"User: {token_info.user}")
print(f"Creator: {token_info.creator}")
print(f"Bonding Curve: {token_info.bonding_curve}") print(f"Bonding Curve: {token_info.bonding_curve}")
print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}") print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}")
print(f"{'=' * 50}\n") print(f"{'=' * 50}\n")
+2 -1
View File
@@ -39,7 +39,8 @@ class TestTokenCallback:
print(f"Symbol: {token_info.symbol}") print(f"Symbol: {token_info.symbol}")
print(f"Mint: {token_info.mint}") print(f"Mint: {token_info.mint}")
print(f"URI: {token_info.uri}") print(f"URI: {token_info.uri}")
print(f"Creator: {token_info.user}") print(f"User: {token_info.user}")
print(f"Creator: {token_info.creator}")
print(f"Bonding Curve: {token_info.bonding_curve}") print(f"Bonding Curve: {token_info.bonding_curve}")
print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}") print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}")
print(f"{'=' * 50}\n") print(f"{'=' * 50}\n")