Build a Solana Copy Trading Bot Using Pump.fun and Yellowstone gRPC

·

Creating a Solana copy trading bot has become increasingly accessible thanks to powerful developer tools like the Pump.fun API and Yellowstone gRPC. In this comprehensive guide, you'll learn how to build a fully functional trading bot that monitors specific wallet addresses on the Pump.fun DEX and automatically replicates their buy trades on Solana in real time.

Whether you're an experienced blockchain developer or diving into DeFi automation for the first time, this step-by-step walkthrough will equip you with the skills to deploy your own Solana trading bot using modern gRPC streaming, transaction parsing, and automated swap execution.

Why Build a Solana Copy Trading Bot?

Copy trading bots allow developers and traders to mirror the moves of high-performing wallets—often referred to as "whales"—on decentralized exchanges. By automating this process, you can react faster than manual trading and potentially capitalize on early momentum in new token launches.

With Pump.fun being one of the most active meme coin launchpads on Solana, and Yellowstone gRPC enabling real-time blockchain monitoring, this combination offers a robust foundation for building responsive, low-latency trading strategies.

👉 Discover how top traders automate their Solana strategies with powerful tools.

Key Features of This Bot

Prerequisites

Before diving into the code, ensure you have the following:

⚠️ Educational Purpose Only: This guide is for learning purposes and does not constitute financial advice. Trading bots carry risks, including potential loss of funds. Always test thoroughly in a safe environment before using real assets.

Understanding the Core Tools

What Is Pump.fun?

Pump.fun is a popular decentralized exchange (DEX) on Solana designed for launching and trading meme tokens. It simplifies token creation and provides liquidity through a bonding curve model. The platform has gained traction due to its low barriers to entry and rapid token listing capabilities.

By leveraging the Pump.fun API via Metis, we can programmatically execute swaps without interacting directly with smart contracts—making integration faster and more reliable.

What Is Yellowstone gRPC?

Yellowstone is a high-performance gRPC-based plugin that streams real-time blockchain data from Solana nodes. Unlike traditional RPC polling, which introduces delays, Yellowstone delivers instant updates on transactions, account changes, and slot confirmations.

This makes it ideal for building:

With fine-grained filtering, we can subscribe only to transactions involving specific programs or wallets—reducing noise and improving efficiency.

👉 Access real-time blockchain data with advanced infrastructure tools.

Setting Up Your Development Environment

Step 1: Initialize the Project

Create a new directory and initialize a Node.js project:

mkdir solana-copy-bot
cd solana-copy-bot
npm init -y

Step 2: Install Dependencies

Install the required packages:

npm install @solana/web3.js@1 bs58 dotenv @triton-one/yellowstone-grpc node-fetch
PackagePurpose
@solana/web3.jsInteract with the Solana blockchain
bs58Handle Base58 encoding for signatures and addresses
dotenvLoad environment variables securely
@triton-one/yellowstone-grpcConnect to real-time transaction streams
node-fetchMake HTTP requests to the Pump.fun API

Step 3: Configure Environment Variables

Create a .env file in your project root:

SOLANA_RPC=https://your-quiknode-endpoint.quiknode.pro/abc123/
SECRET_KEY=[1,2,3,...] # Your wallet's secret key array
METIS_ENDPOINT=https://jupiter-swap-api.quiknode.pro/YOUR_METIS_ID/
YELLOWSTONE_ENDPOINT=https://your-yellowstone-endpoint.solana-mainnet.quiknode.pro:10000
YELLOWSTONE_TOKEN=your_yellowstone_api_token

Replace placeholders with actual values from your QuickNode dashboard.

🔐 Never commit .env files to version control. Add .env to your .gitignore.

Building the Copy Trading Bot

We'll structure our bot as a class called CopyTradeBot, encapsulating all logic for monitoring, decision-making, and execution.

Core Configuration

require("dotenv").config();
const fs = require("fs");
const fetch = require("node-fetch");
const bs58 = require("bs58").default;
const {
  Connection,
  Keypair,
  VersionedTransaction,
  LAMPORTS_PER_SOL,
  PublicKey,
} = require("@solana/web3.js");
const Client = require("@triton-one/yellowstone-grpc").default;
const { CommitmentLevel } = require("@triton-one/yellowstone-grpc");

