Last active
July 10, 2025 19:29
-
-
Save itxtoledo/1444f88e8dbd868c245bcbe77619f5db to your computer and use it in GitHub Desktop.
Bot to create random trades
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Connection, | |
Keypair, | |
PublicKey, | |
VersionedTransaction, | |
TOKEN_PROGRAM_ID, | |
sendAndConfirmTransaction, | |
type Commitment | |
} from "@solana/web3.js"; | |
import { | |
getAssociatedTokenAddress, | |
getAccount, | |
TokenAccountNotFoundError, | |
TokenInvalidAccountOwnerError | |
} from "@solana/spl-token"; | |
import * as fs from "fs/promises"; | |
import bs58 from "bs58"; | |
// Token addresses - defined at the top for easy configuration | |
const TOKEN_ADDRESS = "5fsvCy3yaLduYJ5mkSS7ERwwraJ5JPVSDakedLfpxaF"; // Your token | |
const USDC_ADDRESS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDC | |
const SOL_ADDRESS = "So11111111111111111111111111111111111111112"; // Wrapped SOL | |
// Trading configuration | |
const BUY_AMOUNT_USDC = 0.5; // Amount of USDC to buy with each cycle | |
const MINIMUM_USDC_BALANCE = 1.0; // Minimum USDC balance to maintain | |
const AUTO_PURCHASE_AMOUNT = 5.0; // Amount of USDC to buy when balance is low | |
// Configuration interface for JSON file | |
interface Config { | |
privateKey?: string; | |
rpcUrl?: string; | |
} | |
// Jupiter API response interfaces | |
interface JupiterQuote { | |
inputMint: string; | |
inAmount: string; | |
outputMint: string; | |
outAmount: string; | |
otherAmountThreshold: string; | |
swapMode: string; | |
slippageBps: number; | |
platformFee?: any; | |
priceImpactPct: string; | |
routePlan: any[]; | |
} | |
interface JupiterSwapResponse { | |
swapTransaction: string; | |
lastValidBlockHeight?: number; | |
} | |
// Function to generate random delay between 2 and 5 minutes (in milliseconds) | |
const getRandomDelay = (): number => { | |
const min = 2 * 60 * 1000; // 2 minutes | |
const max = 5 * 60 * 1000; // 5 minutes | |
return Math.floor(Math.random() * (max - min + 1)) + min; | |
}; | |
// Function to load or create config | |
async function loadConfig(): Promise<Config> { | |
try { | |
const data = await fs.readFile("config.json", "utf-8"); | |
return JSON.parse(data) as Config; | |
} catch (error) { | |
console.log("Config not found, generating new keypair..."); | |
const keypair = Keypair.generate(); | |
const config: Config = { | |
privateKey: bs58.encode(keypair.secretKey), | |
rpcUrl: "https://api.mainnet-beta.solana.com" // Default RPC | |
}; | |
await fs.writeFile("config.json", JSON.stringify(config, null, 2)); | |
console.log("New config created. Please add your RPC URL to config.json"); | |
return config; | |
} | |
} | |
// Function to get token balance with proper error handling | |
async function getTokenBalance( | |
connection: Connection, | |
wallet: PublicKey, | |
tokenMint: string, | |
decimals: number = 9 | |
): Promise<number> { | |
try { | |
const tokenMintPubkey = new PublicKey(tokenMint); | |
const associatedTokenAddress = await getAssociatedTokenAddress( | |
tokenMintPubkey, | |
wallet, | |
false | |
); | |
try { | |
const tokenAccount = await getAccount(connection, associatedTokenAddress); | |
return Number(tokenAccount.amount) / Math.pow(10, decimals); | |
} catch (error) { | |
if (error instanceof TokenAccountNotFoundError || | |
error instanceof TokenInvalidAccountOwnerError) { | |
console.log(`Token account not found for ${tokenMint}`); | |
return 0; | |
} | |
throw error; | |
} | |
} catch (error) { | |
console.error("Error fetching token balance:", error); | |
return 0; | |
} | |
} | |
// Function to get SOL balance | |
async function getSolBalance(connection: Connection, wallet: PublicKey): Promise<number> { | |
try { | |
const balance = await connection.getBalance(wallet); | |
return balance / 1e9; // Convert lamports to SOL | |
} catch (error) { | |
console.error("Error fetching SOL balance:", error); | |
return 0; | |
} | |
} | |
// Function to get USDC balance specifically | |
async function getUSDCBalance(connection: Connection, wallet: PublicKey): Promise<number> { | |
return await getTokenBalance(connection, wallet, USDC_ADDRESS, 6); // USDC has 6 decimals | |
} | |
// Function to automatically purchase USDC with SOL when balance is low | |
async function ensureUSDCBalance( | |
connection: Connection, | |
wallet: Keypair | |
): Promise<boolean> { | |
try { | |
const usdcBalance = await getUSDCBalance(connection, wallet.publicKey); | |
console.log(`💰 Current USDC balance: ${usdcBalance.toFixed(4)} USDC`); | |
if (usdcBalance >= MINIMUM_USDC_BALANCE) { | |
console.log("✅ USDC balance is sufficient"); | |
return true; | |
} | |
console.log(`⚠️ USDC balance is low (${usdcBalance.toFixed(4)} < ${MINIMUM_USDC_BALANCE})`); | |
console.log(`🔄 Attempting to purchase ${AUTO_PURCHASE_AMOUNT} USDC with SOL...`); | |
const solBalance = await getSolBalance(connection, wallet.publicKey); | |
console.log(`💰 Current SOL balance: ${solBalance.toFixed(4)} SOL`); | |
if (solBalance < 0.1) { // Need some SOL for the transaction + fees | |
console.error("❌ Insufficient SOL balance to purchase USDC"); | |
return false; | |
} | |
// Perform SOL to USDC swap | |
const success = await performSwap( | |
connection, | |
wallet, | |
SOL_ADDRESS, | |
USDC_ADDRESS, | |
AUTO_PURCHASE_AMOUNT, | |
true, | |
false, | |
"SOL to USDC" | |
); | |
if (success > 0) { | |
console.log(`✅ Successfully purchased USDC`); | |
// Wait a moment for the transaction to settle | |
await new Promise(resolve => setTimeout(resolve, 5000)); | |
const newUsdcBalance = await getUSDCBalance(connection, wallet.publicKey); | |
console.log(`💰 New USDC balance: ${newUsdcBalance.toFixed(4)} USDC`); | |
return newUsdcBalance >= MINIMUM_USDC_BALANCE; | |
} else { | |
console.error("❌ Failed to purchase USDC"); | |
return false; | |
} | |
} catch (error) { | |
console.error("❌ Error ensuring USDC balance:", error); | |
return false; | |
} | |
} | |
// Main swap function using Jupiter API | |
async function performSwap( | |
connection: Connection, | |
wallet: Keypair, | |
tokenAddress: string, | |
quoteAddress: string, | |
amount: number, | |
isBuy: boolean, | |
isFullSell: boolean = false, | |
swapDescription: string = "" | |
): Promise<number> { | |
try { | |
const inputMint = isBuy ? quoteAddress : tokenAddress; | |
const outputMint = isBuy ? tokenAddress : quoteAddress; | |
let amountInLamports: number; | |
let decimals: number; | |
if (isFullSell) { | |
// Use entire token balance for full sell | |
const balance = await getTokenBalance(connection, wallet.publicKey, tokenAddress); | |
if (balance <= 0) { | |
console.log("No tokens to sell in full sell."); | |
return 0; | |
} | |
amountInLamports = Math.floor(balance * 1e9); // Convert to lamports | |
console.log(`Full sell amount: ${balance} tokens`); | |
} else { | |
// Determine decimals based on token type | |
if (quoteAddress === USDC_ADDRESS) { | |
decimals = isBuy ? 6 : 9; // USDC has 6 decimals, tokens have 9 | |
} else if (quoteAddress === SOL_ADDRESS) { | |
decimals = 9; // SOL has 9 decimals | |
} else { | |
decimals = 9; // Default to 9 decimals | |
} | |
amountInLamports = Math.floor(amount * Math.pow(10, decimals)); | |
} | |
const description = swapDescription || `${isBuy ? 'Buying' : 'Selling'}`; | |
console.log(`${description} with amount: ${amountInLamports} lamports`); | |
// Fetch quote from Jupiter API with error handling | |
const quoteUrl = `https://quote-api.jup.ag/v6/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amountInLamports}&slippageBps=300`; | |
const quoteResponse = await fetch(quoteUrl); | |
if (!quoteResponse.ok) { | |
throw new Error(`Quote API error: ${quoteResponse.status} ${quoteResponse.statusText}`); | |
} | |
const quote: JupiterQuote = await quoteResponse.json(); | |
if (!quote || !quote.outAmount) { | |
throw new Error("Invalid quote response from Jupiter API"); | |
} | |
console.log(`Quote received: ${quote.inAmount} -> ${quote.outAmount}`); | |
// Store amount bought for tracking (if buying) | |
let outputAmount = 0; | |
if (isBuy) { | |
const outputDecimals = outputMint === USDC_ADDRESS ? 6 : 9; | |
outputAmount = Number(quote.outAmount) / Math.pow(10, outputDecimals); | |
} | |
// Fetch swap transaction | |
const swapResponse = await fetch("https://quote-api.jup.ag/v6/swap", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
"Accept": "application/json" | |
}, | |
body: JSON.stringify({ | |
quoteResponse: quote, | |
userPublicKey: wallet.publicKey.toBase58(), | |
wrapAndUnwrapSol: true, | |
dynamicComputeUnitLimit: true, | |
prioritizationFeeLamports: 1000 | |
}), | |
}); | |
if (!swapResponse.ok) { | |
throw new Error(`Swap API error: ${swapResponse.status} ${swapResponse.statusText}`); | |
} | |
const swapData: JupiterSwapResponse = await swapResponse.json(); | |
if (!swapData.swapTransaction) { | |
throw new Error("No swap transaction returned from Jupiter API"); | |
} | |
// Deserialize and sign transaction | |
const transactionBuf = Buffer.from(swapData.swapTransaction, "base64"); | |
const transaction = VersionedTransaction.deserialize(transactionBuf); | |
transaction.sign([wallet]); | |
// Send transaction with proper confirmation | |
const signature = await connection.sendRawTransaction(transaction.serialize(), { | |
skipPreflight: false, | |
preflightCommitment: "confirmed" as Commitment, | |
maxRetries: 3 | |
}); | |
console.log(`Transaction sent: ${signature}`); | |
// Confirm transaction | |
const latestBlockhash = await connection.getLatestBlockhash(); | |
const confirmation = await connection.confirmTransaction({ | |
signature, | |
blockhash: latestBlockhash.blockhash, | |
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight | |
}, "confirmed"); | |
if (confirmation.value.err) { | |
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`); | |
} | |
console.log(`✅ Swap ${description || (isBuy ? "buy" : isFullSell ? "full sell" : "sell")} completed: ${signature}`); | |
return isBuy ? outputAmount : 0; | |
} catch (error) { | |
console.error(`❌ Error during ${swapDescription || (isBuy ? "buy" : isFullSell ? "full sell" : "sell")}:`, error); | |
return 0; | |
} | |
} | |
// Main execution | |
async function main(): Promise<void> { | |
const config = await loadConfig(); | |
if (!config.rpcUrl) { | |
console.error("❌ RPC URL not provided in config.json"); | |
console.log("Please add your RPC URL to config.json and restart"); | |
process.exit(1); | |
} | |
if (!config.privateKey) { | |
console.error("❌ Private key not found in config"); | |
process.exit(1); | |
} | |
const connection = new Connection(config.rpcUrl, { | |
commitment: "confirmed", | |
confirmTransactionInitialTimeout: 60000 | |
}); | |
const wallet = Keypair.fromSecretKey(bs58.decode(config.privateKey)); | |
let lastFullSellTime = 0; | |
const FULL_SELL_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds | |
console.log("🚀 Starting trading bot..."); | |
console.log("📍 Wallet public key:", wallet.publicKey.toBase58()); | |
console.log("🎯 Trading token:", TOKEN_ADDRESS); | |
console.log("💵 Quote token:", USDC_ADDRESS); | |
// Check initial balances | |
const solBalance = await getSolBalance(connection, wallet.publicKey); | |
const usdcBalance = await getUSDCBalance(connection, wallet.publicKey); | |
const tokenBalance = await getTokenBalance(connection, wallet.publicKey, TOKEN_ADDRESS); | |
console.log(`💰 Initial SOL balance: ${solBalance.toFixed(4)} SOL`); | |
console.log(`💵 Initial USDC balance: ${usdcBalance.toFixed(4)} USDC`); | |
console.log(`🪙 Initial token balance: ${tokenBalance.toFixed(4)} tokens`); | |
if (solBalance < 0.01) { | |
console.warn("⚠️ Low SOL balance. Make sure you have enough SOL for transaction fees."); | |
} | |
// Main trading loop | |
while (true) { | |
try { | |
const currentTime = Date.now(); | |
// Check if it's time for a full sell (every 1 hour) | |
if (currentTime - lastFullSellTime >= FULL_SELL_INTERVAL) { | |
console.log("⏰ Time for full sell..."); | |
await performSwap(connection, wallet, TOKEN_ADDRESS, USDC_ADDRESS, 0, false, true); | |
lastFullSellTime = currentTime; | |
console.log("✅ Full sell completed. Next full sell in 1 hour."); | |
// Wait before starting next cycle | |
await new Promise((resolve) => setTimeout(resolve, 5000)); | |
continue; | |
} | |
// Ensure we have enough USDC before trading | |
const hasEnoughUSDC = await ensureUSDCBalance(connection, wallet); | |
if (!hasEnoughUSDC) { | |
console.log("❌ Unable to ensure sufficient USDC balance, waiting 2 minutes before retry..."); | |
await new Promise((resolve) => setTimeout(resolve, 2 * 60 * 1000)); | |
continue; | |
} | |
// Regular buy-sell cycle | |
console.log("💰 Starting buy phase..."); | |
const boughtAmount = await performSwap(connection, wallet, TOKEN_ADDRESS, USDC_ADDRESS, BUY_AMOUNT_USDC, true); | |
if (boughtAmount <= 0) { | |
console.log("❌ Buy failed, skipping sell phase"); | |
await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait 30 seconds before retry | |
continue; | |
} | |
// Wait for random delay | |
const delay1 = getRandomDelay(); | |
console.log(`⏳ Waiting ${(delay1 / 1000 / 60).toFixed(1)} minutes before sell...`); | |
await new Promise((resolve) => setTimeout(resolve, delay1)); | |
// Sell half of the bought amount | |
console.log("💸 Starting sell phase..."); | |
const sellAmount = boughtAmount / 2; | |
await performSwap(connection, wallet, TOKEN_ADDRESS, USDC_ADDRESS, sellAmount, false); | |
// Wait for random delay before next cycle | |
const delay2 = getRandomDelay(); | |
console.log(`⏳ Waiting ${(delay2 / 1000 / 60).toFixed(1)} minutes before next cycle...`); | |
await new Promise((resolve) => setTimeout(resolve, delay2)); | |
} catch (error) { | |
console.error("❌ Error in main loop:", error); | |
console.log("⏳ Waiting 30 seconds before retry..."); | |
await new Promise((resolve) => setTimeout(resolve, 30000)); | |
} | |
} | |
} | |
// Handle graceful shutdown | |
process.on('SIGINT', () => { | |
console.log('\n🛑 Shutting down trading bot...'); | |
process.exit(0); | |
}); | |
process.on('SIGTERM', () => { | |
console.log('\n🛑 Shutting down trading bot...'); | |
process.exit(0); | |
}); | |
// Run the script | |
console.log("🤖 Solana Trading Bot Starting..."); | |
main().catch((error) => { | |
console.error("💥 Fatal error:", error); | |
process.exit(1); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment