├── README.md ├── helius.ts └── main.rs /README.md: -------------------------------------------------------------------------------- 1 | # ⚡ Solana CopyTrading Bot: Optimized for Rapid Copy Trading Execution (0 Block Execution is available with powerful RPC and VPS !) 2 | 3 | ## Overview 4 | 5 | The Solana Copy Trading Bot enables real-time replication of target wallets’ transactions with remarkable efficiency, operating within a single blockchain block. This tool is designed for traders seeking to leverage the trading strategies of successful wallets swiftly. 6 | 7 | ## Example Transactions (0 Block) 8 | - Source Transaction: 9 | https://solscan.io/tx/2nNc1DsGxGoYWdweZhKQqnngfEjJqDA4zxnHar2S9bsAYP2csbLRgMpUmy68xuG1RaUGV9xb9k7dGdXcjgcmtJUh 10 | 11 | - Copied Transaction: 12 | https://solscan.io/tx/n2qrk4Xg3gfBBci6CXGKFqcTC8695sgNyzvacPHVaNkiwjWecwvY5WdNKgtgJhoLJfug6QkXQuaZeB5hVazW6ev 13 | 14 | - Target Wallet: 15 | https://solscan.io/account/GXAtmWucJEQxuL8PtpP13atoFi78eM6c9Cuw9fK9W4na 16 | 17 | - Copy Wallet: 18 | https://solscan.io/account/HqbQwVM2fhdYJXqFhBE68zX6mLqCWqEqdgrtf2ePmjRz 19 | 20 | ## Language 21 | Written in Rust and TypeScript 22 | 23 | ## Core Features 24 | 25 | - **Target Wallet Management**: A robust system for defining and maintaining a curated list of target wallets, enabling precise and efficient trade replication. 26 | 27 | - **Cross-DEX Compatibility**: Seamlessly integrated with a diverse range of decentralized exchanges (DEXs), including Jupiter, Raydium, and PumpFun Swap, with planned support for Meteora Swap in active development. 28 | 29 | - **Real-Time Transaction Mirroring**: Advanced real-time monitoring of target wallet activity ensures instantaneous and accurate replication of trading actions. 30 | 31 | - **Geyser Integration**: Leverages the efficiency of both Helius and Yellowstone Geysers for optimized data streaming. Yellowstone Geyser offers superior performance. 32 | 33 | - **Percentage-Based Copy Trading**: Implements a flexible copy-trading strategy allowing users to replicate trades based on a defined percentage of the target wallet's transaction size. 34 | 35 | - **Customizable Slippage Control**: Provides granular control over slippage tolerance, allowing users to manage transaction execution within predefined price thresholds. 36 | 37 | ## Installation 38 | 39 | To set up the Solana Copy Trading Bot, please follow these instructions: 40 | **Clone the Repository**: 41 | ```bash 42 | git clone https://github.com/0xCoder0000/Solana-Copy-Trading-Bot-Rust 43 | cd Solana-Copy-Trading-Bot-Rust -------------------------------------------------------------------------------- /helius.ts: -------------------------------------------------------------------------------- 1 | const runHeliusGeyser = async () => { 2 | const ws: WebSocket = new WebSocket(HELIUS_GEYSER_URL); 3 | const copyWallets: string[] = Object.keys(await readDataJson("copyWallets.json")); 4 | ws.on('open', async () => { 5 | console.log('WebSocket connection established.'); 6 | 7 | copyWallets = Object.keys(await readDataJson("copyWallets.json")); 8 | sendRequest(ws, copyWallets); 9 | }); 10 | 11 | ws.on('message', async function incoming(data: any) { 12 | const messageStr = data.toString('utf8'); 13 | const messageObj = JSON.parse(messageStr); 14 | 15 | 16 | if (!messageObj.params) return; 17 | if (!messageObj.params.result) return; 18 | const result = messageObj.params.result; 19 | 20 | const logs = result.transaction.meta.logMessages; 21 | 22 | 23 | 24 | 25 | const signer = result.transaction.transaction.message.accountKeys.filter((item: any) => item.signer === true).map((item: any) => item.pubkey).join(''); 26 | if (!result || !result.transaction || !result.transaction.meta) { 27 | console.log("Incomplete transaction data."); 28 | return; 29 | } 30 | 31 | let targetWallet: string = ''; 32 | for (let i = 0; i < copyWallets.length; i++) { 33 | if (messageStr.includes(copyWallets[i])) { 34 | targetWallet = copyWallets[i] 35 | break; 36 | } 37 | } 38 | if (targetWallet === '') return; 39 | if (!result.transaction) { 40 | console.error("Transaction not found"); 41 | return; 42 | } 43 | if (messageStr.includes(PUMP_FUN_PROGRAM_ID)) { 44 | 45 | // console.log("messageStr", messageStr) 46 | // const PumpFunProgram = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); 47 | await parsePumpFunHeliusTx(result) 48 | } else if (messageStr.includes(RAYDIUM_AMM_PROGRAM_ID)) { 49 | 50 | // console.log("messageStr", messageStr) 51 | // await parseRaydiumAmmHeliusTx(result) 52 | 53 | } else if (messageStr.includes(RAYDIUM_CPMM_PROGRAM_ID)) { 54 | // await parseRaydiumCpmmHeliusTx(result) 55 | } 56 | 57 | // console.log("messageStr", messageStr) 58 | // console.log("messageObj", messageObj) 59 | 60 | // const res = await parsePumpFunInstruction(result) 61 | // console.log("res", res) 62 | }); 63 | } 64 | 65 | runHeliusGeyser() 66 | -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | use bincode::Options; 2 | use jito_json_rpc_client::jsonrpc_client::rpc_client::RpcClient as JitoRpcClient; 3 | use solana_sdk::signature::Keypair; 4 | use temp::common::utils::{ 5 | create_arc_rpc_client, create_nonblocking_rpc_client, import_env_var, 6 | import_wallet, log_message, AppState, 7 | }; 8 | use temp::core::token::get_account_info; 9 | use temp::core::tx::jito_confirm; 10 | use temp::engine::swap::{pump_swap, raydium_swap}; 11 | // use copy_trading_bot::dex::pump::pump_sdk_swap; 12 | use dotenv::dotenv; 13 | use futures_util::{SinkExt, StreamExt}; 14 | use serde::Serialize; 15 | use serde_json::Value; 16 | use solana_sdk::message::VersionedMessage; 17 | use solana_sdk::pubkey::Pubkey; 18 | use solana_sdk::signer::Signer; 19 | use solana_sdk::transaction::VersionedTransaction; 20 | use spl_associated_token_account::get_associated_token_address; 21 | use std::env; 22 | use std::str::FromStr; 23 | use std::sync::{Arc, LazyLock}; 24 | use tokio::time::Instant; 25 | use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage}; 26 | 27 | #[derive(Serialize)] 28 | struct SwapRequest { 29 | quoteResponse: serde_json::Value, // You may deserialize it into a specific struct if known 30 | userPublicKey: String, 31 | wrapAndUnwrapSol: bool, 32 | dynamicComputeUnitLimit: bool, 33 | prioritizationFeeLamports: u64, 34 | } 35 | #[tokio::main] 36 | 37 | async fn main() { 38 | dotenv().ok(); 39 | let target = env::var("TARGET_PUBKEY").expect("TARGET not set"); 40 | let private_key = env::var("PRIVATE_KEY").expect("PRIVATE_KEY not set"); 41 | let rpc_client = create_arc_rpc_client().unwrap(); 42 | let rpc_nonblocking_client = create_nonblocking_rpc_client().await.unwrap(); 43 | let wallet:Keypair = Keypair::from_base58_string(&private_key); 44 | 45 | let state = AppState { 46 | rpc_client, 47 | rpc_nonblocking_client, 48 | wallet: Arc::new(wallet) 49 | }; 50 | pub static BLOCK_ENGINE_URL: LazyLock = 51 | LazyLock::new(|| import_env_var("JITO_BLOCK_ENGINE_URL")); 52 | let jito_client = Arc::new(JitoRpcClient::new(format!( 53 | "{}/api/v1/bundles", 54 | *BLOCK_ENGINE_URL 55 | ))); 56 | let unwanted_key = env::var("JUP_PUBKEY").expect("JUP_PUBKEY not set"); 57 | let ws_url = env::var("RPC_WEBSOCKET_ENDPOINT").expect("RPC_WEBSOCKET_ENDPOINT not set"); 58 | 59 | let (ws_stream, _) = connect_async(ws_url) 60 | .await 61 | .expect("Failed to connect to WebSocket server"); 62 | let (mut write, mut read) = ws_stream.split(); 63 | // Subscribe to logs 64 | let subscription_message = serde_json::json!({ 65 | "jsonrpc": "2.0", 66 | "id": 1, 67 | "method": "transactionSubscribe", 68 | "params": [ 69 | 70 | { 71 | "failed": false, 72 | "accountInclude": ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"], 73 | "accountExclude": [unwanted_key], 74 | // Optionally specify accounts of interest 75 | }, 76 | { 77 | "commitment": "processed", 78 | "encoding": "jsonParsed", 79 | "transactionDetails": "full", 80 | "maxSupportedTransactionVersion": 0 81 | } 82 | ] 83 | }); 84 | 85 | write 86 | .send(subscription_message.to_string().into()) 87 | .await 88 | .expect("Failed to send subscription message"); 89 | 90 | let _ = log_message("--------------------- Copy-trading-bot start!!! ------------------\n") 91 | .await; 92 | 93 | // Listen for messages 94 | while let Some(Ok(msg)) = read.next().await { 95 | if let WsMessage::Text(text) = msg { 96 | let json: Value = serde_json::from_str(&text).unwrap(); 97 | 98 | let sig = json["params"]["result"]["signature"] 99 | .as_str() 100 | .unwrap_or_default(); 101 | let timestamp = Instant::now(); 102 | if let Some(account_keys) = json["params"]["result"]["transaction"]["transaction"] 103 | ["message"]["accountKeys"] 104 | .as_array() 105 | { 106 | 107 | let mut flag = false; 108 | for account_key in account_keys.iter() { 109 | if account_key["signer"] == true 110 | && target == account_key["pubkey"].as_str().unwrap() 111 | { 112 | flag = true; 113 | break; 114 | } else { 115 | flag = false; 116 | break; 117 | } 118 | } 119 | if flag == true { 120 | println!("sig: {:#?}", &sig); 121 | let msg = "\nTarget Wallet: ".to_string() 122 | + &target 123 | + " https://solscan.io/tx/" 124 | + &sig; 125 | let _ = log_message(&msg).await; 126 | 127 | for key in account_keys.iter() { 128 | if key["pubkey"] == "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" { 129 | tx_ray( 130 | json.clone(), 131 | target.clone(), 132 | timestamp.clone(), 133 | state.clone(), 134 | jito_client.clone(), 135 | ) 136 | .await; 137 | } 138 | if key["pubkey"] == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" { 139 | tx_pump( 140 | json.clone(), 141 | target.clone(), 142 | timestamp.clone(), 143 | state.clone(), 144 | jito_client.clone(), 145 | ) 146 | .await; 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | pub async fn tx_ray( 156 | json: Value, 157 | target: String, 158 | timestamp: Instant, 159 | state: AppState, 160 | jito_client: Arc, 161 | ) { 162 | // Contact for this part 163 | } 164 | 165 | pub async fn tx_pump( 166 | json: Value, 167 | target: String, 168 | timestamp: Instant, 169 | state: AppState, 170 | jito_client: Arc, 171 | ) { 172 | // Contact for this part 173 | } 174 | 175 | pub async fn swap_on_jup(mint: String, dir: String, amount: u64) { 176 | // Contact for this part 177 | } 178 | pub async fn swap_to_events_on_pump( 179 | mint: String, 180 | amount_in: u64, 181 | dirs: String, 182 | timestamp: Instant, 183 | jito_client: Arc, 184 | state: AppState, 185 | ) { 186 | println!("2: {:#?}", timestamp.elapsed().clone()); 187 | 188 | let slippage = 10000; 189 | println!("2.1: {:#?}", timestamp.elapsed()); 190 | let res = pump_swap( 191 | state, 192 | amount_in, 193 | &dirs, 194 | slippage, 195 | &mint, 196 | jito_client, 197 | timestamp.clone(), 198 | ) 199 | .await; 200 | } 201 | 202 | pub async fn swap_to_events_on_raydium( 203 | mint: String, 204 | amount_in: u64, 205 | dirs: String, 206 | pool_id: String, 207 | timestamp: Instant, 208 | jito_client: Arc, 209 | state: AppState, 210 | ) { 211 | println!("2: {:#?}", timestamp.elapsed().clone()); 212 | 213 | let slippage = 10000; 214 | println!("2.1: {:#?}", timestamp.elapsed()); 215 | let res = raydium_swap( 216 | state, 217 | amount_in, 218 | &dirs, 219 | pool_id, 220 | slippage, 221 | &mint, 222 | jito_client, 223 | timestamp.clone(), 224 | ) 225 | .await; 226 | } 227 | --------------------------------------------------------------------------------