├── .gitignore ├── README.md ├── auto_sell.py ├── data ├── config_template.ini ├── payer_keypair.json └── wallet_tokens.json ├── dexscreener.py ├── example_buy.py ├── example_sell.py ├── loadkey.py ├── pumpfun ├── coin_data.py ├── constants.py ├── pump_fun.py ├── test.py └── utils.py ├── raydium ├── Raydium.py ├── buy_swap.py ├── create_close_account.py ├── layouts.py └── sell_swap.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | config.ini 3 | __pycache__/ 4 | all_pools.json 5 | .DS_Store 6 | .python-version -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This script `auto_sell.py` continously scans a solana wallet for new tokens. Upon detection, it will cache this token along with a few details (`token_address`, `balance`, `detection_time`) in `data\wallet_tokens.json`. When the token is older than a certain time duration it will sell the token. 4 | 5 | Note: This implementation is very old, there are many areas for improvement and enhancements in terms of functionality or performance. If you have other needs, you may contact me by referring to my details in the last section. 6 | 7 | 8 | # Configs/Parameters 9 | 10 | 1. WALLET_ADDRESS - Wallet Address to be tracked (only one at a time) 11 | 2. PRIVATE_KEY - Private Key for the wallet (to allow selling) 12 | 3. SOLANA_RPC_URL - RPC URL / I used helius for development 13 | 4. X_SECONDS - Amount in seconds between token detection and initiating the sell swap. 14 | 5. SLIPPAGE - Allowable slippage 15 | 6. PERCENT_TO_SELL 16 | 17 | 18 | 19 | # How to use 20 | 1. Clone the repo by running: `git clone git@github.com:lorenzourera/solana-auto-sell-bot.git` 21 | 2. Create a virtual environment (`python -m venv venv`), activate it (`source venv/bin/activate`) and install all dependencies found in `requirements.txt` (`pip install -r requirements.txt`) 22 | 3. Create a new file in `data/` called `config.ini`. The contents of this file should be identical to `config_template.ini` but with the values. 23 | 4. Run the script: `python auto_sell.py` 24 | 25 | 26 | 27 | # Contacts 28 | For business inquiries or custom scripts reach me on telegram at `zo125` 29 | 30 | If this script has helped you, tips are much appreciated: 31 | * Solana `4AJbAQVSPd8pZkcox67ouYjQHqnQsHkxxuj5cY4rCwK1` 32 | -------------------------------------------------------------------------------- /auto_sell.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import requests 5 | from loguru import logger 6 | import json 7 | from solana.rpc.api import Client 8 | from solana.rpc.commitment import Commitment 9 | from solders.keypair import Keypair 10 | import sys 11 | from configparser import ConfigParser 12 | import base58, logging,time, re, os,sys, json 13 | from raydium.Raydium import * 14 | from pumpfun.pump_fun import pf_sell 15 | 16 | 17 | def get_assets_by_owner(RPC_URL, wallet_address): 18 | logger.info("Checking Wallet for New Tokens") 19 | payload = { 20 | "jsonrpc": "2.0", 21 | "id": "my-id", 22 | "method": "getAssetsByOwner", 23 | "params": { 24 | "ownerAddress": wallet_address, 25 | "page": 1, # Starts at 1 26 | "limit": 1000, 27 | "displayOptions": { 28 | "showFungible": True 29 | } 30 | } 31 | } 32 | 33 | headers = { 34 | "Content-Type": "application/json" 35 | } 36 | 37 | response = requests.post(RPC_URL, headers=headers, json=payload) 38 | 39 | if response.status_code == 200: 40 | data = response.json() 41 | if "result" in data: 42 | assets = data["result"]["items"] 43 | spl_tokens = [] 44 | for asset in assets: 45 | interface_type = asset.get("interface", "") 46 | if interface_type == "V1_NFT": 47 | continue # Skip NFT assets 48 | token_info = asset.get("token_info", {}) 49 | balance = token_info.get("balance", None) 50 | if balance and float(balance) > 0: 51 | spl_tokens.append({ 52 | "id": asset["id"], 53 | "symbol": token_info.get("symbol", ""), 54 | "balance": balance, 55 | "token_info": token_info 56 | }) 57 | for token in spl_tokens: 58 | logger.info("Token ID: {}", token["id"]) 59 | logger.info("Symbol: {}", token["symbol"]) 60 | logger.info("Balance: {}", token["balance"]) 61 | logger.info("Metadata: {}", token["token_info"]) 62 | else: 63 | logger.error("No result found in response") 64 | else: 65 | logger.error("Error: {}, {}", response.status_code, response.text) 66 | 67 | 68 | logger.info(f"Current SPL Tokens {spl_tokens}") 69 | return spl_tokens 70 | 71 | 72 | def write_wallet_tokens(tokens): 73 | 74 | 75 | # clear wallet_tokens.json if no SPL tokens are detected 76 | if not tokens: 77 | with open("data/wallet_tokens.json", "w") as file: 78 | file.write("[]") 79 | logger.info("Wallet tokens JSON file cleared") 80 | return 81 | 82 | current_time = int(time.time()) 83 | 84 | # Load existing data from the JSON file 85 | try: 86 | with open("data/wallet_tokens.json", "r") as file: 87 | existing_tokens = json.load(file) 88 | except FileNotFoundError: 89 | existing_tokens = [] 90 | 91 | # Filter out existing tokens and add new tokens using list comprehensions 92 | new_tokens = [ 93 | { 94 | "symbol": token.get("token_info", {}).get("symbol", ""), 95 | "token_id": token.get("id"), 96 | "balance": token.get("token_info", {}).get("balance", ""), 97 | "detection_time": current_time 98 | } 99 | for token in tokens 100 | if not any(existing_token.get("token_id") == token.get("id") for existing_token in existing_tokens) 101 | ] 102 | 103 | # Append new tokens to the existing data 104 | existing_tokens.extend(new_tokens) 105 | 106 | # Write the updated data back to the JSON file 107 | with open("data/wallet_tokens.json", "w") as file: 108 | json.dump(existing_tokens, file, indent=4) 109 | 110 | def detect_old_tokens(json_file, threshold_seconds): 111 | current_time = int(time.time()) 112 | 113 | try: 114 | with open(json_file, "r") as file: 115 | existing_tokens = json.load(file) 116 | except FileNotFoundError: 117 | existing_tokens = [] 118 | 119 | # Use a list comprehension to filter old tokens 120 | old_tokens = [ 121 | token for token in existing_tokens 122 | if current_time - token.get("detection_time", 0) > threshold_seconds 123 | ] 124 | 125 | return old_tokens 126 | 127 | 128 | def remove_token_from_json(token_id): 129 | json_file = "data/wallet_tokens.json" 130 | 131 | try: 132 | # Load existing data from the JSON file 133 | with open(json_file, "r") as file: 134 | existing_tokens = json.load(file) 135 | except FileNotFoundError: 136 | # If the file doesn't exist, there's nothing to remove 137 | return 138 | 139 | # Filter out the token to be removed 140 | updated_tokens = [token for token in existing_tokens if token.get("token_id") != token_id] 141 | 142 | # Write the updated data back to the JSON file 143 | with open(json_file, "w") as file: 144 | json.dump(updated_tokens, file, indent=4) 145 | 146 | 147 | def main(): 148 | 149 | # Load Configs 150 | config = ConfigParser() 151 | config.read(os.path.join(sys.path[0], 'data', 'config.ini')) 152 | 153 | # Infura settings - register at infura and get your mainnet url. 154 | RPC_HTTPS_URL = config.get("DEFAULT", "SOLANA_RPC_URL") 155 | # Wallet Address 156 | wallet_address = config.get("DEFAULT", "WALLET_ADDRESS") 157 | # Wallets private key 158 | private_key = config.get("DEFAULT", "PRIVATE_KEY") 159 | # Time to Hold 160 | threshold_seconds = int(config.get("DEFAULT", "X_SECONDS")) 161 | percentage = float(config.get("DEFAULT", "PERCENT_TO_SELL")) 162 | slippage = float(config.get('DEFAULT', "SLIPPAGE")) 163 | 164 | ctx = Client(RPC_HTTPS_URL, commitment=Commitment("confirmed"), timeout=30,blockhash_cache=True) 165 | payer = Keypair.from_bytes(base58.b58decode(private_key)) 166 | 167 | while True: 168 | spl_tokens = get_assets_by_owner(RPC_URL=RPC_HTTPS_URL, wallet_address=wallet_address) 169 | write_wallet_tokens(spl_tokens) 170 | 171 | # Detect and process old tokens 172 | 173 | old_tokens = detect_old_tokens("data/wallet_tokens.json", threshold_seconds) 174 | for token in old_tokens: 175 | logger.info(f"Detected old token: {token}. Selling now.") 176 | try: 177 | if token['token_id'].endswith('pump'): 178 | logger.info("Selling on pumpfun") 179 | pf_sell(client=ctx, mint_str=str(token['token_id']), payer_keypair=payer, percentage=percentage, slippage=slippage) 180 | remove_token_from_json(token_id=token['token_id']) 181 | else: 182 | raydium_swap(ctx=ctx, payer=payer, desired_token_address=token['token_id']) 183 | remove_token_from_json(token_id=token['token_id']) 184 | except Exception as e: 185 | logger.warning(f"Issue encountered during sell of {token} Message {e}") 186 | time.sleep(0.5) 187 | continue 188 | 189 | # Pause for some time before the next iteration 190 | time.sleep(1) # 1 second 191 | 192 | if __name__ == "__main__": 193 | main() 194 | -------------------------------------------------------------------------------- /data/config_template.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | WALLET_ADDRESS = 3 | PRIVATE_KEY = 4 | SOLANA_RPC_URL = 5 | X_SECONDS = 6 | PERCENT_TO_SELL = 7 | SLIPPAGE = 8 | -------------------------------------------------------------------------------- /data/payer_keypair.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzourera/solana-auto-sell-bot/1cac9e9bd755ef788aaf09dbf56075b714b7bc03/data/payer_keypair.json -------------------------------------------------------------------------------- /data/wallet_tokens.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /dexscreener.py: -------------------------------------------------------------------------------- 1 | import requests, json, os, sys 2 | 3 | 4 | 5 | def getBaseToken(token_address): 6 | url = f"https://api.dexscreener.com/latest/dex/pairs/solana/{token_address}" 7 | response = requests.get(url).json() 8 | return response['pair']['baseToken']['address'] 9 | 10 | 11 | def get_price(token_address): 12 | """ 13 | USDT and USDC prices will be excluded 14 | """ 15 | url = f"https://api.dexscreener.com/latest/dex/tokens/{token_address}" 16 | exclude = ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'] 17 | response = requests.get(url).json() 18 | 19 | if token_address not in exclude: 20 | for pair in response['pairs']: 21 | if pair['quoteToken']['address'] == 'So11111111111111111111111111111111111111112': 22 | return float(pair['priceUsd']) 23 | else: 24 | return response['pairs'][0]['priceUsd'] 25 | return None 26 | 27 | 28 | 29 | """Common addresses like usdc and usdt will be excluded as we know their symbols""" 30 | def getSymbol(token): 31 | # usdc and usdt 32 | exclude = ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'] 33 | 34 | if token not in exclude: 35 | url = f"https://api.dexscreener.com/latest/dex/tokens/{token}" 36 | 37 | Token_Symbol = "" 38 | Sol_symbol="" 39 | try: 40 | response = requests.get(url) 41 | 42 | # Check if the request was successful (status code 200) 43 | if response.status_code == 200: 44 | resp = response.json() 45 | print("Response:",resp['pairs'][0]['baseToken']['symbol']) 46 | for pair in resp['pairs']: 47 | quoteToken = pair['quoteToken']['symbol'] 48 | 49 | if quoteToken == 'SOL': 50 | Token_Symbol = pair['baseToken']['symbol'] 51 | Sol_symbol = quoteToken 52 | return Token_Symbol, Sol_symbol 53 | 54 | 55 | else: 56 | print(f"[getSymbol] Request failed with status code {response.status_code}") 57 | 58 | except requests.exceptions.RequestException as e: 59 | print(f"[getSymbol] error occurred: {e}") 60 | except: 61 | a = 1 62 | 63 | return Token_Symbol, Sol_symbol 64 | else: 65 | if token == 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 66 | return "USDC", "SOL" 67 | elif token == 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 68 | return "USDT", "SOL" 69 | -------------------------------------------------------------------------------- /example_buy.py: -------------------------------------------------------------------------------- 1 | from pumpfun.pump_fun import pf_buy 2 | from configparser import ConfigParser 3 | import os, sys 4 | from solana.rpc.api import Client 5 | from solana.rpc.commitment import Commitment 6 | from solders.keypair import Keypair 7 | 8 | config = ConfigParser() 9 | config.read(os.path.join(sys.path[0], 'data', 'config.ini')) 10 | RPC_HTTPS_URL = config.get("DEFAULT", "SOLANA_RPC_URL") 11 | payer_keypair = config.get("DEFAULT", "PRIVATE_KEY") 12 | payer_keypair = Keypair.from_base58_string(payer_keypair) 13 | 14 | client = Client(RPC_HTTPS_URL, commitment=Commitment("confirmed"), timeout=30,blockhash_cache=True) 15 | 16 | # Sell Example 17 | mint_str = "CU64vmX5MBTXBu85HmswbGdDbwfHredQQZgLoEZxCAkJ" 18 | sol_in = 0.0025 19 | slippage = 25 20 | 21 | pf_buy(client=client, payer_keypair=payer_keypair, mint_str=mint_str, slippage=slippage) -------------------------------------------------------------------------------- /example_sell.py: -------------------------------------------------------------------------------- 1 | from pumpfun.pump_fun import pf_sell 2 | from configparser import ConfigParser 3 | import os, sys 4 | from solana.rpc.api import Client 5 | from solana.rpc.commitment import Commitment 6 | from solders.keypair import Keypair 7 | 8 | config = ConfigParser() 9 | config.read(os.path.join(sys.path[0], 'data', 'config.ini')) 10 | RPC_HTTPS_URL = config.get("DEFAULT", "SOLANA_RPC_URL") 11 | payer_keypair = config.get("DEFAULT", "PRIVATE_KEY") 12 | payer_keypair = Keypair.from_base58_string(payer_keypair) 13 | 14 | client = Client(RPC_HTTPS_URL, commitment=Commitment("confirmed"), timeout=30,blockhash_cache=True) 15 | 16 | # Sell Example 17 | mint_str = "5syFBzELxeG4TvjBAt5Koq9BKTJeF1jJ679RYy2wpump" 18 | percentage = 100 19 | slippage = 25 20 | 21 | pf_sell(client=client, payer_keypair=payer_keypair, mint_str=mint_str, percentage=percentage, slippage=slippage) -------------------------------------------------------------------------------- /loadkey.py: -------------------------------------------------------------------------------- 1 | import json, os, sys 2 | import base58 3 | from solders.keypair import Keypair 4 | 5 | def load_keypair_from_file(filename): 6 | curr = os.path.join(sys.path[0], 'data', filename) 7 | with open(curr, 'r') as file: 8 | secret = json.load(file) 9 | secret_key = bytes(secret) 10 | # print(base58.b58encode(secret_key)) 11 | return Keypair.from_bytes(secret_key) 12 | -------------------------------------------------------------------------------- /pumpfun/coin_data.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | from construct import Flag, Int64ul, Padding, Struct 4 | from solders.pubkey import Pubkey # type: ignore 5 | from spl.token.instructions import get_associated_token_address 6 | from pumpfun.constants import PUMP_FUN_PROGRAM 7 | from loguru import logger 8 | 9 | @dataclass 10 | class CoinData: 11 | mint: Pubkey 12 | bonding_curve: Pubkey 13 | associated_bonding_curve: Pubkey 14 | virtual_token_reserves: int 15 | virtual_sol_reserves: int 16 | token_total_supply: int 17 | complete: bool 18 | 19 | def get_virtual_reserves(client, bonding_curve: Pubkey): 20 | bonding_curve_struct = Struct( 21 | Padding(8), 22 | "virtualTokenReserves" / Int64ul, 23 | "virtualSolReserves" / Int64ul, 24 | "realTokenReserves" / Int64ul, 25 | "realSolReserves" / Int64ul, 26 | "tokenTotalSupply" / Int64ul, 27 | "complete" / Flag 28 | ) 29 | 30 | try: 31 | account_info = client.get_account_info(bonding_curve) 32 | data = account_info.value.data 33 | parsed_data = bonding_curve_struct.parse(data) 34 | return parsed_data 35 | except Exception: 36 | return None 37 | 38 | def derive_bonding_curve_accounts(mint_str: str): 39 | try: 40 | logger.info(f"Deriving bonding curve for {mint_str}") 41 | mint = Pubkey.from_string(mint_str) 42 | logger.info(f'mint {mint}') 43 | bonding_curve, _ = Pubkey.find_program_address( 44 | ["bonding-curve".encode(), bytes(mint)], 45 | PUMP_FUN_PROGRAM 46 | ) 47 | associated_bonding_curve = get_associated_token_address(bonding_curve, mint) 48 | logger.info(f"associated bonding curve {associated_bonding_curve}") 49 | return bonding_curve, associated_bonding_curve 50 | except Exception as e: 51 | logger.error(f'PF - error occured in deriving bonding curve account - {e}') 52 | return None, None 53 | 54 | def get_coin_data(client, mint_str: str) -> Optional[CoinData]: 55 | logger.info("PF - retrieving coin data") 56 | bonding_curve, associated_bonding_curve = derive_bonding_curve_accounts(mint_str) 57 | if bonding_curve is None or associated_bonding_curve is None: 58 | return None 59 | 60 | virtual_reserves = get_virtual_reserves(client, bonding_curve) 61 | if virtual_reserves is None: 62 | return None 63 | 64 | try: 65 | return CoinData( 66 | mint=Pubkey.from_string(mint_str), 67 | bonding_curve=bonding_curve, 68 | associated_bonding_curve=associated_bonding_curve, 69 | virtual_token_reserves=int(virtual_reserves.virtualTokenReserves), 70 | virtual_sol_reserves=int(virtual_reserves.virtualSolReserves), 71 | token_total_supply=int(virtual_reserves.tokenTotalSupply), 72 | complete=bool(virtual_reserves.complete), 73 | ) 74 | except Exception as e: 75 | logger.error(e) 76 | return None 77 | -------------------------------------------------------------------------------- /pumpfun/constants.py: -------------------------------------------------------------------------------- 1 | from solders.pubkey import Pubkey #type: ignore 2 | 3 | GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") 4 | FEE_RECIPIENT = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") 5 | SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111") 6 | TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") 7 | ASSOC_TOKEN_ACC_PROG = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") 8 | RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111") 9 | EVENT_AUTHORITY = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1") 10 | PUMP_FUN_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") 11 | SOL_DECIMAL = 10**9 -------------------------------------------------------------------------------- /pumpfun/pump_fun.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from solana.rpc.types import TokenAccountOpts, TxOpts 3 | from solana.transaction import AccountMeta 4 | from spl.token.instructions import ( 5 | CloseAccountParams, 6 | close_account, 7 | create_associated_token_account, 8 | get_associated_token_address, 9 | ) 10 | from solana.rpc.api import RPCException 11 | from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price # type: ignore 12 | from solders.instruction import Instruction # type: ignore 13 | from solders.message import MessageV0 # type: ignore 14 | from solders.transaction import VersionedTransaction # type: ignore 15 | from solders.pubkey import Pubkey 16 | from pumpfun.utils import confirm_txn, get_token_balance 17 | from pumpfun.coin_data import get_coin_data 18 | from loguru import logger 19 | import time 20 | from raydium.Raydium import raydium_swap 21 | 22 | GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf") 23 | FEE_RECIPIENT = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") 24 | SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111") 25 | TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") 26 | ASSOC_TOKEN_ACC_PROG = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") 27 | RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111") 28 | EVENT_AUTHORITY = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1") 29 | PUMP_FUN_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") 30 | SOL_DECIMAL = 10**9 31 | UNIT_BUDGET = 100_000 32 | UNIT_PRICE = 1_000_000 33 | 34 | 35 | 36 | 37 | 38 | 39 | def pf_buy(client, payer_keypair, mint_str: str, sol_in: float = 0.01, slippage: int = 15) -> bool: 40 | try: 41 | logger.info(f"PF - Starting buy transaction for mint: {mint_str}") 42 | 43 | coin_data = get_coin_data(client=client, mint_str=mint_str) 44 | 45 | if not coin_data: 46 | logger.warning("Failed to retrieve coin data.") 47 | return False 48 | 49 | if coin_data.complete: 50 | logger.warning("Warning: This token has bonded and is only tradable on Raydium.") 51 | logger.info('Initiating swap on raydium') 52 | raydium_swap(ctx=client, payer=payer_keypair, desired_token_address=mint_str) 53 | 54 | return 55 | 56 | MINT = coin_data.mint 57 | BONDING_CURVE = coin_data.bonding_curve 58 | ASSOCIATED_BONDING_CURVE = coin_data.associated_bonding_curve 59 | USER = payer_keypair.pubkey() 60 | 61 | logger.info("Fetching or creating associated token account...") 62 | try: 63 | ASSOCIATED_USER = client.get_token_accounts_by_owner(USER, TokenAccountOpts(MINT)).value[0].pubkey 64 | token_account_instruction = None 65 | logger.info(f"Token account found: {ASSOCIATED_USER}") 66 | except: 67 | ASSOCIATED_USER = get_associated_token_address(USER, MINT) 68 | token_account_instruction = create_associated_token_account(USER, USER, MINT) 69 | logger.info(f"Creating token account : {ASSOCIATED_USER}") 70 | 71 | logger.info("Calculating transaction amounts...") 72 | virtual_sol_reserves = coin_data.virtual_sol_reserves 73 | virtual_token_reserves = coin_data.virtual_token_reserves 74 | sol_in_lamports = sol_in * SOL_DECIMAL 75 | amount = int(sol_in_lamports * virtual_token_reserves / virtual_sol_reserves) 76 | slippage_adjustment = 1 + (slippage / 100) 77 | max_sol_cost = int(sol_in * slippage_adjustment * SOL_DECIMAL) 78 | logger.info(f"Amount: {amount}, Max Sol Cost: {max_sol_cost}") 79 | 80 | logger.info("Creating swap instructions...") 81 | keys = [ 82 | AccountMeta(pubkey=GLOBAL, is_signer=False, is_writable=False), 83 | AccountMeta(pubkey=FEE_RECIPIENT, is_signer=False, is_writable=True), 84 | AccountMeta(pubkey=MINT, is_signer=False, is_writable=False), 85 | AccountMeta(pubkey=BONDING_CURVE, is_signer=False, is_writable=True), 86 | AccountMeta(pubkey=ASSOCIATED_BONDING_CURVE, is_signer=False, is_writable=True), 87 | AccountMeta(pubkey=ASSOCIATED_USER, is_signer=False, is_writable=True), 88 | AccountMeta(pubkey=USER, is_signer=True, is_writable=True), 89 | AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), 90 | AccountMeta(pubkey=TOKEN_PROGRAM, is_signer=False, is_writable=False), 91 | AccountMeta(pubkey=RENT, is_signer=False, is_writable=False), 92 | AccountMeta(pubkey=EVENT_AUTHORITY, is_signer=False, is_writable=False), 93 | AccountMeta(pubkey=PUMP_FUN_PROGRAM, is_signer=False, is_writable=False) 94 | ] 95 | 96 | data = bytearray() 97 | data.extend(bytes.fromhex("66063d1201daebea")) 98 | data.extend(struct.pack(' bool: 136 | try: 137 | logger.info(f"PF - Starting sell transaction for mint: {mint_str}") 138 | 139 | if not (1 <= percentage <= 100): 140 | logger.info("Percentage must be between 1 and 100.") 141 | return False 142 | 143 | coin_data = get_coin_data(client=client, mint_str=mint_str) 144 | 145 | if not coin_data: 146 | logger.info("Failed to retrieve coin data.") 147 | return False 148 | 149 | if coin_data.complete: 150 | logger.info("Warning: This token has bonded and is only tradable on Raydium.") 151 | logger.info('Initiating swap on raydium') 152 | raydium_swap(ctx=client, payer=payer_keypair, desired_token_address=mint_str) 153 | return 154 | 155 | MINT = coin_data.mint 156 | BONDING_CURVE = coin_data.bonding_curve 157 | ASSOCIATED_BONDING_CURVE = coin_data.associated_bonding_curve 158 | USER = payer_keypair.pubkey() 159 | ASSOCIATED_USER = get_associated_token_address(USER, MINT) 160 | 161 | logger.info("Calculating token price...") 162 | sol_decimal = 10**9 163 | token_decimal = 10**6 164 | token_price = ( 165 | coin_data.virtual_sol_reserves / sol_decimal 166 | ) / (coin_data.virtual_token_reserves / token_decimal) 167 | logger.info(f"Token Price: {token_price:.8f} SOL") 168 | 169 | logger.info("Retrieving token balance...") 170 | token_balance = get_token_balance(client, payer_keypair, mint_str) 171 | 172 | ## Edgecase: token balance is mixed number (i.e. 1.5), then sell the whole number part (1) 173 | if token_balance % 1 != 0 and token_balance > 1: # is a mixed number 174 | token_balance = int(token_balance) 175 | 176 | 177 | logger.info(f"token_balance {token_balance}") 178 | if token_balance == 0: 179 | logger.info("Token balance is zero. Nothing to sell.") 180 | return False 181 | 182 | logger.info("Calculating transaction amounts...") 183 | token_balance *= percentage / 100 184 | # for debugging 185 | logger.info(f"token_balance {token_balance}") 186 | logger.info(f"token_decimal {token_decimal}") 187 | amount = int(token_balance * token_decimal) 188 | sol_out = token_balance * token_price 189 | slippage_adjustment = 1 - (slippage / 100) 190 | min_sol_output = int(sol_out * slippage_adjustment * SOL_DECIMAL) 191 | logger.info(f"Amount: {amount}, Minimum Sol Out: {min_sol_output}") 192 | 193 | logger.info("Creating swap instructions...") 194 | keys = [ 195 | AccountMeta(pubkey=GLOBAL, is_signer=False, is_writable=False), 196 | AccountMeta(pubkey=FEE_RECIPIENT, is_signer=False, is_writable=True), 197 | AccountMeta(pubkey=MINT, is_signer=False, is_writable=False), 198 | AccountMeta(pubkey=BONDING_CURVE, is_signer=False, is_writable=True), 199 | AccountMeta(pubkey=ASSOCIATED_BONDING_CURVE, is_signer=False, is_writable=True), 200 | AccountMeta(pubkey=ASSOCIATED_USER, is_signer=False, is_writable=True), 201 | AccountMeta(pubkey=USER, is_signer=True, is_writable=True), 202 | AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), 203 | AccountMeta(pubkey=ASSOC_TOKEN_ACC_PROG, is_signer=False, is_writable=False), 204 | AccountMeta(pubkey=TOKEN_PROGRAM, is_signer=False, is_writable=False), 205 | AccountMeta(pubkey=EVENT_AUTHORITY, is_signer=False, is_writable=False), 206 | AccountMeta(pubkey=PUMP_FUN_PROGRAM, is_signer=False, is_writable=False) 207 | ] 208 | 209 | data = bytearray() 210 | data.extend(bytes.fromhex("33e685a4017f83ad")) 211 | data.extend(struct.pack(' float | None: 21 | try: 22 | mint = Pubkey.from_string(mint_str) 23 | # for debugging 24 | logger.info(f'mint {mint}') 25 | logger.info(f'payer pubkey {payer_keypair.pubkey()}') 26 | response = client.get_token_accounts_by_owner_json_parsed( 27 | payer_keypair.pubkey(), 28 | TokenAccountOpts(mint=mint), 29 | commitment=Processed 30 | ) 31 | # logger.info(f'response {response}') 32 | accounts = response.value 33 | # logger.info(f'accounts {accounts}') 34 | if accounts: 35 | token_amount = accounts[0].account.data.parsed['info']['tokenAmount']['uiAmount'] 36 | return float(token_amount) 37 | 38 | 39 | except Exception as e: 40 | logger.error(f"Error fetching token balance: {e}") 41 | return None 42 | 43 | def confirm_txn(client, txn_sig: Signature, max_retries: int = 20, retry_interval: int = 3) -> bool: 44 | retries = 1 45 | 46 | while retries < max_retries: 47 | try: 48 | txn_res = client.get_transaction(txn_sig, encoding="json", commitment=Confirmed, max_supported_transaction_version=0) 49 | txn_json = json.loads(txn_res.value.transaction.meta.to_json()) 50 | 51 | if txn_json['err'] is None: 52 | logger.info("Transaction confirmed... try count:", retries) 53 | return True 54 | 55 | logger.warning("Error: Transaction not confirmed. Retrying...") 56 | if txn_json['err']: 57 | logger.error("Transaction failed.") 58 | return False 59 | except Exception as e: 60 | logger.error("Awaiting confirmation... try count:", retries) 61 | retries += 1 62 | time.sleep(retry_interval) 63 | 64 | logger.info("Max retries reached. Transaction confirmation failed.") 65 | return None 66 | 67 | def get_token_price(mint_str: str) -> float: 68 | try: 69 | coin_data = get_coin_data(mint_str) 70 | 71 | if not coin_data: 72 | logger.info("Failed to retrieve coin data...") 73 | return None 74 | 75 | virtual_sol_reserves = coin_data.virtual_sol_reserves / 10**9 76 | virtual_token_reserves = coin_data.virtual_token_reserves / 10**6 77 | 78 | token_price = virtual_sol_reserves / virtual_token_reserves 79 | logger.info(f"Token Price: {token_price:.20f} SOL") 80 | 81 | return token_price 82 | 83 | except Exception as e: 84 | logger.info(f"Error calculating token price: {e}") 85 | return None 86 | 87 | 88 | def is_tradeable_on_pumpfun(token_address): 89 | try: 90 | # PumpFun uses a specific API endpoint to check token status 91 | response = requests.get(f"https://api.pump.fun/token/{token_address}") 92 | if response.status_code == 200: 93 | data = response.json() 94 | # Check if the token has active liquidity or is listed 95 | return data.get('is_active', False) and data.get('liquidity', 0) > 0 96 | except Exception as e: 97 | logger.error(f"Error checking PumpFun tradability: {e}") 98 | return False -------------------------------------------------------------------------------- /raydium/Raydium.py: -------------------------------------------------------------------------------- 1 | from raydium.sell_swap import sell 2 | from dexscreener import get_price, getSymbol 3 | import time 4 | from loguru import logger 5 | 6 | def raydium_swap(ctx, payer, desired_token_address): 7 | 8 | token_symbol, SOl_Symbol = getSymbol(desired_token_address) 9 | logger.info(f"Raydium - Selling token {token_symbol} CA {desired_token_address}") 10 | bought_token_curr_price = get_price(desired_token_address) 11 | 12 | start_time = time.time() 13 | txS = sell(solana_client=ctx, TOKEN_TO_SWAP_SELL=desired_token_address, payer=payer, token_symbol=token_symbol, S0l_Symbol=SOl_Symbol) 14 | end_time = time.time() 15 | execution_time = end_time - start_time 16 | logger.info(f"Total Sell Execution time: {execution_time} seconds") 17 | 18 | if str(txS) != 'failed': 19 | txS = str(txS) 20 | logger.info("-" * 79) 21 | logger.info(f"| {'Sold Price':<15} | {'Tx Sell':<40} |") 22 | logger.info("-" * 79) 23 | -------------------------------------------------------------------------------- /raydium/buy_swap.py: -------------------------------------------------------------------------------- 1 | from spl.token.instructions import close_account, CloseAccountParams 2 | from spl.token.client import Token 3 | from spl.token.core import _TokenCore 4 | 5 | from solana.rpc.commitment import Commitment 6 | from solana.rpc.api import RPCException 7 | 8 | from solders.pubkey import Pubkey 9 | 10 | from raydium.create_close_account import get_token_account,fetch_pool_keys, get_token_account, make_swap_instruction 11 | from birdeye import getSymbol 12 | from webhook import sendWebhook 13 | 14 | import time 15 | 16 | LAMPORTS_PER_SOL = 1000000000 17 | 18 | 19 | def buy(solana_client, TOKEN_TO_SWAP_BUY, payer, amount): 20 | token_symbol, SOl_Symbol = getSymbol(TOKEN_TO_SWAP_BUY) 21 | 22 | mint = Pubkey.from_string(TOKEN_TO_SWAP_BUY) 23 | 24 | pool_keys = fetch_pool_keys(str(mint)) 25 | if pool_keys == "failed": 26 | sendWebhook(f"a|BUY Pool ERROR {token_symbol}",f"[Raydium]: Pool Key Not Found") 27 | return "failed" 28 | 29 | """ 30 | Calculate amount 31 | """ 32 | amount_in = int(amount * LAMPORTS_PER_SOL) 33 | # slippage = 0.1 34 | # lamports_amm = amount * LAMPORTS_PER_SOL 35 | # amount_in = int(lamports_amm - (lamports_amm * (slippage/100))) 36 | 37 | txnBool = True 38 | while txnBool: 39 | 40 | """Get swap token program id""" 41 | print("1. Get TOKEN_PROGRAM_ID...") 42 | accountProgramId = solana_client.get_account_info_json_parsed(mint) 43 | TOKEN_PROGRAM_ID = accountProgramId.value.owner 44 | 45 | """ 46 | Set Mint Token accounts addresses 47 | """ 48 | print("2. Get Mint Token accounts addresses...") 49 | swap_associated_token_address,swap_token_account_Instructions = get_token_account(solana_client, payer.pubkey(), mint) 50 | 51 | 52 | """ 53 | Create Wrap Sol Instructions 54 | """ 55 | print("3. Create Wrap Sol Instructions...") 56 | balance_needed = Token.get_min_balance_rent_for_exempt_for_account(solana_client) 57 | WSOL_token_account, swap_tx, payer, Wsol_account_keyPair, opts, = _TokenCore._create_wrapped_native_account_args(TOKEN_PROGRAM_ID, payer.pubkey(), payer,amount_in, 58 | False, balance_needed, Commitment("confirmed")) 59 | """ 60 | Create Swap Instructions 61 | """ 62 | print("4. Create Swap Instructions...") 63 | instructions_swap = make_swap_instruction( amount_in, 64 | WSOL_token_account, 65 | swap_associated_token_address, 66 | pool_keys, 67 | mint, 68 | solana_client, 69 | payer 70 | ) 71 | 72 | 73 | print("5. Create Close Account Instructions...") 74 | params = CloseAccountParams(account=WSOL_token_account, dest=payer.pubkey(), owner=payer.pubkey(), program_id=TOKEN_PROGRAM_ID) 75 | closeAcc =(close_account(params)) 76 | 77 | print("6. Add instructions to transaction...") 78 | if swap_token_account_Instructions != None: 79 | swap_tx.add(swap_token_account_Instructions) 80 | swap_tx.add(instructions_swap) 81 | swap_tx.add(closeAcc) 82 | 83 | try: 84 | print("7. Execute Transaction...") 85 | start_time = time.time() 86 | txn = solana_client.send_transaction(swap_tx, payer, Wsol_account_keyPair) 87 | txid_string_sig = txn.value 88 | 89 | print("8. Confirm transaction...") 90 | checkTxn = True 91 | while checkTxn: 92 | try: 93 | status = solana_client.get_transaction(txid_string_sig,"json") 94 | FeesUsed = (status.value.transaction.meta.fee) / 1000000000 95 | if status.value.transaction.meta.err == None: 96 | print("[create_account] Transaction Success",txn.value) 97 | print(f"[create_account] Transaction Fees: {FeesUsed:.10f} SOL") 98 | 99 | end_time = time.time() 100 | execution_time = end_time - start_time 101 | print(f"Execution time: {execution_time} seconds") 102 | 103 | txnBool = False 104 | checkTxn = False 105 | return txid_string_sig 106 | 107 | else: 108 | print("Transaction Failed") 109 | end_time = time.time() 110 | execution_time = end_time - start_time 111 | print(f"Execution time: {execution_time} seconds") 112 | checkTxn = False 113 | 114 | except Exception as e: 115 | sendWebhook(f"e|BUY ERROR {token_symbol}",f"[Raydium]: {e}") 116 | print("Sleeping...",e) 117 | time.sleep(0.500) 118 | print("Retrying...") 119 | 120 | except RPCException as e: 121 | print(f"Error: [{e.args[0].message}]...\nRetrying...") 122 | sendWebhook(f"e|BUY ERROR {token_symbol}",f"[Raydium]: {e.args[0].message}") 123 | time.sleep(1) 124 | 125 | except Exception as e: 126 | sendWebhook(f"e|BUY Exception ERROR {token_symbol}",f"[Raydium]: {e}") 127 | print(f"Error: [{e}]...\nEnd...") 128 | txnBool = False 129 | return "failed" -------------------------------------------------------------------------------- /raydium/create_close_account.py: -------------------------------------------------------------------------------- 1 | from spl.token.instructions import create_associated_token_account, get_associated_token_address 2 | 3 | from solders.pubkey import Pubkey 4 | from solders.instruction import Instruction 5 | 6 | from solana.rpc.types import TokenAccountOpts 7 | from solana.transaction import AccountMeta 8 | 9 | from raydium.layouts import SWAP_LAYOUT 10 | 11 | import json,requests 12 | 13 | LAMPORTS_PER_SOL = 1000000000 14 | AMM_PROGRAM_ID = Pubkey.from_string('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8') 15 | SERUM_PROGRAM_ID = Pubkey.from_string('srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX') 16 | 17 | def make_swap_instruction(amount_in: int, token_account_in: Pubkey.from_string, token_account_out: Pubkey.from_string, 18 | accounts: dict, mint, ctx, owner) -> Instruction: 19 | tokenPk = mint 20 | accountProgramId = ctx.get_account_info_json_parsed(tokenPk) 21 | TOKEN_PROGRAM_ID = accountProgramId.value.owner 22 | 23 | keys = [ 24 | AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), 25 | AccountMeta(pubkey=accounts["amm_id"], is_signer=False, is_writable=True), 26 | AccountMeta(pubkey=accounts["authority"], is_signer=False, is_writable=False), 27 | AccountMeta(pubkey=accounts["open_orders"], is_signer=False, is_writable=True), 28 | AccountMeta(pubkey=accounts["target_orders"], is_signer=False, is_writable=True), 29 | AccountMeta(pubkey=accounts["base_vault"], is_signer=False, is_writable=True), 30 | AccountMeta(pubkey=accounts["quote_vault"], is_signer=False, is_writable=True), 31 | AccountMeta(pubkey=SERUM_PROGRAM_ID, is_signer=False, is_writable=False), 32 | AccountMeta(pubkey=accounts["market_id"], is_signer=False, is_writable=True), 33 | AccountMeta(pubkey=accounts["bids"], is_signer=False, is_writable=True), 34 | AccountMeta(pubkey=accounts["asks"], is_signer=False, is_writable=True), 35 | AccountMeta(pubkey=accounts["event_queue"], is_signer=False, is_writable=True), 36 | AccountMeta(pubkey=accounts["market_base_vault"], is_signer=False, is_writable=True), 37 | AccountMeta(pubkey=accounts["market_quote_vault"], is_signer=False, is_writable=True), 38 | AccountMeta(pubkey=accounts["market_authority"], is_signer=False, is_writable=False), 39 | AccountMeta(pubkey=token_account_in, is_signer=False, is_writable=True), #UserSourceTokenAccount 40 | AccountMeta(pubkey=token_account_out, is_signer=False, is_writable=True), #UserDestTokenAccount 41 | AccountMeta(pubkey=owner.pubkey(), is_signer=True, is_writable=False) #UserOwner 42 | ] 43 | 44 | data = SWAP_LAYOUT.build( 45 | dict( 46 | instruction=9, 47 | amount_in=int(amount_in), 48 | min_amount_out=0 49 | ) 50 | ) 51 | return Instruction(AMM_PROGRAM_ID, data, keys) 52 | 53 | def get_token_account(ctx, 54 | owner: Pubkey.from_string, 55 | mint: Pubkey.from_string): 56 | try: 57 | account_data = ctx.get_token_accounts_by_owner(owner, TokenAccountOpts(mint)) 58 | return account_data.value[0].pubkey, None 59 | except: 60 | swap_associated_token_address = get_associated_token_address(owner, mint) 61 | swap_token_account_Instructions = create_associated_token_account(owner, owner, mint) 62 | return swap_associated_token_address, swap_token_account_Instructions 63 | 64 | def sell_get_token_account(ctx, 65 | owner: Pubkey.from_string, 66 | mint: Pubkey.from_string): 67 | try: 68 | account_data = ctx.get_token_accounts_by_owner(owner, TokenAccountOpts(mint)) 69 | return account_data.value[0].pubkey 70 | except: 71 | print("Mint Token Not found") 72 | return None 73 | 74 | 75 | def extract_pool_info(pools_list: list, mint: str) -> dict: 76 | for pool in pools_list: 77 | 78 | if pool['baseMint'] == mint and pool['quoteMint'] == 'So11111111111111111111111111111111111111112': 79 | return pool 80 | elif pool['quoteMint'] == mint and pool['baseMint'] == 'So11111111111111111111111111111111111111112': 81 | return pool 82 | raise Exception(f'{mint} pool not found!') 83 | 84 | 85 | def fetch_pool_keys(mint: str): 86 | amm_info = {} 87 | all_pools = {} 88 | try: 89 | # Using this so it will be faster else no option, we go the slower way. 90 | with open('all_pools.json', 'r') as file: 91 | all_pools = json.load(file) 92 | amm_info = extract_pool_info(all_pools, mint) 93 | except: 94 | resp = requests.get('https://api.raydium.io/v2/sdk/liquidity/mainnet.json', stream=True) 95 | pools = resp.json() 96 | official = pools['official'] 97 | unofficial = pools['unOfficial'] 98 | all_pools = official + unofficial 99 | 100 | # Store all_pools in a JSON file 101 | with open('all_pools.json', 'w') as file: 102 | json.dump(all_pools, file) 103 | try: 104 | amm_info = extract_pool_info(all_pools, mint) 105 | except: 106 | return "failed" 107 | 108 | return { 109 | 'amm_id': Pubkey.from_string(amm_info['id']), 110 | 'authority': Pubkey.from_string(amm_info['authority']), 111 | 'base_mint': Pubkey.from_string(amm_info['baseMint']), 112 | 'base_decimals': amm_info['baseDecimals'], 113 | 'quote_mint': Pubkey.from_string(amm_info['quoteMint']), 114 | 'quote_decimals': amm_info['quoteDecimals'], 115 | 'lp_mint': Pubkey.from_string(amm_info['lpMint']), 116 | 'open_orders': Pubkey.from_string(amm_info['openOrders']), 117 | 'target_orders': Pubkey.from_string(amm_info['targetOrders']), 118 | 'base_vault': Pubkey.from_string(amm_info['baseVault']), 119 | 'quote_vault': Pubkey.from_string(amm_info['quoteVault']), 120 | 'market_id': Pubkey.from_string(amm_info['marketId']), 121 | 'market_base_vault': Pubkey.from_string(amm_info['marketBaseVault']), 122 | 'market_quote_vault': Pubkey.from_string(amm_info['marketQuoteVault']), 123 | 'market_authority': Pubkey.from_string(amm_info['marketAuthority']), 124 | 'bids': Pubkey.from_string(amm_info['marketBids']), 125 | 'asks': Pubkey.from_string(amm_info['marketAsks']), 126 | 'event_queue': Pubkey.from_string(amm_info['marketEventQueue']) 127 | } 128 | -------------------------------------------------------------------------------- /raydium/layouts.py: -------------------------------------------------------------------------------- 1 | from construct import Bytes, Int8ul, Int64ul, BytesInteger 2 | from construct import Struct as cStruct 3 | 4 | """Thanks to v0idum for creating layouts in python""" 5 | 6 | 7 | POOL_INFO_LAYOUT = cStruct( 8 | "instruction" / Int8ul, 9 | "simulate_type" / Int8ul 10 | ) 11 | 12 | SWAP_LAYOUT = cStruct( 13 | "instruction" / Int8ul, 14 | "amount_in" / Int64ul, 15 | "min_amount_out" / Int64ul 16 | ) 17 | 18 | # Not in use right now, might be useful in future 19 | AMM_INFO_LAYOUT_V4 = cStruct( 20 | 'status' / Int64ul, 21 | 'nonce' / Int64ul, 22 | 'order_num' / Int64ul, 23 | 'depth' / Int64ul, 24 | 'base_decimal' / Int64ul, 25 | 'quote_decimal' / Int64ul, 26 | 'state' / Int64ul, 27 | 'reset_flag' / Int64ul, 28 | 'min_size' / Int64ul, 29 | 'vol_max_cut_ratio' / Int64ul, 30 | 'amount_wave_ratio' / Int64ul, 31 | 'base_lot_size' / Int64ul, 32 | 'quote_lot_size' / Int64ul, 33 | 'min_price_multiplier' / Int64ul, 34 | 'max_price_multiplier' / Int64ul, 35 | 'system_decimal_value' / Int64ul, 36 | # Fees 37 | 'min_separate_numerator' / Int64ul, 38 | 'min_separate_denominator' / Int64ul, 39 | 'trade_fee_numerator' / Int64ul, 40 | 'trade_fee_denominator' / Int64ul, 41 | 'pnl_numerator' / Int64ul, 42 | 'pnl_denominator' / Int64ul, 43 | 'swap_fee_numerator' / Int64ul, 44 | 'swap_fee_denominator' / Int64ul, 45 | # OutPutData 46 | 'base_need_take_pnl' / Int64ul, 47 | 'quote_need_take_pnl' / Int64ul, 48 | 'quote_total_pnl' / Int64ul, 49 | 'base_total_pnl' / Int64ul, 50 | # 128 51 | 'quote_total_deposited' / BytesInteger(16, signed=False, swapped=True), 52 | 'base_total_deposited' / BytesInteger(16, signed=False, swapped=True), 53 | 'swap_base_in_amount' / BytesInteger(16, signed=False, swapped=True), 54 | 'swap_quote_out_amount' / BytesInteger(16, signed=False, swapped=True), 55 | 56 | 'swap_base2_quote_fee' / Int64ul, 57 | # 128 58 | 'swap_quote_in_amount' / BytesInteger(16, signed=False, swapped=True), 59 | 'swap_base_out_amount' / BytesInteger(16, signed=False, swapped=True), 60 | 61 | 'swap_quote2_base_fee' / Int64ul, 62 | # AMM Vault 63 | 'base_vault' / Bytes(32), 64 | 'quote_vault' / Bytes(32), 65 | # Mint 66 | 'base_mint' / Bytes(32), 67 | 'quote_mint' / Bytes(32), 68 | 'lp_mint' / Bytes(32), 69 | # Market 70 | 'open_orders' / Bytes(32), 71 | 'market_id' / Bytes(32), 72 | 'serum_program_id' / Bytes(32), 73 | 'target_orders' / Bytes(32), 74 | 'withdraw_queue' / Bytes(32), 75 | 'lp_vault' / Bytes(32), 76 | 'amm_owner' / Bytes(32), 77 | 78 | 'lpReserve' / Int64ul, 79 | ) 80 | -------------------------------------------------------------------------------- /raydium/sell_swap.py: -------------------------------------------------------------------------------- 1 | from spl.token.instructions import close_account, CloseAccountParams 2 | from solana.rpc.types import TokenAccountOpts 3 | from solana.rpc.api import RPCException 4 | from solana.transaction import Transaction 5 | from solders.pubkey import Pubkey 6 | from raydium.create_close_account import fetch_pool_keys, sell_get_token_account,get_token_account, make_swap_instruction 7 | from loguru import logger 8 | import time 9 | 10 | 11 | LAMPORTS_PER_SOL = 1000000000 12 | 13 | 14 | def sell(solana_client, TOKEN_TO_SWAP_SELL, payer, token_symbol, S0l_Symbol): 15 | 16 | mint = Pubkey.from_string(TOKEN_TO_SWAP_SELL) 17 | sol = Pubkey.from_string("So11111111111111111111111111111111111111112") 18 | 19 | """Get swap token program id""" 20 | logger.info("1. Get TOKEN_PROGRAM_ID...") 21 | TOKEN_PROGRAM_ID = solana_client.get_account_info_json_parsed(mint).value.owner 22 | 23 | """Get Pool Keys""" 24 | logger.info("2. Get Pool Keys...") 25 | pool_keys = fetch_pool_keys(str(mint)) 26 | if pool_keys == "failed": 27 | logger.info(f"a|Sell Pool ERROR {token_symbol}",f"[Raydium]: Pool Key Not Found") 28 | return "failed" 29 | 30 | txnBool = True 31 | while txnBool: 32 | """Get Token Balance from wallet""" 33 | logger.info("3. Get oken Balance from wallet...") 34 | 35 | balanceBool = True 36 | while balanceBool: 37 | tokenPk = mint 38 | 39 | accountProgramId = solana_client.get_account_info_json_parsed(tokenPk) 40 | programid_of_token = accountProgramId.value.owner 41 | 42 | accounts = solana_client.get_token_accounts_by_owner_json_parsed(payer.pubkey(),TokenAccountOpts(program_id=programid_of_token)).value 43 | for account in accounts: 44 | mint_in_acc = account.account.data.parsed['info']['mint'] 45 | if mint_in_acc == str(mint): 46 | amount_in = int(account.account.data.parsed['info']['tokenAmount']['amount']) 47 | logger.info("3.1 Token Balance [Lamports]: ",amount_in) 48 | break 49 | if int(amount_in) > 0: 50 | balanceBool = False 51 | else: 52 | logger.info("No Balance, Retrying...") 53 | time.sleep(2) 54 | 55 | """Get token accounts""" 56 | logger.info("4. Get token accounts for swap...") 57 | swap_token_account = sell_get_token_account(solana_client, payer.pubkey(), mint) 58 | WSOL_token_account, WSOL_token_account_Instructions = get_token_account(solana_client,payer.pubkey(), sol) 59 | 60 | if swap_token_account == None: 61 | logger.info("swap_token_account not found...") 62 | return "failed" 63 | 64 | else: 65 | """Make swap instructions""" 66 | logger.info("5. Create Swap Instructions...") 67 | instructions_swap = make_swap_instruction( amount_in, 68 | swap_token_account, 69 | WSOL_token_account, 70 | pool_keys, 71 | mint, 72 | solana_client, 73 | payer 74 | ) 75 | 76 | """Close wsol account""" 77 | logger.info("6. Create Instructions to Close WSOL account...") 78 | params = CloseAccountParams(account=WSOL_token_account, dest=payer.pubkey(), owner=payer.pubkey(), program_id=TOKEN_PROGRAM_ID) 79 | closeAcc =(close_account(params)) 80 | 81 | """Create transaction and add instructions""" 82 | logger.info("7. Create transaction and add instructions to Close WSOL account...") 83 | swap_tx = Transaction() 84 | signers = [payer] 85 | if WSOL_token_account_Instructions != None: 86 | swap_tx.add(WSOL_token_account_Instructions) 87 | swap_tx.add(instructions_swap) 88 | swap_tx.add(closeAcc) 89 | 90 | """Send transaction""" 91 | try: 92 | logger.info("8. Execute Transaction...") 93 | start_time = time.time() 94 | txn = solana_client.send_transaction(swap_tx, *signers) 95 | 96 | """Confirm it has been sent""" 97 | txid_string_sig = txn.value 98 | logger.info("9. Confirm it has been sent...") 99 | checkTxn = True 100 | while checkTxn: 101 | try: 102 | status = solana_client.get_transaction(txid_string_sig,"json") 103 | FeesUsed = (status.value.transaction.meta.fee) / 1000000000 104 | if status.value.transaction.meta.err == None: 105 | logger.info("[create_account] Transaction Success",txn.value) 106 | logger.info(f"[create_account] Transaction Fees: {FeesUsed:.10f} SOL") 107 | 108 | end_time = time.time() 109 | execution_time = end_time - start_time 110 | logger.info(f"Execution time: {execution_time} seconds") 111 | 112 | txnBool = False 113 | checkTxn = False 114 | return txid_string_sig 115 | else: 116 | 117 | logger.info("Transaction Failed") 118 | 119 | end_time = time.time() 120 | execution_time = end_time - start_time 121 | logger.info(f"Execution time: {execution_time} seconds") 122 | 123 | checkTxn = False 124 | 125 | except Exception as e: 126 | logger.info(f"e|Sell ERROR {token_symbol}",f"[Raydium]: {e}") 127 | 128 | logger.info("Sleeping...",e) 129 | time.sleep(0.500) 130 | logger.info("Retrying...") 131 | 132 | except RPCException as e: 133 | logger.info(f"Error: [{e.args[0].message}]...\nRetrying...") 134 | logger.info(f"e|SELL ERROR {token_symbol}",f"[Raydium]: {e.args[0].message}") 135 | 136 | except Exception as e: 137 | logger.info(f"Error: [{e}]...\nEnd...") 138 | logger.info(f"e|SELL Exception ERROR {token_symbol}",f"[Raydium]: {e.args[0].message}") 139 | txnBool = False 140 | return "failed" 141 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==4.2.0 2 | base58==2.1.1 3 | cachetools==4.2.4 4 | certifi==2024.2.2 5 | charset-normalizer==3.3.2 6 | construct==2.10.68 7 | construct-typing==0.5.6 8 | exceptiongroup==1.2.0 9 | h11==0.14.0 10 | httpcore==0.16.3 11 | httpx==0.23.3 12 | idna==3.6 13 | jsonalias==0.1.1 14 | loguru==0.7.2 15 | requests==2.31.0 16 | rfc3986==1.5.0 17 | sniffio==1.3.0 18 | solana==0.32.0 19 | solders==0.20.0 20 | types-cachetools==4.2.10 21 | typing_extensions==4.9.0 22 | urllib3==2.2.0 23 | websockets==11.0.3 24 | --------------------------------------------------------------------------------