
Comment surveiller les transactions Solana avec Geyser Enhanced Websockets
Introduction
Pendant longtemps, je me suis demandé comment fonctionnaient toutes ces applications de surveillance et tous ces bots. Après une recherche laborieuse, j’ai découvert Geyser Enhanced Websockets de Helius. Leur utilisation n’est pas gratuite (vous avez besoin d’un forfait Business ou Professional), mais ils constituent un outil très puissant.
Utiliser Geyser Enhanced Websockets avec Helius est simple : collez une adresse à surveiller et exécutez du code. Vous pouvez tout surveiller : NFT, portefeuilles, programmes, plateformes, absolument tout. Vous pouvez créer des outils de suivi de portefeuilles ou de tokens, des systèmes de surveillance des achats et ventes ou des volumes, etc.
Les API et outils de ce type peuvent parfois coûter plusieurs milliers de dollars, mais cet article présente quelques exemples de ce que vous pouvez créer pour une fraction de ce prix. Avec 499 $ (forfait Business), le retour sur investissement de votre propre outil est infini. Que vous le partagiez ou l’utilisiez vous-même, il vous donnera un avantage et d’innombrables façons d’exploiter les données Solana en temps réel.
Si vous débutez avec Helius Geyser Websockets, consultez cet article de blog et la documentation. Vous pouvez suivre les étapes, car ce premier exemple devrait être assez accessible.
Outil de surveillance des nouveaux pools 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 première partie de cet extrait de code est très simple. Nous définissons notre clé API et envoyons une requête avec le compte à surveiller, ici 675kPX9…, c’est-à-dire le programme on-chain Raydium. Cette requête nous permet de récupérer toutes les transactions confirmées et réussies qui interagissent avec Raydium. Une telle requête renverrait normalement des milliers de transactions chaque seconde. Concentrons-nous donc sur une méthode simple pour éliminer le bruit.
Éliminer le bruit
L’extrait de code ci-dessous contient la principale logique de filtrage utilisée pour gérer les événements. La fonction on message analyse les données renvoyées par les transactions à partir des logs. Ici, nous recherchons toutes les transactions comportant le log « initialize2: InitializeInstruction2 ». Celui-ci nous indique lorsqu’un utilisateur crée un nouveau pool de liquidité sur Raydium. Vous pouvez également utiliser n’importe quel autre log selon ce que vous souhaitez surveiller. Je vous recommande d’effectuer une transaction de test correspondant à l’opération que vous voulez surveiller. Par exemple, vous pourriez ajouter de la liquidité à un pool et examiner les logs de cette transaction, puis les filtrer pour récupérer toutes les transactions dans lesquelles de la liquidité est ajoutée.
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');
});Sachant que la transaction contient ce log, nous extrayons deux éléments : la signature, afin de pouvoir comparer et vérifier la précision de notre programme, et la clé du compte, qui correspond à l’ID de l’AMM (c’est-à-dire l’adresse AMM de ce pool), car de nombreux bots/snipers utilisent l’ID de l’AMM pour lancer des transactions.
Vous pouvez également extraire davantage de données si vous le souhaitez. Par exemple, vous pouvez obtenir le créateur (généralement la 17e clé publique dans accountKeys), les tokens utilisés pour créer le pool (à partir des soldes de tokens avant/après ou des instructions internes), le nombre de tokens et même les tokens LP reçus par le créateur. Le nombre de tokens LP reçus par le créateur pourrait servir à concevoir un outil de surveillance de leur burn. En effet, lorsque le créateur du pool burn ses tokens LP, qui représentent essentiellement les tokens de reçu correspondant à sa part du pool, il renonce à sa capacité de retirer la liquidité. Attention toutefois : s’il contrôle encore l’autorité de mint, il pourrait créer davantage de tokens et les vendre dans le pool.
Structure JSON
Pour obtenir davantage de données, vous pouvez examiner la structure JSON de la transaction, disponible dans la documentation officielle de Solana. Vous pouvez également enregistrer la réponse JSON et utiliser cet outil de formatage pour visualiser sa structure. C’est très important, car une fois que vous connaissez la structure générale des transactions, vous pouvez extraire toutes les données visibles dans un explorateur de blocs.
Vous trouverez ci-dessous la structure JSON générale, qui contient deux objets imbriqués importants : Transaction et Meta. Dans Transaction se trouve l’objet message, qui contient le blockhash récent, accountKeys et instructions. Dans Meta se trouvent les Balances avant/après (soldes en lamports), innerInstructions, logMessages et les token balances avant/après.
Exemple d’outil de surveillance pour Pump.Fun
Compte tenu de la tendance actuelle des memecoins sur Solana, j’ai créé un outil de surveillance très simple pour pump.fun. Il suffit de remplacer l’adresse surveillée par « 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P », qui correspond au programme on-chain pump.fun, puis d’utiliser cette fonction 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) {
}
});Comme avec Raydium, nous examinons toutes les transactions qui interagissent avec le programme pump.fun et les filtrons selon le log recherché (vous remarquerez une constante : des logs, des logs et encore des logs). Une fois que nous avons une transaction contenant les logs souhaités, nous extrayons la signature, le créateur et le token lui-même !
Vous pouvez voir que accountKeys et la signature sont imbriqués au même endroit dans le JSON que dans l’exemple Raydium. Il est donc facile de récupérer le créateur, le token et la signature. La structure JSON est globalement la même pour la plupart des transactions, mais certaines valeurs peuvent se trouver à d’autres endroits ou être ordonnées différemment — vous le constaterez dans l’exemple suivant.
Dans tous les cas, il s’agit d’un outil assez simple pour surveiller les nouveaux tokens pump.fun. À partir de ces informations, vous pouvez acheter le token ou simplement connaître son existence à des fins de surveillance. Vous pouvez également les combiner avec différentes API Helius DAS pour créer un outil encore plus puissant, par exemple afin d’obtenir les métadonnées ou des informations sur le créateur.
Outil de surveillance DCA de Jupiter
Cet exemple répond à un besoin relativement spécifique, mais il montre que les logs permettent de tout voir. Avec la requête suivante, nous allons examiner le programme DCA (Dollar-Cost Averaging) de Jupiter et, plus précisément, chaque achat DCA effectué avec Jupiter. Cet exemple peut servir aux calculs de transactions d’arbitrage ou à l’évaluation de l’impact potentiel sur le marché. L’analyse des achats DCA pour anticiper leur impact potentiel sur le marché reste largement sous-exploitée.
Comme vous pouvez le constater, rien de compliqué n’a été fait jusqu’ici. Nous avons ajouté un import base58 en haut et remplacé l’adresse par celle du programme DCA de Jupiter. La partie suivante peut sembler assez complexe — et elle l’est un peu —, mais il me semble important de présenter un exemple plus avancé.
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));
}Voici ce que nous faisons :
- Nous utilisons le log DCA pour ne conserver que ces types de transactions.
- Nous extrayons
User,Input MintetOutput Mintde la transaction. - Nous récupérons
Instruction Data Rawet convertissons les octets en arguments d’entrée, comme le montant saisi par l’utilisateur, la fréquence de vente et la quantité vendue à chaque fois.
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) {}
});*Déchiffrer les données brutes des instructions
Lorsque vous examinez les données brutes d’une instruction, elles correspondent généralement aux arguments d’entrée. Il n’est pas difficile de récupérer les données de cette manière, et nous n’avons rien à désérialiser, une opération qui peut en rebuter plus d’un.
Prenons par exemple les données de cette transaction :
8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000
Accédez maintenant à hexed.it et collez-les :
Recherchons maintenant ces arguments d’entrée :
Sur la droite, saisissons 336971797613 dans le champ de recherche. Cliquez sur la recherche de l’occurrence suivante pour afficher l’emplacement de la valeur inAmount dans les octets.
Comme vous pouvez le constater, les octets allant de 6D jusqu’au dernier 00 précédant 37 contiennent la valeur inAmount. Pour chaque transaction DCA au format que nous surveillons, inAmount se trouve à la même position dans les octets. Les 8 paires d’octets suivantes contiennent la valeur inAmountPerCycle. C’est une méthode pratique pour trouver des valeurs généralement difficiles à identifier dans le JSON de la transaction, car celui-ci contient principalement des adresses et des soldes, pas des arguments d’entrée.
Vous êtes arrivé au bout !
Si vous êtes arrivé jusqu’ici, vous disposez désormais d’une excellente base pour commencer à utiliser Helius Geyser WebSockets. Le plus difficile est de se lancer. Vous pouvez maintenant surveiller des portefeuilles, des programmes, des pools et tout ce que vous voulez. Plus besoin d’API hors de prix ni d’outils similaires. Si vous rencontrez un problème ou avez des questions, vous pouvez toujours les poser sur le Discord de Helius !
Ressources
Articles associés
Abonnez-vous à Helius
Suivez les dernières actualités du développement sur Solana et recevez une notification à chaque publication


