fixed formatting

This commit is contained in:
smypmsa
2025-03-05 07:03:32 +00:00
parent faba3d8306
commit 8bf3700187
24 changed files with 1260 additions and 707 deletions
+3
View File
@@ -0,0 +1,3 @@
RPC_ENDPOINT=...
WSS_ENDPOINT=...
PRIVATE_KEY=...
+3
View File
@@ -1,3 +1,6 @@
.pylintrc
.ruff_cache
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
+12
View File
@@ -0,0 +1,12 @@
{
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "basic",
"ruff.lint.enable": true,
"ruff.format.enable": true,
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
+163 -85
View File
@@ -1,34 +1,32 @@
import asyncio import asyncio
import json
import base64 import base64
import struct
import base58
import hashlib import hashlib
import websockets import json
import struct
import time import time
import base58
import spl.token.instructions as spl_token
import websockets
from construct import Flag, Int64ul, Struct
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solana.transaction import Transaction
from solana.rpc.commitment import Confirmed from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Transaction
from solders.pubkey import Pubkey from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair from solders.keypair import Keypair
from solders.instruction import Instruction, AccountMeta from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer from solders.system_program import TransferParams, transfer
from solders.transaction import VersionedTransaction from solders.transaction import VersionedTransaction
from spl.token.instructions import get_associated_token_address from spl.token.instructions import get_associated_token_address
import spl.token.instructions as spl_token
from config import * from config import *
from construct import Struct, Int64ul, Flag
# Here and later all the discriminators are precalculated. See learning-examples/discriminator.py # Here and later all the discriminators are precalculated. See learning-examples/discriminator.py
EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399)
TOKEN_DECIMALS = 6 TOKEN_DECIMALS = 6
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -36,14 +34,17 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> BondingCurveState:
async def get_pump_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
response = await conn.get_account_info(curve_address) response = await conn.get_account_info(curve_address)
if not response.value or not response.value.data: if not response.value or not response.value.data:
raise ValueError("Invalid curve state: No data") raise ValueError("Invalid curve state: No data")
@@ -54,13 +55,24 @@ async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> Bond
return BondingCurveState(data) return BondingCurveState(data)
def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: def calculate_pump_curve_price(curve_state: BondingCurveState) -> float:
if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0:
raise ValueError("Invalid reserve state") raise ValueError("Invalid reserve state")
return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (curve_state.virtual_token_reserves / 10 ** TOKEN_DECIMALS) return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (
curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS
)
async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curve: Pubkey, amount: float, slippage: float = 0.01, max_retries=5):
async def buy_token(
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
amount: float,
slippage: float = 0.01,
max_retries=5,
):
private_key = base58.b58decode(PRIVATE_KEY) private_key = base58.b58decode(PRIVATE_KEY)
payer = Keypair.from_bytes(private_key) payer = Keypair.from_bytes(private_key)
@@ -81,11 +93,11 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
try: try:
account_info = await client.get_account_info(associated_token_account) account_info = await client.get_account_info(associated_token_account)
if account_info.value is None: if account_info.value is None:
print(f"Creating associated token account (Attempt {ata_attempt + 1})...") print(
f"Creating associated token account (Attempt {ata_attempt + 1})..."
)
create_ata_ix = spl_token.create_associated_token_account( create_ata_ix = spl_token.create_associated_token_account(
payer=payer.pubkey(), payer=payer.pubkey(), owner=payer.pubkey(), mint=mint
owner=payer.pubkey(),
mint=mint
) )
create_ata_tx = Transaction() create_ata_tx = Transaction()
create_ata_tx.add(create_ata_ix) create_ata_tx.add(create_ata_ix)
@@ -93,20 +105,28 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
create_ata_tx.recent_blockhash = recent_blockhash.value.blockhash create_ata_tx.recent_blockhash = recent_blockhash.value.blockhash
await client.send_transaction(create_ata_tx, payer) await client.send_transaction(create_ata_tx, payer)
print("Associated token account created.") print("Associated token account created.")
print(f"Associated token account address: {associated_token_account}") print(
f"Associated token account address: {associated_token_account}"
)
break break
else: else:
print("Associated token account already exists.") print("Associated token account already exists.")
print(f"Associated token account address: {associated_token_account}") print(
f"Associated token account address: {associated_token_account}"
)
break break
except Exception as e: except Exception as e:
print(f"Attempt {ata_attempt + 1} to create associated token account failed: {str(e)}") print(
f"Attempt {ata_attempt + 1} to create associated token account failed: {str(e)}"
)
if ata_attempt < max_retries - 1: if ata_attempt < max_retries - 1:
wait_time = 2 ** ata_attempt wait_time = 2**ata_attempt
print(f"Retrying in {wait_time} seconds...") print(f"Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
print("Max retries reached. Unable to create associated token account.") print(
"Max retries reached. Unable to create associated token account."
)
return return
# Continue with the buy transaction # Continue with the buy transaction
@@ -116,19 +136,43 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False), AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True),
AccountMeta(pubkey=mint, is_signer=False, is_writable=False), AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), AccountMeta(
AccountMeta(pubkey=associated_bonding_curve, is_signer=False, is_writable=True), pubkey=bonding_curve, is_signer=False, is_writable=True
AccountMeta(pubkey=associated_token_account, is_signer=False, is_writable=True), ),
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True), AccountMeta(
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), pubkey=associated_bonding_curve,
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=associated_token_account,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=payer.pubkey(), is_signer=True, is_writable=True
),
AccountMeta(
pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(pubkey=SYSTEM_RENT, is_signer=False, is_writable=False), AccountMeta(pubkey=SYSTEM_RENT, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False), AccountMeta(
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False), pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False
),
] ]
discriminator = struct.pack("<Q", 16927863322537952870) discriminator = struct.pack("<Q", 16927863322537952870)
data = discriminator + struct.pack("<Q", int(token_amount * 10**6)) + struct.pack("<Q", max_amount_lamports) data = (
discriminator
+ struct.pack("<Q", int(token_amount * 10**6))
+ struct.pack("<Q", max_amount_lamports)
)
buy_ix = Instruction(PUMP_PROGRAM, data, accounts) buy_ix = Instruction(PUMP_PROGRAM, data, accounts)
recent_blockhash = await client.get_latest_blockhash() recent_blockhash = await client.get_latest_blockhash()
@@ -151,61 +195,66 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
except Exception as e: except Exception as e:
print(f"Attempt {attempt + 1} failed: {str(e)}") print(f"Attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
wait_time = 2 ** attempt wait_time = 2**attempt
print(f"Retrying in {wait_time} seconds...") print(f"Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
print("Max retries reached. Unable to complete the transaction.") print("Max retries reached. Unable to complete the transaction.")
def load_idl(file_path): def load_idl(file_path):
with open(file_path, 'r') as f: with open(file_path, "r") as f:
return json.load(f) return json.load(f)
def decode_create_instruction(ix_data, ix_def, accounts): def decode_create_instruction(ix_data, ix_def, accounts):
args = {} args = {}
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': if arg["type"] == "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'] == 'publicKey': elif arg["type"] == "publicKey":
value = base64.b64encode(ix_data[offset:offset+32]).decode('utf-8') value = base64.b64encode(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']}")
args[arg['name']] = value args[arg["name"]] = value
# Add accounts # Add accounts
args['mint'] = str(accounts[0]) args["mint"] = str(accounts[0])
args['bondingCurve'] = str(accounts[2]) args["bondingCurve"] = str(accounts[2])
args['associatedBondingCurve'] = str(accounts[3]) args["associatedBondingCurve"] = str(accounts[3])
args['user'] = str(accounts[7]) args["user"] = str(accounts[7])
return args return args
async def listen_for_create_transaction(websocket): async def listen_for_create_transaction(websocket):
idl = load_idl('idl/pump_fun_idl.json') idl = load_idl("idl/pump_fun_idl.json")
create_discriminator = 8576854823835016728 create_discriminator = 8576854823835016728
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "blockSubscribe", "id": 1,
"params": [ "method": "blockSubscribe",
{"mentionsAccountOrProgram": str(PUMP_PROGRAM)}, "params": [
{ {"mentionsAccountOrProgram": str(PUMP_PROGRAM)},
"commitment": "confirmed", {
"encoding": "base64", "commitment": "confirmed",
"showRewards": False, "encoding": "base64",
"transactionDetails": "full", "showRewards": False,
"maxSupportedTransactionVersion": 0 "transactionDetails": "full",
} "maxSupportedTransactionVersion": 0,
] },
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}") print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}")
@@ -221,27 +270,52 @@ async def listen_for_create_transaction(websocket):
response = await asyncio.wait_for(websocket.recv(), timeout=30) response = await asyncio.wait_for(websocket.recv(), timeout=30)
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'blockNotification': if "method" in data and data["method"] == "blockNotification":
if 'params' in data and 'result' in data['params']: if "params" in data and "result" in data["params"]:
block_data = data['params']['result'] block_data = data["params"]["result"]
if 'value' in block_data and 'block' in block_data['value']: if "value" in block_data and "block" in block_data["value"]:
block = block_data['value']['block'] block = block_data["value"]["block"]
if 'transactions' in block: if "transactions" in block:
for tx in block['transactions']: for tx in block["transactions"]:
if isinstance(tx, dict) and 'transaction' in tx: if isinstance(tx, dict) and "transaction" in tx:
tx_data_decoded = base64.b64decode(tx['transaction'][0]) tx_data_decoded = base64.b64decode(
transaction = VersionedTransaction.from_bytes(tx_data_decoded) tx["transaction"][0]
)
transaction = VersionedTransaction.from_bytes(
tx_data_decoded
)
for ix in transaction.message.instructions: for ix in transaction.message.instructions:
if str(transaction.message.account_keys[ix.program_id_index]) == str(PUMP_PROGRAM): if str(
transaction.message.account_keys[
ix.program_id_index
]
) == str(PUMP_PROGRAM):
ix_data = bytes(ix.data) ix_data = bytes(ix.data)
discriminator = struct.unpack('<Q', ix_data[:8])[0] discriminator = struct.unpack(
"<Q", ix_data[:8]
)[0]
if discriminator == create_discriminator: if discriminator == create_discriminator:
create_ix = next(instr for instr in idl['instructions'] if instr['name'] == 'create') create_ix = next(
account_keys = [str(transaction.message.account_keys[index]) for index in ix.accounts] instr
decoded_args = decode_create_instruction(ix_data, create_ix, account_keys) for instr in idl["instructions"]
if instr["name"] == "create"
)
account_keys = [
str(
transaction.message.account_keys[
index
]
)
for index in ix.accounts
]
decoded_args = (
decode_create_instruction(
ix_data, create_ix, account_keys
)
)
return decoded_args return decoded_args
except asyncio.TimeoutError: except asyncio.TimeoutError:
print("No data received for 30 seconds, sending ping...") print("No data received for 30 seconds, sending ping...")
@@ -251,6 +325,7 @@ async def listen_for_create_transaction(websocket):
print("WebSocket connection closed. Reconnecting...") print("WebSocket connection closed. Reconnecting...")
raise raise
async def main(yolo_mode=False): async def main(yolo_mode=False):
if yolo_mode: if yolo_mode:
while True: while True:
@@ -264,7 +339,9 @@ async def main(yolo_mode=False):
break break
except Exception as e: except Exception as e:
print(f"An error occurred: {e}") print(f"An error occurred: {e}")
print("Waiting for 5 seconds before looking for the next token...") print(
"Waiting for 5 seconds before looking for the next token..."
)
await asyncio.sleep(5) await asyncio.sleep(5)
except Exception as e: except Exception as e:
print(f"Connection error: {e}") print(f"Connection error: {e}")
@@ -273,5 +350,6 @@ async def main(yolo_mode=False):
else: else:
await trade() await trade()
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
+23 -7
View File
@@ -1,14 +1,25 @@
import os
from dotenv import load_dotenv
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
load_dotenv()
# System & pump.fun addresses # System & pump.fun addresses
PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
PUMP_EVENT_AUTHORITY = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1") PUMP_EVENT_AUTHORITY = Pubkey.from_string(
"Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"
)
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
PUMP_LIQUIDITY_MIGRATOR = Pubkey.from_string("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg") PUMP_LIQUIDITY_MIGRATOR = Pubkey.from_string(
"39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg"
)
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_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
)
SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111") SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111")
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
LAMPORTS_PER_SOL = 1_000_000_000 LAMPORTS_PER_SOL = 1_000_000_000
@@ -17,11 +28,16 @@ LAMPORTS_PER_SOL = 1_000_000_000
BUY_AMOUNT = 0.0001 # Amount of SOL to spend when buying BUY_AMOUNT = 0.0001 # Amount of SOL to spend when buying
BUY_SLIPPAGE = 0.2 # 20% slippage tolerance for buying BUY_SLIPPAGE = 0.2 # 20% slippage tolerance for buying
SELL_SLIPPAGE = 0.2 # 20% slippage tolerance for selling SELL_SLIPPAGE = 0.2 # 20% slippage tolerance for selling
ENABLE_DYNAMIC_PRIORITY_FEE = (
True # getRecentPriorityFee is used to get current priority fee
)
EXTRA_PRIORITY_FEE = 0.1 # 10% increase in dynamic priority fee
# Your nodes # Your nodes
# You can also get a trader node https://docs.chainstack.com/docs/solana-trader-nodes # You can also get a trader node https://docs.chainstack.com/docs/solana-trader-nodes
RPC_ENDPOINT = "SOLANA_NODE_RPC_ENDPOINT" RPC_ENDPOINT = os.getenv("RPC_ENDPOINT")
WSS_ENDPOINT = "SOLANA_NODE_WSS_ENDPOINT" WSS_ENDPOINT = os.getenv("WSS_ENDPOINT")
MAX_RPS = 25 # RPS of your node to avoid 429 errors
#Private key # Private key
PRIVATE_KEY = "SOLANA_PRIVATE_KEY" PRIVATE_KEY = os.getenv("PRIVATE_KEY")
@@ -3,36 +3,41 @@ import hashlib
import json import json
import os import os
import sys import sys
import websockets import websockets
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import WSS_ENDPOINT, PUMP_PROGRAM from config import PUMP_PROGRAM, WSS_ENDPOINT
async def save_transaction(tx_data, tx_signature): async def save_transaction(tx_data, tx_signature):
os.makedirs("blockSubscribe-transactions", exist_ok=True) os.makedirs("blockSubscribe-transactions", exist_ok=True)
hashed_signature = hashlib.sha256(tx_signature.encode()).hexdigest() hashed_signature = hashlib.sha256(tx_signature.encode()).hexdigest()
file_path = os.path.join("blockSubscribe-transactions", f"{hashed_signature}.json") file_path = os.path.join("blockSubscribe-transactions", f"{hashed_signature}.json")
with open(file_path, 'w') as f: with open(file_path, "w") as f:
json.dump(tx_data, f, indent=2) json.dump(tx_data, f, indent=2)
print(f"Saved transaction: {hashed_signature[:8]}...") print(f"Saved transaction: {hashed_signature[:8]}...")
async def listen_for_transactions(): async def listen_for_transactions():
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "blockSubscribe", "id": 1,
"params": [ "method": "blockSubscribe",
{"mentionsAccountOrProgram": str(PUMP_PROGRAM)}, "params": [
{ {"mentionsAccountOrProgram": str(PUMP_PROGRAM)},
"commitment": "confirmed", {
"encoding": "base64", "commitment": "confirmed",
"showRewards": False, "encoding": "base64",
"transactionDetails": "full", "showRewards": False,
"maxSupportedTransactionVersion": 0 "transactionDetails": "full",
} "maxSupportedTransactionVersion": 0,
] },
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}") print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}")
@@ -40,27 +45,36 @@ async def listen_for_transactions():
try: try:
response = await websocket.recv() response = await websocket.recv()
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'blockNotification': if "method" in data and data["method"] == "blockNotification":
if 'params' in data and 'result' in data['params']: if "params" in data and "result" in data["params"]:
block_data = data['params']['result'] block_data = data["params"]["result"]
if 'value' in block_data and 'block' in block_data['value']: if "value" in block_data and "block" in block_data["value"]:
block = block_data['value']['block'] block = block_data["value"]["block"]
if 'transactions' in block: if "transactions" in block:
transactions = block['transactions'] transactions = block["transactions"]
for tx in transactions: for tx in transactions:
if isinstance(tx, dict) and 'transaction' in tx: if isinstance(tx, dict) and "transaction" in tx:
if isinstance(tx['transaction'], list) and len(tx['transaction']) > 0: if (
tx_signature = tx['transaction'][0] isinstance(tx["transaction"], list)
elif isinstance(tx['transaction'], dict) and 'signatures' in tx['transaction']: and len(tx["transaction"]) > 0
tx_signature = tx['transaction']['signatures'][0] ):
tx_signature = tx["transaction"][0]
elif (
isinstance(tx["transaction"], dict)
and "signatures" in tx["transaction"]
):
tx_signature = tx["transaction"][
"signatures"
][0]
else: else:
continue continue
await save_transaction(tx, tx_signature) await save_transaction(tx, tx_signature)
elif 'result' in data: elif "result" in data:
print(f"Subscription confirmed") print(f"Subscription confirmed")
except Exception as e: except Exception as e:
print(f"An error occurred: {str(e)}") print(f"An error occurred: {str(e)}")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(listen_for_transactions()) asyncio.run(listen_for_transactions())
+9 -7
View File
@@ -5,21 +5,23 @@ import struct
# Set the instruction name here # Set the instruction name here
instruction_name = "account:BondingCurve" instruction_name = "account:BondingCurve"
def calculate_discriminator(instruction_name): def calculate_discriminator(instruction_name):
# Create a SHA256 hash object # Create a SHA256 hash object
sha = hashlib.sha256() sha = hashlib.sha256()
# Update the hash with the instruction name # Update the hash with the instruction name
sha.update(instruction_name.encode('utf-8')) sha.update(instruction_name.encode("utf-8"))
# Get the first 8 bytes of the hash # Get the first 8 bytes of the hash
discriminator_bytes = sha.digest()[:8] discriminator_bytes = sha.digest()[:8]
# Convert the bytes to a 64-bit unsigned integer (little-endian) # Convert the bytes to a 64-bit unsigned integer (little-endian)
discriminator = struct.unpack('<Q', discriminator_bytes)[0] discriminator = struct.unpack("<Q", discriminator_bytes)[0]
return discriminator return discriminator
# Calculate the discriminator for the specified instruction # Calculate the discriminator for the specified instruction
discriminator = calculate_discriminator(instruction_name) discriminator = calculate_discriminator(instruction_name)
@@ -28,4 +30,4 @@ print(f"Discriminator for '{instruction_name}' instruction: {discriminator}")
# global:buy discriminator - 16927863322537952870 # global:buy discriminator - 16927863322537952870
# global:sell discriminator - 12502976635542562355 # global:sell discriminator - 12502976635542562355
# global:create discriminator - 8576854823835016728 # global:create discriminator - 8576854823835016728
# account:BondingCurve discriminator - 6966180631402821399 # account:BondingCurve discriminator - 6966180631402821399
+43 -30
View File
@@ -1,19 +1,21 @@
import argparse
import asyncio import asyncio
import os
import struct import struct
import sys
from typing import Final from typing import Final
from construct import Struct, Int64ul, Flag
from construct import Flag, Int64ul, Struct
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
import argparse
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import RPC_ENDPOINT, PUMP_PROGRAM from config import PUMP_PROGRAM, RPC_ENDPOINT
# Constants # Constants
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399)
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -21,26 +23,26 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
def get_associated_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]:
def get_associated_bonding_curve_address(
mint: Pubkey, program_id: Pubkey
) -> tuple[Pubkey, int]:
""" """
Derives the associated bonding curve address for a given mint Derives the associated bonding curve address for a given mint
""" """
return Pubkey.find_program_address( return Pubkey.find_program_address([b"bonding-curve", bytes(mint)], program_id)
[
b"bonding-curve",
bytes(mint)
],
program_id
)
async def get_bonding_curve_state(conn: AsyncClient, curve_address: Pubkey) -> BondingCurveState:
async def get_bonding_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
response = await conn.get_account_info(curve_address) response = await conn.get_account_info(curve_address)
if not response.value or not response.value.data: if not response.value or not response.value.data:
raise ValueError("Invalid curve state: No data") raise ValueError("Invalid curve state: No data")
@@ -51,46 +53,57 @@ async def get_bonding_curve_state(conn: AsyncClient, curve_address: Pubkey) -> B
return BondingCurveState(data) return BondingCurveState(data)
async def check_token_status(mint_address: str) -> None: async def check_token_status(mint_address: str) -> None:
try: try:
mint = Pubkey.from_string(mint_address) mint = Pubkey.from_string(mint_address)
# Get the associated bonding curve address # Get the associated bonding curve address
bonding_curve_address, bump = get_associated_bonding_curve_address(mint, PUMP_PROGRAM) bonding_curve_address, bump = get_associated_bonding_curve_address(
mint, PUMP_PROGRAM
)
print("\nToken Status:") print("\nToken Status:")
print("-" * 50) print("-" * 50)
print(f"Token Mint: {mint}") print(f"Token Mint: {mint}")
print(f"Associated Bonding Curve: {bonding_curve_address}") print(f"Associated Bonding Curve: {bonding_curve_address}")
print(f"Bump Seed: {bump}") print(f"Bump Seed: {bump}")
print("-" * 50) print("-" * 50)
# Check completion status # Check completion status
async with AsyncClient(RPC_ENDPOINT) as client: async with AsyncClient(RPC_ENDPOINT) as client:
try: try:
curve_state = await get_bonding_curve_state(client, bonding_curve_address) curve_state = await get_bonding_curve_state(
client, bonding_curve_address
)
print("\nBonding Curve Status:") print("\nBonding Curve Status:")
print("-" * 50) print("-" * 50)
print(f"Completion Status: {'Completed' if curve_state.complete else 'Not Completed'}") print(
f"Completion Status: {'Completed' if curve_state.complete else 'Not Completed'}"
)
if curve_state.complete: if curve_state.complete:
print("\nNote: This bonding curve has completed and liquidity has been migrated to Raydium.") print(
"\nNote: This bonding curve has completed and liquidity has been migrated to Raydium."
)
print("-" * 50) print("-" * 50)
except ValueError as e: except ValueError as e:
print(f"\nError accessing bonding curve: {e}") print(f"\nError accessing bonding curve: {e}")
except ValueError as e: except ValueError as e:
print(f"\nError: Invalid address format - {e}") print(f"\nError: Invalid address format - {e}")
except Exception as e: except Exception as e:
print(f"\nUnexpected error: {e}") print(f"\nUnexpected error: {e}")
def main(): def main():
parser = argparse.ArgumentParser(description='Check token bonding curve status') parser = argparse.ArgumentParser(description="Check token bonding curve status")
parser.add_argument('mint_address', help='The token mint address') parser.add_argument("mint_address", help="The token mint address")
args = parser.parse_args() args = parser.parse_args()
asyncio.run(check_token_status(args.mint_address)) asyncio.run(check_token_status(args.mint_address))
if __name__ == "__main__": if __name__ == "__main__":
main() main()
@@ -1,52 +1,51 @@
import sys
import os import os
import sys
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import PUMP_PROGRAM from config import PUMP_PROGRAM
def get_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]: def get_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]:
""" """
Derives the bonding curve address for a given mint Derives the bonding curve address for a given mint
""" """
return Pubkey.find_program_address( return Pubkey.find_program_address([b"bonding-curve", bytes(mint)], program_id)
[
b"bonding-curve",
bytes(mint)
],
program_id
)
def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey: def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey:
""" """
Find the associated bonding curve for a given mint and bonding curve. Find the associated bonding curve for a given mint and bonding curve.
This uses the standard ATA derivation. This uses the standard ATA derivation.
""" """
from config import SYSTEM_TOKEN_PROGRAM as TOKEN_PROGRAM_ID
from config import SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM as ATA_PROGRAM_ID from config import SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM as ATA_PROGRAM_ID
from config import SYSTEM_TOKEN_PROGRAM as TOKEN_PROGRAM_ID
derived_address, _ = Pubkey.find_program_address( derived_address, _ = Pubkey.find_program_address(
[ [
bytes(bonding_curve), bytes(bonding_curve),
bytes(TOKEN_PROGRAM_ID), bytes(TOKEN_PROGRAM_ID),
bytes(mint), bytes(mint),
], ],
ATA_PROGRAM_ID ATA_PROGRAM_ID,
) )
return derived_address return derived_address
def main():
def main():
mint_address = input("Enter the token mint address: ") mint_address = input("Enter the token mint address: ")
try: try:
mint = Pubkey.from_string(mint_address) mint = Pubkey.from_string(mint_address)
bonding_curve_address, bump = get_bonding_curve_address(mint, PUMP_PROGRAM) bonding_curve_address, bump = get_bonding_curve_address(mint, PUMP_PROGRAM)
# Calculate the associated bonding curve # Calculate the associated bonding curve
associated_bonding_curve = find_associated_bonding_curve(mint, bonding_curve_address) associated_bonding_curve = find_associated_bonding_curve(
mint, bonding_curve_address
)
print("\nResults:") print("\nResults:")
print("-" * 50) print("-" * 50)
print(f"Token Mint: {mint}") print(f"Token Mint: {mint}")
@@ -54,9 +53,10 @@ def main():
print(f"Associated Bonding Curve: {associated_bonding_curve}") print(f"Associated Bonding Curve: {associated_bonding_curve}")
print(f"Bonding Curve Bump: {bump}") print(f"Bonding Curve Bump: {bump}")
print("-" * 50) print("-" * 50)
except ValueError as e: except ValueError as e:
print(f"Error: Invalid address format - {str(e)}") print(f"Error: Invalid address format - {str(e)}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+78 -61
View File
@@ -1,59 +1,65 @@
import base64 import base64
import hashlib
import json import json
import struct import struct
import hashlib
from solana.transaction import Transaction
from solders.transaction import VersionedTransaction
from solders.pubkey import Pubkey
import sys import sys
from solana.transaction import Transaction
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
def load_idl(file_path): def load_idl(file_path):
with open(file_path, 'r') as f: with open(file_path, "r") as f:
return json.load(f) return json.load(f)
def load_transaction(file_path): def load_transaction(file_path):
with open(file_path, 'r') as f: with open(file_path, "r") as f:
data = json.load(f) data = json.load(f)
return data return data
def decode_instruction(ix_data, ix_def): def decode_instruction(ix_data, ix_def):
args = {} args = {}
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'] == 'u64': if arg["type"] == "u64":
value = struct.unpack_from('<Q', ix_data, offset)[0] value = struct.unpack_from("<Q", ix_data, offset)[0]
offset += 8 offset += 8
elif arg['type'] == 'publicKey': elif arg["type"] == "publicKey":
value = ix_data[offset:offset+32].hex() value = ix_data[offset : offset + 32].hex()
offset += 32 offset += 32
elif arg['type'] == 'string': elif arg["type"] == "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
else: else:
raise ValueError(f"Unsupported type: {arg['type']}") raise ValueError(f"Unsupported type: {arg['type']}")
args[arg['name']] = value args[arg["name"]] = value
return args return args
def calculate_discriminator(instruction_name): def calculate_discriminator(instruction_name):
sha = hashlib.sha256() sha = hashlib.sha256()
sha.update(instruction_name.encode('utf-8')) sha.update(instruction_name.encode("utf-8"))
discriminator_bytes = sha.digest()[:8] discriminator_bytes = sha.digest()[:8]
discriminator = struct.unpack('<Q', discriminator_bytes)[0] discriminator = struct.unpack("<Q", discriminator_bytes)[0]
return discriminator return discriminator
def decode_transaction(tx_data, idl): def decode_transaction(tx_data, idl):
decoded_instructions = [] decoded_instructions = []
# Decode the base64 transaction data # Decode the base64 transaction data
tx_data_decoded = base64.b64decode(tx_data['transaction'][0]) tx_data_decoded = base64.b64decode(tx_data["transaction"][0])
# Check if it's a versioned transaction # Check if it's a versioned transaction
if tx_data.get('version') == 0: if tx_data.get("version") == 0:
# Use solders library for versioned transactions # Use solders library for versioned transactions
transaction = VersionedTransaction.from_bytes(tx_data_decoded) transaction = VersionedTransaction.from_bytes(tx_data_decoded)
instructions = transaction.message.instructions instructions = transaction.message.instructions
@@ -65,68 +71,79 @@ def decode_transaction(tx_data, idl):
instructions = transaction.instructions instructions = transaction.instructions
account_keys = transaction.message.account_keys account_keys = transaction.message.account_keys
print("Legacy transaction detected") print("Legacy transaction detected")
print(f"Number of instructions: {len(instructions)}") print(f"Number of instructions: {len(instructions)}")
for idx, ix in enumerate(instructions): for idx, ix in enumerate(instructions):
program_id = str(account_keys[ix.program_id_index]) program_id = str(account_keys[ix.program_id_index])
print(f"\nInstruction {idx}:") print(f"\nInstruction {idx}:")
print(f"Program ID: {program_id}") print(f"Program ID: {program_id}")
print(f"IDL program address: {idl['metadata']['address']}") print(f"IDL program address: {idl['metadata']['address']}")
if program_id == idl['metadata']['address']: if program_id == idl["metadata"]["address"]:
ix_data = bytes(ix.data) ix_data = bytes(ix.data)
discriminator = struct.unpack('<Q', ix_data[:8])[0] discriminator = struct.unpack("<Q", ix_data[:8])[0]
print(f"Discriminator: {discriminator:016x}") print(f"Discriminator: {discriminator:016x}")
for idl_ix in idl['instructions']: for idl_ix in idl["instructions"]:
idl_discriminator = calculate_discriminator(f"global:{idl_ix['name']}") idl_discriminator = calculate_discriminator(f"global:{idl_ix['name']}")
print(f"Checking against IDL instruction: {idl_ix['name']} with discriminator {idl_discriminator:016x}") print(
f"Checking against IDL instruction: {idl_ix['name']} with discriminator {idl_discriminator:016x}"
)
if discriminator == idl_discriminator: if discriminator == idl_discriminator:
decoded_args = decode_instruction(ix_data, idl_ix) decoded_args = decode_instruction(ix_data, idl_ix)
accounts = [str(account_keys[acc_idx]) for acc_idx in ix.accounts] accounts = [str(account_keys[acc_idx]) for acc_idx in ix.accounts]
decoded_instructions.append({ decoded_instructions.append(
'name': idl_ix['name'], {
'args': decoded_args, "name": idl_ix["name"],
'accounts': accounts, "args": decoded_args,
'program': program_id "accounts": accounts,
}) "program": program_id,
}
)
break break
else: else:
decoded_instructions.append({ decoded_instructions.append(
'name': 'Unknown', {
'data': ix_data.hex(), "name": "Unknown",
'accounts': [str(account_keys[acc_idx]) for acc_idx in ix.accounts], "data": ix_data.hex(),
'program': program_id "accounts": [
}) str(account_keys[acc_idx]) for acc_idx in ix.accounts
],
"program": program_id,
}
)
else: else:
instruction_name = 'External' instruction_name = "External"
if program_id == 'ComputeBudget111111111111111111111111111111': if program_id == "ComputeBudget111111111111111111111111111111":
if ix.data[:1] == b'\x03': if ix.data[:1] == b"\x03":
instruction_name = 'ComputeBudget: Set compute unit limit' instruction_name = "ComputeBudget: Set compute unit limit"
elif ix.data[:1] == b'\x02': elif ix.data[:1] == b"\x02":
instruction_name = 'ComputeBudget: Set compute unit price' instruction_name = "ComputeBudget: Set compute unit price"
elif program_id == 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL': elif program_id == "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL":
instruction_name = 'Associated Token Account: Create' instruction_name = "Associated Token Account: Create"
decoded_instructions.append({ decoded_instructions.append(
'name': instruction_name, {
'programId': program_id, "name": instruction_name,
'data': bytes(ix.data).hex(), "programId": program_id,
'accounts': [str(account_keys[acc_idx]) for acc_idx in ix.accounts] "data": bytes(ix.data).hex(),
}) "accounts": [str(account_keys[acc_idx]) for acc_idx in ix.accounts],
}
)
return decoded_instructions return decoded_instructions
if len(sys.argv) != 2: if len(sys.argv) != 2:
print("Usage: python decode_fromBlock.py <transaction_file_path>") print("Usage: python decode_fromBlock.py <transaction_file_path>")
sys.exit(1) sys.exit(1)
tx_file_path = sys.argv[1] tx_file_path = sys.argv[1]
idl = load_idl('../idl/pump_fun_idl.json') idl = load_idl("../idl/pump_fun_idl.json")
tx_data = load_transaction(tx_file_path) tx_data = load_transaction(tx_file_path)
decoded_instructions = decode_transaction(tx_data, idl) decoded_instructions = decode_transaction(tx_data, idl)
print(json.dumps(decoded_instructions, indent=2)) print(json.dumps(decoded_instructions, indent=2))
@@ -1,12 +1,14 @@
import json
import base64 import base64
import json
import struct import struct
from construct import Struct, Int64ul, Flag
from construct import Flag, Int64ul, Struct
LAMPORTS_PER_SOL = 1_000_000_000 LAMPORTS_PER_SOL = 1_000_000_000
TOKEN_DECIMALS = 6 TOKEN_DECIMALS = 6
EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399)
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -14,18 +16,22 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float: def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float:
if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0:
raise ValueError("Invalid reserve state") raise ValueError("Invalid reserve state")
return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (curve_state.virtual_token_reserves / 10 ** TOKEN_DECIMALS) return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (
curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS
)
def decode_bonding_curve_data(raw_data: str) -> BondingCurveState: def decode_bonding_curve_data(raw_data: str) -> BondingCurveState:
decoded_data = base64.b64decode(raw_data) decoded_data = base64.b64decode(raw_data)
@@ -33,12 +39,13 @@ def decode_bonding_curve_data(raw_data: str) -> BondingCurveState:
raise ValueError("Invalid curve state discriminator") raise ValueError("Invalid curve state discriminator")
return BondingCurveState(decoded_data) return BondingCurveState(decoded_data)
# Load the JSON data # Load the JSON data
with open('raw_bondingCurve_from_getAccountInfo.json', 'r') as file: with open("raw_bondingCurve_from_getAccountInfo.json", "r") as file:
json_data = json.load(file) json_data = json.load(file)
# Extract the base64 encoded data # Extract the base64 encoded data
encoded_data = json_data['result']['value']['data'][0] encoded_data = json_data["result"]["value"]["data"][0]
# Decode the data # Decode the data
bonding_curve_state = decode_bonding_curve_data(encoded_data) bonding_curve_state = decode_bonding_curve_data(encoded_data)
@@ -53,4 +60,4 @@ print(f" Real Token Reserves: {bonding_curve_state.real_token_reserves}")
print(f" Real SOL Reserves: {bonding_curve_state.real_sol_reserves}") print(f" Real SOL Reserves: {bonding_curve_state.real_sol_reserves}")
print(f" Token Total Supply: {bonding_curve_state.token_total_supply}") print(f" Token Total Supply: {bonding_curve_state.token_total_supply}")
print(f" Complete: {bonding_curve_state.complete}") print(f" Complete: {bonding_curve_state.complete}")
print(f"\nToken Price: {token_price_sol:.10f} SOL") print(f"\nToken Price: {token_price_sol:.10f} SOL")
+33 -31
View File
@@ -1,12 +1,11 @@
import base64
import json import json
import struct
import sys
import base58 import base58
from solana.transaction import Transaction from solana.transaction import Transaction
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
import struct
import sys
import base64
import sys
if len(sys.argv) != 2: if len(sys.argv) != 2:
print("Usage: python decode_getTransaction.py <transaction_file_path>") print("Usage: python decode_getTransaction.py <transaction_file_path>")
@@ -15,18 +14,19 @@ if len(sys.argv) != 2:
tx_file_path = sys.argv[1] tx_file_path = sys.argv[1]
# Load the IDL # Load the IDL
with open('../idl/pump_fun_idl.json', 'r') as f: with open("../idl/pump_fun_idl.json", "r") as f:
idl = json.load(f) idl = json.load(f)
# Load the transaction log # Load the transaction log
with open(tx_file_path, 'r') as f: with open(tx_file_path, "r") as f:
tx_log = json.load(f) tx_log = json.load(f)
# Extract the transaction data # Extract the transaction data
tx_data = tx_log['result']['transaction'] tx_data = tx_log["result"]["transaction"]
print(json.dumps(tx_data, indent=2)) print(json.dumps(tx_data, indent=2))
def decode_create_instruction(data): def decode_create_instruction(data):
# The Create instruction has 3 string arguments: name, symbol, uri # The Create instruction has 3 string arguments: name, symbol, uri
offset = 8 # Skip the 8-byte discriminator offset = 8 # Skip the 8-byte discriminator
@@ -34,59 +34,61 @@ def decode_create_instruction(data):
for _ in range(3): for _ in range(3):
length = struct.unpack_from("<I", data, offset)[0] length = struct.unpack_from("<I", data, offset)[0]
offset += 4 offset += 4
string_data = data[offset:offset+length].decode('utf-8') string_data = data[offset : offset + length].decode("utf-8")
results.append(string_data) results.append(string_data)
offset += length offset += length
return { return {"name": results[0], "symbol": results[1], "uri": results[2]}
"name": results[0],
"symbol": results[1],
"uri": results[2]
}
def decode_buy_instruction(data): def decode_buy_instruction(data):
# Assuming the buy instruction has a u64 argument for amount # Assuming the buy instruction has a u64 argument for amount
amount = struct.unpack_from("<Q", data, 8)[0] amount = struct.unpack_from("<Q", data, 8)[0]
return {"amount": amount} return {"amount": amount}
def decode_instruction_data(instruction, accounts, data): def decode_instruction_data(instruction, accounts, data):
if instruction['name'] == 'create': if instruction["name"] == "create":
return decode_create_instruction(data) return decode_create_instruction(data)
elif instruction['name'] == 'buy': elif instruction["name"] == "buy":
return decode_buy_instruction(data) return decode_buy_instruction(data)
else: else:
return f"Unhandled instruction type: {instruction['name']}" return f"Unhandled instruction type: {instruction['name']}"
def find_matching_instruction(accounts, data): def find_matching_instruction(accounts, data):
if 'instructions' not in idl: if "instructions" not in idl:
print("Warning: No instructions found in IDL") print("Warning: No instructions found in IDL")
return None return None
for instruction in idl['instructions']: for instruction in idl["instructions"]:
if len(instruction['accounts']) == len(accounts): if len(instruction["accounts"]) == len(accounts):
return instruction return instruction
return None return None
# Parse the transaction # Parse the transaction
tx_message = tx_data['message'] tx_message = tx_data["message"]
instructions = tx_message['instructions'] instructions = tx_message["instructions"]
for ix in instructions: for ix in instructions:
program_id = ix.get('programId') program_id = ix.get("programId")
accounts = ix.get('accounts', []) accounts = ix.get("accounts", [])
data = ix.get('data', '') data = ix.get("data", "")
if 'parsed' in ix: if "parsed" in ix:
print(f"Parsed instruction: {ix['program']} - {ix['parsed']['type']}") print(f"Parsed instruction: {ix['program']} - {ix['parsed']['type']}")
print(f"Info: {json.dumps(ix['parsed']['info'], indent=2)}") print(f"Info: {json.dumps(ix['parsed']['info'], indent=2)}")
elif program_id == idl['metadata']['address']: elif program_id == idl["metadata"]["address"]:
matching_instruction = find_matching_instruction(accounts, data) matching_instruction = find_matching_instruction(accounts, data)
if matching_instruction: if matching_instruction:
decoded_data = decode_instruction_data(matching_instruction, accounts, base58.b58decode(data)) decoded_data = decode_instruction_data(
matching_instruction, accounts, base58.b58decode(data)
)
print(f"Instruction: {matching_instruction['name']}") print(f"Instruction: {matching_instruction['name']}")
print(f"Decoded data: {decoded_data}") print(f"Decoded data: {decoded_data}")
print("\nAccounts:") print("\nAccounts:")
for i, account in enumerate(accounts): for i, account in enumerate(accounts):
account_info = matching_instruction['accounts'][i] account_info = matching_instruction["accounts"][i]
print(f" {account_info['name']}: {account}") print(f" {account_info['name']}: {account}")
else: else:
print(f"Unable to match instruction for program {program_id}") print(f"Unable to match instruction for program {program_id}")
@@ -97,4 +99,4 @@ for ix in instructions:
print("\nTransaction Information:") print("\nTransaction Information:")
print(f"Blockhash: {tx_message['recentBlockhash']}") print(f"Blockhash: {tx_message['recentBlockhash']}")
print(f"Fee payer: {tx_message['accountKeys'][0]['pubkey']}") print(f"Fee payer: {tx_message['accountKeys'][0]['pubkey']}")
print(f"Signature: {tx_data['signatures'][0]}") print(f"Signature: {tx_data['signatures'][0]}")
+16 -7
View File
@@ -1,14 +1,14 @@
import asyncio import asyncio
import os
import struct import struct
import sys import sys
import os
from typing import Final from typing import Final
from construct import Struct, Int64ul, Flag from construct import Flag, Int64ul, Struct
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import RPC_ENDPOINT from config import RPC_ENDPOINT
LAMPORTS_PER_SOL: Final[int] = 1_000_000_000 LAMPORTS_PER_SOL: Final[int] = 1_000_000_000
@@ -18,6 +18,7 @@ CURVE_ADDRESS: Final[str] = "6GXfUqrmPM4VdN1NoDZsE155jzRegJngZRjMkGyby7do"
# Here and later all the discriminators are precalculated. See learning-examples/discriminator.py # Here and later all the discriminators are precalculated. See learning-examples/discriminator.py
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399)
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -25,14 +26,17 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
async def get_bonding_curve_state(conn: AsyncClient, curve_address: Pubkey) -> BondingCurveState:
async def get_bonding_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
response = await conn.get_account_info(curve_address) response = await conn.get_account_info(curve_address)
if not response.value or not response.value.data: if not response.value or not response.value.data:
raise ValueError("Invalid curve state: No data") raise ValueError("Invalid curve state: No data")
@@ -43,11 +47,15 @@ async def get_bonding_curve_state(conn: AsyncClient, curve_address: Pubkey) -> B
return BondingCurveState(data) return BondingCurveState(data)
def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float: def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float:
if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0:
raise ValueError("Invalid reserve state") raise ValueError("Invalid reserve state")
return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (curve_state.virtual_token_reserves / 10 ** TOKEN_DECIMALS) return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (
curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS
)
async def main() -> None: async def main() -> None:
try: try:
@@ -63,5 +71,6 @@ async def main() -> None:
except Exception as e: except Exception as e:
print(f"An unexpected error occurred: {e}") print(f"An unexpected error occurred: {e}")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
@@ -1,68 +1,74 @@
import asyncio import asyncio
import json
import websockets
import base64 import base64
import struct
import hashlib import hashlib
import sys import json
import os import os
import struct
import sys
import websockets
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction from solders.transaction import VersionedTransaction
from solders.pubkey import Pubkey
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import PUMP_PROGRAM, WSS_ENDPOINT
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from config import WSS_ENDPOINT, PUMP_PROGRAM
def load_idl(file_path): def load_idl(file_path):
with open(file_path, 'r') as f: with open(file_path, "r") as f:
return json.load(f) return json.load(f)
def decode_create_instruction(ix_data, ix_def, accounts): def decode_create_instruction(ix_data, ix_def, accounts):
args = {} args = {}
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': if arg["type"] == "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'] == 'publicKey': elif arg["type"] == "publicKey":
value = base64.b64encode(ix_data[offset:offset+32]).decode('utf-8') value = base64.b64encode(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']}")
args[arg['name']] = value args[arg["name"]] = value
# Add accounts # Add accounts
args['mint'] = str(accounts[0]) args["mint"] = str(accounts[0])
args['bondingCurve'] = str(accounts[2]) args["bondingCurve"] = str(accounts[2])
args['associatedBondingCurve'] = str(accounts[3]) args["associatedBondingCurve"] = str(accounts[3])
args['user'] = str(accounts[7]) args["user"] = str(accounts[7])
return args return args
# Here and later all the discriminators are precalculated. See learning-examples/discriminator.py # Here and later all the discriminators are precalculated. See learning-examples/discriminator.py
async def listen_and_decode_create(): async def listen_and_decode_create():
idl = load_idl('../idl/pump_fun_idl.json') idl = load_idl("../idl/pump_fun_idl.json")
create_discriminator = 8576854823835016728 create_discriminator = 8576854823835016728
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "blockSubscribe", "id": 1,
"params": [ "method": "blockSubscribe",
{"mentionsAccountOrProgram": str(PUMP_PROGRAM)}, "params": [
{ {"mentionsAccountOrProgram": str(PUMP_PROGRAM)},
"commitment": "confirmed", {
"encoding": "base64", "commitment": "confirmed",
"showRewards": False, "encoding": "base64",
"transactionDetails": "full", "showRewards": False,
"maxSupportedTransactionVersion": 0 "transactionDetails": "full",
} "maxSupportedTransactionVersion": 0,
] },
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}") print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}")
@@ -70,40 +76,78 @@ async def listen_and_decode_create():
try: try:
response = await websocket.recv() response = await websocket.recv()
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'blockNotification': if "method" in data and data["method"] == "blockNotification":
if 'params' in data and 'result' in data['params']: if "params" in data and "result" in data["params"]:
block_data = data['params']['result'] block_data = data["params"]["result"]
if 'value' in block_data and 'block' in block_data['value']: if "value" in block_data and "block" in block_data["value"]:
block = block_data['value']['block'] block = block_data["value"]["block"]
if 'transactions' in block: if "transactions" in block:
for tx in block['transactions']: for tx in block["transactions"]:
if isinstance(tx, dict) and 'transaction' in tx: if isinstance(tx, dict) and "transaction" in tx:
tx_data_decoded = base64.b64decode(tx['transaction'][0]) tx_data_decoded = base64.b64decode(
transaction = VersionedTransaction.from_bytes(tx_data_decoded) tx["transaction"][0]
)
transaction = VersionedTransaction.from_bytes(
tx_data_decoded
)
for ix in transaction.message.instructions: for ix in transaction.message.instructions:
if str(transaction.message.account_keys[ix.program_id_index]) == str(PUMP_PROGRAM): if str(
transaction.message.account_keys[
ix.program_id_index
]
) == str(PUMP_PROGRAM):
ix_data = bytes(ix.data) ix_data = bytes(ix.data)
discriminator = struct.unpack('<Q', ix_data[:8])[0] discriminator = struct.unpack(
"<Q", ix_data[:8]
if discriminator == create_discriminator: )[0]
create_ix = next(instr for instr in idl['instructions'] if instr['name'] == 'create')
account_keys = [str(transaction.message.account_keys[index]) for index in ix.accounts] if (
decoded_args = decode_create_instruction(ix_data, create_ix, account_keys) discriminator
print(json.dumps(decoded_args, indent=2)) == create_discriminator
):
create_ix = next(
instr
for instr in idl["instructions"]
if instr["name"] == "create"
)
account_keys = [
str(
transaction.message.account_keys[
index
]
)
for index in ix.accounts
]
decoded_args = (
decode_create_instruction(
ix_data,
create_ix,
account_keys,
)
)
print(
json.dumps(
decoded_args, indent=2
)
)
print("--------------------") print("--------------------")
elif 'result' in data: elif "result" in data:
print(f"Subscription confirmed") print(f"Subscription confirmed")
else: else:
print(f"Received unexpected message type: {data.get('method', 'Unknown')}") print(
f"Received unexpected message type: {data.get('method', 'Unknown')}"
)
except Exception as e: except Exception as e:
print(f"An error occurred: {str(e)}") print(f"An error occurred: {str(e)}")
print(f"Error details: {type(e).__name__}") print(f"Error details: {type(e).__name__}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
print("WebSocket connection closed.") print("WebSocket connection closed.")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(listen_and_decode_create()) asyncio.run(listen_and_decode_create())
+64 -43
View File
@@ -1,21 +1,25 @@
import asyncio import asyncio
import json
import websockets
import base58
import base64 import base64
import json
import os
import struct import struct
import sys import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import base58
from config import WSS_ENDPOINT, PUMP_PROGRAM import websockets
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import PUMP_PROGRAM, WSS_ENDPOINT
# Load the IDL JSON file # Load the IDL JSON file
with open('../idl/pump_fun_idl.json', 'r') as f: with open("../idl/pump_fun_idl.json", "r") as f:
idl = json.load(f) idl = json.load(f)
# Extract the "create" instruction definition # Extract the "create" instruction definition
create_instruction = next(instr for instr in idl['instructions'] if instr['name'] == 'create') create_instruction = next(
instr for instr in idl["instructions"] if instr["name"] == "create"
)
def parse_create_instruction(data): def parse_create_instruction(data):
if len(data) < 8: if len(data) < 8:
@@ -25,23 +29,23 @@ def parse_create_instruction(data):
# Parse fields based on CreateEvent structure # Parse fields based on CreateEvent structure
fields = [ fields = [
('name', 'string'), ("name", "string"),
('symbol', 'string'), ("symbol", "string"),
('uri', 'string'), ("uri", "string"),
('mint', 'publicKey'), ("mint", "publicKey"),
('bondingCurve', 'publicKey'), ("bondingCurve", "publicKey"),
('user', 'publicKey'), ("user", "publicKey"),
] ]
try: try:
for field_name, field_type in fields: for field_name, field_type in fields:
if field_type == 'string': if field_type == "string":
length = struct.unpack('<I', data[offset:offset+4])[0] length = struct.unpack("<I", data[offset : offset + 4])[0]
offset += 4 offset += 4
value = data[offset:offset+length].decode('utf-8') value = data[offset : offset + length].decode("utf-8")
offset += length offset += length
elif field_type == 'publicKey': elif field_type == "publicKey":
value = base58.b58encode(data[offset:offset+32]).decode('utf-8') value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
offset += 32 offset += 32
parsed_data[field_name] = value parsed_data[field_name] = value
@@ -50,30 +54,34 @@ def parse_create_instruction(data):
except: except:
return None return None
def print_transaction_details(log_data): def print_transaction_details(log_data):
print(f"Signature: {log_data.get('signature')}") print(f"Signature: {log_data.get('signature')}")
for log in log_data.get('logs', []): for log in log_data.get("logs", []):
if log.startswith("Program data:"): if log.startswith("Program data:"):
try: try:
data = base58.b58decode(log.split(": ")[1]).decode('utf-8') data = base58.b58decode(log.split(": ")[1]).decode("utf-8")
print(f"Data: {data}") print(f"Data: {data}")
except: except:
pass pass
async def listen_for_new_tokens(): async def listen_for_new_tokens():
while True: while True:
try: try:
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "logsSubscribe", "id": 1,
"params": [ "method": "logsSubscribe",
{"mentions": [str(PUMP_PROGRAM)]}, "params": [
{"commitment": "processed"} {"mentions": [str(PUMP_PROGRAM)]},
] {"commitment": "processed"},
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
print(f"Listening for new token creations from program: {PUMP_PROGRAM}") print(f"Listening for new token creations from program: {PUMP_PROGRAM}")
@@ -86,26 +94,38 @@ async def listen_for_new_tokens():
response = await websocket.recv() response = await websocket.recv()
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'logsNotification': if "method" in data and data["method"] == "logsNotification":
log_data = data['params']['result']['value'] log_data = data["params"]["result"]["value"]
logs = log_data.get('logs', []) logs = log_data.get("logs", [])
if any("Program log: Instruction: Create" in log for log in logs): if any(
"Program log: Instruction: Create" in log
for log in logs
):
for log in logs: for log in logs:
if "Program data:" in log: if "Program data:" in log:
try: try:
encoded_data = log.split(": ")[1] encoded_data = log.split(": ")[1]
decoded_data = base64.b64decode(encoded_data) decoded_data = base64.b64decode(
parsed_data = parse_create_instruction(decoded_data) encoded_data
if parsed_data and 'name' in parsed_data: )
print("Signature:", log_data.get('signature')) parsed_data = parse_create_instruction(
decoded_data
)
if parsed_data and "name" in parsed_data:
print(
"Signature:",
log_data.get("signature"),
)
for key, value in parsed_data.items(): for key, value in parsed_data.items():
print(f"{key}: {value}") print(f"{key}: {value}")
print("##########################################################################################") print(
"##########################################################################################"
)
except Exception as e: except Exception as e:
print(f"Failed to decode: {log}") print(f"Failed to decode: {log}")
print(f"Error: {str(e)}") print(f"Error: {str(e)}")
except Exception as e: except Exception as e:
print(f"An error occurred while processing message: {e}") print(f"An error occurred while processing message: {e}")
break break
@@ -115,5 +135,6 @@ async def listen_for_new_tokens():
print("Reconnecting in 5 seconds...") print("Reconnecting in 5 seconds...")
await asyncio.sleep(5) await asyncio.sleep(5)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(listen_for_new_tokens()) asyncio.run(listen_for_new_tokens())
@@ -1,20 +1,24 @@
import asyncio import asyncio
import json
import websockets
import base58
import base64 import base64
import json
import os
import struct import struct
import sys import sys
import os
import base58
import websockets
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import ( from config import (
WSS_ENDPOINT,
PUMP_PROGRAM, PUMP_PROGRAM,
SYSTEM_TOKEN_PROGRAM as TOKEN_PROGRAM_ID,
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM as ATA_PROGRAM_ID
) )
from config import SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM as ATA_PROGRAM_ID
from config import SYSTEM_TOKEN_PROGRAM as TOKEN_PROGRAM_ID
from config import (
WSS_ENDPOINT,
)
def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey: def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey:
""" """
@@ -27,16 +31,20 @@ def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey
bytes(TOKEN_PROGRAM_ID), bytes(TOKEN_PROGRAM_ID),
bytes(mint), bytes(mint),
], ],
ATA_PROGRAM_ID ATA_PROGRAM_ID,
) )
return derived_address return derived_address
# Load the IDL JSON file # Load the IDL JSON file
with open('../idl/pump_fun_idl.json', 'r') as f: with open("../idl/pump_fun_idl.json", "r") as f:
idl = json.load(f) idl = json.load(f)
# Extract the "create" instruction definition # Extract the "create" instruction definition
create_instruction = next(instr for instr in idl['instructions'] if instr['name'] == 'create') create_instruction = next(
instr for instr in idl["instructions"] if instr["name"] == "create"
)
def parse_create_instruction(data): def parse_create_instruction(data):
if len(data) < 8: if len(data) < 8:
@@ -46,23 +54,23 @@ def parse_create_instruction(data):
# Parse fields based on CreateEvent structure # Parse fields based on CreateEvent structure
fields = [ fields = [
('name', 'string'), ("name", "string"),
('symbol', 'string'), ("symbol", "string"),
('uri', 'string'), ("uri", "string"),
('mint', 'publicKey'), ("mint", "publicKey"),
('bondingCurve', 'publicKey'), ("bondingCurve", "publicKey"),
('user', 'publicKey'), ("user", "publicKey"),
] ]
try: try:
for field_name, field_type in fields: for field_name, field_type in fields:
if field_type == 'string': if field_type == "string":
length = struct.unpack('<I', data[offset:offset+4])[0] length = struct.unpack("<I", data[offset : offset + 4])[0]
offset += 4 offset += 4
value = data[offset:offset+length].decode('utf-8') value = data[offset : offset + length].decode("utf-8")
offset += length offset += length
elif field_type == 'publicKey': elif field_type == "publicKey":
value = base58.b58encode(data[offset:offset+32]).decode('utf-8') value = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
offset += 32 offset += 32
parsed_data[field_name] = value parsed_data[field_name] = value
@@ -71,30 +79,34 @@ def parse_create_instruction(data):
except: except:
return None return None
def print_transaction_details(log_data): def print_transaction_details(log_data):
print(f"Signature: {log_data.get('signature')}") print(f"Signature: {log_data.get('signature')}")
for log in log_data.get('logs', []): for log in log_data.get("logs", []):
if log.startswith("Program data:"): if log.startswith("Program data:"):
try: try:
data = base58.b58decode(log.split(": ")[1]).decode('utf-8') data = base58.b58decode(log.split(": ")[1]).decode("utf-8")
print(f"Data: {data}") print(f"Data: {data}")
except: except:
pass pass
async def listen_for_new_tokens(): async def listen_for_new_tokens():
while True: while True:
try: try:
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "logsSubscribe", "id": 1,
"params": [ "method": "logsSubscribe",
{"mentions": [str(PUMP_PROGRAM)]}, "params": [
{"commitment": "processed"} {"mentions": [str(PUMP_PROGRAM)]},
] {"commitment": "processed"},
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
print(f"Listening for new token creations from program: {PUMP_PROGRAM}") print(f"Listening for new token creations from program: {PUMP_PROGRAM}")
@@ -107,28 +119,50 @@ async def listen_for_new_tokens():
response = await websocket.recv() response = await websocket.recv()
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'logsNotification': if "method" in data and data["method"] == "logsNotification":
log_data = data['params']['result']['value'] log_data = data["params"]["result"]["value"]
logs = log_data.get('logs', []) logs = log_data.get("logs", [])
if any("Program log: Instruction: Create" in log for log in logs): if any(
"Program log: Instruction: Create" in log
for log in logs
):
for log in logs: for log in logs:
if "Program data:" in log: if "Program data:" in log:
try: try:
encoded_data = log.split(": ")[1] encoded_data = log.split(": ")[1]
decoded_data = base64.b64decode(encoded_data) decoded_data = base64.b64decode(
parsed_data = parse_create_instruction(decoded_data) encoded_data
if parsed_data and 'name' in parsed_data: )
print("Signature:", log_data.get('signature')) parsed_data = parse_create_instruction(
decoded_data
)
if parsed_data and "name" in parsed_data:
print(
"Signature:",
log_data.get("signature"),
)
for key, value in parsed_data.items(): for key, value in parsed_data.items():
print(f"{key}: {value}") print(f"{key}: {value}")
# Calculate associated bonding curve # Calculate associated bonding curve
mint = Pubkey.from_string(parsed_data['mint']) mint = Pubkey.from_string(
bonding_curve = Pubkey.from_string(parsed_data['bondingCurve']) parsed_data["mint"]
associated_curve = find_associated_bonding_curve(mint, bonding_curve) )
print(f"Associated Bonding Curve: {associated_curve}") bonding_curve = Pubkey.from_string(
print("##########################################################################################") parsed_data["bondingCurve"]
)
associated_curve = (
find_associated_bonding_curve(
mint, bonding_curve
)
)
print(
f"Associated Bonding Curve: {associated_curve}"
)
print(
"##########################################################################################"
)
except Exception as e: except Exception as e:
print(f"Failed to decode: {log}") print(f"Failed to decode: {log}")
print(f"Error: {str(e)}") print(f"Error: {str(e)}")
@@ -142,5 +176,6 @@ async def listen_for_new_tokens():
print("Reconnecting in 5 seconds...") print("Reconnecting in 5 seconds...")
await asyncio.sleep(5) await asyncio.sleep(5)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(listen_for_new_tokens()) asyncio.run(listen_for_new_tokens())
+25 -14
View File
@@ -1,24 +1,25 @@
import websockets
import asyncio import asyncio
import json import json
from datetime import datetime from datetime import datetime
import websockets
# PumpPortal WebSocket URL # PumpPortal WebSocket URL
WS_URL = "wss://pumpportal.fun/api/data" WS_URL = "wss://pumpportal.fun/api/data"
def format_sol(value): def format_sol(value):
return f"{value:.6f} SOL" return f"{value:.6f} SOL"
def format_timestamp(timestamp): def format_timestamp(timestamp):
return datetime.fromtimestamp(timestamp / 1000).strftime('%Y-%m-%d %H:%M:%S') return datetime.fromtimestamp(timestamp / 1000).strftime("%Y-%m-%d %H:%M:%S")
async def listen_for_new_tokens(): async def listen_for_new_tokens():
async with websockets.connect(WS_URL) as websocket: async with websockets.connect(WS_URL) as websocket:
# Subscribe to new token events # Subscribe to new token events
await websocket.send(json.dumps({ await websocket.send(json.dumps({"method": "subscribeNewToken", "params": []}))
"method": "subscribeNewToken",
"params": []
}))
print("Listening for new token creations...") print("Listening for new token creations...")
@@ -27,23 +28,31 @@ async def listen_for_new_tokens():
message = await websocket.recv() message = await websocket.recv()
data = json.loads(message) data = json.loads(message)
if 'method' in data and data['method'] == 'newToken': if "method" in data and data["method"] == "newToken":
token_info = data.get('params', [{}])[0] token_info = data.get("params", [{}])[0]
elif 'signature' in data and 'mint' in data: elif "signature" in data and "mint" in data:
token_info = data token_info = data
else: else:
continue continue
print("\n" + "=" * 50) print("\n" + "=" * 50)
print(f"New token created: {token_info.get('name')} ({token_info.get('symbol')})") print(
f"New token created: {token_info.get('name')} ({token_info.get('symbol')})"
)
print("=" * 50) print("=" * 50)
print(f"Address: {token_info.get('mint')}") print(f"Address: {token_info.get('mint')}")
print(f"Creator: {token_info.get('traderPublicKey')}") print(f"Creator: {token_info.get('traderPublicKey')}")
print(f"Initial Buy: {format_sol(token_info.get('initialBuy', 0))}") print(f"Initial Buy: {format_sol(token_info.get('initialBuy', 0))}")
print(f"Market Cap: {format_sol(token_info.get('marketCapSol', 0))}") print(
f"Market Cap: {format_sol(token_info.get('marketCapSol', 0))}"
)
print(f"Bonding Curve: {token_info.get('bondingCurveKey')}") print(f"Bonding Curve: {token_info.get('bondingCurveKey')}")
print(f"Virtual SOL: {format_sol(token_info.get('vSolInBondingCurve', 0))}") print(
print(f"Virtual Tokens: {token_info.get('vTokensInBondingCurve', 0):,.0f}") f"Virtual SOL: {format_sol(token_info.get('vSolInBondingCurve', 0))}"
)
print(
f"Virtual Tokens: {token_info.get('vTokensInBondingCurve', 0):,.0f}"
)
print(f"Metadata URI: {token_info.get('uri')}") print(f"Metadata URI: {token_info.get('uri')}")
print(f"Signature: {token_info.get('signature')}") print(f"Signature: {token_info.get('signature')}")
print("=" * 50) print("=" * 50)
@@ -55,6 +64,7 @@ async def listen_for_new_tokens():
except Exception as e: except Exception as e:
print(f"\nAn error occurred: {e}") print(f"\nAn error occurred: {e}")
async def main(): async def main():
while True: while True:
try: try:
@@ -64,5 +74,6 @@ async def main():
print("Reconnecting in 5 seconds...") print("Reconnecting in 5 seconds...")
await asyncio.sleep(5) await asyncio.sleep(5)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
@@ -1,57 +1,62 @@
import websockets
import asyncio import asyncio
import json
import base64 import base64
from solders.pubkey import Pubkey import json
import sys
import os import os
import sys
import websockets
from solders.pubkey import Pubkey
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from config import PUMP_LIQUIDITY_MIGRATOR, WSS_ENDPOINT
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from config import WSS_ENDPOINT, PUMP_LIQUIDITY_MIGRATOR
def process_initialize2_transaction(data): def process_initialize2_transaction(data):
"""Process and decode an initialize2 transaction""" """Process and decode an initialize2 transaction"""
try: try:
signature = data['transaction']['signatures'][0] signature = data["transaction"]["signatures"][0]
account_keys = data['transaction']['message']['accountKeys'] account_keys = data["transaction"]["message"]["accountKeys"]
# Check raydium_amm_idl.json for the account keys # Check raydium_amm_idl.json for the account keys
# The token address is typically the 19th account (index 18) # The token address is typically the 19th account (index 18)
# The liquidity pool address is typically the 3rd account (index 2) # The liquidity pool address is typically the 3rd account (index 2)
if len(account_keys) > 18: if len(account_keys) > 18:
token_address = account_keys[18] token_address = account_keys[18]
liquidity_address = account_keys[2] liquidity_address = account_keys[2]
print(f"\nSignature: {signature}") print(f"\nSignature: {signature}")
print(f"Token Address: {token_address}") print(f"Token Address: {token_address}")
print(f"Liquidity Address: {liquidity_address}") print(f"Liquidity Address: {liquidity_address}")
print("=" * 50) print("=" * 50)
else: else:
print(f"\nError: Not enough account keys (found {len(account_keys)})") print(f"\nError: Not enough account keys (found {len(account_keys)})")
except Exception as e: except Exception as e:
print(f"\nError: {str(e)}") print(f"\nError: {str(e)}")
async def listen_for_events(): async def listen_for_events():
while True: while True:
try: try:
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "blockSubscribe", "id": 1,
"params": [ "method": "blockSubscribe",
{"mentionsAccountOrProgram": str(PUMP_LIQUIDITY_MIGRATOR)}, "params": [
{ {"mentionsAccountOrProgram": str(PUMP_LIQUIDITY_MIGRATOR)},
"commitment": "confirmed", {
"encoding": "json", "commitment": "confirmed",
"showRewards": False, "encoding": "json",
"transactionDetails": "full", "showRewards": False,
"maxSupportedTransactionVersion": 0 "transactionDetails": "full",
} "maxSupportedTransactionVersion": 0,
] },
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
response = await websocket.recv() response = await websocket.recv()
print(f"Subscription response: {response}") print(f"Subscription response: {response}")
@@ -61,32 +66,43 @@ async def listen_for_events():
try: try:
response = await asyncio.wait_for(websocket.recv(), timeout=30) response = await asyncio.wait_for(websocket.recv(), timeout=30)
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'blockNotification': if "method" in data and data["method"] == "blockNotification":
if 'params' in data and 'result' in data['params']: if "params" in data and "result" in data["params"]:
block_data = data['params']['result'] block_data = data["params"]["result"]
if 'value' in block_data and 'block' in block_data['value']: if (
block = block_data['value']['block'] "value" in block_data
if 'transactions' in block: and "block" in block_data["value"]
for tx in block['transactions']: ):
logs = tx.get('meta', {}).get('logMessages', []) block = block_data["value"]["block"]
if "transactions" in block:
for tx in block["transactions"]:
logs = tx.get("meta", {}).get(
"logMessages", []
)
# Check for initialize2 instruction # Check for initialize2 instruction
for log in logs: for log in logs:
if "Program log: initialize2: InitializeInstruction2" in log: if (
print("Found initialize2 instruction!") "Program log: initialize2: InitializeInstruction2"
in log
):
print(
"Found initialize2 instruction!"
)
process_initialize2_transaction(tx) process_initialize2_transaction(tx)
break break
except asyncio.TimeoutError: except asyncio.TimeoutError:
print("\nChecking connection...") print("\nChecking connection...")
print("Connection alive") print("Connection alive")
continue continue
except Exception as e: except Exception as e:
print(f"\nConnection error: {str(e)}") print(f"\nConnection error: {str(e)}")
print("Retrying in 5 seconds...") print("Retrying in 5 seconds...")
await asyncio.sleep(5) await asyncio.sleep(5)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(listen_for_events()) asyncio.run(listen_for_events())
+202 -105
View File
@@ -1,29 +1,26 @@
import asyncio import asyncio
import json
import base64 import base64
import struct import hashlib
import base58 import json
import os import os
import struct
from solana.transaction import Message import base58
import spl.token.instructions as spl_token
import websockets
from construct import Flag, Int64ul, Struct
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Confirmed from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Message
from solders.pubkey import Pubkey
from solders.keypair import Keypair
from solders.instruction import Instruction, AccountMeta
from solders.system_program import TransferParams, transfer
from solders.transaction import VersionedTransaction, Transaction
from solders.compute_budget import set_compute_unit_price from solders.compute_budget import set_compute_unit_price
from solders.instruction import AccountMeta, Instruction
import spl.token.instructions as spl_token from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer
from solders.transaction import Transaction, VersionedTransaction
from spl.token.instructions import get_associated_token_address from spl.token.instructions import get_associated_token_address
import websockets
import hashlib
from construct import Struct, Int64ul, Flag
# Here and later all the discriminators are precalculated. See learning-examples/discriminator.py # Here and later all the discriminators are precalculated. See learning-examples/discriminator.py
EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399)
TOKEN_DECIMALS = 6 TOKEN_DECIMALS = 6
@@ -31,11 +28,15 @@ TOKEN_DECIMALS = 6
# Global constants # Global constants
PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
PUMP_EVENT_AUTHORITY = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1") PUMP_EVENT_AUTHORITY = Pubkey.from_string(
"Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"
)
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
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_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
)
SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111") SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111")
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
LAMPORTS_PER_SOL = 1_000_000_000 LAMPORTS_PER_SOL = 1_000_000_000
@@ -44,6 +45,7 @@ LAMPORTS_PER_SOL = 1_000_000_000
RPC_ENDPOINT = "ENTER_YOUR_CHAINSTACK_HTTP_ENDPOINT" RPC_ENDPOINT = "ENTER_YOUR_CHAINSTACK_HTTP_ENDPOINT"
RPC_WEBSOCKET = "ENTER_YOUR_CHAINSTACK_WS_ENDPOINT" RPC_WEBSOCKET = "ENTER_YOUR_CHAINSTACK_WS_ENDPOINT"
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -51,14 +53,17 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> BondingCurveState:
async def get_pump_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
response = await conn.get_account_info(curve_address) response = await conn.get_account_info(curve_address)
if not response.value or not response.value.data: if not response.value or not response.value.data:
raise ValueError("Invalid curve state: No data") raise ValueError("Invalid curve state: No data")
@@ -69,13 +74,24 @@ async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> Bond
return BondingCurveState(data) return BondingCurveState(data)
def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: def calculate_pump_curve_price(curve_state: BondingCurveState) -> float:
if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0:
raise ValueError("Invalid reserve state") raise ValueError("Invalid reserve state")
return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (curve_state.virtual_token_reserves / 10 ** TOKEN_DECIMALS) return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (
curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS
)
async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curve: Pubkey, amount: float, slippage: float = 0.25, max_retries=5):
async def buy_token(
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
amount: float,
slippage: float = 0.25,
max_retries=5,
):
private_key = base58.b58decode("ENTER_PRIVATE_KEY") private_key = base58.b58decode("ENTER_PRIVATE_KEY")
payer = Keypair.from_bytes(private_key) payer = Keypair.from_bytes(private_key)
@@ -96,36 +112,52 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
try: try:
account_info = await client.get_account_info(associated_token_account) account_info = await client.get_account_info(associated_token_account)
if account_info.value is None: if account_info.value is None:
print(f"Creating associated token account (Attempt {ata_attempt + 1})...") print(
f"Creating associated token account (Attempt {ata_attempt + 1})..."
)
create_ata_ix = spl_token.create_associated_token_account( create_ata_ix = spl_token.create_associated_token_account(
payer=payer.pubkey(), payer=payer.pubkey(), owner=payer.pubkey(), mint=mint
owner=payer.pubkey(),
mint=mint
) )
msg = Message([create_ata_ix], payer.pubkey()) msg = Message([create_ata_ix], payer.pubkey())
tx_ata = await client.send_transaction( tx_ata = await client.send_transaction(
Transaction([payer], msg, (await client.get_latest_blockhash()).value.blockhash), Transaction(
opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed) [payer],
) msg,
(await client.get_latest_blockhash()).value.blockhash,
await client.confirm_transaction(tx_ata.value, commitment="confirmed") ),
opts=TxOpts(
skip_preflight=True, preflight_commitment=Confirmed
),
)
await client.confirm_transaction(
tx_ata.value, commitment="confirmed"
)
print("Associated token account created.") print("Associated token account created.")
print(f"Associated token account address: {associated_token_account}") print(
f"Associated token account address: {associated_token_account}"
)
break break
else: else:
print("Associated token account already exists.") print("Associated token account already exists.")
print(f"Associated token account address: {associated_token_account}") print(
f"Associated token account address: {associated_token_account}"
)
break break
except Exception as e: except Exception as e:
print(f"Attempt {ata_attempt + 1} to create associated token account failed: {str(e)}") print(
f"Attempt {ata_attempt + 1} to create associated token account failed: {str(e)}"
)
if ata_attempt < max_retries - 1: if ata_attempt < max_retries - 1:
wait_time = 2 ** ata_attempt wait_time = 2**ata_attempt
print(f"Retrying in {wait_time} seconds...") print(f"Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
print("Max retries reached. Unable to create associated token account.") print(
"Max retries reached. Unable to create associated token account."
)
return return
# Continue with the buy transaction # Continue with the buy transaction
@@ -135,28 +167,58 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False), AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True),
AccountMeta(pubkey=mint, is_signer=False, is_writable=False), AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), AccountMeta(
AccountMeta(pubkey=associated_bonding_curve, is_signer=False, is_writable=True), pubkey=bonding_curve, is_signer=False, is_writable=True
AccountMeta(pubkey=associated_token_account, is_signer=False, is_writable=True), ),
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True), AccountMeta(
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), pubkey=associated_bonding_curve,
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=associated_token_account,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=payer.pubkey(), is_signer=True, is_writable=True
),
AccountMeta(
pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(pubkey=SYSTEM_RENT, is_signer=False, is_writable=False), AccountMeta(pubkey=SYSTEM_RENT, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False), AccountMeta(
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False), pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False
),
] ]
discriminator = struct.pack("<Q", 16927863322537952870) discriminator = struct.pack("<Q", 16927863322537952870)
data = discriminator + struct.pack("<Q", int(token_amount * 10**6)) + struct.pack("<Q", max_amount_lamports) data = (
discriminator
+ struct.pack("<Q", int(token_amount * 10**6))
+ struct.pack("<Q", max_amount_lamports)
)
buy_ix = Instruction(PUMP_PROGRAM, data, accounts) buy_ix = Instruction(PUMP_PROGRAM, data, accounts)
msg = Message([set_compute_unit_price(1_000), buy_ix], payer.pubkey()) msg = Message([set_compute_unit_price(1_000), buy_ix], payer.pubkey())
tx_buy = await client.send_transaction( tx_buy = await client.send_transaction(
Transaction([payer], msg, (await client.get_latest_blockhash()).value.blockhash), Transaction(
opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed) [payer],
) msg,
(await client.get_latest_blockhash()).value.blockhash,
),
opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed),
)
print(f"Transaction sent: https://explorer.solana.com/tx/{tx_buy.value}") print(
f"Transaction sent: https://explorer.solana.com/tx/{tx_buy.value}"
)
await client.confirm_transaction(tx_buy.value, commitment="confirmed") await client.confirm_transaction(tx_buy.value, commitment="confirmed")
print("Transaction confirmed") print("Transaction confirmed")
@@ -165,97 +227,129 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv
except Exception as e: except Exception as e:
print(f"Attempt {attempt + 1} failed: {str(e)[:50]}") print(f"Attempt {attempt + 1} failed: {str(e)[:50]}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
wait_time = 2 ** attempt wait_time = 2**attempt
print(f"Retrying in {wait_time} seconds...") print(f"Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
print("Max retries reached. Unable to complete the transaction.") print("Max retries reached. Unable to complete the transaction.")
def load_idl(file_path): def load_idl(file_path):
with open(file_path, 'r') as f: with open(file_path, "r") as f:
return json.load(f) return json.load(f)
def calculate_discriminator(instruction_name): def calculate_discriminator(instruction_name):
sha = hashlib.sha256() sha = hashlib.sha256()
sha.update(instruction_name.encode('utf-8')) sha.update(instruction_name.encode("utf-8"))
return struct.unpack('<Q', sha.digest()[:8])[0] return struct.unpack("<Q", sha.digest()[:8])[0]
def decode_create_instruction(ix_data, ix_def, accounts): def decode_create_instruction(ix_data, ix_def, accounts):
args = {} args = {}
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': if arg["type"] == "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'] == 'publicKey': elif arg["type"] == "publicKey":
value = base64.b64encode(ix_data[offset:offset+32]).decode('utf-8') value = base64.b64encode(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']}")
args[arg['name']] = value args[arg["name"]] = value
# Add accounts # Add accounts
args['mint'] = str(accounts[0]) args["mint"] = str(accounts[0])
args['bondingCurve'] = str(accounts[2]) args["bondingCurve"] = str(accounts[2])
args['associatedBondingCurve'] = str(accounts[3]) args["associatedBondingCurve"] = str(accounts[3])
args['user'] = str(accounts[7]) args["user"] = str(accounts[7])
return args return args
async def listen_for_create_transaction(): async def listen_for_create_transaction():
idl_path = os.path.join(os.path.dirname(__file__), '..', 'idl', 'pump_fun_idl.json') idl_path = os.path.join(os.path.dirname(__file__), "..", "idl", "pump_fun_idl.json")
idl = load_idl(idl_path) idl = load_idl(idl_path)
create_discriminator = calculate_discriminator("global:create") create_discriminator = calculate_discriminator("global:create")
async with websockets.connect(RPC_WEBSOCKET) as websocket: async with websockets.connect(RPC_WEBSOCKET) as websocket:
subscription_message = json.dumps({ subscription_message = json.dumps(
"jsonrpc": "2.0", {
"id": 1, "jsonrpc": "2.0",
"method": "blockSubscribe", "id": 1,
"params": [ "method": "blockSubscribe",
{"mentionsAccountOrProgram": str(PUMP_PROGRAM)}, "params": [
{ {"mentionsAccountOrProgram": str(PUMP_PROGRAM)},
"commitment": "confirmed", {
"encoding": "base64", "commitment": "confirmed",
"showRewards": False, "encoding": "base64",
"transactionDetails": "full", "showRewards": False,
"maxSupportedTransactionVersion": 0 "transactionDetails": "full",
} "maxSupportedTransactionVersion": 0,
] },
}) ],
}
)
await websocket.send(subscription_message) await websocket.send(subscription_message)
print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}") print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}")
while True: while True:
response = await websocket.recv() response = await websocket.recv()
data = json.loads(response) data = json.loads(response)
if 'method' in data and data['method'] == 'blockNotification': if "method" in data and data["method"] == "blockNotification":
if 'params' in data and 'result' in data['params']: if "params" in data and "result" in data["params"]:
block_data = data['params']['result'] block_data = data["params"]["result"]
if 'value' in block_data and 'block' in block_data['value']: if "value" in block_data and "block" in block_data["value"]:
block = block_data['value']['block'] block = block_data["value"]["block"]
if 'transactions' in block: if "transactions" in block:
for tx in block['transactions']: for tx in block["transactions"]:
if isinstance(tx, dict) and 'transaction' in tx: if isinstance(tx, dict) and "transaction" in tx:
tx_data_decoded = base64.b64decode(tx['transaction'][0]) tx_data_decoded = base64.b64decode(
transaction = VersionedTransaction.from_bytes(tx_data_decoded) tx["transaction"][0]
)
transaction = VersionedTransaction.from_bytes(
tx_data_decoded
)
for ix in transaction.message.instructions: for ix in transaction.message.instructions:
if str(transaction.message.account_keys[ix.program_id_index]) == str(PUMP_PROGRAM): if str(
transaction.message.account_keys[
ix.program_id_index
]
) == str(PUMP_PROGRAM):
ix_data = bytes(ix.data) ix_data = bytes(ix.data)
discriminator = struct.unpack('<Q', ix_data[:8])[0] discriminator = struct.unpack(
"<Q", ix_data[:8]
)[0]
if discriminator == create_discriminator: if discriminator == create_discriminator:
create_ix = next(instr for instr in idl['instructions'] if instr['name'] == 'create') create_ix = next(
account_keys = [str(transaction.message.account_keys[index]) for index in ix.accounts] instr
decoded_args = decode_create_instruction(ix_data, create_ix, account_keys) for instr in idl["instructions"]
if instr["name"] == "create"
)
account_keys = [
str(
transaction.message.account_keys[
index
]
)
for index in ix.accounts
]
decoded_args = (
decode_create_instruction(
ix_data, create_ix, account_keys
)
)
return decoded_args return decoded_args
async def main(): async def main():
print("Waiting for a new token creation...") print("Waiting for a new token creation...")
token_data = await listen_for_create_transaction() token_data = await listen_for_create_transaction()
@@ -266,9 +360,9 @@ async def main():
print(f"Waiting for {sleep_duration_sec} seconds for things to stabilize...") print(f"Waiting for {sleep_duration_sec} seconds for things to stabilize...")
await asyncio.sleep(sleep_duration_sec) await asyncio.sleep(sleep_duration_sec)
mint = Pubkey.from_string(token_data['mint']) mint = Pubkey.from_string(token_data["mint"])
bonding_curve = Pubkey.from_string(token_data['bondingCurve']) bonding_curve = Pubkey.from_string(token_data["bondingCurve"])
associated_bonding_curve = Pubkey.from_string(token_data['associatedBondingCurve']) associated_bonding_curve = Pubkey.from_string(token_data["associatedBondingCurve"])
# Fetch the token price # Fetch the token price
async with AsyncClient(RPC_ENDPOINT) as client: async with AsyncClient(RPC_ENDPOINT) as client:
@@ -281,8 +375,11 @@ async def main():
print(f"Bonding curve address: {bonding_curve}") print(f"Bonding curve address: {bonding_curve}")
print(f"Token price: {token_price_sol:.10f} SOL") print(f"Token price: {token_price_sol:.10f} SOL")
print(f"Buying {amount:.6f} SOL worth of the new token with {slippage*100:.1f}% slippage tolerance...") print(
f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..."
)
await buy_token(mint, bonding_curve, associated_bonding_curve, amount, slippage) await buy_token(mint, bonding_curve, associated_bonding_curve, amount, slippage)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
+81 -29
View File
@@ -1,19 +1,20 @@
import asyncio import asyncio
import json
import base64 import base64
import json
import struct import struct
import base58 import base58
import spl.token.instructions as spl_token
from construct import Flag, Int64ul, Struct
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solana.transaction import Transaction
from solana.rpc.commitment import Confirmed from solana.rpc.commitment import Confirmed
from solders.pubkey import Pubkey
from solders.keypair import Keypair
from solders.instruction import Instruction, AccountMeta
from solana.rpc.types import TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Transaction
from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer from solders.system_program import TransferParams, transfer
from spl.token.instructions import get_associated_token_address from spl.token.instructions import get_associated_token_address
import spl.token.instructions as spl_token
from construct import Struct, Int64ul, Flag
# Here and later all the discriminators are precalculated. See learning-examples/discriminator.py # Here and later all the discriminators are precalculated. See learning-examples/discriminator.py
EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR = struct.pack("<Q", 6966180631402821399)
@@ -22,11 +23,15 @@ TOKEN_DECIMALS = 6
# Global constants # Global constants
PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") PUMP_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
PUMP_EVENT_AUTHORITY = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1") PUMP_EVENT_AUTHORITY = Pubkey.from_string(
"Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"
)
PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
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_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
)
SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111") SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111")
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
LAMPORTS_PER_SOL = 1_000_000_000 LAMPORTS_PER_SOL = 1_000_000_000
@@ -36,6 +41,7 @@ UNIT_BUDGET = 100_000
# RPC endpoint # RPC endpoint
RPC_ENDPOINT = "SOLANA_NODE_RPC_ENDPOINT" RPC_ENDPOINT = "SOLANA_NODE_RPC_ENDPOINT"
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -43,14 +49,17 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> BondingCurveState:
async def get_pump_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
response = await conn.get_account_info(curve_address) response = await conn.get_account_info(curve_address)
if not response.value or not response.value.data: if not response.value or not response.value.data:
raise ValueError("Invalid curve state: No data") raise ValueError("Invalid curve state: No data")
@@ -61,11 +70,15 @@ async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> Bond
return BondingCurveState(data) return BondingCurveState(data)
def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: def calculate_pump_curve_price(curve_state: BondingCurveState) -> float:
if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0:
raise ValueError("Invalid reserve state") raise ValueError("Invalid reserve state")
return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (curve_state.virtual_token_reserves / 10 ** TOKEN_DECIMALS) return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (
curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS
)
async def get_token_balance(conn: AsyncClient, associated_token_account: Pubkey): async def get_token_balance(conn: AsyncClient, associated_token_account: Pubkey):
response = await conn.get_token_account_balance(associated_token_account) response = await conn.get_token_account_balance(associated_token_account)
@@ -73,13 +86,20 @@ async def get_token_balance(conn: AsyncClient, associated_token_account: Pubkey)
return int(response.value.amount) return int(response.value.amount)
return 0 return 0
async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curve: Pubkey, slippage: float = 0.25, max_retries=5):
async def sell_token(
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
slippage: float = 0.25,
max_retries=5,
):
private_key = base58.b58decode("SOLANA_PRIVATE_KEY") private_key = base58.b58decode("SOLANA_PRIVATE_KEY")
payer = Keypair.from_bytes(private_key) payer = Keypair.from_bytes(private_key)
async with AsyncClient(RPC_ENDPOINT) as client: async with AsyncClient(RPC_ENDPOINT) as client:
associated_token_account = get_associated_token_address(payer.pubkey(), mint) associated_token_account = get_associated_token_address(payer.pubkey(), mint)
# Get token balance # Get token balance
token_balance = await get_token_balance(client, associated_token_account) token_balance = await get_token_balance(client, associated_token_account)
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
@@ -98,7 +118,7 @@ async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_cur
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
min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL) min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL)
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")
@@ -109,19 +129,47 @@ async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_cur
AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False), AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True),
AccountMeta(pubkey=mint, is_signer=False, is_writable=False), AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), AccountMeta(
AccountMeta(pubkey=associated_bonding_curve, is_signer=False, is_writable=True), pubkey=bonding_curve, is_signer=False, is_writable=True
AccountMeta(pubkey=associated_token_account, is_signer=False, is_writable=True), ),
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True), AccountMeta(
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), pubkey=associated_bonding_curve,
AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False), is_signer=False,
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), is_writable=True,
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False), ),
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False), AccountMeta(
pubkey=associated_token_account,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=payer.pubkey(), is_signer=True, is_writable=True
),
AccountMeta(
pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM,
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False
),
] ]
discriminator = struct.pack("<Q", 12502976635542562355) discriminator = struct.pack("<Q", 12502976635542562355)
data = discriminator + struct.pack("<Q", amount) + struct.pack("<Q", min_sol_output) data = (
discriminator
+ struct.pack("<Q", amount)
+ struct.pack("<Q", min_sol_output)
)
sell_ix = Instruction(PUMP_PROGRAM, data, accounts) sell_ix = Instruction(PUMP_PROGRAM, data, accounts)
recent_blockhash = await client.get_latest_blockhash() recent_blockhash = await client.get_latest_blockhash()
@@ -144,23 +192,27 @@ async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_cur
except Exception as e: except Exception as e:
print(f"Attempt {attempt + 1} failed: {str(e)}") print(f"Attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff wait_time = 2**attempt # Exponential backoff
print(f"Retrying in {wait_time} seconds...") print(f"Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
print("Max retries reached. Unable to complete the transaction.") print("Max retries reached. Unable to complete the transaction.")
async def main(): async def main():
# Replace these with the actual values for the token you want to sell # Replace these with the actual values for the token you want to sell
mint = Pubkey.from_string("EyLuyWV5N1GVSqLLeumFDWYkmmSkLED5s2xqu37Lpump") mint = Pubkey.from_string("EyLuyWV5N1GVSqLLeumFDWYkmmSkLED5s2xqu37Lpump")
bonding_curve = Pubkey.from_string("AGZLYVwmGL9cXCLN4C3ki9hXGix1kXYjr59B2H7jwRMQ") bonding_curve = Pubkey.from_string("AGZLYVwmGL9cXCLN4C3ki9hXGix1kXYjr59B2H7jwRMQ")
associated_bonding_curve = Pubkey.from_string("74H2bo2hjNnWgf9oDHzsVsu3mcsoiYYWJ3s9dVnL5erV") associated_bonding_curve = Pubkey.from_string(
"74H2bo2hjNnWgf9oDHzsVsu3mcsoiYYWJ3s9dVnL5erV"
)
slippage = 0.25 # 25% slippage tolerance slippage = 0.25 # 25% slippage tolerance
print(f"Bonding curve address: {bonding_curve}") print(f"Bonding curve address: {bonding_curve}")
print(f"Selling tokens with {slippage*100:.1f}% slippage tolerance...") print(f"Selling tokens with {slippage * 100:.1f}% slippage tolerance...")
await sell_token(mint, bonding_curve, associated_bonding_curve, slippage) await sell_token(mint, bonding_curve, associated_bonding_curve, slippage)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
+6
View File
@@ -0,0 +1,6 @@
[tool.black]
line-length = 88
target-version = ['py39']
[tool.isort]
profile = "black"
+6 -5
View File
@@ -1,7 +1,8 @@
base58>=2.1.1 base58>=2.1.1
borsh-construct>=0.1.0 borsh-construct>=0.1.0
construct>=2.10.68 construct>=2.10.67
construct-typing>=0.5.6 construct-typing>=0.5.2
solana==0.34.3 solana==0.36.6
solders>=0.21.0 solders>=0.26.0
websockets>=10.4 websockets>=15.0
python-dotenv>=1.0.1
+68 -28
View File
@@ -1,24 +1,21 @@
import asyncio import asyncio
import json
import base64 import base64
import json
import struct import struct
import base58
from typing import Final from typing import Final
import base58
import spl.token.instructions as spl_token
from construct import Flag, Int64ul, Struct
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solana.transaction import Transaction
from solana.rpc.commitment import Confirmed from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Transaction
from solders.pubkey import Pubkey from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair from solders.keypair import Keypair
from solders.instruction import Instruction, AccountMeta from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer from solders.system_program import TransferParams, transfer
from spl.token.instructions import get_associated_token_address from spl.token.instructions import get_associated_token_address
import spl.token.instructions as spl_token
from construct import Struct, Int64ul, Flag
from config import * from config import *
@@ -26,6 +23,7 @@ from config import *
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399) EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399)
TOKEN_DECIMALS: Final[int] = 6 TOKEN_DECIMALS: Final[int] = 6
class BondingCurveState: class BondingCurveState:
_STRUCT = Struct( _STRUCT = Struct(
"virtual_token_reserves" / Int64ul, "virtual_token_reserves" / Int64ul,
@@ -33,14 +31,17 @@ class BondingCurveState:
"real_token_reserves" / Int64ul, "real_token_reserves" / Int64ul,
"real_sol_reserves" / Int64ul, "real_sol_reserves" / Int64ul,
"token_total_supply" / Int64ul, "token_total_supply" / Int64ul,
"complete" / Flag "complete" / Flag,
) )
def __init__(self, data: bytes) -> None: def __init__(self, data: bytes) -> None:
parsed = self._STRUCT.parse(data[8:]) parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed) self.__dict__.update(parsed)
async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> BondingCurveState:
async def get_pump_curve_state(
conn: AsyncClient, curve_address: Pubkey
) -> BondingCurveState:
response = await conn.get_account_info(curve_address) response = await conn.get_account_info(curve_address)
if not response.value or not response.value.data: if not response.value or not response.value.data:
raise ValueError("Invalid curve state: No data") raise ValueError("Invalid curve state: No data")
@@ -51,11 +52,15 @@ async def get_pump_curve_state(conn: AsyncClient, curve_address: Pubkey) -> Bond
return BondingCurveState(data) return BondingCurveState(data)
def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: def calculate_pump_curve_price(curve_state: BondingCurveState) -> float:
if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0:
raise ValueError("Invalid reserve state") raise ValueError("Invalid reserve state")
return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (curve_state.virtual_token_reserves / 10 ** TOKEN_DECIMALS) return (curve_state.virtual_sol_reserves / LAMPORTS_PER_SOL) / (
curve_state.virtual_token_reserves / 10**TOKEN_DECIMALS
)
async def get_token_balance(conn: AsyncClient, associated_token_account: Pubkey): async def get_token_balance(conn: AsyncClient, associated_token_account: Pubkey):
response = await conn.get_token_account_balance(associated_token_account) response = await conn.get_token_account_balance(associated_token_account)
@@ -63,13 +68,20 @@ async def get_token_balance(conn: AsyncClient, associated_token_account: Pubkey)
return int(response.value.amount) return int(response.value.amount)
return 0 return 0
async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curve: Pubkey, slippage: float = 0.25, max_retries=5):
async def sell_token(
mint: Pubkey,
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
slippage: float = 0.25,
max_retries=5,
):
private_key = base58.b58decode(PRIVATE_KEY) private_key = base58.b58decode(PRIVATE_KEY)
payer = Keypair.from_bytes(private_key) payer = Keypair.from_bytes(private_key)
async with AsyncClient(RPC_ENDPOINT) as client: async with AsyncClient(RPC_ENDPOINT) as client:
associated_token_account = get_associated_token_address(payer.pubkey(), mint) associated_token_account = get_associated_token_address(payer.pubkey(), mint)
# Get token balance # Get token balance
token_balance = await get_token_balance(client, associated_token_account) token_balance = await get_token_balance(client, associated_token_account)
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
@@ -88,7 +100,7 @@ async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_cur
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
min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL) min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL)
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")
@@ -98,19 +110,47 @@ async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_cur
AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False), AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True),
AccountMeta(pubkey=mint, is_signer=False, is_writable=False), AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), AccountMeta(
AccountMeta(pubkey=associated_bonding_curve, is_signer=False, is_writable=True), pubkey=bonding_curve, is_signer=False, is_writable=True
AccountMeta(pubkey=associated_token_account, is_signer=False, is_writable=True), ),
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True), AccountMeta(
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), pubkey=associated_bonding_curve,
AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False), is_signer=False,
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), is_writable=True,
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False), ),
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False), AccountMeta(
pubkey=associated_token_account,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=payer.pubkey(), is_signer=True, is_writable=True
),
AccountMeta(
pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM,
is_signer=False,
is_writable=False,
),
AccountMeta(
pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False
),
] ]
discriminator = struct.pack("<Q", 12502976635542562355) discriminator = struct.pack("<Q", 12502976635542562355)
data = discriminator + struct.pack("<Q", amount) + struct.pack("<Q", min_sol_output) data = (
discriminator
+ struct.pack("<Q", amount)
+ struct.pack("<Q", min_sol_output)
)
sell_ix = Instruction(PUMP_PROGRAM, data, accounts) sell_ix = Instruction(PUMP_PROGRAM, data, accounts)
recent_blockhash = await client.get_latest_blockhash() recent_blockhash = await client.get_latest_blockhash()
@@ -134,8 +174,8 @@ async def sell_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_cur
except Exception as e: except Exception as e:
print(f"Attempt {attempt + 1} failed: {str(e)}") print(f"Attempt {attempt + 1} failed: {str(e)}")
if attempt < max_retries - 1: if attempt < max_retries - 1:
wait_time = 2 ** attempt wait_time = 2**attempt
print(f"Retrying in {wait_time} seconds...") print(f"Retrying in {wait_time} seconds...")
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
print("Max retries reached. Unable to complete the transaction.") print("Max retries reached. Unable to complete the transaction.")
+97 -43
View File
@@ -1,89 +1,107 @@
import asyncio
import json
import base64
import struct
import base58
import hashlib
import websockets
import os
import argparse import argparse
import asyncio
import base64
import hashlib
import json
import os
import struct
from datetime import datetime from datetime import datetime
import base58
import spl.token.instructions as spl_token
import websockets
from solana.rpc.async_api import AsyncClient from solana.rpc.async_api import AsyncClient
from solana.transaction import Transaction
from solana.rpc.commitment import Confirmed from solana.rpc.commitment import Confirmed
from solana.rpc.types import TxOpts from solana.rpc.types import TxOpts
from solana.transaction import Transaction
from solders.pubkey import Pubkey from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair from solders.keypair import Keypair
from solders.instruction import Instruction, AccountMeta from solders.pubkey import Pubkey
from solders.system_program import TransferParams, transfer from solders.system_program import TransferParams, transfer
from solders.transaction import VersionedTransaction from solders.transaction import VersionedTransaction
from spl.token.instructions import get_associated_token_address from spl.token.instructions import get_associated_token_address
import spl.token.instructions as spl_token
from buy import (
buy_token,
calculate_pump_curve_price,
get_pump_curve_state,
listen_for_create_transaction,
)
from config import * from config import *
# Import functions from buy.py
from buy import get_pump_curve_state, calculate_pump_curve_price, buy_token, listen_for_create_transaction
# Import functions from sell.py
from sell import sell_token from sell import sell_token
def log_trade(action, token_data, price, tx_hash): def log_trade(action, token_data, price, tx_hash):
os.makedirs("trades", exist_ok=True) os.makedirs("trades", exist_ok=True)
log_entry = { log_entry = {
"timestamp": datetime.utcnow().isoformat(), "timestamp": datetime.utcnow().isoformat(),
"action": action, "action": action,
"token_address": token_data['mint'], "token_address": token_data["mint"],
"price": price, "price": price,
"tx_hash": tx_hash "tx_hash": tx_hash,
} }
with open("trades/trades.log", 'a') as log_file: with open("trades/trades.log", "a") as log_file:
json.dump(log_entry, log_file) json.dump(log_entry, log_file)
log_file.write("\n") log_file.write("\n")
async def trade(websocket=None, match_string=None, bro_address=None, marry_mode=False, yolo_mode=False):
async def trade(
websocket=None,
match_string=None,
bro_address=None,
marry_mode=False,
yolo_mode=False,
):
if websocket is None: if websocket is None:
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
await _trade(websocket, match_string, bro_address, marry_mode, yolo_mode) await _trade(websocket, match_string, bro_address, marry_mode, yolo_mode)
else: else:
await _trade(websocket, match_string, bro_address, marry_mode, yolo_mode) await _trade(websocket, match_string, bro_address, marry_mode, yolo_mode)
async def _trade(websocket, match_string=None, bro_address=None, marry_mode=False, yolo_mode=False):
async def _trade(
websocket, match_string=None, bro_address=None, marry_mode=False, yolo_mode=False
):
while True: while True:
print("Waiting for a new token creation...") print("Waiting for a new token creation...")
token_data = await listen_for_create_transaction(websocket) token_data = await listen_for_create_transaction(websocket)
print("New token created:") print("New token created:")
print(json.dumps(token_data, indent=2)) print(json.dumps(token_data, indent=2))
if match_string and not (match_string.lower() in token_data['name'].lower() or match_string.lower() in token_data['symbol'].lower()): if match_string and not (
match_string.lower() in token_data["name"].lower()
or match_string.lower() in token_data["symbol"].lower()
):
print(f"Token does not match the criteria '{match_string}'. Skipping...") print(f"Token does not match the criteria '{match_string}'. Skipping...")
if not yolo_mode: if not yolo_mode:
break break
continue continue
if bro_address and token_data['user'] != bro_address: if bro_address and token_data["user"] != bro_address:
print(f"Token not created by the specified user '{bro_address}'. Skipping...") print(
f"Token not created by the specified user '{bro_address}'. Skipping..."
)
if not yolo_mode: if not yolo_mode:
break break
continue continue
# Save token information to a .txt file in the "trades" directory # Save token information to a .txt file in the "trades" directory
mint_address = token_data['mint'] mint_address = token_data["mint"]
os.makedirs("trades", exist_ok=True) os.makedirs("trades", exist_ok=True)
file_name = os.path.join("trades", f"{mint_address}.txt") file_name = os.path.join("trades", f"{mint_address}.txt")
with open(file_name, 'w') as file: with open(file_name, "w") as file:
file.write(json.dumps(token_data, indent=2)) file.write(json.dumps(token_data, indent=2))
print(f"Token information saved to {file_name}") print(f"Token information saved to {file_name}")
print("Waiting for 15 seconds for things to stabilize...") print("Waiting for 15 seconds for things to stabilize...")
await asyncio.sleep(15) await asyncio.sleep(15)
mint = Pubkey.from_string(token_data['mint']) mint = Pubkey.from_string(token_data["mint"])
bonding_curve = Pubkey.from_string(token_data['bondingCurve']) bonding_curve = Pubkey.from_string(token_data["bondingCurve"])
associated_bonding_curve = Pubkey.from_string(token_data['associatedBondingCurve']) associated_bonding_curve = Pubkey.from_string(
token_data["associatedBondingCurve"]
)
# Fetch the token price # Fetch the token price
async with AsyncClient(RPC_ENDPOINT) as client: async with AsyncClient(RPC_ENDPOINT) as client:
@@ -92,8 +110,12 @@ async def _trade(websocket, match_string=None, bro_address=None, marry_mode=Fals
print(f"Bonding curve address: {bonding_curve}") print(f"Bonding curve address: {bonding_curve}")
print(f"Token price: {token_price_sol:.10f} SOL") print(f"Token price: {token_price_sol:.10f} SOL")
print(f"Buying {BUY_AMOUNT:.6f} SOL worth of the new token with {BUY_SLIPPAGE*100:.1f}% slippage tolerance...") print(
buy_tx_hash = await buy_token(mint, bonding_curve, associated_bonding_curve, BUY_AMOUNT, BUY_SLIPPAGE) f"Buying {BUY_AMOUNT:.6f} SOL worth of the new token with {BUY_SLIPPAGE * 100:.1f}% slippage tolerance..."
)
buy_tx_hash = await buy_token(
mint, bonding_curve, associated_bonding_curve, BUY_AMOUNT, BUY_SLIPPAGE
)
if buy_tx_hash: if buy_tx_hash:
log_trade("buy", token_data, token_price_sol, str(buy_tx_hash)) log_trade("buy", token_data, token_price_sol, str(buy_tx_hash))
else: else:
@@ -103,8 +125,12 @@ async def _trade(websocket, match_string=None, bro_address=None, marry_mode=Fals
print("Waiting for 20 seconds before selling...") print("Waiting for 20 seconds before selling...")
await asyncio.sleep(20) await asyncio.sleep(20)
print(f"Selling tokens with {SELL_SLIPPAGE*100:.1f}% slippage tolerance...") print(
sell_tx_hash = await sell_token(mint, bonding_curve, associated_bonding_curve, SELL_SLIPPAGE) f"Selling tokens with {SELL_SLIPPAGE * 100:.1f}% slippage tolerance..."
)
sell_tx_hash = await sell_token(
mint, bonding_curve, associated_bonding_curve, SELL_SLIPPAGE
)
if sell_tx_hash: if sell_tx_hash:
log_trade("sell", token_data, token_price_sol, str(sell_tx_hash)) log_trade("sell", token_data, token_price_sol, str(sell_tx_hash))
else: else:
@@ -115,6 +141,7 @@ async def _trade(websocket, match_string=None, bro_address=None, marry_mode=Fals
if not yolo_mode: if not yolo_mode:
break break
async def main(yolo_mode=False, match_string=None, bro_address=None, marry_mode=False): async def main(yolo_mode=False, match_string=None, bro_address=None, marry_mode=False):
if yolo_mode: if yolo_mode:
while True: while True:
@@ -122,13 +149,21 @@ async def main(yolo_mode=False, match_string=None, bro_address=None, marry_mode=
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
while True: while True:
try: try:
await trade(websocket, match_string, bro_address, marry_mode, yolo_mode) await trade(
websocket,
match_string,
bro_address,
marry_mode,
yolo_mode,
)
except websockets.exceptions.ConnectionClosed: except websockets.exceptions.ConnectionClosed:
print("WebSocket connection closed. Reconnecting...") print("WebSocket connection closed. Reconnecting...")
break break
except Exception as e: except Exception as e:
print(f"An error occurred: {e}") print(f"An error occurred: {e}")
print("Waiting for 5 seconds before looking for the next token...") print(
"Waiting for 5 seconds before looking for the next token..."
)
await asyncio.sleep(5) await asyncio.sleep(5)
except Exception as e: except Exception as e:
print(f"Connection error: {e}") print(f"Connection error: {e}")
@@ -139,19 +174,38 @@ async def main(yolo_mode=False, match_string=None, bro_address=None, marry_mode=
async with websockets.connect(WSS_ENDPOINT) as websocket: async with websockets.connect(WSS_ENDPOINT) as websocket:
await trade(websocket, match_string, bro_address, marry_mode, yolo_mode) await trade(websocket, match_string, bro_address, marry_mode, yolo_mode)
async def ping_websocket(websocket): async def ping_websocket(websocket):
while True: while True:
try: try:
await websocket.ping() await websocket.ping()
await asyncio.sleep(20) # Send a ping every 20 seconds await asyncio.sleep(20) # Send a ping every 20 seconds
except: except:
break break
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Trade tokens on Solana.") parser = argparse.ArgumentParser(description="Trade tokens on Solana.")
parser.add_argument("--yolo", action="store_true", help="Run in YOLO mode (continuous trading)") parser.add_argument(
parser.add_argument("--match", type=str, help="Only trade tokens with names or symbols matching this string") "--yolo", action="store_true", help="Run in YOLO mode (continuous trading)"
parser.add_argument("--bro", type=str, help="Only trade tokens created by this user address") )
parser.add_argument("--marry", action="store_true", help="Only buy tokens, skip selling") parser.add_argument(
"--match",
type=str,
help="Only trade tokens with names or symbols matching this string",
)
parser.add_argument(
"--bro", type=str, help="Only trade tokens created by this user address"
)
parser.add_argument(
"--marry", action="store_true", help="Only buy tokens, skip selling"
)
args = parser.parse_args() args = parser.parse_args()
asyncio.run(main(yolo_mode=args.yolo, match_string=args.match, bro_address=args.bro, marry_mode=args.marry)) asyncio.run(
main(
yolo_mode=args.yolo,
match_string=args.match,
bro_address=args.bro,
marry_mode=args.marry,
)
)