mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 15:27:44 +00:00
feat(core): add support for creator fee update
This commit is contained in:
@@ -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
|
||||
|
||||
# Geyser configuration (fastest method for getting updates)
|
||||
|
||||
@@ -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
|
||||
+2718
-421
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 struct
|
||||
|
||||
import base58
|
||||
import websockets
|
||||
from dotenv import load_dotenv
|
||||
from solders.pubkey import Pubkey
|
||||
@@ -37,8 +38,8 @@ def decode_create_instruction(ix_data, ix_def, accounts):
|
||||
offset += 4
|
||||
value = ix_data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
elif arg["type"] == "publicKey":
|
||||
value = base64.b64encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||
elif arg["type"] == "pubkey":
|
||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||
offset += 32
|
||||
else:
|
||||
raise ValueError(f"Unsupported type: {arg['type']}")
|
||||
|
||||
@@ -77,14 +77,22 @@ def decode_create_instruction(ix_data: bytes, keys, accounts) -> dict:
|
||||
offset += length
|
||||
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()
|
||||
symbol = read_string()
|
||||
uri = read_string()
|
||||
creator = read_pubkey()
|
||||
|
||||
token_info = {
|
||||
"name": name,
|
||||
"symbol": symbol,
|
||||
"uri": uri,
|
||||
"creator": creator,
|
||||
"mint": get_account_key(0),
|
||||
"metadata": get_account_key(1),
|
||||
"bonding_curve": get_account_key(2),
|
||||
@@ -105,6 +113,7 @@ def print_token_info(info, signature):
|
||||
print(f"Mint: {info['mint']}")
|
||||
print(f"Bonding curve: {info['bonding_curve']}")
|
||||
print(f"Associated bonding curve: {info['associated_bonding_curve']}")
|
||||
print(f"Creator: {info['creator']}")
|
||||
print(f"Signature: {signature}")
|
||||
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ def parse_create_instruction(data):
|
||||
("mint", "publicKey"),
|
||||
("bondingCurve", "publicKey"),
|
||||
("user", "publicKey"),
|
||||
("creator", "publicKey"),
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
@@ -36,6 +36,7 @@ def parse_create_instruction(data):
|
||||
("mint", "publicKey"),
|
||||
("bondingCurve", "publicKey"),
|
||||
("user", "publicKey"),
|
||||
("creator", "publicKey"),
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
+8
-3
@@ -5,7 +5,7 @@ Bonding curve operations for pump.fun tokens.
|
||||
import struct
|
||||
from typing import Final
|
||||
|
||||
from construct import Flag, Int64ul, Struct
|
||||
from construct import Bytes, Flag, Int64ul, Struct
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from core.client import SolanaClient
|
||||
@@ -28,6 +28,7 @@ class BondingCurveState:
|
||||
"real_sol_reserves" / Int64ul,
|
||||
"token_total_supply" / Int64ul,
|
||||
"complete" / Flag,
|
||||
"creator" / Bytes(32), # Added new creator field - 32 bytes for Pubkey
|
||||
)
|
||||
|
||||
def __init__(self, data: bytes) -> None:
|
||||
@@ -44,6 +45,10 @@ class BondingCurveState:
|
||||
|
||||
parsed = self._STRUCT.parse(data[8:])
|
||||
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:
|
||||
"""Calculate token price in SOL.
|
||||
@@ -103,8 +108,8 @@ class BondingCurveManager:
|
||||
return BondingCurveState(account.data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get curve state: {str(e)}")
|
||||
raise ValueError(f"Invalid curve state: {str(e)}")
|
||||
logger.error(f"Failed to get curve state: {e!s}")
|
||||
raise ValueError(f"Invalid curve state: {e!s}")
|
||||
|
||||
async def calculate_price(self, curve_address: Pubkey) -> float:
|
||||
"""Calculate the current price of a token.
|
||||
|
||||
@@ -7,9 +7,11 @@ import json
|
||||
import struct
|
||||
from typing import Any
|
||||
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.transaction import VersionedTransaction
|
||||
|
||||
from core.pubkeys import PumpAddresses
|
||||
from trading.base import TokenInfo
|
||||
from utils.logger import get_logger
|
||||
|
||||
@@ -41,7 +43,7 @@ class PumpEventProcessor:
|
||||
with open("idl/pump_fun_idl.json") as f:
|
||||
return json.load(f)
|
||||
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
|
||||
return {
|
||||
"instructions": [
|
||||
@@ -111,6 +113,8 @@ class PumpEventProcessor:
|
||||
decoded_args = self._decode_create_instruction(
|
||||
ix_data, create_ix, account_keys
|
||||
)
|
||||
creator = Pubkey.from_string(decoded_args["creator"])
|
||||
creator_vault = self._find_creator_vault(creator)
|
||||
|
||||
return TokenInfo(
|
||||
name=decoded_args["name"],
|
||||
@@ -122,10 +126,12 @@ class PumpEventProcessor:
|
||||
decoded_args["associatedBondingCurve"]
|
||||
),
|
||||
user=Pubkey.from_string(decoded_args["user"]),
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing transaction: {str(e)}")
|
||||
logger.error(f"Error processing transaction: {e!s}")
|
||||
|
||||
return None
|
||||
|
||||
@@ -151,8 +157,8 @@ class PumpEventProcessor:
|
||||
offset += 4
|
||||
value = ix_data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
elif arg["type"] == "publicKey":
|
||||
value = base64.b64encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||
elif arg["type"] == "pubkey":
|
||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||
offset += 32
|
||||
else:
|
||||
logger.warning(f"Unsupported type: {arg['type']}")
|
||||
@@ -166,3 +172,22 @@ class PumpEventProcessor:
|
||||
args["user"] = str(accounts[7])
|
||||
|
||||
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
|
||||
|
||||
@@ -5,8 +5,10 @@ Event processing for pump.fun tokens using Geyser data.
|
||||
import struct
|
||||
from typing import Final
|
||||
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from core.pubkeys import PumpAddresses
|
||||
from trading.base import TokenInfo
|
||||
from utils.logger import get_logger
|
||||
|
||||
@@ -55,6 +57,12 @@ class GeyserEventProcessor:
|
||||
offset += length
|
||||
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
|
||||
def get_account_key(index):
|
||||
if index >= len(accounts):
|
||||
@@ -67,11 +75,14 @@ class GeyserEventProcessor:
|
||||
name = read_string()
|
||||
symbol = read_string()
|
||||
uri = read_string()
|
||||
creator = read_pubkey()
|
||||
|
||||
mint = get_account_key(0)
|
||||
bonding_curve = get_account_key(2)
|
||||
associated_bonding_curve = get_account_key(3)
|
||||
user = get_account_key(7)
|
||||
|
||||
creator_vault = self._find_creator_vault(creator)
|
||||
|
||||
if not all([mint, bonding_curve, associated_bonding_curve, user]):
|
||||
logger.warning("Missing required account keys in token creation")
|
||||
@@ -85,8 +96,29 @@ class GeyserEventProcessor:
|
||||
bonding_curve=bonding_curve,
|
||||
associated_bonding_curve=associated_bonding_curve,
|
||||
user=user,
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process transaction data: {e}")
|
||||
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
|
||||
@@ -9,7 +9,7 @@ from typing import Final
|
||||
import base58
|
||||
from solders.pubkey import Pubkey
|
||||
|
||||
from core.pubkeys import SystemAddresses
|
||||
from core.pubkeys import PumpAddresses, SystemAddresses
|
||||
from trading.base import TokenInfo
|
||||
from utils.logger import get_logger
|
||||
|
||||
@@ -62,6 +62,8 @@ class LogsEventProcessor:
|
||||
associated_curve = self._find_associated_bonding_curve(
|
||||
mint, bonding_curve
|
||||
)
|
||||
creator = Pubkey.from_string(parsed_data["creator"])
|
||||
creator_vault = self._find_creator_vault(creator)
|
||||
|
||||
return TokenInfo(
|
||||
name=parsed_data["name"],
|
||||
@@ -71,6 +73,8 @@ class LogsEventProcessor:
|
||||
bonding_curve=bonding_curve,
|
||||
associated_bonding_curve=associated_curve,
|
||||
user=Pubkey.from_string(parsed_data["user"]),
|
||||
creator=creator,
|
||||
creator_vault=creator_vault,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process log data: {e}")
|
||||
@@ -106,6 +110,7 @@ class LogsEventProcessor:
|
||||
("mint", "publicKey"),
|
||||
("bondingCurve", "publicKey"),
|
||||
("user", "publicKey"),
|
||||
("creator", "publicKey"),
|
||||
]
|
||||
|
||||
try:
|
||||
@@ -149,3 +154,22 @@ class LogsEventProcessor:
|
||||
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
|
||||
)
|
||||
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
|
||||
|
||||
@@ -22,6 +22,8 @@ class TokenInfo:
|
||||
bonding_curve: Pubkey
|
||||
associated_bonding_curve: Pubkey
|
||||
user: Pubkey
|
||||
creator: Pubkey
|
||||
creator_vault: Pubkey
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> "TokenInfo":
|
||||
@@ -41,6 +43,8 @@ class TokenInfo:
|
||||
bonding_curve=Pubkey.from_string(data["bondingCurve"]),
|
||||
associated_bonding_curve=Pubkey.from_string(data["associatedBondingCurve"]),
|
||||
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]:
|
||||
@@ -57,6 +61,8 @@ class TokenInfo:
|
||||
"bondingCurve": str(self.bonding_curve),
|
||||
"associatedBondingCurve": str(self.associated_bonding_curve),
|
||||
"user": str(self.user),
|
||||
"creator": str(self.creator),
|
||||
"creatorVault": str(self.creator_vault),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ class TokenBuyer(Trader):
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.RENT, is_signer=False, is_writable=False
|
||||
pubkey=token_info.creator_vault, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PumpAddresses.EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
|
||||
@@ -128,7 +128,7 @@ class TokenSeller(Trader):
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
async def _send_sell_transaction(
|
||||
@@ -175,9 +175,7 @@ class TokenSeller(Trader):
|
||||
pubkey=SystemAddresses.PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
|
||||
is_signer=False,
|
||||
is_writable=False,
|
||||
pubkey=token_info.creator_vault, is_signer=False, is_writable=True,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
|
||||
@@ -209,5 +207,5 @@ class TokenSeller(Trader):
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Sell transaction failed: {str(e)}")
|
||||
logger.error(f"Sell transaction failed: {e!s}")
|
||||
raise
|
||||
|
||||
@@ -39,7 +39,8 @@ class TestTokenCallback:
|
||||
print(f"Symbol: {token_info.symbol}")
|
||||
print(f"Mint: {token_info.mint}")
|
||||
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"Associated Bonding Curve: {token_info.associated_bonding_curve}")
|
||||
print(f"{'=' * 50}\n")
|
||||
|
||||
@@ -39,7 +39,8 @@ class TestTokenCallback:
|
||||
print(f"Symbol: {token_info.symbol}")
|
||||
print(f"Mint: {token_info.mint}")
|
||||
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"Associated Bonding Curve: {token_info.associated_bonding_curve}")
|
||||
print(f"{'=' * 50}\n")
|
||||
|
||||
@@ -39,7 +39,8 @@ class TestTokenCallback:
|
||||
print(f"Symbol: {token_info.symbol}")
|
||||
print(f"Mint: {token_info.mint}")
|
||||
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"Associated Bonding Curve: {token_info.associated_bonding_curve}")
|
||||
print(f"{'=' * 50}\n")
|
||||
|
||||
Reference in New Issue
Block a user