NEU: Helius übernimmt Light Protocol
So überwachst du Solana-Transaktionen mit Geyser Enhanced Websockets
Blog/Entwicklung

So überwachst du Solana-Transaktionen mit Geyser Enhanced Websockets

Integration Engineerzerkerzzz auf X
7 Min. Lesezeit

Einführung

Lange habe ich mich gefragt, wie all diese Monitoring-Apps und Bots funktionieren. Nach einer mühsamen Suche stieß ich auf die Geyser Enhanced Websockets von Helius. Ihre Nutzung ist zwar nicht kostenlos (du brauchst einen Business- oder Professional-Plan), aber sie sind ein sehr leistungsfähiges Werkzeug.

Die Nutzung von Geyser Enhanced Websockets mit Helius ist einfach: Füge eine Adresse ein, die du überwachen möchtest, und führe etwas Code aus. Du kannst alles überwachen: NFTs, Wallets, Programme, Plattformen – buchstäblich alles. Du kannst Wallet-Tracker, Token-Tracker, Kauf- und Verkaufsmonitore, Volumenmonitore und vieles mehr erstellen.

APIs und Tools dieser Art können mitunter Tausende Dollar kosten. Dieser Artikel zeigt dir dagegen einige Beispiele, die sich für einen Bruchteil der Kosten umsetzen lassen. Mit 499 $ für den Business-Plan ist der ROI deines eigenen Tools praktisch unbegrenzt. Ob du es mit anderen teilst oder selbst nutzt: Du verschaffst dir einen Vorteil und unzählige Möglichkeiten, mit Live-Daten von Solana zu arbeiten.

Wenn du noch keine Erfahrung mit Helius Geyser Websockets hast, lies diesen Blogbeitrag und die Dokumentation. Du kannst direkt mitmachen, denn das erste Beispiel ist recht leicht verständlich.

Monitor für neue Raydium-Pools

Code
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));
}

Der erste Teil dieses Codeausschnitts ist sehr einfach. Wir legen unseren API-Schlüssel fest und senden eine Anfrage mit dem Account, den wir überwachen möchten. Hier ist das 675kPX9…, also das On-Chain-Programm von Raydium. Mit dieser Anfrage erhalten wir alle bestätigten, nicht fehlgeschlagenen Transaktionen, die mit Raydium interagieren. Normalerweise würde diese Anfrage jede Sekunde Tausende Transaktionen zurückgeben. Konzentrieren wir uns daher auf eine einfache Methode, um das Rauschen herauszufiltern.

Rauschen herausfiltern

Der folgende Codeausschnitt enthält die zentrale Filterlogik für die Ereignisverarbeitung. Die „on message“-Funktion analysiert die von den Transaktionen zurückgegebenen Daten anhand der Logs. In diesem Fall suchen wir nach allen Transaktionen mit dem Log „initialize2: InitializeInstruction2“. Es zeigt uns, wann ein Benutzer einen neuen Liquiditätspool auf Raydium erstellt. Je nachdem, was du überwachen möchtest, kannst du auch jedes andere Log verwenden. Ich empfehle, eine Testtransaktion für den Vorgang auszuführen, den du überwachen möchtest. Du könntest beispielsweise einem Pool Liquidität hinzufügen und prüfen, wie die Logs dieser Transaktion aussehen. Anschließend filterst du diese Logs, um alle Transaktionen zu erhalten, bei denen Liquidität hinzugefügt wird.

Code
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');
});

Da wir wissen, dass die Transaktion dieses Log enthält, extrahieren wir zwei Dinge: die Signatur, damit wir die Genauigkeit unseres Programms vergleichen und überprüfen können, und den Account-Schlüssel. Dieser ist die AMM-ID, also die AMM-Adresse des betreffenden Pools. Viele Bots und Sniper verwenden die AMM-ID, um Trades zu starten.

Wenn du möchtest, kannst du noch mehr Daten extrahieren. Du kannst beispielsweise den Ersteller ermitteln – normalerweise der 17. öffentliche Schlüssel in accountKeys –, die zum Erstellen des Pools verwendeten Token aus den Token-Guthaben vor und nach der Transaktion oder den inneren Anweisungen, die Anzahl der Token und sogar die LP-Token, die der Ersteller erhält. Anhand der Anzahl der erhaltenen LP-Token könntest du einen Monitor für das Verbrennen von Liquidität erstellen. Wenn der Ersteller eines Pools seine LP-Token verbrennt, die im Grunde die Beleg-Token für seinen Anteil am Pool sind, gibt er damit seine Möglichkeit auf, Liquidität zu entfernen. Sei jedoch vorsichtig: Wenn er weiterhin die Minting-Berechtigung besitzt, könnte er weitere Token erzeugen und in den Pool verkaufen.

JSON-Struktur

Weitere Daten findest du in der JSON-Struktur der Transaktion, die in der offiziellen Solana-Dokumentation beschrieben ist. Alternativ kannst du die JSON-Antwort speichern und ihre Struktur mit diesem Formatierer anzeigen. Das ist wichtig: Sobald du die allgemeine Struktur von Transaktionen kennst, kannst du alle Daten extrahieren, die du auch in einem Block-Explorer sehen würdest.

Unten siehst du die allgemeine JSON-Struktur. Sie enthält zwei wichtige, tief verschachtelte Objekte: Transaction und Meta. In Transaction befindet sich das Nachrichtenobjekt mit dem aktuellen Blockhash, accountKeys und instructions. In Meta befinden sich die Balances vor und nach der Transaktion (Lamport-Guthaben), innerInstructions, logMessages sowie token balances vor und nach der Transaktion.