class CopyTradeBot {
  config = {
    WATCH_LIST: ["YourWalletAddressHere"], // Wallets to monitor
    PUMP_FUN: {
      PROGRAM_ID: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
      FEE_ACCOUNT: "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM",
      BUY_DISCRIMINATOR: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
      SELL_DISCRIMINATOR: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
      TOKEN_DECIMALS: 6,
      TARGET_ACCOUNTS: {
        BUY: [{ name: "mint", index: 2 }, { name: "user", index: 6 }],
        SELL: [{ name: "mint", index: 2 }, { name: "user", index: 6 }],
      },
    },
    MIN_TX_AMOUNT: LAMPORTS_PER_SOL / 1000, // Minimum SOL to trigger copy (0.001 SOL)
    BUY_AMOUNT: LAMPORTS_PER_SOL / 1000,    // Amount to spend when copying
    LOG_FILE: "pump_fun_swaps.json",
    COMMITMENT: CommitmentLevel.CONFIRMED,
    TEST_MODE: true,
  };

  constructor() {
    this.validateEnv();
    this.connection = new Connection(process.env.SOLANA_RPC);
    this.wallet = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.SECRET_KEY)));
    console.log("🤖 Bot Wallet:", this.wallet.publicKey.toBase58());
    console.log("Monitoring addresses:");
    this.config.WATCH_LIST.forEach((addr) => console.log(" -", addr));
  }
}

Validate Environment Variables

Ensure all required credentials are present:

validateEnv = () => {
  const requiredEnvs = [
    "SOLANA_RPC",
    "SECRET_KEY",
    "METIS_ENDPOINT",
    "YELLOWSTONE_ENDPOINT",
    "YELLOWSTONE_TOKEN",
  ];
  requiredEnvs.forEach((env) => {
    if (!process.env[env]) {
      throw new Error(`Missing required environment variable: ${env}`);
    }
  });
};

Fetch Swap Transaction from Pump.fun API

Use the Metis-powered Pump.fun endpoint to generate a valid swap transaction:

fetchSwapTransaction = async ({ wallet, type, mint, inAmount }) => {
  const body = JSON.stringify({
    wallet,
    type,
    mint,
    inAmount,
    priorityFeeLevel: "high",
    slippageBps: "300",
  });

  const res = await fetch(`${process.env.METIS_ENDPOINT}/pump-fun/swap`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body,
  });

  if (!res.ok) throw new Error(`Swap fetch error: ${await res.text()}`);
  return res.json();
};

Sign and Serialize Transactions

Locally sign the transaction using your bot’s wallet:

signTransaction = async (swapTransaction) => {
  const transaction = VersionedTransaction.deserialize(Buffer.from(swapTransaction, "base64"));
  const latestBlockHash = await this.connection.getLatestBlockhash();
  transaction.message.recentBlockhash = latestBlockHash.blockhash;
  transaction.sign([this.wallet]);
  return Buffer.from(transaction.serialize()).toString("base64");
};

Broadcast Transaction to Solana Network

Send and confirm the signed transaction:

sendAndConfirmTransaction = async (signedTxBase64) => {
  try {
    const txid = await this.connection.sendEncodedTransaction(signedTxBase64, {
      skipPreflight: false,
      encoding: 'base64'
    });

    const timeout = 30000;
    const pollInterval = 3000;
    const start = Date.now();

    while (Date.now() - start < timeout) {
      const response = await this.connection.getSignatureStatuses([txid]);
      const status = response?.value?.[0];
      
      if (status?.err) throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
      if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') {
        return txid;
      }
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }
    throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
  } catch (error) {
    throw error;
  }
};

Implementing Real-Time Monitoring with Yellowstone

Create Subscription Request

Filter only relevant transactions from monitored wallets and Pump.fun program:

createSubscribeRequest = () => {
  const { WATCH_LIST, PUMP_FUN, COMMITMENT } = this.config;
  return {
    transactions: {
      pumpFun: {
        accountInclude: WATCH_LIST,
        accountRequired: [PUMP_FUN.FEE_ACCOUNT, PUMP_FUN.PROGRAM_ID],
      },
    },
    commitment: COMMITMENT,
  };
};

Handle Incoming Data Stream

Parse incoming transactions and detect valid buy events:

