
Cómo monitorear transacciones de Solana con Geyser Enhanced Websockets
Introducción
Durante mucho tiempo, me pregunté cómo funcionaban todas estas aplicaciones y bots de monitoreo. Después de una búsqueda complicada, encontré Geyser Enhanced Websockets de Helius. Aunque usarlos no es gratis (necesitas un plan Business o Professional), son una herramienta muy potente.
Usar Geyser Enhanced Websockets con Helius es sencillo: pega una dirección que quieras monitorear y ejecuta un poco de código. Puedes monitorear cualquier cosa: NFT, billeteras, programas, plataformas, literalmente lo que quieras. Puedes crear rastreadores de billeteras y tokens, monitores de compraventa y volumen, entre otras herramientas.
Las API y herramientas de este tipo pueden costar miles de dólares, pero este artículo mostrará algunos ejemplos de lo que puedes crear por una fracción del costo. Con $499 (plan Business), el ROI de crear tu propia herramienta es infinito. Compartirla con otros o usarla tú mismo te dará una ventaja y un sinfín de formas de trabajar con datos de Solana en tiempo real.
Si nunca has trabajado con Helius Geyser Websockets, lee esta publicación del blog y la documentación. Puedes seguir los pasos, ya que este primer ejemplo debería ser bastante fácil de entender.
Monitor de nuevos pools de 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));
}La primera parte de este fragmento de código es muy sencilla. Configuramos nuestra clave de API y enviamos una solicitud con la cuenta que queremos monitorear, que en este caso es 675kPX9…, también conocida como el programa on-chain de Raydium. Con esta solicitud, obtendremos todas las transacciones confirmadas y sin errores que interactúen con Raydium. Por lo general, enviar esta solicitud devolvería miles de transacciones por segundo. Por eso, nos centraremos en una forma sencilla de filtrar el ruido.
Filtrar el ruido
El siguiente fragmento de código contiene la lógica principal para gestionar y filtrar eventos. La función on message analiza los datos devueltos por las transacciones según los registros. En este caso, buscamos todas las transacciones con el registro “initialize2: InitializeInstruction2”, que nos indica cuándo un usuario crea un nuevo pool de liquidez en Raydium. También puedes usar cualquier otro registro, según lo que quieras monitorear. Recomiendo hacer una transacción de prueba de la acción que quieras monitorear. Por ejemplo, podrías agregar liquidez a un pool y revisar cómo se ven los registros de esa transacción. Luego, puedes filtrarlos para obtener todas las transacciones en las que se agrega 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');
});Una vez que sabemos que la transacción contiene el registro, extraemos dos elementos: la firma, para comparar y verificar la precisión de nuestro programa, y la clave de la cuenta, que es el AMM ID, es decir, la dirección AMM de ese pool. Muchos bots y snipers usan el AMM ID para iniciar operaciones.
También puedes extraer más datos si lo deseas. Por ejemplo, puedes obtener el creador —normalmente la decimoséptima clave pública en accountKeys—, los tokens usados para crear el pool —ya sea de los saldos de tokens previos y posteriores o de las instrucciones internas—, la cantidad de tokens e incluso los tokens LP que recibe el creador. La cantidad de tokens LP que recibe el creador podría servir para crear un monitor de quema de liquidez. Cuando el creador de un pool quema sus tokens LP, que básicamente son los tokens que acreditan su participación en el pool, renuncia a su capacidad de retirar liquidez. Ten cuidado si todavía controla la autoridad de acuñación, porque podría acuñar más tokens y venderlos en el pool.
Estructura JSON
Para obtener más datos, puedes consultar la estructura JSON de la transacción en la documentación oficial de Solana. También puedes guardar la respuesta JSON y usar este formateador para ver la estructura. Esto es muy importante porque, cuando conoces la estructura general de las transacciones, puedes extraer cualquier dato que encontrarías en un explorador de bloques.
A continuación se muestra la estructura JSON general, que contiene dos objetos importantes con varios niveles de anidamiento: Transaction y Meta. Dentro de Transaction está el objeto de mensaje, que contiene el blockhash reciente, accountKeys y instructions. Dentro de Meta están los valores previos y posteriores de Balances (saldos en lamports), innerInstructions, logMessages y los valores previos y posteriores de token balances.
Ejemplo de monitor de Pump.Fun
Al ver la tendencia actual de las meme coins en Solana, creé un monitor muy sencillo para pump.fun. Solo cambia la dirección que estamos monitoreando por “6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P”, que corresponde al programa on-chain de pump.fun, y usa esta función 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) {
}
});Al igual que con Raydium, revisamos todas las transacciones que interactúan con el programa de pump.fun y las filtramos según el registro deseado. Aquí hay un patrón: registros, registros y más registros. Cuando tenemos una transacción con los registros que buscamos, extraemos la firma, el creador y el propio token.
Puedes ver que accountKeys y la firma están anidados en la misma ubicación del JSON que en el ejemplo de Raydium. Por eso, es fácil obtener el creador, el token y la firma. La estructura JSON es básicamente la misma para la mayoría de las transacciones, pero algunos valores pueden estar en ubicaciones diferentes o en otro orden. Lo verás en el siguiente ejemplo.
En cualquier caso, este es un monitor bastante sencillo para nuevos tokens de pump.fun. Con esta información, puedes comprar el token o simplemente saber que existe para monitorearlo. También puedes combinarla con diferentes API DAS de Helius para crear una herramienta aún más potente. Por ejemplo, puedes obtener los metadatos o la información del creador.
Monitor de DCA de Jupiter
Este ejemplo es relativamente específico, pero demuestra que puedes verlo todo mediante los registros. Con la siguiente solicitud, revisaremos el programa de DCA (promedio del costo en dólares) de Jupiter y, más específicamente, veremos cada compra DCA realizada con Jupiter. Este ejemplo puede usarse para calcular operaciones de arbitraje o analizar el posible impacto en el mercado. Los datos de las compras DCA están muy desaprovechados para analizar ese posible impacto.
Como puedes ver, hasta ahora no hemos hecho nada complicado. Agregamos una importación de base58 al principio y cambiamos la dirección por la del programa DCA de Jupiter. La siguiente parte puede parecer bastante complicada —y, en cierta medida, lo es—, pero considero importante incluir un ejemplo un poco más complejo.
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));
}Esto es lo que hacemos:
- Usamos el registro de DCA para filtrar solo este tipo de transacciones.
- Extraemos
User,Input MintyOutput Mintde la transacción. - Tomamos
Instruction Data Rawy convertimos los bytes en los argumentos de entrada, como la cantidad que ingresa el usuario, la frecuencia con la que vende y cuánto vende 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) {}
});*Descifrar los datos sin procesar de la instrucción
Cuando ves los datos sin procesar de una instrucción, normalmente corresponden a los argumentos de entrada. No es difícil obtener los datos de esta forma y no tenemos que deserializar nada, algo que puede desanimar a muchos.
Por ejemplo, tomemos estos datos de esta transacción:
8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000
Ahora, ve a hexed.it y pégalos:
Ahora, busquemos estos argumentos de entrada:
En el lado derecho, ingresemos 336971797613 en la búsqueda. Haz clic en buscar siguiente para ver dónde se encuentra el valor inAmount en los bytes.
Como puedes ver, esto nos muestra que los bytes desde 6D hasta el último 00 anterior a 37 contienen el valor inAmount. Todas las transacciones DCA con el formato que monitoreamos tienen inAmount en la misma posición de bytes. Los siguientes 8 pares de bytes contienen el valor inAmountPerCycle. Esta es una forma práctica de encontrar valores que normalmente no están claros en el JSON de la transacción, ya que el JSON contiene principalmente direcciones y saldos, no argumentos de entrada.
¡Lo lograste!
Si llegaste hasta aquí, ya tienes una base excelente para comenzar a usar Helius Geyser WebSockets. Lo más difícil es empezar. Ahora puedes monitorear billeteras, programas, pools y cualquier otra cosa que quieras. No necesitas API con precios exorbitantes ni nada parecido. Si tienes algún problema o alguna pregunta, siempre puedes consultar en el Discord de Helius.
Recursos
Artículos relacionados
Suscríbete a Helius
Mantente al día con las novedades del desarrollo en Solana y recibe actualizaciones cuando publiquemos


