mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-30 00:37:43 +00:00
feat(examples): add 18-account buy / 16-17-account sell for 2026-04-28 upgrade
pump.fun program upgrade scheduled for 2026-04-28 16:00 UTC adds one new
mutable account (one of 8 fee recipients) at the end of buy/sell ixs,
after bonding-curve-v2. The new format is already accepted on mainnet
ahead of the cutover, so applying now keeps scripts working on both
sides of the upgrade.
Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
Per file:
manual_buy.py — 18-account buy. Also fixes a pre-existing
listener crash on Address-Lookup-Table create
txs (skips them) and adds bool/OptionBool
handling to decode_create_instruction so
create_v2 doesn't raise on its trailing args.
manual_buy_cu_optimized.py — 18-account buy. Bumps the CU-optimization
account-data limit to 16MB (tested) — older
values trigger MaxLoadedAccountsDataSizeExceeded
on Token-2022/cashback coins. Same listener
ALT-skip and decoder bool fixes.
manual_buy_geyser.py — 18-account buy. Re-enables price calculation
and mayhem-mode-aware fee_recipient detection
(was scaffolded out for testing).
manual_sell.py — 16-account non-cashback / 17-account cashback
sell. Reads is_cashback_coin from BC byte 82
and inserts user_volume_accumulator before
bonding-curve-v2 when needed. TOKEN_MINT now
accepts argv[1] override.
Live-verified on mainnet:
buy manual_buy.py 3AVnLC3sdBD598cs3... + 3bWU9bB8U9aemA2BFhS2...
buy manual_buy_cu_optimized 2PfH5rHw62o3KkQ3N4z8...
sell manual_sell.py 3E7dyPBPBRe95BtPQXmb... + 5L4wWSzPV36m6XqXAswB...
All four show the expected 18 (buy) / 16 (sell non-cashback) account count
with one of the 8 BREAKING_FEE_RECIPIENTS as the trailing mutable account.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import base64
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
@@ -43,6 +44,20 @@ SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
|
|||||||
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
||||||
LAMPORTS_PER_SOL = 1_000_000_000
|
LAMPORTS_PER_SOL = 1_000_000_000
|
||||||
|
|
||||||
|
# 8 breaking-upgrade fee recipients (pump.fun program upgrade 2026-04-28).
|
||||||
|
# One must be appended (mutable) AFTER bonding-curve-v2 on every buy/sell.
|
||||||
|
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
|
||||||
|
BREAKING_FEE_RECIPIENTS = [
|
||||||
|
Pubkey.from_string("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||||
|
Pubkey.from_string("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||||
|
Pubkey.from_string("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||||
|
Pubkey.from_string("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||||
|
Pubkey.from_string("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||||
|
Pubkey.from_string("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||||
|
Pubkey.from_string("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||||
|
Pubkey.from_string("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||||
|
]
|
||||||
|
|
||||||
# RPC ENDPOINTS
|
# RPC ENDPOINTS
|
||||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||||
RPC_WEBSOCKET = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
|
RPC_WEBSOCKET = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
|
||||||
@@ -295,6 +310,12 @@ async def buy_token(
|
|||||||
is_signer=False,
|
is_signer=False,
|
||||||
is_writable=False,
|
is_writable=False,
|
||||||
),
|
),
|
||||||
|
# 18th account: breaking-upgrade fee recipient (mutable) — required from 2026-04-28
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=random.choice(BREAKING_FEE_RECIPIENTS),
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
discriminator = struct.pack("<Q", 16927863322537952870)
|
discriminator = struct.pack("<Q", 16927863322537952870)
|
||||||
@@ -359,16 +380,24 @@ def decode_create_instruction(ix_data, ix_def, accounts):
|
|||||||
offset = 8 # Skip 8-byte discriminator
|
offset = 8 # Skip 8-byte discriminator
|
||||||
|
|
||||||
for arg in ix_def["args"]:
|
for arg in ix_def["args"]:
|
||||||
if arg["type"] == "string":
|
t = arg["type"]
|
||||||
|
if t == "string":
|
||||||
length = struct.unpack_from("<I", ix_data, offset)[0]
|
length = struct.unpack_from("<I", ix_data, offset)[0]
|
||||||
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"] == "pubkey":
|
elif t == "pubkey":
|
||||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||||
offset += 32
|
offset += 32
|
||||||
|
elif t == "bool":
|
||||||
|
value = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
|
elif isinstance(t, dict) and "defined" in t:
|
||||||
|
# OptionBool = struct { bool } = 1 byte
|
||||||
|
value = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported type: {arg['type']}")
|
raise ValueError(f"Unsupported type: {t}")
|
||||||
|
|
||||||
args[arg["name"]] = value
|
args[arg["name"]] = value
|
||||||
|
|
||||||
@@ -457,12 +486,14 @@ async def listen_for_create_transaction():
|
|||||||
for instr in idl["instructions"]
|
for instr in idl["instructions"]
|
||||||
if instr["name"] == instruction_name
|
if instr["name"] == instruction_name
|
||||||
)
|
)
|
||||||
|
# Skip txs that use Address Lookup Tables — their
|
||||||
|
# instruction account indices reference ALT-loaded keys
|
||||||
|
# not present in transaction.message.account_keys.
|
||||||
|
static_keys = transaction.message.account_keys
|
||||||
|
if any(idx >= len(static_keys) for idx in ix.accounts):
|
||||||
|
continue
|
||||||
account_keys = [
|
account_keys = [
|
||||||
str(
|
str(static_keys[index])
|
||||||
transaction.message.account_keys[
|
|
||||||
index
|
|
||||||
]
|
|
||||||
)
|
|
||||||
for index in ix.accounts
|
for index in ix.accounts
|
||||||
]
|
]
|
||||||
decoded_args = (
|
decoded_args = (
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import base64
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
@@ -53,6 +54,20 @@ PUMP_EVENT_AUTHORITY = Pubkey.from_string(
|
|||||||
)
|
)
|
||||||
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
|
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
|
||||||
PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
|
PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
|
||||||
|
|
||||||
|
# 8 breaking-upgrade fee recipients (pump.fun program upgrade 2026-04-28).
|
||||||
|
# One must be appended (mutable) AFTER bonding-curve-v2 on every buy/sell.
|
||||||
|
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
|
||||||
|
BREAKING_FEE_RECIPIENTS = [
|
||||||
|
Pubkey.from_string("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||||
|
Pubkey.from_string("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||||
|
Pubkey.from_string("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||||
|
Pubkey.from_string("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||||
|
Pubkey.from_string("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||||
|
Pubkey.from_string("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||||
|
Pubkey.from_string("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||||
|
Pubkey.from_string("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||||
|
]
|
||||||
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
||||||
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||||
TOKEN_2022_PROGRAM = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
|
TOKEN_2022_PROGRAM = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
|
||||||
@@ -333,6 +348,12 @@ async def buy_token(
|
|||||||
is_signer=False,
|
is_signer=False,
|
||||||
is_writable=False,
|
is_writable=False,
|
||||||
),
|
),
|
||||||
|
# 18th account: breaking-upgrade fee recipient (mutable) — required from 2026-04-28
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=random.choice(BREAKING_FEE_RECIPIENTS),
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
discriminator = struct.pack("<Q", 16927863322537952870)
|
discriminator = struct.pack("<Q", 16927863322537952870)
|
||||||
@@ -350,7 +371,10 @@ async def buy_token(
|
|||||||
# CU OPTIMIZATION: Limit account data to 512KB (down from 64MB default)
|
# CU OPTIMIZATION: Limit account data to 512KB (down from 64MB default)
|
||||||
# This reduces CU cost from 16k to ~128 CU and improves tx priority.
|
# This reduces CU cost from 16k to ~128 CU and improves tx priority.
|
||||||
# Must be placed FIRST in the instruction list.
|
# Must be placed FIRST in the instruction list.
|
||||||
cu_limit_ix = set_loaded_accounts_data_size_limit(512_000)
|
# 16MB limit: works for cashback-coin Token-2022 buys on mainnet.
|
||||||
|
# Smaller values (4–8MB) trigger MaxLoadedAccountsDataSizeExceeded for
|
||||||
|
# Token-2022 mints with extensions. Still 4× smaller than the 64MB default.
|
||||||
|
cu_limit_ix = set_loaded_accounts_data_size_limit(16_384_000)
|
||||||
|
|
||||||
msg = Message(
|
msg = Message(
|
||||||
[cu_limit_ix, set_compute_unit_price(1_000), idempotent_ata_ix, buy_ix],
|
[cu_limit_ix, set_compute_unit_price(1_000), idempotent_ata_ix, buy_ix],
|
||||||
@@ -402,16 +426,21 @@ def decode_create_instruction(ix_data, ix_def, accounts):
|
|||||||
args = {}
|
args = {}
|
||||||
offset = 8
|
offset = 8
|
||||||
for arg in ix_def["args"]:
|
for arg in ix_def["args"]:
|
||||||
if arg["type"] == "string":
|
t = arg["type"]
|
||||||
|
if t == "string":
|
||||||
length = struct.unpack_from("<I", ix_data, offset)[0]
|
length = struct.unpack_from("<I", ix_data, offset)[0]
|
||||||
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"] == "pubkey":
|
elif t == "pubkey":
|
||||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||||
offset += 32
|
offset += 32
|
||||||
|
elif t == "bool" or (isinstance(t, dict) and "defined" in t):
|
||||||
|
# `bool` and `OptionBool` (struct {bool}) both serialize as 1 byte
|
||||||
|
value = bool(ix_data[offset])
|
||||||
|
offset += 1
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported type: {arg['type']}")
|
raise ValueError(f"Unsupported type: {t}")
|
||||||
args[arg["name"]] = value
|
args[arg["name"]] = value
|
||||||
|
|
||||||
args["mint"] = str(accounts[0])
|
args["mint"] = str(accounts[0])
|
||||||
@@ -499,6 +528,12 @@ async def listen_for_create_transaction():
|
|||||||
for instr in idl["instructions"]
|
for instr in idl["instructions"]
|
||||||
if instr["name"] == instruction_name
|
if instr["name"] == instruction_name
|
||||||
)
|
)
|
||||||
|
# Skip txs that use Address Lookup Tables — their
|
||||||
|
# instruction account indices reference ALT-loaded keys
|
||||||
|
# not present in transaction.message.account_keys.
|
||||||
|
static_keys = transaction.message.account_keys
|
||||||
|
if any(idx >= len(static_keys) for idx in ix.accounts):
|
||||||
|
continue
|
||||||
account_keys = [
|
account_keys = [
|
||||||
str(
|
str(
|
||||||
transaction.message.account_keys[
|
transaction.message.account_keys[
|
||||||
@@ -554,7 +589,7 @@ async def main():
|
|||||||
print(
|
print(
|
||||||
f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..."
|
f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..."
|
||||||
)
|
)
|
||||||
print("CU Optimization: Enabled (512KB account data limit)")
|
print("CU Optimization: Enabled (16MB account data limit)")
|
||||||
await buy_token(
|
await buy_token(
|
||||||
mint,
|
mint,
|
||||||
bonding_curve,
|
bonding_curve,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -39,6 +40,20 @@ PUMP_EVENT_AUTHORITY = Pubkey.from_string(
|
|||||||
)
|
)
|
||||||
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
|
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
|
||||||
PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
|
PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
|
||||||
|
|
||||||
|
# 8 breaking-upgrade fee recipients (pump.fun program upgrade 2026-04-28).
|
||||||
|
# One must be appended (mutable) AFTER bonding-curve-v2 on every buy/sell.
|
||||||
|
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
|
||||||
|
BREAKING_FEE_RECIPIENTS = [
|
||||||
|
Pubkey.from_string("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||||
|
Pubkey.from_string("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||||
|
Pubkey.from_string("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||||
|
Pubkey.from_string("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||||
|
Pubkey.from_string("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||||
|
Pubkey.from_string("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||||
|
Pubkey.from_string("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||||
|
Pubkey.from_string("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||||
|
]
|
||||||
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
||||||
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||||
SYSTEM_TOKEN_2022_PROGRAM = Pubkey.from_string(
|
SYSTEM_TOKEN_2022_PROGRAM = Pubkey.from_string(
|
||||||
@@ -365,19 +380,11 @@ async def buy_token(
|
|||||||
)
|
)
|
||||||
amount_lamports = int(amount * LAMPORTS_PER_SOL)
|
amount_lamports = int(amount * LAMPORTS_PER_SOL)
|
||||||
|
|
||||||
# Fetch bonding curve state to calculate price and determine fee recipient
|
# Fetch bonding curve state for price + mayhem-mode-aware fee recipient.
|
||||||
# NOTE: Price calculation is commented out to speed up testing - using fixed values
|
curve_state = await get_pump_curve_state(client, bonding_curve)
|
||||||
# For production, uncomment the lines below to:
|
token_price_sol = calculate_pump_curve_price(curve_state)
|
||||||
# 1. Calculate proper token amounts based on current price
|
token_amount = amount / token_price_sol
|
||||||
# 2. Detect mayhem mode and use correct fee recipient
|
fee_recipient = await get_fee_recipient(client, curve_state)
|
||||||
# curve_state = await get_pump_curve_state(client, bonding_curve)
|
|
||||||
# token_price_sol = calculate_pump_curve_price(curve_state)
|
|
||||||
# token_amount = amount / token_price_sol
|
|
||||||
# fee_recipient = await get_fee_recipient(client, curve_state)
|
|
||||||
|
|
||||||
# Testing values - replace with code above for production
|
|
||||||
token_amount = 100 # Fixed token amount
|
|
||||||
fee_recipient = PUMP_FEE # Standard fee recipient (doesn't detect mayhem mode)
|
|
||||||
|
|
||||||
# Calculate maximum SOL to spend with slippage
|
# Calculate maximum SOL to spend with slippage
|
||||||
max_amount_lamports = int(amount_lamports * (1 + slippage))
|
max_amount_lamports = int(amount_lamports * (1 + slippage))
|
||||||
@@ -433,6 +440,12 @@ async def buy_token(
|
|||||||
is_signer=False,
|
is_signer=False,
|
||||||
is_writable=False,
|
is_writable=False,
|
||||||
),
|
),
|
||||||
|
# 18th account: breaking-upgrade fee recipient (mutable) — required from 2026-04-28
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=random.choice(BREAKING_FEE_RECIPIENTS),
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
discriminator = struct.pack("<Q", 16927863322537952870)
|
discriminator = struct.pack("<Q", 16927863322537952870)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import struct
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
from construct import Flag, Int64ul, Struct
|
from construct import Flag, Int64ul, Struct
|
||||||
@@ -18,7 +20,7 @@ from spl.token.instructions import get_associated_token_address
|
|||||||
# Here and later all the discriminators are precalculated. See learning-examples/calculate_discriminator.py
|
# Here and later all the discriminators are precalculated. See learning-examples/calculate_discriminator.py
|
||||||
EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399)
|
EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399)
|
||||||
TOKEN_DECIMALS = 6
|
TOKEN_DECIMALS = 6
|
||||||
TOKEN_MINT = Pubkey.from_string("...") # Replace with actual token mint address
|
TOKEN_MINT = Pubkey.from_string(sys.argv[1] if len(sys.argv) > 1 else "...") # Pass mint as argv[1] or hardcode here
|
||||||
|
|
||||||
# Global constants
|
# Global constants
|
||||||
PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||||
@@ -42,6 +44,20 @@ UNIT_BUDGET = 100_000
|
|||||||
|
|
||||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||||
|
|
||||||
|
# 8 breaking-upgrade fee recipients (pump.fun program upgrade 2026-04-28).
|
||||||
|
# One must be appended (mutable) AFTER bonding-curve-v2 on every buy/sell.
|
||||||
|
# Doc: github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md
|
||||||
|
BREAKING_FEE_RECIPIENTS = [
|
||||||
|
Pubkey.from_string("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||||
|
Pubkey.from_string("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||||
|
Pubkey.from_string("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||||
|
Pubkey.from_string("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||||
|
Pubkey.from_string("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||||
|
Pubkey.from_string("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||||
|
Pubkey.from_string("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||||
|
Pubkey.from_string("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class BondingCurveState:
|
class BondingCurveState:
|
||||||
"""Bonding curve state parser with progressive field parsing.
|
"""Bonding curve state parser with progressive field parsing.
|
||||||
@@ -95,9 +111,16 @@ class BondingCurveState:
|
|||||||
# Parse mayhem mode flag if bytes remaining (added in V3)
|
# Parse mayhem mode flag if bytes remaining (added in V3)
|
||||||
if len(data) >= offset + 1:
|
if len(data) >= offset + 1:
|
||||||
self.is_mayhem_mode = bool(data[offset])
|
self.is_mayhem_mode = bool(data[offset])
|
||||||
|
offset += 1
|
||||||
else:
|
else:
|
||||||
self.is_mayhem_mode = False
|
self.is_mayhem_mode = False
|
||||||
|
|
||||||
|
# Parse cashback flag if bytes remaining (added in V4 — late-Feb 2026 cashback upgrade)
|
||||||
|
if len(data) >= offset + 1:
|
||||||
|
self.is_cashback_coin = bool(data[offset])
|
||||||
|
else:
|
||||||
|
self.is_cashback_coin = False
|
||||||
|
|
||||||
|
|
||||||
async def get_pump_curve_state(
|
async def get_pump_curve_state(
|
||||||
conn: AsyncClient, curve_address: Pubkey
|
conn: AsyncClient, curve_address: Pubkey
|
||||||
@@ -155,6 +178,14 @@ def _find_bonding_curve_v2(mint: Pubkey) -> Pubkey:
|
|||||||
return derived_address
|
return derived_address
|
||||||
|
|
||||||
|
|
||||||
|
def _find_user_volume_accumulator(user: Pubkey) -> Pubkey:
|
||||||
|
derived_address, _ = Pubkey.find_program_address(
|
||||||
|
[b"user_volume_accumulator", bytes(user)],
|
||||||
|
PUMP_PROGRAM,
|
||||||
|
)
|
||||||
|
return derived_address
|
||||||
|
|
||||||
|
|
||||||
async def get_fee_recipient(
|
async def get_fee_recipient(
|
||||||
client: AsyncClient, curve_state: BondingCurveState
|
client: AsyncClient, curve_state: BondingCurveState
|
||||||
) -> Pubkey:
|
) -> Pubkey:
|
||||||
@@ -320,13 +351,31 @@ async def sell_token(
|
|||||||
is_signer=False,
|
is_signer=False,
|
||||||
is_writable=False,
|
is_writable=False,
|
||||||
),
|
),
|
||||||
# Remaining account: bonding_curve_v2 (readonly, required for all coins)
|
]
|
||||||
|
# For cashback coins, insert user_volume_accumulator before bonding-curve-v2 (17 accounts total).
|
||||||
|
if curve_state.is_cashback_coin:
|
||||||
|
accounts.append(
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=_find_user_volume_accumulator(payer.pubkey()),
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
accounts.extend([
|
||||||
|
# bonding_curve_v2 (readonly, required for all coins)
|
||||||
AccountMeta(
|
AccountMeta(
|
||||||
pubkey=_find_bonding_curve_v2(mint),
|
pubkey=_find_bonding_curve_v2(mint),
|
||||||
is_signer=False,
|
is_signer=False,
|
||||||
is_writable=False,
|
is_writable=False,
|
||||||
),
|
),
|
||||||
]
|
# Breaking-upgrade fee recipient (mutable) — required from 2026-04-28.
|
||||||
|
# 16 accounts non-cashback / 17 accounts cashback.
|
||||||
|
AccountMeta(
|
||||||
|
pubkey=random.choice(BREAKING_FEE_RECIPIENTS),
|
||||||
|
is_signer=False,
|
||||||
|
is_writable=True,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
discriminator = struct.pack("<Q", 12502976635542562355)
|
discriminator = struct.pack("<Q", 12502976635542562355)
|
||||||
# Encode OptionBool for track_volume: [1, 1] = Some(true)
|
# Encode OptionBool for track_volume: [1, 1] = Some(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user