handleData = (data) => {
  if (!this.isSubscribeUpdateTransaction(data)) return;

  const transaction = data.transaction.transaction;
  const message = transaction.message;
  const meta = transaction.meta;
  if (meta?.err) return;

  const allInstructions = [
    ...message.instructions,
    ...(meta.innerInstructions?.flatMap(ix => ix.instructions || []) || [])
  ];

  const matchingIx = allInstructions.find(this.matchesInstructionDiscriminator);
  if (!matchingIx) return;

  const { amount, solAmount } = this.getInstructionData(matchingIx.data);
  if (solAmount < this.config.MIN_TX_AMOUNT) return;

  const txType = this.getTransactionType(matchingIx.data);
  const formattedSig = bs58.encode(Buffer.from(data.transaction.signature));

  console.log(`${txType === 'BUY' ? '🎯' : '📉'} - ${txType} - TxID: ${formattedSig}`);

  if (txType === "BUY") {
    const accountKeys = message.accountKeys;
    const accountsToInclude = this.config.PUMP_FUN.TARGET_ACCOUNTS.BUY;
    
    const includedAccounts = accountsToInclude.reduce((acc, { name, index }) => {
      const accountIndex = matchingIx.accounts[index];
      acc[name] = new PublicKey(accountKeys[accountIndex]).toBase58();
      return acc;
    }, {});

    this.handleWhaleBuy(includedAccounts.user, includedAccounts.mint, solAmount, formattedSig);
  }
};

Execute Copy Trade Logic

Trigger replication when conditions are met:

handleWhaleBuy = async (whalePubkey, tokenMint, lamportsSpent, copiedTxid) => {
  try {
    const response = await this.fetchSwapTransaction({
      wallet: this.wallet.publicKey.toBase58(),
      type: "BUY",
      mint: tokenMint,
      inAmount: this.config.BUY_AMOUNT,
    });

    const signedTx = await this.signTransaction(response.tx);
    
    let txid = 'simulated-TxID';
    if (!this.config.TEST_MODE) {
      txid = await this.sendAndConfirmTransaction(signedTx);
    }

    console.log("✅ Copied Buy - TxID:", txid);
    this.logSwap({
      event: "COPY_BUY",
      txid,
      copiedTxid,
      tokenMint,
      lamportsSpent,
      whalePubkey,
      timestamp: new Date().toISOString(),
    });
  } catch (err) {
    console.error("Failed to copy trade:", err);
    this.logSwap({
      event: "COPY_BUY_ERROR",
      error: err.message || String(err),
      copiedTxid,
      timestamp: new Date().toISOString(),
    });
  }
};

Launch and Test Your Bot

Start the Bot

Add the entry point:

start = async () => {
  console.log("🚀 Starting Pump.fun Copy Trading Bot...");
  await this.monitorWhales();
};

async function main() {
  const bot = new CopyTradeBot();
  await bot.start();
}

main().catch(console.error);

Run it:

node bot.js

Testing Steps

  1. Fund your bot wallet with at least 0.01 SOL
  2. Set TEST_MODE=true initially
  3. Use a wallet from WATCH_LIST to buy a token on Pump.fun
  4. Observe console logs for detected buys and simulated copies
  5. Review pump_fun_swaps.json for recorded actions

Once confident, set TEST_MODE=false for live trading—but only with small amounts at first.

Frequently Asked Questions (FAQ)

Can I use TypeScript instead of JavaScript?

Yes! While this guide uses JavaScript for simplicity, you can easily convert it to TypeScript by adding type definitions for transactions, responses, and configuration objects. Many developers prefer TypeScript for better tooling and compile-time safety when working with complex blockchain logic.

How do I avoid frontrunning or failed transactions?

To improve success rates:

👉 Explore advanced execution strategies used by professional traders.

Is it safe to run a bot with real funds?

Only after extensive testing in simulation mode. Always:

Can I monitor multiple wallets or programs?

Absolutely. Expand the WATCH_LIST array with additional public keys. You can also modify the subscription filter to include other DeFi protocols like Raydium or Orca by adjusting accountRequired fields.

Does this work on devnet or localnet?

Yes. Replace your RPC and Yellowstone endpoints with devnet equivalents. This allows risk-free testing of logic and integration without spending real SOL.

How can I improve the bot’s strategy?

Advanced enhancements include:

Final Thoughts

You've now built a functional Solana copy trading bot capable of detecting and mirroring Pump.fun trades in real time using Yellowstone gRPC and the Pump.fun API. This setup demonstrates how modern blockchain tooling enables powerful automation in decentralized finance.

As you continue refining your bot, consider integrating additional signals, improving error handling, and exploring portfolio-level risk management.

Happy coding—and trade safely!