diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ade398d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +RPC_ENDPOINT=... +WSS_ENDPOINT=... +PRIVATE_KEY=... \ No newline at end of file diff --git a/.gitignore b/.gitignore index efa407c..55804a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.pylintrc +.ruff_cache + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..87a399e --- /dev/null +++ b/.vscode/settings.json @@ -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" + } +} \ No newline at end of file diff --git a/buy.py b/buy.py index 0d39617..4e35ee1 100644 --- a/buy.py +++ b/buy.py @@ -1,34 +1,32 @@ import asyncio -import json import base64 -import struct -import base58 import hashlib -import websockets +import json +import struct 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.transaction import Transaction from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts - -from solders.pubkey import Pubkey +from solana.transaction import Transaction +from solders.instruction import AccountMeta, Instruction 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.transaction import VersionedTransaction - from spl.token.instructions import get_associated_token_address -import spl.token.instructions as spl_token from config import * -from construct import Struct, Int64ul, Flag - # Here and later all the discriminators are precalculated. See learning-examples/discriminator.py EXPECTED_DISCRIMINATOR = struct.pack(" None: parsed = self._STRUCT.parse(data[8:]) 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) if not response.value or not response.value.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) + def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: 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) payer = Keypair.from_bytes(private_key) @@ -81,11 +93,11 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv try: account_info = await client.get_account_info(associated_token_account) 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( - payer=payer.pubkey(), - owner=payer.pubkey(), - mint=mint + payer=payer.pubkey(), owner=payer.pubkey(), mint=mint ) create_ata_tx = Transaction() 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 await client.send_transaction(create_ata_tx, payer) print("Associated token account created.") - print(f"Associated token account address: {associated_token_account}") + print( + f"Associated token account address: {associated_token_account}" + ) break else: 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 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: - wait_time = 2 ** ata_attempt + wait_time = 2**ata_attempt print(f"Retrying in {wait_time} seconds...") await asyncio.sleep(wait_time) else: - print("Max retries reached. Unable to create associated token account.") + print( + "Max retries reached. Unable to create associated token account." + ) return # 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_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=mint, is_signer=False, is_writable=False), - AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), - AccountMeta(pubkey=associated_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(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), + AccountMeta( + pubkey=bonding_curve, is_signer=False, is_writable=True + ), + AccountMeta( + pubkey=associated_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( + 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=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False), - AccountMeta(pubkey=PUMP_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(" 0: - tx_signature = tx['transaction'][0] - elif isinstance(tx['transaction'], dict) and 'signatures' in tx['transaction']: - tx_signature = tx['transaction']['signatures'][0] + if isinstance(tx, dict) and "transaction" in tx: + if ( + isinstance(tx["transaction"], list) + and len(tx["transaction"]) > 0 + ): + tx_signature = tx["transaction"][0] + elif ( + isinstance(tx["transaction"], dict) + and "signatures" in tx["transaction"] + ): + tx_signature = tx["transaction"][ + "signatures" + ][0] else: continue await save_transaction(tx, tx_signature) - elif 'result' in data: + elif "result" in data: print(f"Subscription confirmed") except Exception as e: print(f"An error occurred: {str(e)}") + if __name__ == "__main__": - asyncio.run(listen_for_transactions()) \ No newline at end of file + asyncio.run(listen_for_transactions()) diff --git a/learning-examples/calculate_discriminator.py b/learning-examples/calculate_discriminator.py index 438262c..1671a22 100644 --- a/learning-examples/calculate_discriminator.py +++ b/learning-examples/calculate_discriminator.py @@ -5,21 +5,23 @@ import struct # Set the instruction name here instruction_name = "account:BondingCurve" + def calculate_discriminator(instruction_name): # Create a SHA256 hash object sha = hashlib.sha256() - + # 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 discriminator_bytes = sha.digest()[:8] - + # Convert the bytes to a 64-bit unsigned integer (little-endian) - discriminator = struct.unpack(' None: parsed = self._STRUCT.parse(data[8:]) 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 """ - return Pubkey.find_program_address( - [ - b"bonding-curve", - bytes(mint) - ], - program_id - ) + return Pubkey.find_program_address([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) if not response.value or not response.value.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) + async def check_token_status(mint_address: str) -> None: try: mint = Pubkey.from_string(mint_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("-" * 50) print(f"Token Mint: {mint}") print(f"Associated Bonding Curve: {bonding_curve_address}") print(f"Bump Seed: {bump}") print("-" * 50) - + # Check completion status async with AsyncClient(RPC_ENDPOINT) as client: 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("-" * 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: - 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) - + except ValueError as e: print(f"\nError accessing bonding curve: {e}") - + except ValueError as e: print(f"\nError: Invalid address format - {e}") except Exception as e: print(f"\nUnexpected error: {e}") + def main(): - parser = argparse.ArgumentParser(description='Check token bonding curve status') - parser.add_argument('mint_address', help='The token mint address') - + parser = argparse.ArgumentParser(description="Check token bonding curve status") + parser.add_argument("mint_address", help="The token mint address") + args = parser.parse_args() asyncio.run(check_token_status(args.mint_address)) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/learning-examples/compute_associated_bonding_curve.py b/learning-examples/compute_associated_bonding_curve.py index 3caa9aa..d354eb1 100644 --- a/learning-examples/compute_associated_bonding_curve.py +++ b/learning-examples/compute_associated_bonding_curve.py @@ -1,52 +1,51 @@ -import sys import os +import sys + 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 + def get_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]: """ Derives the bonding curve address for a given mint """ - return Pubkey.find_program_address( - [ - b"bonding-curve", - bytes(mint) - ], - program_id - ) + return Pubkey.find_program_address([b"bonding-curve", bytes(mint)], program_id) + def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey: """ Find the associated bonding curve for a given mint and bonding curve. 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_TOKEN_PROGRAM as TOKEN_PROGRAM_ID + derived_address, _ = Pubkey.find_program_address( [ bytes(bonding_curve), bytes(TOKEN_PROGRAM_ID), - bytes(mint), + bytes(mint), ], - ATA_PROGRAM_ID + ATA_PROGRAM_ID, ) return derived_address -def main(): +def main(): mint_address = input("Enter the token mint address: ") - + try: mint = Pubkey.from_string(mint_address) - + bonding_curve_address, bump = get_bonding_curve_address(mint, PUMP_PROGRAM) - + # 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("-" * 50) print(f"Token Mint: {mint}") @@ -54,9 +53,10 @@ def main(): print(f"Associated Bonding Curve: {associated_bonding_curve}") print(f"Bonding Curve Bump: {bump}") print("-" * 50) - + except ValueError as e: print(f"Error: Invalid address format - {str(e)}") + if __name__ == "__main__": main() diff --git a/learning-examples/decode_from_blockSubscribe.py b/learning-examples/decode_from_blockSubscribe.py index f0a87e4..4124568 100644 --- a/learning-examples/decode_from_blockSubscribe.py +++ b/learning-examples/decode_from_blockSubscribe.py @@ -1,59 +1,65 @@ import base64 +import hashlib import json import struct -import hashlib -from solana.transaction import Transaction -from solders.transaction import VersionedTransaction -from solders.pubkey import Pubkey import sys +from solana.transaction import Transaction +from solders.pubkey import Pubkey +from solders.transaction import VersionedTransaction + + def load_idl(file_path): - with open(file_path, 'r') as f: + with open(file_path, "r") as f: return json.load(f) + def load_transaction(file_path): - with open(file_path, 'r') as f: + with open(file_path, "r") as f: data = json.load(f) return data + def decode_instruction(ix_data, ix_def): args = {} offset = 8 # Skip 8-byte discriminator - for arg in ix_def['args']: - if arg['type'] == 'u64': - value = struct.unpack_from('") sys.exit(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) decoded_instructions = decode_transaction(tx_data, idl) -print(json.dumps(decoded_instructions, indent=2)) \ No newline at end of file +print(json.dumps(decoded_instructions, indent=2)) diff --git a/learning-examples/decode_from_getAccountInfo.py b/learning-examples/decode_from_getAccountInfo.py index 7dd5e1c..1a62ac3 100644 --- a/learning-examples/decode_from_getAccountInfo.py +++ b/learning-examples/decode_from_getAccountInfo.py @@ -1,12 +1,14 @@ -import json import base64 +import json import struct -from construct import Struct, Int64ul, Flag + +from construct import Flag, Int64ul, Struct LAMPORTS_PER_SOL = 1_000_000_000 TOKEN_DECIMALS = 6 EXPECTED_DISCRIMINATOR = struct.pack(" None: parsed = self._STRUCT.parse(data[8:]) self.__dict__.update(parsed) + def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: 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: 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") return BondingCurveState(decoded_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) # Extract the base64 encoded data -encoded_data = json_data['result']['value']['data'][0] +encoded_data = json_data["result"]["value"]["data"][0] # Decode the 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" Token Total Supply: {bonding_curve_state.token_total_supply}") print(f" Complete: {bonding_curve_state.complete}") -print(f"\nToken Price: {token_price_sol:.10f} SOL") \ No newline at end of file +print(f"\nToken Price: {token_price_sol:.10f} SOL") diff --git a/learning-examples/decode_from_getTransaction.py b/learning-examples/decode_from_getTransaction.py index 519f146..5be9b6d 100644 --- a/learning-examples/decode_from_getTransaction.py +++ b/learning-examples/decode_from_getTransaction.py @@ -1,12 +1,11 @@ +import base64 import json +import struct +import sys + import base58 from solana.transaction import Transaction from solders.pubkey import Pubkey -import struct -import sys -import base64 - -import sys if len(sys.argv) != 2: print("Usage: python decode_getTransaction.py ") @@ -15,18 +14,19 @@ if len(sys.argv) != 2: tx_file_path = sys.argv[1] # 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) # 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) # Extract the transaction data -tx_data = tx_log['result']['transaction'] +tx_data = tx_log["result"]["transaction"] print(json.dumps(tx_data, indent=2)) + def decode_create_instruction(data): # The Create instruction has 3 string arguments: name, symbol, uri offset = 8 # Skip the 8-byte discriminator @@ -34,59 +34,61 @@ def decode_create_instruction(data): for _ in range(3): length = struct.unpack_from(" None: parsed = self._STRUCT.parse(data[8:]) 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) if not response.value or not response.value.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) + def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: 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: try: @@ -63,5 +71,6 @@ async def main() -> None: except Exception as e: print(f"An unexpected error occurred: {e}") + if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/learning-examples/listen_create_from_blocksubscribe.py b/learning-examples/listen_create_from_blocksubscribe.py index d3d726a..8233b51 100644 --- a/learning-examples/listen_create_from_blocksubscribe.py +++ b/learning-examples/listen_create_from_blocksubscribe.py @@ -1,68 +1,74 @@ import asyncio -import json -import websockets import base64 -import struct import hashlib -import sys +import json import os -from solders.transaction import VersionedTransaction -from solders.pubkey import Pubkey +import struct +import sys + +import websockets +from solders.pubkey import Pubkey +from solders.transaction import VersionedTransaction + +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): - with open(file_path, 'r') as f: + with open(file_path, "r") as f: return json.load(f) + def decode_create_instruction(ix_data, ix_def, accounts): args = {} offset = 8 # Skip 8-byte discriminator - for arg in ix_def['args']: - if arg['type'] == 'string': - length = struct.unpack_from(' Pubkey: """ @@ -27,16 +31,20 @@ def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey bytes(TOKEN_PROGRAM_ID), bytes(mint), ], - ATA_PROGRAM_ID + ATA_PROGRAM_ID, ) return derived_address + # 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) # 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): if len(data) < 8: @@ -46,23 +54,23 @@ def parse_create_instruction(data): # Parse fields based on CreateEvent structure fields = [ - ('name', 'string'), - ('symbol', 'string'), - ('uri', 'string'), - ('mint', 'publicKey'), - ('bondingCurve', 'publicKey'), - ('user', 'publicKey'), + ("name", "string"), + ("symbol", "string"), + ("uri", "string"), + ("mint", "publicKey"), + ("bondingCurve", "publicKey"), + ("user", "publicKey"), ] try: for field_name, field_type in fields: - if field_type == 'string': - length = struct.unpack(' 18: token_address = account_keys[18] liquidity_address = account_keys[2] - + print(f"\nSignature: {signature}") print(f"Token Address: {token_address}") print(f"Liquidity Address: {liquidity_address}") print("=" * 50) else: print(f"\nError: Not enough account keys (found {len(account_keys)})") - + except Exception as e: print(f"\nError: {str(e)}") + async def listen_for_events(): while True: try: async with websockets.connect(WSS_ENDPOINT) as websocket: - subscription_message = json.dumps({ - "jsonrpc": "2.0", - "id": 1, - "method": "blockSubscribe", - "params": [ - {"mentionsAccountOrProgram": str(PUMP_LIQUIDITY_MIGRATOR)}, - { - "commitment": "confirmed", - "encoding": "json", - "showRewards": False, - "transactionDetails": "full", - "maxSupportedTransactionVersion": 0 - } - ] - }) - + subscription_message = json.dumps( + { + "jsonrpc": "2.0", + "id": 1, + "method": "blockSubscribe", + "params": [ + {"mentionsAccountOrProgram": str(PUMP_LIQUIDITY_MIGRATOR)}, + { + "commitment": "confirmed", + "encoding": "json", + "showRewards": False, + "transactionDetails": "full", + "maxSupportedTransactionVersion": 0, + }, + ], + } + ) + await websocket.send(subscription_message) response = await websocket.recv() print(f"Subscription response: {response}") @@ -61,32 +66,43 @@ async def listen_for_events(): try: response = await asyncio.wait_for(websocket.recv(), timeout=30) data = json.loads(response) - - if 'method' in data and data['method'] == 'blockNotification': - if 'params' in data and 'result' in data['params']: - block_data = data['params']['result'] - if 'value' in block_data and 'block' in block_data['value']: - block = block_data['value']['block'] - if 'transactions' in block: - for tx in block['transactions']: - logs = tx.get('meta', {}).get('logMessages', []) - + + if "method" in data and data["method"] == "blockNotification": + if "params" in data and "result" in data["params"]: + block_data = data["params"]["result"] + if ( + "value" in block_data + and "block" in block_data["value"] + ): + block = block_data["value"]["block"] + if "transactions" in block: + for tx in block["transactions"]: + logs = tx.get("meta", {}).get( + "logMessages", [] + ) + # Check for initialize2 instruction for log in logs: - if "Program log: initialize2: InitializeInstruction2" in log: - print("Found initialize2 instruction!") + if ( + "Program log: initialize2: InitializeInstruction2" + in log + ): + print( + "Found initialize2 instruction!" + ) process_initialize2_transaction(tx) break - + except asyncio.TimeoutError: print("\nChecking connection...") print("Connection alive") continue - + except Exception as e: print(f"\nConnection error: {str(e)}") print("Retrying in 5 seconds...") await asyncio.sleep(5) + if __name__ == "__main__": - asyncio.run(listen_for_events()) \ No newline at end of file + asyncio.run(listen_for_events()) diff --git a/learning-examples/manual_buy.py b/learning-examples/manual_buy.py index 2482f5b..91d8616 100644 --- a/learning-examples/manual_buy.py +++ b/learning-examples/manual_buy.py @@ -1,29 +1,26 @@ import asyncio -import json import base64 -import struct -import base58 +import hashlib +import json 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.commitment import Confirmed from solana.rpc.types import TxOpts - -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 solana.transaction import Message from solders.compute_budget import set_compute_unit_price - -import spl.token.instructions as spl_token +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.transaction import Transaction, VersionedTransaction 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 EXPECTED_DISCRIMINATOR = struct.pack(" None: parsed = self._STRUCT.parse(data[8:]) 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) if not response.value or not response.value.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) + def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: 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") payer = Keypair.from_bytes(private_key) @@ -96,36 +112,52 @@ async def buy_token(mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curv try: account_info = await client.get_account_info(associated_token_account) 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( - payer=payer.pubkey(), - owner=payer.pubkey(), - mint=mint + payer=payer.pubkey(), owner=payer.pubkey(), mint=mint ) msg = Message([create_ata_ix], payer.pubkey()) tx_ata = await client.send_transaction( - Transaction([payer], msg, (await client.get_latest_blockhash()).value.blockhash), - opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed) - ) - - await client.confirm_transaction(tx_ata.value, commitment="confirmed") + Transaction( + [payer], + msg, + (await client.get_latest_blockhash()).value.blockhash, + ), + opts=TxOpts( + skip_preflight=True, preflight_commitment=Confirmed + ), + ) + + await client.confirm_transaction( + tx_ata.value, commitment="confirmed" + ) print("Associated token account created.") - print(f"Associated token account address: {associated_token_account}") + print( + f"Associated token account address: {associated_token_account}" + ) break else: 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 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: - wait_time = 2 ** ata_attempt + wait_time = 2**ata_attempt print(f"Retrying in {wait_time} seconds...") await asyncio.sleep(wait_time) else: - print("Max retries reached. Unable to create associated token account.") + print( + "Max retries reached. Unable to create associated token account." + ) return # 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_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=mint, is_signer=False, is_writable=False), - AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), - AccountMeta(pubkey=associated_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(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), + AccountMeta( + pubkey=bonding_curve, is_signer=False, is_writable=True + ), + AccountMeta( + pubkey=associated_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( + 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=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False), - AccountMeta(pubkey=PUMP_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(" None: parsed = self._STRUCT.parse(data[8:]) 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) if not response.value or not response.value.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) + def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: 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): 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 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") payer = Keypair.from_bytes(private_key) async with AsyncClient(RPC_ENDPOINT) as client: associated_token_account = get_associated_token_address(payer.pubkey(), mint) - + # Get token balance token_balance = await get_token_balance(client, associated_token_account) 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) slippage_factor = 1 - slippage min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL) - + print(f"Selling {token_balance_decimal} tokens") print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL") @@ -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_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=mint, is_signer=False, is_writable=False), - AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), - AccountMeta(pubkey=associated_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(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), + AccountMeta( + pubkey=bonding_curve, is_signer=False, is_writable=True + ), + AccountMeta( + pubkey=associated_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( + 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("=2.1.1 borsh-construct>=0.1.0 -construct>=2.10.68 -construct-typing>=0.5.6 -solana==0.34.3 -solders>=0.21.0 -websockets>=10.4 +construct>=2.10.67 +construct-typing>=0.5.2 +solana==0.36.6 +solders>=0.26.0 +websockets>=15.0 +python-dotenv>=1.0.1 \ No newline at end of file diff --git a/sell.py b/sell.py index f35efb2..9c57e20 100644 --- a/sell.py +++ b/sell.py @@ -1,24 +1,21 @@ import asyncio -import json import base64 +import json import struct -import base58 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.transaction import Transaction from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts - -from solders.pubkey import Pubkey +from solana.transaction import Transaction +from solders.instruction import AccountMeta, Instruction from solders.keypair import Keypair -from solders.instruction import Instruction, AccountMeta +from solders.pubkey import Pubkey from solders.system_program import TransferParams, transfer - 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 * @@ -26,6 +23,7 @@ from config import * EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack(" None: parsed = self._STRUCT.parse(data[8:]) 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) if not response.value or not response.value.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) + def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: 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): 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 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) payer = Keypair.from_bytes(private_key) async with AsyncClient(RPC_ENDPOINT) as client: associated_token_account = get_associated_token_address(payer.pubkey(), mint) - + # Get token balance token_balance = await get_token_balance(client, associated_token_account) 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) slippage_factor = 1 - slippage min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL) - + print(f"Selling {token_balance_decimal} tokens") print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL") @@ -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_FEE, is_signer=False, is_writable=True), AccountMeta(pubkey=mint, is_signer=False, is_writable=False), - AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True), - AccountMeta(pubkey=associated_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(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), + AccountMeta( + pubkey=bonding_curve, is_signer=False, is_writable=True + ), + AccountMeta( + pubkey=associated_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( + 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("