Beispiel für einen Pump.Fun-Monitor

Angesichts des aktuellen Meme-Coin-Trends auf Solana habe ich einen sehr einfachen pump.fun-Monitor erstellt. Ersetze einfach die zu überwachende Adresse durch „6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P“. Das ist das On-Chain-Programm von pump.fun. Verwende dann diese „on message“-Funktion:

Code
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) {
        
    }
});

Wie bei Raydium betrachten wir alle Transaktionen, die mit dem pump.fun-Programm interagieren, und filtern sie anhand des gewünschten Logs. Das Muster ist klar: Logs, Logs, Logs. Sobald wir eine Transaktion mit den gewünschten Logs haben, extrahieren wir die Signatur, den Ersteller und den Token selbst!

Du siehst, dass accountKeys und die Signatur im JSON an derselben Stelle verschachtelt sind wie im Raydium-Beispiel. Dadurch lassen sich Ersteller, Token und Signatur leicht abrufen. Die JSON-Struktur ist bei den meisten Transaktionen im Wesentlichen gleich. Einige Werte können sich jedoch an anderen Stellen befinden oder anders angeordnet sein – das siehst du im nächsten Beispiel.

So erhältst du einen recht einfachen Monitor für neue pump.fun-Token. Mit diesen Informationen kannst du den Token kaufen oder ihn einfach zu Überwachungszwecken erfassen. Du kannst den Monitor außerdem mit verschiedenen Helius DAS APIs kombinieren und so ein noch leistungsfähigeres Tool erstellen, das beispielsweise Metadaten oder Informationen zum Ersteller abruft.

Jupiter-DCA-Monitor

Dieses Beispiel ist relativ speziell, zeigt aber, dass du über Logs alles sehen kannst. Mit der folgenden Anfrage betrachten wir das DCA-Programm (Dollar-Cost Averaging) von Jupiter. Genauer gesagt sehen wir jeden einzelnen DCA-Kauf, der über Jupiter ausgeführt wird. Du kannst dieses Beispiel für die Berechnung von Arbitrage-Trades verwenden oder um mögliche Auswirkungen auf den Markt zu erkennen. DCA-Käufe im Hinblick auf mögliche Marktbewegungen zu analysieren, ist eine bislang wenig genutzte Datenquelle.

Wie du siehst, haben wir bisher nichts Kompliziertes getan. Wir haben oben einen base58-Import hinzugefügt und die Adresse in die des Jupiter-DCA-Programms geändert. Der nächste Teil wirkt vielleicht recht kompliziert – und das ist er teilweise auch. Ich halte es jedoch für wichtig, ein etwas komplexeres Beispiel zu zeigen.

Code
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));
}

Dabei führen wir folgende Schritte aus:

  • Wir verwenden das DCA-Log, um nur Transaktionen dieses Typs herauszufiltern.
  • Wir extrahieren User, Input Mint und Output Mint aus der Transaktion.
  • Wir nehmen Instruction Data Raw und konvertieren die Bytes in die Eingabeargumente, etwa den vom Benutzer eingegebenen Betrag, das Verkaufsintervall und die bei jedem Vorgang verkaufte Menge.
Code
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) {}
});*

Entschlüsseln der rohen Anweisungsdaten

Die rohen Anweisungsdaten entsprechen normalerweise den Eingabeargumenten. Auf diese Weise lassen sich die Daten leicht abrufen, und wir müssen nichts deserialisieren, was viele abschrecken kann.

Nehmen wir zum Beispiel diese Daten aus dieser Transaktion:

8e772b6da2340bb12e783a66000000006d9415754e00000037ca8a3a270000003c00000000000000010000000000000000010000000000000000010000000000000000

Öffne jetzt hexed.it und füge sie ein:

Suchen wir nun diese Eingabeargumente:

Gib auf der rechten Seite 336971797613 in die Suche ein. Klicke auf „Weitersuchen“. Dadurch sehen wir, wo sich der Wert inAmount in den Bytes befindet.

Wie du siehst, zeigt uns die Ansicht, dass die Bytes von 6D bis zum letzten 00 vor 37 den Wert inAmount enthalten. Bei jeder DCA-Transaktion in dem von uns überwachten Format befindet sich inAmount an derselben Byteposition. Die folgenden acht Bytepaare enthalten den Wert inAmountPerCycle. Das ist eine praktische Methode, um Werte zu finden, die im JSON der Transaktion meist nicht eindeutig erkennbar sind. JSON enthält hauptsächlich Adressen und Guthaben, aber keine Eingabeargumente.

Du hast es geschafft!

Wenn du bis hierher gekommen bist, hast du jetzt eine hervorragende Grundlage für die Arbeit mit Helius Geyser WebSockets. Der Einstieg ist der schwierigste Teil. Nun kannst du Wallets, Programme, Pools und alles andere überwachen, was du möchtest. Du brauchst keine absurd teuren APIs oder Ähnliches. Wenn du auf Probleme stößt oder Fragen hast, kannst du sie jederzeit im Helius Discord stellen!

Ressourcen

Helius abonnieren

Bleib bei der Solana-Entwicklung auf dem Laufenden und erhalte Updates, wenn wir neue Beiträge veröffentlichen

Vergrößertes Bild