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
- Node.js and npm installed on your system.
- Bitquery Free Developer Account with OAuth token (follow instructions here).
- Solana Wallet with some SOL for transaction fees.
Step 1: Setting Up the Environment
Initialize a new Node.js project:
mkdir solana-sniper-bot
cd solana-sniper-bot
npm init -yInstall the necessary dependencies:
npm install @solana/web3.js cross-fetch lodash @project-serum/anchor bs58
Step 2: Creating the Bot
Create a new file
index.js
:touch index.js
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");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
}
}
}
}
}
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));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();
}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: "" };
}
}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);
}
}
- 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
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.
- Replace
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.