
Как отслеживать транзакции Solana с помощью Geyser Enhanced Websockets
Введение
Долгое время я не понимал, как работают все эти приложения для мониторинга и боты. После долгих и мучительных поисков я нашел Geyser Enhanced Websockets от Helius. Хотя пользоваться ими можно только за плату (вам потребуется план Business или Professional), это очень мощный инструмент.
Использовать Geyser Enhanced Websockets с Helius просто: укажите адрес, который хотите отслеживать, и запустите код. Можно отслеживать что угодно: NFT, кошельки, программы, платформы — буквально всё. Вы можете создавать трекеры кошельков и токенов, мониторы покупок и продаж, объемов и многое другое.
Подобные API и инструменты иногда стоят тысячи долларов, но в этой статье мы покажем несколько примеров того, что можно создать за гораздо меньшую сумму. При стоимости $499 (план Business) окупаемость собственного инструмента практически безгранична. Вы можете поделиться им с другими или использовать самостоятельно, чтобы получить преимущество и безграничные возможности для работы с данными Solana в реальном времени.
Если вы только начинаете работать с Helius Geyser Websockets, прочитайте эту статью в блоге и документацию. Можете повторять действия по ходу чтения: первый пример довольно прост.
Монитор новых пулов Raydium
const WebSocket = require('ws');
// Create a WebSocket connection
const ws = new WebSocket('wss://atlas-mainnet.helius-rpc.com?api-key=YOUR_API_KEY');
// Function to send a request to the WebSocket server
function sendRequest(ws) {
const request = {
jsonrpc: "2.0",
id: 420,
method: "transactionSubscribe",
params: [
{ failed: false,
accountInclude: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"]
},
{
commitment: "confirmed",
encoding: "jsonParsed",
transactionDetails: "full",
maxSupportedTransactionVersion: 0
}
]
};
ws.send(JSON.stringify(request));
}Первая часть этого фрагмента кода очень проста. Мы задаем ключ API и отправляем запрос с аккаунтом, который хотим отслеживать. В данном случае это 675kPX9…, то есть ончейн-программа Raydium. В ответ мы получим все подтвержденные и успешно выполненные транзакции, взаимодействующие с Raydium. Обычно такой запрос возвращает тысячи транзакций в секунду, поэтому рассмотрим простой способ отфильтровать шум.
Фильтрация шума
В приведенном ниже фрагменте кода содержится основная логика фильтрации при обработке событий. Функция on message анализирует возвращенные данные транзакций на основе логов. В данном случае мы ищем все транзакции с логом «initialize2: InitializeInstruction2». Он сообщает, когда пользователь создает новый пул ликвидности в Raydium. В зависимости от цели мониторинга можно использовать любой другой лог. Рекомендую выполнить тестовую транзакцию нужного типа. Например, можно добавить ликвидность в пул, изучить логи этой транзакции, а затем отфильтровать по ним все транзакции добавления ликвидности.
ws.on('open', function open() {
console.log('WebSocket is open');
sendRequest(ws); // Send a request once the WebSocket is open
});
ws.on('message', async function incoming(data) {
const messageStr = data.toString('utf8');
try {
const messageObj = JSON.parse(messageStr);
const result = messageObj.params.result;
const logs = result.transaction.meta.logMessages;
const signature = result.signature; // Extract the signature
const accountKeys = result.transaction.transaction.message.accountKeys.map(ak => ak.pubkey); // Extract only pubkeys
if (logs && logs.some(log => log.includes("initialize2: InitializeInstruction2"))) {
// Log the signature, and the public keys of the AMM ID
console.log('Transaction signature:', signature);
console.log('AMM ID:', accountKeys[2]); // Corrected to the third account for AMM ID
}
} catch (e) {
}
});
ws.on('error', function error(err) {
console.error('WebSocket error:', err);
});
ws.on('close', function close() {
console.log('WebSocket is closed');
});Зная, что транзакция содержит нужный лог, мы извлекаем два значения: подпись для сравнения и проверки точности нашей программы, а также ключ аккаунта — идентификатор AMM (то есть адрес AMM данного пула). Многие боты и снайперы используют идентификатор AMM для начала торговли.
При необходимости можно извлечь и другие данные. Например, автора (обычно это 17-й публичный ключ в accountKeys), токены, используемые для создания пула (из балансов токенов до и после транзакции либо из внутренних инструкций), количество токенов и даже LP-токены, полученные автором. Количество полученных автором LP-токенов можно использовать для создания монитора сжигания ликвидности. Когда автор пула сжигает свои LP-токены, которые фактически служат подтверждением его доли в пуле, он отказывается от возможности изъять ликвидность. Однако будьте осторожны: если право выпуска по-прежнему принадлежит автору, он может выпустить дополнительные токены и продать их в пул.
Структура JSON
Чтобы получить больше данных, изучите структуру JSON транзакции в официальной документации Solana. Также можно сохранить ответ JSON и просмотреть его структуру с помощью этого средства форматирования . Это важно: зная общую структуру транзакций, вы сможете извлечь любые данные, доступные в обозревателе блокчейна.
Ниже показана общая структура JSON с двумя важными глубоко вложенными объектами: Transaction и Meta. Внутри Transaction находится объект message, содержащий недавний хеш блока, accountKeys и instructions. Внутри Meta находятся Balances до и после транзакции (балансы в лампортах), innerInstructions, logMessages и token balances до и после транзакции.
Пример монитора Pump.Fun
Учитывая текущую популярность мем-коинов в Solana, я создал очень простой монитор pump.fun. Достаточно заменить отслеживаемый адрес на «6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P» — ончейн-программу pump.fun — и использовать следующую функцию on message:
ws.on('message', function incoming(data) {
const messageStr = data.toString('utf8');
try {
const messageObj = JSON.parse(messageStr);
const result = messageObj.params.result;
const logs = result.transaction.meta.logMessages;
const signature = result.signature; // Extract the signature
const accountKeys = result.transaction.transaction.message.accountKeys.map(ak => ak.pubkey);
if (logs && logs.some(log => log.includes('Program log: Instruction: InitializeMint2'))) {
console.log('New pump.fun token!');
console.log('tx:', signature);
console.log('Creator:', accountKeys[0]);
console.log('Token:', accountKeys[1]);
// Log the first and second account keys if they exist
}
} catch (e) {
}
});Как и в примере с Raydium, мы просматриваем все транзакции, взаимодействующие с программой pump.fun, и фильтруем их по нужному логу. Здесь прослеживается закономерность: логи, логи и еще раз логи. Получив транзакцию с нужными логами, мы извлекаем подпись, автора и сам токен!
Как видите, accountKeys и подпись находятся в том же месте структуры JSON, что и в примере с Raydium. Поэтому получить автора, токен и подпись несложно. Структура JSON большинства транзакций практически одинакова, но некоторые значения могут находиться в других местах или располагаться в другом порядке. Это будет видно в следующем примере.
В любом случае это довольно простой монитор новых токенов pump.fun. Используя полученную информацию, можно купить токен или просто отслеживать его существование. Также можно объединить монитор с различными API Helius DAS, чтобы создать еще более мощный инструмент, например получать метаданные или информацию об авторе.
Монитор Jupiter DCA
Этот пример рассчитан на довольно узкую задачу, но он показывает, что с помощью логов можно увидеть всё. Следующий запрос обращается к программе DCA (усреднения долларовой стоимости) Jupiter. Точнее, он позволяет увидеть каждую покупку DCA, совершенную через Jupiter. Этот пример можно использовать для расчета арбитражных сделок или оценки потенциального влияния на рынок. Данные о покупках DCA редко используют для оценки потенциального влияния на рынок.
Как видите, пока ничего сложного мы не сделали. Добавили импорт base58 в начале и заменили адрес на программу Jupiter DCA. Следующая часть может показаться довольно сложной — отчасти так и есть. Но я считаю важным привести более сложный пример.
const WebSocket = require("ws");
const bs58 = require("bs58");
// Create a WebSocket connection
const ws = new WebSocket(
"wss://atlas-mainnet.helius-rpc.com?api-key=YOUR_API_KEY"
);
// Function to send a request to the WebSocket server
function sendRequest(ws) {
const request = {
jsonrpc: "2.0",
id: 420,
method: "transactionSubscribe",
params: [
{
failed: false,
accountInclude: ["DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M"],
},
{
commitment: "confirmed",
encoding: "jsonParsed",
transactionDetails: "full",
maxSupportedTransactionVersion: 0,
},
],
};
ws.send(JSON.stringify(request));
}Вот что мы делаем:
- Используем лог DCA, чтобы отфильтровать только транзакции этого типа.
- Извлекаем из транзакции
User,Input MintиOutput Mint. - Берем
Instruction Data Rawи преобразуем байты во входные аргументы: введенное пользователем количество, частоту продаж и количество для каждой продажи.
ws.on("message", async function incoming(data) {
const messageStr = data.toString("utf8");
try {
const messageObj = JSON.parse(messageStr);
const instructions = messageObj.params.result.transaction.transaction.message.instructions;
const result = messageObj.params.result;
const logs = result.transaction.meta.logMessages;
// Extract only pubkeys
if (
logs &&
logs.some((log) => log.includes("Program log: Instruction: OpenDcaV2"))
) {
instructions.forEach((instruction) => {
if (instruction.programId.includes("DCA265")) {
if (instruction.accounts.length === 13) {
console.log("User:", instruction.accounts[2]);
console.log("Input Mint:", instruction.accounts[3]);
console.log("Output Mint:", instruction.accounts[4]);
const data = instruction.data;
const bytedata = bs58.decode(data);
const hexString = bytedata.toString("hex");
const inAmountbytes = hexString.substring(16 * 2, 24 * 2);
const cycleFrequencyBytes = hexString.substring(32 * 2, (32 + 8) * 2);
const inAmountPerCycleBytes = hexString.substring(24 * 2, 32 * 2);
// Reverse the byte order for little-endian interpretation
const reversedCycleFrequencyBytes = cycleFrequencyBytes
.match(/.{1,2}/g)
.reverse()
.join("");
const reversedInAmountBytes = inAmountbytes
.match(/.{1,2}/g)
.reverse()
.join("");
const reversedInAmountPerCycleBytes = inAmountPerCycleBytes
.match(/.{1,2}/g)
.reverse()
.join("");
const cycleFrequency = BigInt("0x" + reversedCycleFrequencyBytes);
const inAmount = BigInt("0x" + reversedInAmountBytes);
const inAmountPerCycle = BigInt("0x" + reversedInAmountPerCycleBytes);
console.log("Cycle Frequency every", cycleFrequency.toString() + " seconds");
console.log("Amount input:", inAmount.toString());
console.log("Amount per cycle:", inAmountPerCycle.toString());
}
}
});
}
} catch (e) {}
});*Расшифровка необработанных данных инструкции
Необработанные данные инструкции обычно соответствуют входным аргументам. Получить данные таким способом несложно, и нам не приходится ничего десериализовывать, что многих может отпугнуть.
Например, возьмем данные из этой транзакции:
8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000
Теперь откройте hexed.it и вставьте их:
Теперь найдем эти входные аргументы:
В поле справа введем 336971797613 для поиска. Нажмите «Найти далее», чтобы увидеть, где в байтах находится значение inAmount.
Как видите, байты от 6D до последнего 00 перед 37 содержат значение inAmount. В каждой отслеживаемой транзакции DCA такого формата inAmount находится в одной и той же позиции байтов. Следующие 8 пар байтов содержат значение inAmountPerCycle. Это удобный способ находить значения, которые обычно неочевидны в JSON транзакции, поскольку JSON в основном содержит адреса и балансы, а не входные аргументы.
Готово!
Если вы дочитали до этого места, у вас уже есть отличная база для начала работы с Helius Geyser WebSockets. Самое сложное — начать. Теперь вы можете отслеживать кошельки, программы, пулы и всё, что пожелаете. Дорогие API и подобные инструменты больше не нужны. Если у вас возникнут проблемы или вопросы, их всегда можно задать в Discord Helius!
Ресурсы
Похожие статьи
Подпишитесь на Helius
Следите за новостями разработки на Solana и получайте уведомления о наших публикациях


