Skip to main content

Tutorial for Building a Solana Sniper Bot Using Bitquery Real-Time Solana Subscriptions and Jupiter Swap API

This tutorial will guide you through building a Solana sniper bot using Bitquery for real-time Solana subscriptions and the Jupiter Swap API for executing swaps.

Tutorial Video

Tutorial

Prerequisites

  1. Node.js and npm installed on your system.
  2. Bitquery Free Developer Account with OAuth token (follow instructions here).
  3. Solana Wallet with some SOL for transaction fees.

Step 1: Setting Up the Environment

  1. Initialize a new Node.js project:

    mkdir solana-sniper-bot
    cd solana-sniper-bot
    npm init -y
  2. Install the necessary dependencies:

    npm install @solana/web3.js cross-fetch lodash @project-serum/anchor bs58

Step 2: Creating the Bot

  1. Create a new file index.js:

    touch index.js
  2. Add the necessary imports:

    const {
    Connection,
    PublicKey,
    VersionedTransaction,
    Keypair,
    } = require("@solana/web3.js");
    const fetch = require("cross-fetch");
    const lodash = require("lodash");
    const { Wallet } = require("@project-serum/anchor");
    const bs58 = require("bs58");
  3. Define the Solana Instructions GraphQL query for Bitquery:

In this step we get new Liquidity Pools created on Solana Raydium DEX along with the token information. To see detailed information on Raydium API, check examples here

    const gql = (strings, ...values) =>
strings.reduce((final, str, i) => final + str + (values[i] || ""), "");

const query = gql`
{
Solana {
Instructions(
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Method: { is: "initializeUserWithNonce" }
Address: { is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" }
}
}
}
limit: { count: 1 }
orderBy: { ascending: Block_Date }
) {
Instruction {
Accounts {
Address
}
}
}
}
}
`;

For the sake of the demo we have used a query, to track new tokens in real-time use the below subscriptions.

 subscription {
Solana {
Instructions(
where: {Transaction: {Result: {Success: true}}, Instruction: {Program: {Method: {is: "initializeUserWithNonce"}, Address: {is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"}}}}
) {
Instruction {
Accounts {
Address
}
}
}
}
}
  1. Set up the Solana connection and wallet:

    const connection = new Connection("https://api.mainnet-beta.solana.com");
    const walletPublicKey = new PublicKey("YOUR_PUBLIC_KEY");
    const secretKeyUint8Array = new Uint8Array([/* YOUR_SECRET_KEY_ARRAY */]);
    const wallet = new Wallet(Keypair.fromSecretKey(secretKeyUint8Array));
  2. Define a function to fetch data from Bitquery:

    async function fetchGraphQL(query) {
    const response = await fetch("https://streaming.bitquery.io/eap", {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer YOUR_BITQUERY_OAUTH_TOKEN",
    },
    body: JSON.stringify({ query }),
    });

    if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
    }
  3. Fetch pool addresses from Bitquery:

    async function getPoolAddresses() {
    try {
    const data = await fetchGraphQL(query);
    const instructions = lodash.get(data, "data.Solana.Instructions", []);

    return instructions.map(({ Instruction: { Accounts } }) => ({
    poolAddress: Accounts.length > 4 ? Accounts[4].Address : undefined,
    tokenA: Accounts.length > 8 ? Accounts[8].Address : undefined,
    tokenB: Accounts.length > 9 ? Accounts[9].Address : undefined,
    }))[0];
    } catch (error) {
    console.error("Error fetching data:", error);
    return { poolAddress: "", tokenA: "", tokenB: "" };
    }
    }
  4. Execute a token swap using Jupiter API:

This code has been derived from sample mentioned in the official docs here. We add the if-check for "TOKEN_NOT_TRADABLE" and "COULD_NOT_FIND_ANY_ROUTE" to accomodate for delays in Jupiter Swap API.

    async function swapTokens(tokenA, tokenB) {
try {
const quoteUrl = `https://quote-api.jup.ag/v6/quote?inputMint=${tokenB}&outputMint=${tokenA}&amount=10000&slippageBps=150`;
console.log("quote url ", quoteUrl);
const quoteResponse = await fetch(quoteUrl);
const quoteData = await quoteResponse.json();
if (
quoteData["errorCode"] != "TOKEN_NOT_TRADABLE" &&
quoteData["errorCode"] != "COULD_NOT_FIND_ANY_ROUTE"
) {
const swapTransactionResponse = await fetch(
"https://quote-api.jup.ag/v6/swap",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
quoteResponse: quoteData,
userPublicKey: wallet.publicKey.toString(),
wrapAndUnwrapSol: true,
}),
}
);

const { swapTransaction } = await swapTransactionResponse.json();

const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
console.log("swapTransactionBuf ", swapTransactionBuf);
const transaction = VersionedTransaction.deserialize(swapTransactionBuf);
transaction.sign([wallet.payer]);
const rawTransaction = transaction.serialize();
const txid = await connection.sendRawTransaction(rawTransaction, {
skipPreflight: false,
maxRetries: 4,
preflightCommitment: "confirmed",
commitment: "confirmed",
});

const confirmation = await connection.confirmTransaction(
txid,
"confirmed"
);
console.log(
`Transaction confirmed: ${confirmation.value.err ? "Error" : "Success"}`
);
console.log(`Transaction successful: https://solscan.io/tx/${txid}`);
}
} catch (error) {
console.error("Error during token swap:", error);
}
}
  1. Define the main function to coordinate the steps:
    async function main() {
const { tokenA, tokenB } = await getPoolAddresses();
await swapTokens(tokenA, tokenB);
}

main();

Step 3: Running the Bot

  1. Replace placeholders:

    • Replace YOUR_PUBLIC_KEY with your actual Solana wallet public key.
    • Replace YOUR_SECRET_KEY_ARRAY with your wallet's secret key array.
    • Replace YOUR_BITQUERY_OAUTH_TOKEN with your actual Bitquery OAuth token.
  2. Run the bot:

    node index.js

Conclusion

You've successfully set up a Solana sniper bot using Bitquery for real-time Solana subscriptions and Jupiter Swap API for executing swaps. This bot listens for specific on-chain instructions and performs swaps based on the detected instructions. Ensure your bot is monitored and managed appropriately, especially when running on the mainnet with real funds.