
Como monitorar transações da Solana usando Geyser Enhanced WebSockets
Introdução
Durante muito tempo, fiquei pensando em como todos esses aplicativos e bots de monitoramento funcionam. Depois de uma busca difícil, encontrei o Geyser Enhanced WebSockets da Helius. Embora o uso não seja gratuito (você precisa de um plano Business ou Professional), essa é uma ferramenta muito poderosa.
Usar o Geyser Enhanced WebSockets com a Helius é simples: cole um endereço que você queira monitorar e execute um código. Você pode monitorar qualquer coisa: NFTs, carteiras, programas, plataformas, literalmente tudo. É possível criar rastreadores de carteiras e tokens, monitores de compra e venda, monitores de volume etc.
APIs e ferramentas desse tipo podem custar milhares de dólares, mas este artigo apresentará alguns exemplos do que é possível criar por uma fração desse valor. Com US$ 499 (plano Business), o ROI de criar sua própria ferramenta é infinito. Compartilhar a ferramenta com outras pessoas ou usá-la por conta própria dará a você uma vantagem e inúmeras maneiras de trabalhar com dados em tempo real da Solana.
Se você ainda não tem experiência com o Helius Geyser WebSockets, leia esta publicação do blog e a documentação. Você pode acompanhar o processo, pois este primeiro exemplo deve ser bem fácil de entender.
Monitor de novos pools da 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));
}A primeira parte deste trecho de código é bastante simples. Definimos nossa chave de API e enviamos uma solicitação com a conta que queremos monitorar, que neste caso é 675kPX9…, também conhecida como o programa on-chain da Raydium. Com essa solicitação, receberemos todas as transações confirmadas e bem-sucedidas que interagem com a Raydium. Normalmente, essa solicitação retornaria milhares de transações por segundo. Portanto, vamos nos concentrar em uma maneira simples de filtrar o ruído.
Filtrando o ruído
O trecho de código abaixo contém a principal lógica de filtragem e tratamento de eventos. A função on message analisa, com base nos logs, os dados retornados pelas transações. Neste caso, estamos analisando todas as transações com o log “initialize2: InitializeInstruction2”, que indica quando um usuário cria um novo pool de liquidez na Raydium. Você também pode usar qualquer outro log, dependendo do que deseja monitorar. Recomendo fazer uma transação de teste para a ação que você quer monitorar. Por exemplo, você pode adicionar liquidez a um pool, verificar os logs dessa transação e então filtrá-los para obter todas as transações em que houve adição de liquidez.
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');
});Sabendo que a transação contém o log, extraímos duas coisas: a assinatura, para comparar e verificar a precisão do nosso programa, e a chave da conta, que é o ID da AMM (ou seja, o endereço da AMM desse pool), pois muitos bots/snipers usam o ID da AMM para iniciar negociações.
Se quiser, você também pode extrair mais dados. Por exemplo, é possível obter o criador (geralmente a 17ª chave pública em accountKeys), os tokens usados para criar o pool (a partir dos saldos de tokens anteriores/posteriores ou das instruções internas), a quantidade de tokens e até mesmo os tokens LP recebidos pelo criador. A quantidade de tokens LP recebidos pelo criador pode ser usada para criar um monitor de queima de liquidez, pois, quando o criador do pool queima seus tokens LP — que são basicamente os tokens de recibo correspondentes à participação dele no pool —, ele revoga sua capacidade de remover liquidez. Tenha cuidado: se a autoridade de emissão ainda estiver nas mãos do criador, ele poderá emitir mais tokens e vendê-los no pool.
Estrutura JSON
Para obter mais dados, você pode consultar a estrutura JSON da transação, disponível na documentação oficial da Solana. Como alternativa, salve a resposta JSON e use este formatador para visualizar a estrutura. Isso é muito importante porque, depois que você entende a estrutura geral das transações, pode extrair qualquer dado que veria ao usar um explorador de blocos.
Abaixo está o JSON geral, que contém dois objetos importantes e profundamente aninhados: Transaction e Meta. Dentro de Transaction, temos o objeto de mensagem, que contém o blockhash recente, accountKeys e instructions. Dentro de Meta, temos Balances anteriores/posteriores (saldos em lamports), innerInstructions, logMessages e token balances anteriores/posteriores.
Exemplo de monitor da Pump.Fun
Observando o cenário atual das meme coins na Solana, criei um monitor muito simples para a pump.fun. Basta trocar o endereço que estamos monitorando por “6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P”, que é o programa on-chain da pump.fun, e usar esta função 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) {
}
});Assim como fizemos com a Raydium, analisamos todas as transações que interagem com o programa da pump.fun e as filtramos com base no log desejado (já temos um padrão aqui: logs, logs, logs). Quando encontramos uma transação com os logs desejados, extraímos a assinatura, o criador e o próprio token!
Você pode ver que accountKeys e a assinatura ficam aninhados no mesmo local do JSON, assim como no exemplo da Raydium. Portanto, é fácil obter o criador, o token e a assinatura. A estrutura JSON é basicamente a mesma para a maioria das transações, mas alguns valores podem estar em locais diferentes ou em outra ordem — você verá isso no próximo exemplo.
De qualquer forma, este é um monitor bastante simples para novos tokens da pump.fun. Com essas informações, você pode comprar o token ou apenas saber que ele existe para fins de monitoramento. Também é possível combiná-lo com diferentes APIs DAS da Helius para criar uma ferramenta ainda mais poderosa, obtendo, por exemplo, os metadados ou as informações sobre o criador.
Monitor de DCA da Jupiter
Este exemplo é relativamente específico, mas demonstra que você pode ver tudo usando logs. Com a solicitação a seguir, analisaremos o programa de DCA (Dollar-Cost Average) da Jupiter e, mais especificamente, veremos todas as compras por DCA feitas pela Jupiter. Este exemplo pode ser usado em cálculos de negociações de arbitragem ou para identificar um possível impacto no mercado. Os dados das compras por DCA ainda são pouco usados para analisar possíveis impactos no mercado.
Como você pode ver, nada complicado foi feito até agora. Adicionamos uma importação de base58 no início e trocamos o endereço pelo programa de DCA da Jupiter. A próxima parte pode parecer bastante complicada — e, de certa forma, é —, mas considero importante ter um exemplo um pouco mais complexo.
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));
}O que estamos fazendo aqui é:
- Usar o log de DCA para filtrar apenas esses tipos de transação.
- Extrair
User,Input MinteOutput Mintda transação. - Pegar
Instruction Data Rawe converter os bytes nos argumentos de entrada, como o valor inserido pelo usuário, a frequência das vendas e quanto é vendido a cada vez.
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) {}
});*Decodificando os dados brutos da instrução
Quando você vê os dados brutos da instrução, eles geralmente correspondem aos argumentos de entrada. Não é difícil obter os dados dessa forma e não precisamos desserializar nada, algo que costuma desanimar muita gente.
Por exemplo, vamos usar estes dados desta transação:
8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000
Agora, acesse hexed.it e cole os dados:
Agora, vamos encontrar estes argumentos de entrada:
No lado direito, vamos inserir 336971797613 na busca. Clique em localizar próximo para ver onde o valor inAmount está nos bytes.
Como você pode ver, a ferramenta mostra que os bytes de 6D até o último 00 antes de 37 contêm o valor inAmount. Toda transação de DCA no formato que monitoramos tem inAmount na mesma posição de byte. Os 8 pares de bytes seguintes contêm o valor inAmountPerCycle. Essa é uma ótima maneira de encontrar valores que geralmente não ficam claros no JSON da transação, pois o JSON contém principalmente endereços e saldos, não argumentos de entrada.
Você conseguiu!
Se você chegou até aqui, já deve ter uma excelente base para começar a usar o Helius Geyser WebSockets. A parte mais difícil é começar. Agora você pode monitorar carteiras, programas, pools e o que mais quiser. Não há necessidade de APIs com preços absurdos nem de nada parecido. Se tiver algum problema ou dúvida, você sempre pode perguntar no Discord da Helius!
Recursos
Artigos relacionados
Assine a Helius
Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos


