mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-17 09:18:05 +00:00
fix: final fixes in instructions, docstrings, comments
This commit is contained in:
@@ -17,15 +17,17 @@ from spl.token.instructions import get_associated_token_address
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
# Configuration constants
|
||||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||||
TOKEN_MINT = Pubkey.from_string("35ySx7Rt3RqeTp75QB81FgRvPT5yDY2m5BupsUYDpump")
|
TOKEN_MINT = Pubkey.from_string("35ySx7Rt3RqeTp75QB81FgRvPT5yDY2m5BupsUYDpump")
|
||||||
PRIVATE_KEY = base58.b58decode(os.environ.get("SOLANA_PRIVATE_KEY"))
|
PRIVATE_KEY = base58.b58decode(os.environ.get("SOLANA_PRIVATE_KEY"))
|
||||||
PAYER = Keypair.from_bytes(PRIVATE_KEY)
|
PAYER = Keypair.from_bytes(PRIVATE_KEY)
|
||||||
SLIPPAGE = 0.25 # Slippage tolerance
|
SLIPPAGE = 0.25 # Slippage tolerance (25%) - the maximum price movement you'll accept
|
||||||
|
|
||||||
TOKEN_DECIMALS = 6
|
TOKEN_DECIMALS = 6
|
||||||
SELL_DISCRIMINATOR = struct.pack("<Q", 3739823480024040365)
|
SELL_DISCRIMINATOR = bytes.fromhex("33e685a4017f83ad") # Program instruction identifier for the sell function
|
||||||
|
|
||||||
|
# Solana system addresses and program IDs
|
||||||
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
||||||
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||||
PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw")
|
PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw")
|
||||||
@@ -36,13 +38,25 @@ SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
|||||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
||||||
PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR")
|
PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR")
|
||||||
LAMPORTS_PER_SOL = 1_000_000_000
|
LAMPORTS_PER_SOL = 1_000_000_000
|
||||||
COMPUTE_UNIT_PRICE = 10_000
|
COMPUTE_UNIT_PRICE = 10_000 # Price in micro-lamports per compute unit
|
||||||
COMPUTE_UNIT_BUDGET = 100_000
|
COMPUTE_UNIT_BUDGET = 100_000 # Maximum compute units to use
|
||||||
|
|
||||||
|
|
||||||
async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey) -> Pubkey:
|
async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey) -> Pubkey:
|
||||||
|
"""Find the market address for a given token mint.
|
||||||
|
|
||||||
|
Searches for the AMM pool that contains the specified token as its base token.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: Solana RPC client instance
|
||||||
|
base_mint_address: Address of the token mint you want to find the market for
|
||||||
|
amm_program_id: Address of the AMM program
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The Pubkey of the market (AMM pool) for the token
|
||||||
|
"""
|
||||||
base_mint_bytes = bytes(base_mint_address)
|
base_mint_bytes = bytes(base_mint_address)
|
||||||
offset = 43 # This should be calculated based on the fields before the base_mint field
|
offset = 43 # Offset where the base_mint field is stored in the account data structure
|
||||||
filters = [
|
filters = [
|
||||||
MemcmpOpts(offset=offset, bytes=base_mint_bytes)
|
MemcmpOpts(offset=offset, bytes=base_mint_bytes)
|
||||||
]
|
]
|
||||||
@@ -56,12 +70,25 @@ async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address
|
|||||||
market_address = [account.pubkey for account in response.value][0]
|
market_address = [account.pubkey for account in response.value][0]
|
||||||
return market_address
|
return market_address
|
||||||
|
|
||||||
async def get_market_data(client: AsyncClient, market_address: Pubkey):
|
async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||||
|
"""Fetch and parse market data from the blockchain.
|
||||||
|
|
||||||
|
Retrieves and deserializes the data stored in the market account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: Solana RPC client instance
|
||||||
|
market_address: Address of the market (AMM pool) to fetch data for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing the parsed market data
|
||||||
|
"""
|
||||||
response = await client.get_account_info_json_parsed(market_address)
|
response = await client.get_account_info_json_parsed(market_address)
|
||||||
data = response.value.data
|
data = response.value.data
|
||||||
parsed_data = {}
|
parsed_data: dict = {}
|
||||||
|
|
||||||
|
# Start after the 8-byte discriminator
|
||||||
offset = 8
|
offset = 8
|
||||||
|
# Define the structure of the market account data
|
||||||
fields = [
|
fields = [
|
||||||
("pool_bump", "u8"),
|
("pool_bump", "u8"),
|
||||||
("index", "u16"),
|
("index", "u16"),
|
||||||
@@ -95,49 +122,93 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey):
|
|||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float:
|
async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float:
|
||||||
|
"""Calculate the price of tokens in the pool.
|
||||||
|
|
||||||
|
Fetches the balance of tokens in the pool and calculates the price ratio.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: Solana RPC client instance
|
||||||
|
pool_base_token_account: Address of the pool's base token account (your token)
|
||||||
|
pool_quote_token_account: Address of the pool's quote token account (SOL)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The price of the base token in terms of the quote token (usually SOL)
|
||||||
|
"""
|
||||||
base_balance_resp = await client.get_token_account_balance(pool_base_token_account)
|
base_balance_resp = await client.get_token_account_balance(pool_base_token_account)
|
||||||
quote_balance_resp = await client.get_token_account_balance(pool_quote_token_account)
|
quote_balance_resp = await client.get_token_account_balance(pool_quote_token_account)
|
||||||
|
|
||||||
|
# Extract the UI amounts (human-readable with decimals)
|
||||||
base_amount = float(base_balance_resp.value.ui_amount)
|
base_amount = float(base_balance_resp.value.ui_amount)
|
||||||
quote_amount = float(quote_balance_resp.value.ui_amount)
|
quote_amount = float(quote_balance_resp.value.ui_amount)
|
||||||
|
|
||||||
token_price = quote_amount / base_amount
|
token_price = quote_amount / base_amount
|
||||||
|
|
||||||
return token_price
|
return token_price
|
||||||
|
|
||||||
def create_ata_idempotent_ix(payer_pubkey, owner_pubkey) -> Instruction:
|
def create_ata_idempotent_ix(payer_pubkey: Pubkey) -> Instruction:
|
||||||
|
"""Create an instruction to create an Associated Token Account (ATA) if it doesn't exist.
|
||||||
|
|
||||||
|
This creates an instruction that will create an Associated Token Account for SOL
|
||||||
|
if it doesn't already exist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
payer_pubkey: The public key of the account that will pay for the creation
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An instruction to create the ATA
|
||||||
"""
|
"""
|
||||||
Create an instruction to create an Associated Token Account for WSOL in an idempotent way.
|
associated_token_address = get_associated_token_address(payer_pubkey, SOL)
|
||||||
"""
|
|
||||||
associated_token_address = get_associated_token_address(owner_pubkey, SOL)
|
|
||||||
instruction_accounts = [
|
instruction_accounts = [
|
||||||
AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True),
|
AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True),
|
||||||
AccountMeta(pubkey=associated_token_address, is_signer=False, is_writable=True),
|
AccountMeta(pubkey=associated_token_address, is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=owner_pubkey, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True),
|
||||||
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# The data for creating an ATA idempotently is just a single byte with value 1
|
||||||
|
# Check the details here:
|
||||||
|
# https://github.com/solana-program/associated-token-account/blob/main/program/src/instruction.rs
|
||||||
data = bytes([1])
|
data = bytes([1])
|
||||||
return Instruction(SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts)
|
return Instruction(SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts)
|
||||||
|
|
||||||
async def sell_pump_swap(client: AsyncClient, pump_fun_amm_pool: Pubkey, payer: Keypair,
|
async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair,
|
||||||
base_mint: Pubkey, user_base_token_account: Pubkey,
|
base_mint: Pubkey, user_base_token_account: Pubkey,
|
||||||
user_quote_token_account: Pubkey, pool_base_token_account: Pubkey,
|
user_quote_token_account: Pubkey, pool_base_token_account: Pubkey,
|
||||||
pool_quote_token_account: Pubkey, slippage: float = 0.25):
|
pool_quote_token_account: Pubkey, slippage: float = 0.25) -> str | None:
|
||||||
|
"""Sell tokens on the PUMP AMM.
|
||||||
|
|
||||||
|
This function sells all tokens in the user's token account with the specified slippage tolerance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: Solana RPC client instance
|
||||||
|
pump_fun_amm_market: Address of the AMM market
|
||||||
|
payer: Keypair of the transaction signer and token seller
|
||||||
|
base_mint: Address of the token mint being sold
|
||||||
|
user_base_token_account: Address of the user's token account for the token being sold
|
||||||
|
user_quote_token_account: Address of the user's SOL token account
|
||||||
|
pool_base_token_account: Address of the pool's token account for the token being sold
|
||||||
|
pool_quote_token_account: Address of the pool's SOL token account
|
||||||
|
slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Transaction signature if successful, None otherwise
|
||||||
|
"""
|
||||||
# Get token balance
|
# Get token balance
|
||||||
token_balance = int((await client.get_token_account_balance(user_base_token_account)).value.amount)
|
token_balance = int((await client.get_token_account_balance(user_base_token_account)).value.amount)
|
||||||
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
|
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
|
||||||
print(f"Token balance: {token_balance_decimal}")
|
print(f"Token balance: {token_balance_decimal}")
|
||||||
if token_balance == 0:
|
if token_balance == 0:
|
||||||
print("No tokens to sell.")
|
print("No tokens to sell.")
|
||||||
return
|
return None
|
||||||
|
|
||||||
# Calculate token price
|
# Calculate token price
|
||||||
token_price_sol = await calculate_token_pool_price(client, pool_base_token_account, pool_quote_token_account)
|
token_price_sol = await calculate_token_pool_price(client, pool_base_token_account, pool_quote_token_account)
|
||||||
print(f"Price per Token: {token_price_sol:.20f} SOL")
|
print(f"Price per Token: {token_price_sol:.20f} SOL")
|
||||||
|
|
||||||
# Calculate minimum SOL output
|
# Calculate minimum SOL output with slippage protection
|
||||||
amount = token_balance
|
amount = token_balance
|
||||||
min_sol_output = float(token_balance_decimal) * float(token_price_sol)
|
min_sol_output = float(token_balance_decimal) * float(token_price_sol)
|
||||||
slippage_factor = 1 - slippage
|
slippage_factor = 1 - slippage
|
||||||
@@ -146,10 +217,11 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_pool: Pubkey, payer:
|
|||||||
print(f"Selling {token_balance_decimal} tokens")
|
print(f"Selling {token_balance_decimal} tokens")
|
||||||
print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL")
|
print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL")
|
||||||
|
|
||||||
|
# Define all accounts needed for the sell instruction
|
||||||
accounts = [
|
accounts = [
|
||||||
AccountMeta(pubkey=pump_fun_amm_pool, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||||
AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=True),
|
||||||
AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
||||||
AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True),
|
AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True),
|
||||||
@@ -173,7 +245,6 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_pool: Pubkey, payer:
|
|||||||
|
|
||||||
create_ata_ix = create_ata_idempotent_ix(
|
create_ata_ix = create_ata_idempotent_ix(
|
||||||
payer_pubkey=payer.pubkey(),
|
payer_pubkey=payer.pubkey(),
|
||||||
owner_pubkey=payer.pubkey()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sell_ix = Instruction(PUMP_AMM_PROGRAM_ID, data, accounts)
|
sell_ix = Instruction(PUMP_AMM_PROGRAM_ID, data, accounts)
|
||||||
@@ -210,13 +281,14 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_pool: Pubkey, payer:
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
"""Main function to execute the token selling process."""
|
||||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||||
market_address = await get_market_address_by_base_mint(client, TOKEN_MINT, PUMP_AMM_PROGRAM_ID)
|
market_address = await get_market_address_by_base_mint(client, TOKEN_MINT, PUMP_AMM_PROGRAM_ID)
|
||||||
market_data = await get_market_data(client, market_address)
|
market_data = await get_market_data(client, market_address)
|
||||||
|
|
||||||
await sell_pump_swap(
|
await sell_pump_swap(
|
||||||
client,
|
client,
|
||||||
PUMP_AMM_PROGRAM_ID,
|
market_address,
|
||||||
PAYER,
|
PAYER,
|
||||||
TOKEN_MINT,
|
TOKEN_MINT,
|
||||||
get_associated_token_address(PAYER.pubkey(), TOKEN_MINT),
|
get_associated_token_address(PAYER.pubkey(), TOKEN_MINT),
|
||||||
|
|||||||
Reference in New Issue
Block a user