const Client = require("@triton-one/yellowstone-grpc").default;
const { CommitmentLevel } = require("@triton-one/yellowstone-grpc");
const { BorshAccountsCoder } = require("@coral-xyz/anchor");
const fs = require('fs');
const PUMP_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
// Load Pump.fun IDL
const pumpFunIdl = JSON.parse(
fs.readFileSync('./idl/pump_0.1.0.json', 'utf8')
);
const accountCoder = new BorshAccountsCoder(pumpFunIdl);
// Initialize and connect to Yellowstone gRPC
const getClient = async () => {
let client = false;
try {
client = new Client(
process.env.GRPC_ENDPOINT || "https://grpc.solanatracker.io",
process.env.GRPC_API_KEY,
{
"grpc.max_receive_message_length": 100 * 1024 * 1024,
}
);
const version = await client.getVersion();
if (version) {
console.log("Connected to Yellowstone gRPC! Version:", version);
return client;
}
} catch (e) {
console.error("Failed to connect:", e);
}
if (!client) {
throw new Error("Failed to connect!");
}
};
// Track account statistics
const stats = {
totalUpdates: 0,
bondingCurves: new Set(),
completedCurves: 0,
lastReportTime: Date.now()
};
// Handle bonding curve updates
function handleBondingCurve(data, account) {
stats.bondingCurves.add(account.pubkey);
console.log(`\n[Bonding Curve Update]`);
console.log(` Address: ${account.pubkey}`);
console.log(` Virtual Token Reserves: ${data.virtualTokenReserves}`);
console.log(` Virtual Sol Reserves: ${data.virtualSolReserves}`);
console.log(` Real Token Reserves: ${data.realTokenReserves}`);
console.log(` Real Sol Reserves: ${data.realSolReserves}`);
console.log(` Token Total Supply: ${data.tokenTotalSupply}`);
console.log(` Complete: ${data.complete}`);
// Calculate current price
const price = Number(data.virtualSolReserves) / Number(data.virtualTokenReserves);
console.log(` Current Price: ${price.toFixed(9)} SOL/token`);
if (data.complete && !stats.bondingCurves.has(account.pubkey + '_completed')) {
stats.completedCurves++;
stats.bondingCurves.add(account.pubkey + '_completed');
console.log(`\n🎉 BONDING CURVE COMPLETED!`);
console.log(` Address: ${account.pubkey}`);
console.log(` Final Sol Reserves: ${(Number(data.realSolReserves) / 1e9).toFixed(4)} SOL`);
console.log(` Token Supply: ${data.tokenTotalSupply}`);
}
}
// Handle global account updates
function handleGlobalAccount(data, account) {
console.log(`\n[Global Configuration Update]`);
console.log(` Address: ${account.pubkey}`);
console.log(` Fee Recipient: ${data.feeRecipient}`);
console.log(` Initial Virtual Token Reserves: ${data.initialVirtualTokenReserves}`);
console.log(` Initial Virtual Sol Reserves: ${data.initialVirtualSolReserves}`);
}
(async () => {
const client = await getClient();
const stream = await client.subscribe();
// Handle stream lifecycle
const streamClosed = new Promise((resolve, reject) => {
stream.on("error", (error) => {
console.error("Stream error:", error);
});
stream.on("end", () => {
console.log("Stream ended");
resolve();
});
stream.on("close", () => {
console.log("Stream closed");
resolve();
});
});
// Handle incoming account updates
stream.on("data", (data) => {
if (data?.account) {
stats.totalUpdates++;
const accountData = data.account.account;
try {
// Decode account data
const decodedData = accountCoder.decodeAny(accountData.data);
console.log(`\n[Account Update #${stats.totalUpdates}]`);
console.log(` Address: ${accountData.pubkey}`);
console.log(` Type: ${decodedData.discriminator}`);
console.log(` Slot: ${data.account.slot}`);
// Process based on account type
if (decodedData.discriminator === 'BondingCurve') {
handleBondingCurve(decodedData, accountData);
} else if (decodedData.discriminator === 'Global') {
handleGlobalAccount(decodedData, accountData);
}
} catch (error) {
// Ignore decoding errors for unknown account types
}
// Report statistics every 100 updates
if (stats.totalUpdates % 100 === 0) {
const now = Date.now();
const elapsed = (now - stats.lastReportTime) / 1000;
const rate = 100 / elapsed;
console.log(`\n=== Statistics ===`);
console.log(` Total Updates: ${stats.totalUpdates}`);
console.log(` Update Rate: ${rate.toFixed(2)}/sec`);
console.log(` Bonding Curves Tracked: ${stats.bondingCurves.size}`);
console.log(` Completed Curves: ${stats.completedCurves}`);
stats.lastReportTime = now;
}
}
});
// Subscribe to Pump.fun accounts
const request = {
accounts: {
pumpfun: {
account: [],
owner: [PUMP_PROGRAM_ID], // Stream all accounts owned by Pump.fun
filters: []
}
},
slots: {},
transactions: {},
transactionsStatus: {},
entry: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
ping: undefined,
commitment: CommitmentLevel.PROCESSED, // Fastest updates
};
// Send subscribe request
await new Promise((resolve, reject) => {
stream.write(request, (err) => {
if (err === null || err === undefined) {
console.log("✅ Monitoring Pump.fun accounts...\n");
resolve();
} else {
reject(err);
}
});
}).catch((reason) => {
console.error("Subscribe failed:", reason);
throw reason;
});
await streamClosed;
})();
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n\nShutting down gracefully...');
console.log(`Final Stats - Updates: ${stats.totalUpdates}, Curves: ${stats.bondingCurves.size}, Completed: ${stats.completedCurves}`);
process.exit(0);
});