Skip to content

Instantly share code, notes, and snippets.

@itxtoledo
Last active July 10, 2025 19:29
Show Gist options
  • Save itxtoledo/1444f88e8dbd868c245bcbe77619f5db to your computer and use it in GitHub Desktop.
Save itxtoledo/1444f88e8dbd868c245bcbe77619f5db to your computer and use it in GitHub Desktop.
Bot to create random trades
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