In Part 2, your bot detects new trading pairs on DEXs like Raydium and Pump.fun using the GoldRush Streaming API. This gives you real-time visibility into liquidity creation.
Now, we will execute instant buys on new tokens using Jupiter Aggregator. This step adds the logic to manage token accounts, handle transactions, and introduce a safe dry-run mode for testing.
By the end of this part, your bot will:
Automatically purchase new token pairs that meet your conditions.
Automatically manage token account (ATA) creation and verification.
Handle transaction confirmations and retries safely.
Implement swap execution via Jupiter, with a DRY_RUN flag to simulate transactions.
Note: Trading with real funds carries significant risk. This code uses your private key and SOL to execute permanent mainnet transactions that you cannot reverse. This tutorial configures the bot to operate exclusively in DRY_RUN mode for safe testing.
Before implementing any of the code below, ensure you’ve completed the following from the previous guide.

In this section, we add new dependencies and Solana imports to handle token creation and transaction execution. Part 2 didn't require these because it focused solely on the Goldrush streaming api and blockchain data monitoring.
import 'dotenv/config';
import WebSocket from 'ws';
import axios from 'axios';
import bs58 from 'bs58';
import { GoldRushClient, StreamingChain, StreamingProtocol } from "@covalenthq/client-sdk";
import {
GOLDRUSH_API_KEY,
PRIVATE_KEY,
RPC_ENDPOINT,
RPC_WEBSOCKET_ENDPOINT,
QUOTE_AMOUNT,
MIN_POOL_SIZE,
MAX_POOL_SIZE,
MONITORED_DEXES,
SLIPPAGE_BPS,
DRY_RUN,
} from './constant';
import {
Connection,
Keypair,
VersionedTransaction,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from '@solana/web3.js';
import {
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
} from '@solana/spl-token';
interface StreamPair {
pair_address: string;
block_signed_at?: string;
base_token_metadata: {
contract_ticker_symbol: string;
contract_address: string;
};
quote_token_metadata: {
contract_ticker_symbol: string;
};
liquidity?: number;
new_liquidity_data: any;
}WebSocket: Establishes the real-time connection to the GoldRush streaming API.
axios: Handles Jupiter API communication for quotes and swaps.
bs58: Decodes base58-encoded private keys from the environment.
@solana/web3.js: Core blockchain interaction and transaction management.
@solana/spl-token: Token account operations and ATA management.
(global as any).WebSocket = WebSocket;
const BOT_START_TIME = Date.now();
let skipFirstBatch = true;
let wallet: Keypair;
try {
wallet = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY));
console.log(`✓ Wallet loaded: ${wallet.publicKey.toString()}`);
} catch (error) {
console.error(' Failed to load wallet. Check PRIVATE_KEY in .env');
process.exit(1);
}
const goldRushClient = new GoldRushClient(GOLDRUSH_API_KEY,
({} as any),
{
onConnecting: () => console.log("Connecting to GoldRush Streaming..."),
onOpened: () => console.log("✓ Connected to GoldRush Streaming!"),
onClosed: () => console.warn(" Disconnected from GoldRush Streaming"),
onError: (error) => console.error("GoldRush Error:", error),
}
);
const solanaConnection = new Connection(RPC_ENDPOINT, {
wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
commitment: 'confirmed',
});
(async () => {
try {
const blockHeight = await solanaConnection.getBlockHeight();
console.log(`✓ Connected to Solana RPC at block height: ${blockHeight}\n`);
} catch (error) {
console.error("Failed to connect to Solana RPC:", error);
}
})();BOT_START_TIME: used to ignore pairs created before the bot started (pre-existing liquidity).
skipFirstBatch: flags to skip the backlog of initial stream data. Streams often output a large backlog when you first connect; you typically skip that only to process fresh events.
The bot loads the wallet synchronously from the base58 key. If loading fails, stop the bot immediately; you cannot trade without a valid key.
GoldRushClient uses connection status callbacks for debugging.
Connection(RPC_ENDPOINT, {wsEndpoint, commitment}): sets up Solana RPC + WebSocket endpoint for confirmations, account info, etc.
The protocol mapping translates your configuration into the GoldRush SDK's internal protocol identifiers. This allows us to use human-readable DEX names while ensuring they work with the streaming service's data format.
function mapToStreamingProtocol(dex: string): StreamingProtocol | undefined {
switch (dex.toLowerCase()) {
case 'raydium':
return StreamingProtocol.RAYDIUM_AMM;
case 'pump.fun':
return StreamingProtocol.PUMP_DOT_FUN;
default:
console.warn(`Unsupported DEX: ${dex}`);
return undefined;
}
}
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));mapToStreamingProtocol: This maps human-readable MONITORED_DEXES to the enum required by GoldRush SDK. If you ever monitor a new DEX, you can extend this mapping accordingly.
Sleep Helper: Handles timing controls for cooldowns, rate limiting, and retry intervals.
On Solana, you need a separate account for each token type you hold. We call these Associated Token Accounts (ATAs). Before buying a token, you must have an ATA for that token.
async function ensureTokenAccount(tokenMint: string): Promise<void> {
try {
const mintPubkey = new PublicKey(tokenMint);
const ata = await getAssociatedTokenAddress(mintPubkey, wallet.publicKey);
const accountInfo = await solanaConnection.getAccountInfo(ata);
if (!accountInfo) {
console.log('🏗️ Creating Associated Token Account...');
const ix = createAssociatedTokenAccountInstruction(
wallet.publicKey,
ata,
wallet.publicKey,
mintPubkey
);
const tx = new Transaction().add(ix);
const signature = await sendAndConfirmTransaction(
solanaConnection,
tx,
[wallet],
{ commitment: 'confirmed' }
);
console.log(` ATA created: ${signature.substring(0, 20)}...`);
await sleep(1000); // Brief pause to ensure ATA is indexed
} else {
console.log(' Token account already exists');
}
} catch (error: any) {
console.error('Failed to ensure token account:', error.message);
}
}
Solana requires an Associated Token Account (ATA) for each token before your wallet can receive it. If you don't have an ATA, the bot creates one automatically; this costs SOL for transaction fees and account rent. The 1-second delay gives the network time to process and index the new ATA. Without this wait, swap attempts might fail with 'account not found' errors.
For production use, pre-create your ATAs to avoid this delay entirely.
The buy execution relies on Jupiter's aggregation API for optimal pricing and routing. These constants configure our interaction with Jupiter.
const JUPITER_BASE_URL = 'https://quote-api.jup.ag/v6';
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 8000; // 8 seconds between retriesasync function buyToken(tokenMint: string, pair: StreamPair): Promise<void> {
console.log('\n' + '='.repeat(60));
console.log(' ATTEMPTING TO BUY TOKEN');
console.log('='.repeat(60));
console.log(`Token: ${pair.base_token_metadata.contract_ticker_symbol}`);
console.log(`Mint: ${tokenMint}`);
console.log(`Pair: ${pair.pair_address}`);
console.log(`Liquidity: $${pair.liquidity?.toFixed(2) || 'Unknown'}`);
console.log(`Buying with: ${QUOTE_AMOUNT / 1e9} SOL`);
console.log('='.repeat(60));
// Comprehensive balance verification
const balance = await solanaConnection.getBalance(wallet.publicKey);
const balanceSOL = balance / 1e9;
console.log(` Wallet Balance: ${balanceSOL.toFixed(4)} SOL`);
const minRequired = (QUOTE_AMOUNT + 5_000_000) / 1e9; // +0.005 SOL buffer
if (balance < QUOTE_AMOUNT + 5_000_000) {
console.error('\n INSUFFICIENT BALANCE!');
console.error(` Need at least: ${minRequired.toFixed(4)} SOL`);
console.error(` Current balance: ${balanceSOL.toFixed(4)} SOL`);
console.error(` Missing: ${(minRequired - balanceSOL).toFixed(4)} SOL`);
console.error(`\n Fund your wallet:`);
console.error(` Address: ${wallet.publicKey.toString()}`);
console.error(` Recommended: 0.1 SOL for multiple trades\n`);
return;
}
// Dry-run safety switch
if (DRY_RUN) {
console.log(' DRY_RUN mode enabled - Would attempt purchase here');
console.log('='.repeat(60) + '\n');
return;
}This logs important details so you can trace what happened (token mint, pair address, liquidity).
It ensures you have at least QUOTE_AMOUNT + 0.005 SOL buffer (5 million lamports = ~0.005 SOL). The 0.005 SOL buffer covers transaction fees and account creation. Adjust this based on your risk tolerance and network conditions.
If the balance is too low, the bot aborts the transaction.
When DRY_RUN = true in constant.ts, the bot simulates swaps without executing real transactions.
We already covered this above. It ensures there’s a target account for the token you’re about to buy.
await ensureTokenAccount(tokenMint);const inputMint = 'So11111111111111111111111111111111111111112'; // Wrapped SOL address
const outputMint = tokenMint; // The new token we're buying// Retry loop for new token indexing delays
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
console.log(`\n Getting quote from Jupiter... (Attempt ${attempt}/${MAX_RETRIES})`);
const quoteResponse = await axios.get(`${JUPITER_BASE_URL}/quote`, {
params: {
inputMint,
outputMint,
amount: QUOTE_AMOUNT,
slippageBps: SLIPPAGE_BPS,
},
timeout: 15000,
});
const quote = quoteResponse.data;
if (!quote || !quote.outAmount) {
throw new Error('Invalid quote response from Jupiter');
}
const outputAmount = parseFloat(quote.outAmount) / 1e6; // Assume 6 decimals
console.log(` Quote received: ~${outputAmount.toFixed(2)} tokens`);
console.log(` Price Impact: ${quote.priceImpactPct || 'N/A'}%`);
// Build swap transaction with priority fees
console.log(' Building swap transaction...');
const swapResponse = await axios.post(`${JUPITER_BASE_URL}/swap`, {
quoteResponse: quote,
userPublicKey: wallet.publicKey.toString(),
wrapAndUnwrapSol: true,
dynamicComputeUnitLimit: true,
prioritizationFeeLamports: {
autoMultiplier: 2, // 2x auto priority fee for better execution
},
}, {
timeout: 15000,
headers: {
'Content-Type': 'application/json',
}
});
if (!swapResponse.data || !swapResponse.data.swapTransaction) {
throw new Error('Invalid swap response from Jupiter');
}
const { swapTransaction } = swapResponse.data;
console.log(' Transaction built');These Jupiter parameters optimize for new token launches:
wrapAndUnwrapSol: Handles SOL/WSOL conversion automatically.
dynamicComputeUnitLimit: Prevents out-of-gas errors on complex routes.
prioritizationFeeLamports: 2x multiplier for faster block inclusion.
// Sign transaction locally (private key never leaves your machine)
console.log('Signing transaction...');
const txBuffer = Buffer.from(swapTransaction, 'base64');
const transaction = VersionedTransaction.deserialize(txBuffer);
transaction.sign([wallet]);
console.log(' Transaction signed');
// Submit to network with retry configuration
console.log(' Sending transaction to network...');
const signature = await solanaConnection.sendRawTransaction(
transaction.serialize(),
{
skipPreflight: false, // Enable preflight checks for safety
preflightCommitment: 'confirmed',
maxRetries: 3,
}
);
console.log(' Transaction sent successfully!');
console.log(`Signature: ${signature}`);
console.log(`Solscan: https://solscan.io/tx/${signature}`);
console.log(`SolanaFM: https://solana.fm/tx/${signature}`);The bot signs transactions locally on your machine. It never sends your private key over the network. With skipPreflight: false enabled, Solana performs comprehensive transaction simulation before broadcast, providing a critical safety check that identifies potential failures before committing real SOL. After submission, the bot logs transaction signatures with direct links to Solana explorers.
console.log('Waiting for confirmation (45s timeout)...');
const latestBlockhash = await solanaConnection.getLatestBlockhash('confirmed');
try {
const confirmPromise = solanaConnection.confirmTransaction(
{
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
},
'confirmed'
);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Confirmation timeout')), 45000)
);
const confirmation = await Promise.race([confirmPromise, timeoutPromise]) as any;
if (confirmation.value?.err) {
console.error(' Transaction FAILED on-chain!');
console.error(' Error:', JSON.stringify(confirmation.value.err));
console.log(' Check Solscan for details');
} else {
console.log(' PURCHASE CONFIRMED! ');
console.log(`Successfully bought ${pair.base_token_metadata.contract_ticker_symbol}!`);
console.log(` Token Mint: ${tokenMint}`);
const newBalance = await solanaConnection.getBalance(wallet.publicKey);
console.log(` New Balance: ${(newBalance / 1e9).toFixed(4)} SOL`);
}
} catch (confirmError: any) {
if (confirmError.message === 'Confirmation timeout') {
console.log(' Confirmation timed out - checking status...');
await sleep(3000);
try {
const status = await solanaConnection.getSignatureStatus(signature);
if (status.value?.confirmationStatus === 'confirmed' ||
status.value?.confirmationStatus === 'finalized') {
console.log(' Transaction confirmed (verified via status check)');
console.log(` Successfully bought ${pair.base_token_metadata.contract_ticker_symbol}!`);
} else if (status.value?.err) {
console.error(' Transaction failed:', status.value.err);
} else {
console.log(' Transaction pending - check links above');
}
} catch (statusError) {
console.log(' Could not verify transaction status');
console.log(' Please check Solscan link above manually');
}
} else {
throw confirmError;
}
}
console.log('='.repeat(60) + '\n');
return; // Exit retry loop on successThe confirmation system uses a 45-second timeout with automatic fallback checking. If the initial confirmation times out, it queries the transaction status directly as a backup. This dual approach manages network delays while clearly showing whether each trade succeeded or failed, along with updated balances.
New tokens often experience indexing delays. This retry system handles temporarily missing timely data.
} catch (error: any) {
const isLastAttempt = attempt === MAX_RETRIES;
console.error(` Attempt ${attempt}/${MAX_RETRIES} failed`);
console.error(` Error: ${error.message}`);
// Enhanced error diagnostics with transaction logs
if (error.logs && Array.isArray(error.logs)) {
console.error(' Transaction Logs:');
error.logs.slice(0, 5).forEach((log: string) => console.error(` ${log}`));
if (error.logs.length > 5) {
console.error(` ... and ${error.logs.length - 5} more`);
}
}
// HTTP error analysis
if (error.response) {
console.error(` HTTP Status: ${error.response.status}`);
if (error.response.status === 404) {
console.log(' Reason: Token not yet indexed by Jupiter (too new)');
} else if (error.response.status === 429) {
console.log(' Reason: Rate limited - consider upgrading RPC');
} else if (error.response.data) {
const errorMsg = JSON.stringify(error.response.data).substring(0, 200);
console.error(` API Response: ${errorMsg}`);
}
}
// Retry logic with cooldown
if (!isLastAttempt) {
console.log(`Waiting ${RETRY_DELAY_MS / 1000}s before retry ${attempt + 1}...`);
await sleep(RETRY_DELAY_MS);
} else {
console.log('\n ALL RETRIES EXHAUSTED');
console.log(' Possible reasons:');
console.log(' • Token too new (not indexed by Jupiter yet)');
console.log(' • Insufficient liquidity for routing');
console.log(' • Token might be a scam with fake liquidity');
console.log(' • RPC connection issues');
console.log(' • Network congestion');
console.log('='.repeat(60) + '\n');
}
}
}
}
The retry system manages delays when new tokens are not yet visible in Jupiter's index. If a swap fails, it provides detailed error information, like transaction logs and status codes, to diagnose the issue.
For example, a 404 error means the token is not yet indexed, while a 429 indicates a rate limit. The system makes up to three attempts, pausing 8 seconds between each to allow time for indexing. If all retries fail, it clearly explains what went wrong and stops retrying to prevent infinite loops.
function shouldBuyPair(pair: StreamPair): boolean {
if (pair.liquidity !== undefined) {
if (MIN_POOL_SIZE > 0 && pair.liquidity < MIN_POOL_SIZE) {
console.log(` Skipping: Liquidity too low ($${pair.liquidity.toFixed(2)} < $${MIN_POOL_SIZE})`);
return false;
}
if (MAX_POOL_SIZE > 0 && pair.liquidity > MAX_POOL_SIZE) {
console.log(` Skipping: Liquidity too high ($${pair.liquidity.toFixed(2)} > $${MAX_POOL_SIZE})`);
return false;
}
}
return true;
}Liquidity Validation: Filters tokens based on minimum and maximum pool size thresholds.
Risk Management: Excludes tokens with insufficient liquidity (high risk) or excessive liquidity.
Configurable Parameters: MIN_POOL_SIZE and MAX_POOL_SIZE allow fine-tuning of liquidity requirements.
The bot monitors high-frequency data on decentralized exchanges for newly created token pairs. It instantly detects when creators add liquidity to new pairs, giving you immediate trading opportunities.
async function subscribeToNewPairs() {
console.log(' Subscribing to new pairs...');
...
const protocols = MONITORED_DEXES
.map(dex => mapToStreamingProtocol(dex))
.filter((p): p is StreamingProtocol => Boolean(p));
goldRushClient.StreamingService.subscribeToNewPairs(
{
chain_name: StreamingChain.SOLANA_MAINNET,
protocols: protocols,
},
{
next: async (data: any) => {
if (skipFirstBatch) {
console.log(' Skipping initial backlog...\n');
skipFirstBatch = false;
return;
}
const pairs: StreamPair[] = Array.isArray(data) ? data : data.newPairs;
if (!pairs?.length) return;
console.log(`\n Received ${pairs.length} new pair(s)`);
for (const pair of pairs) {
if (pair.block_signed_at) {
const pairTime = new Date(pair.block_signed_at).getTime();
if (pairTime < BOT_START_TIME) {
console.log(` Skipping old pair: ${pair.base_token_metadata.contract_ticker_symbol}`);
continue;
}
}
console.log(`\n NEW PAIR DETECTED!`);
console.log(` Token: ${pair.base_token_metadata.contract_ticker_symbol}/${pair.quote_token_metadata.contract_ticker_symbol}`);
console.log(` Pair Address: ${pair.pair_address}`);
console.log(` Token Mint: ${pair.base_token_metadata.contract_address}`);
console.log(` Liquidity: $${pair.liquidity?.toFixed(2) || 'Unknown'}`);
if (shouldBuyPair(pair)) {
console.log(` Criteria met - Initiating purchase...`);
await buyToken(pair.base_token_metadata.contract_address, pair);
}
}
},
error: (err) => {
console.error(' Stream error:', err);
console.log('Attempting to reconnect...');
},
}
);
console.log('Bot is running!');
console.log(' Monitoring for new token pairs...\n');
}Real-time Event Processing: Listens exclusively for new trading pairs via the GoldRush streaming API.
Backlog Prevention: Skips initial historical data using the skipFirstBatch flag to avoid processing stale pairs for efficient data integrity.
Freshness Validation: The bot compares pair creation times against BOT_START_TIME to exclude older pairs.
Automated Execution: Triggers buyToken() for pairs passing all filter criteria via shouldBuyPair().
Multi-DEX Support: Maps configured DEX names to GoldRush protocol enums for cross-platform monitoring streaming hook.
console.log('═══════════════════════════════════════════════════');
console.log(' Solana Memecoin Sniper Bot');
console.log(' Powered by GoldRush + Jupiter');
console.log('═══════════════════════════════════════════════════');
subscribeToNewPairs().catch(error => {
console.error('\n Failed to start bot:', error);
process.exit(1);
});
process.on('SIGINT', () => {
console.log('\n\n Shutting down bot...');
console.log(' Goodbye!');
process.exit(0);
});Update the DRY_RUN value to true in the constant.ts configuration file.
To start the bot, execute the following command in your terminal:
npx ts-node bot.tsYou should get an output similar to this:

This verifies that the connection was established, token detection is active, and purchase simulation is working.
This completes the Solana memecoin sniper bot. It uses GoldRush for real-time token detection and Jupiter for optimal trade execution. The bot can now monitor new token launches, validate liquidity, and execute purchases rapidly. Happy Building!