mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 23:37:45 +00:00
fix: repair pumpswap buy tx
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
"""
|
||||
WORK IN PROGRESS
|
||||
Solana PUMP AMM Interface
|
||||
|
||||
This module provides functionality to:
|
||||
- Find market addresses by token mint.
|
||||
- Fetch and parse market data from PUMP AMM pools.
|
||||
- Calculate token prices in AMM pools.
|
||||
- Create associated token accounts (ATAs) idempotently.
|
||||
- Sell tokens on the PUMP AMM with slippage protection.
|
||||
This module provides functionality to interact with the PUMP AMM program on Solana, enabling:
|
||||
- Finding market addresses by token mint
|
||||
- Fetching and parsing market data from PUMP AMM pools
|
||||
- Calculating token prices in AMM pools
|
||||
- Creating associated token accounts (ATAs) idempotently
|
||||
- Buying tokens on the PUMP AMM with slippage protection
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -24,19 +24,22 @@ from solders.keypair import Keypair
|
||||
from solders.message import Message
|
||||
from solders.pubkey import Pubkey
|
||||
from solders.transaction import VersionedTransaction
|
||||
from spl.token.instructions import get_associated_token_address
|
||||
from spl.token.instructions import (
|
||||
create_idempotent_associated_token_account,
|
||||
get_associated_token_address,
|
||||
)
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Configuration constants
|
||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||
TOKEN_MINT = Pubkey.from_string("8XNrSusWrzYpizYXJb5yeB66AWX1cfQ86SBJFqVTpump")
|
||||
TOKEN_MINT = Pubkey.from_string("...")
|
||||
PRIVATE_KEY = base58.b58decode(os.environ.get("SOLANA_PRIVATE_KEY"))
|
||||
PAYER = Keypair.from_bytes(PRIVATE_KEY)
|
||||
SLIPPAGE = 0.25 # Slippage tolerance (25%) - the maximum price movement you'll accept
|
||||
SLIPPAGE = 0.3 # Slippage tolerance (30%) - the maximum price movement you'll accept
|
||||
|
||||
TOKEN_DECIMALS = 6
|
||||
BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea28") # Program instruction identifier for the sell function
|
||||
BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea") # Program instruction identifier for the buy function
|
||||
|
||||
# Solana system addresses and program IDs
|
||||
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
||||
@@ -56,7 +59,8 @@ 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:
|
||||
"""Find the market address for a given token mint.
|
||||
|
||||
Searches for the AMM pool that contains the specified token as its base token.
|
||||
Searches for the AMM pool that contains the specified token mint as its base token
|
||||
by querying program accounts with a filter for the base_mint field.
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
@@ -84,7 +88,8 @@ async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address
|
||||
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.
|
||||
Retrieves and deserializes the binary data stored in the market account
|
||||
into a structured dictionary containing key market information.
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
@@ -134,6 +139,20 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
return parsed_data
|
||||
|
||||
def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey:
|
||||
"""Derive the Program Derived Address (PDA) for a coin creator's vault.
|
||||
|
||||
Calculates the deterministic PDA that serves as the vault authority
|
||||
for a specific coin creator in the PUMP AMM protocol.
|
||||
|
||||
Args:
|
||||
coin_creator: Pubkey of the coin creator account
|
||||
|
||||
Returns:
|
||||
Pubkey of the derived coin creator vault authority
|
||||
|
||||
Note:
|
||||
This vault is used to collect creator fees from token transactions
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator_vault",
|
||||
@@ -144,9 +163,10 @@ def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey:
|
||||
return derived_address
|
||||
|
||||
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.
|
||||
"""Calculate the current price of tokens in an AMM pool.
|
||||
|
||||
Fetches the balance of tokens in the pool and calculates the price ratio.
|
||||
Fetches the balance of tokens in the pool and calculates the price ratio
|
||||
between the base token and quote token (typically SOL).
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
@@ -154,7 +174,7 @@ async def calculate_token_pool_price(client: AsyncClient, pool_base_token_accoun
|
||||
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)
|
||||
The price of the base token in terms of the quote token (SOL per token)
|
||||
"""
|
||||
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)
|
||||
@@ -167,53 +187,28 @@ async def calculate_token_pool_price(client: AsyncClient, pool_base_token_accoun
|
||||
|
||||
return token_price
|
||||
|
||||
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
|
||||
"""
|
||||
associated_token_address = get_associated_token_address(payer_pubkey, SOL)
|
||||
|
||||
instruction_accounts = [
|
||||
AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=associated_token_address, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=SOL, 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),
|
||||
]
|
||||
|
||||
# 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])
|
||||
return Instruction(SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts)
|
||||
|
||||
async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair,
|
||||
async def buy_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair,
|
||||
base_mint: Pubkey, user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey, pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey,
|
||||
coin_creator_vault_ata: Pubkey,slippage: float = 0.25) -> str | None:
|
||||
"""Sell tokens on the PUMP AMM.
|
||||
coin_creator_vault_ata: Pubkey, sol_amount_to_spend: int, slippage: float = 0.25) -> str | None:
|
||||
"""Buy tokens on the PUMP AMM with slippage protection.
|
||||
|
||||
This function sells all tokens in the user's token account with the specified slippage tolerance.
|
||||
Executes a token purchase on the PUMP AMM protocol, calculating the expected
|
||||
token amount based on the current pool price and applying slippage protection.
|
||||
|
||||
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
|
||||
payer: Keypair of the transaction signer and token buyer
|
||||
base_mint: Address of the token mint being purchased
|
||||
user_base_token_account: Address of the user's token account for receiving purchased tokens
|
||||
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_base_token_account: Address of the pool's token account for the token being purchased
|
||||
pool_quote_token_account: Address of the pool's SOL token account
|
||||
coin_creator_vault_authority: Address of the coin creator's vault authority
|
||||
coin_creator_vault_ata: Address of the coin creator's associated token account for fees
|
||||
sol_amount_to_spend: Amount of SOL to spend on the purchase (in SOL, not lamports)
|
||||
slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%)
|
||||
|
||||
Returns:
|
||||
@@ -221,18 +216,17 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
"""
|
||||
# Calculate token price
|
||||
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"Token price in SOL: {token_price_sol:.10f} SOL")
|
||||
|
||||
# Calculate minimum SOL output with slippage protection
|
||||
amount = token_balance
|
||||
min_sol_output = float(token_balance_decimal) * float(token_price_sol)
|
||||
slippage_factor = 1 - slippage
|
||||
min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL)
|
||||
# Calculate maximum SOL input with slippage protection
|
||||
base_amount_out = int((sol_amount_to_spend / token_price_sol) * 10**TOKEN_DECIMALS)
|
||||
slippage_factor = 1 + slippage
|
||||
max_sol_input = int((sol_amount_to_spend * slippage_factor) * LAMPORTS_PER_SOL)
|
||||
|
||||
print(f"Selling {token_balance_decimal} tokens")
|
||||
print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL")
|
||||
print(f"Buying {base_amount_out / (10**TOKEN_DECIMALS):.10f} tokens")
|
||||
print(f"Maximum SOL input: {max_sol_input / LAMPORTS_PER_SOL:.10f} SOL")
|
||||
|
||||
# Define all accounts needed for the sell instruction
|
||||
# Define all accounts needed for the buy instruction
|
||||
accounts = [
|
||||
AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||
@@ -255,34 +249,37 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
AccountMeta(pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False),
|
||||
]
|
||||
|
||||
data = BUY_DISCRIMINATOR + struct.pack("<Q", amount) + struct.pack("<Q", min_sol_output)
|
||||
data = BUY_DISCRIMINATOR + struct.pack("<Q", base_amount_out) + struct.pack("<Q", max_sol_input)
|
||||
|
||||
compute_limit_ix = set_compute_unit_limit(COMPUTE_UNIT_BUDGET)
|
||||
compute_price_ix = set_compute_unit_price(COMPUTE_UNIT_PRICE)
|
||||
|
||||
create_ata_ix = create_ata_idempotent_ix(
|
||||
payer_pubkey=payer.pubkey(),
|
||||
idempotent_ata_ix = create_idempotent_associated_token_account(
|
||||
payer.pubkey(),
|
||||
payer.pubkey(),
|
||||
base_mint,
|
||||
SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
sell_ix = Instruction(PUMP_AMM_PROGRAM_ID, data, accounts)
|
||||
buy_ix = Instruction(PUMP_AMM_PROGRAM_ID, data, accounts)
|
||||
|
||||
blockhash_resp = await client.get_latest_blockhash()
|
||||
recent_blockhash = blockhash_resp.value.blockhash
|
||||
|
||||
msg = Message.new_with_blockhash(
|
||||
[compute_limit_ix, compute_price_ix, create_ata_ix, sell_ix],
|
||||
[compute_limit_ix, compute_price_ix, idempotent_ata_ix, buy_ix],
|
||||
payer.pubkey(),
|
||||
recent_blockhash
|
||||
)
|
||||
|
||||
tx_sell = VersionedTransaction(
|
||||
tx_buy = VersionedTransaction(
|
||||
message=msg,
|
||||
keypairs=[payer]
|
||||
)
|
||||
|
||||
try:
|
||||
tx_sig = await client.send_transaction(
|
||||
tx_sell,
|
||||
tx_buy,
|
||||
opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed),
|
||||
)
|
||||
|
||||
@@ -298,14 +295,16 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main function to execute the token selling process."""
|
||||
"""Main function to execute the token buying process."""
|
||||
sol_amount_to_spend = 0.000001
|
||||
|
||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||
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)
|
||||
coin_creator_vault_authority = find_coin_creator_vault(Pubkey.from_string(market_data["coin_creator"]))
|
||||
coin_creator_vault_ata = get_associated_token_address(coin_creator_vault_authority, SOL)
|
||||
|
||||
await sell_pump_swap(
|
||||
await buy_pump_swap(
|
||||
client,
|
||||
market_address,
|
||||
PAYER,
|
||||
@@ -316,7 +315,8 @@ async def main():
|
||||
Pubkey.from_string(market_data["pool_quote_token_account"]),
|
||||
coin_creator_vault_authority,
|
||||
coin_creator_vault_ata,
|
||||
SLIPPAGE
|
||||
sol_amount_to_spend,
|
||||
SLIPPAGE,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -28,7 +28,7 @@ load_dotenv()
|
||||
|
||||
# Configuration constants
|
||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||
TOKEN_MINT = Pubkey.from_string("8XNrSusWrzYpizYXJb5yeB66AWX1cfQ86SBJFqVTpump")
|
||||
TOKEN_MINT = Pubkey.from_string("...")
|
||||
PRIVATE_KEY = base58.b58decode(os.environ.get("SOLANA_PRIVATE_KEY"))
|
||||
PAYER = Keypair.from_bytes(PRIVATE_KEY)
|
||||
SLIPPAGE = 0.25 # Slippage tolerance (25%) - the maximum price movement you'll accept
|
||||
@@ -132,6 +132,20 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
return parsed_data
|
||||
|
||||
def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey:
|
||||
"""Derive the Program Derived Address (PDA) for a coin creator's vault.
|
||||
|
||||
Calculates the deterministic PDA that serves as the vault authority
|
||||
for a specific coin creator in the PUMP AMM protocol.
|
||||
|
||||
Args:
|
||||
coin_creator: Pubkey of the coin creator account
|
||||
|
||||
Returns:
|
||||
Pubkey of the derived coin creator vault authority
|
||||
|
||||
Note:
|
||||
This vault is used to collect creator fees from token transactions
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator_vault",
|
||||
@@ -198,7 +212,7 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
base_mint: Pubkey, user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey, pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey,
|
||||
coin_creator_vault_ata: Pubkey,slippage: float = 0.25) -> str | None:
|
||||
coin_creator_vault_ata: 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.
|
||||
@@ -212,6 +226,8 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
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
|
||||
coin_creator_vault_authority: Address of the coin creator's vault authority
|
||||
coin_creator_vault_ata: Address of the coin creator's associated token account for fees
|
||||
slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%)
|
||||
|
||||
Returns:
|
||||
|
||||
Reference in New Issue
Block a user