- Message → Was der Benutzer tun wollte (sein signierter Vorschlag)
- Meta → Was tatsächlich passiert ist (das Ausführungsergebnis)
<Buffer 00 bf a0 e8...> vor und nicht als lesbare Adressen und Signaturen.
Dieser Leitfaden zeigt dir, wie du: Die Binärdaten in ein menschenlesbares Format decodierst, relevante Informationen extrahierst und den gesamten Ablauf einer Transaktion vom Vorschlag bis zur Ausführung verstehst.
Ein Live-stream ohne Decodierung
Führe den folgenden minimalen Client aus. Die Filter-Flags verwerfen Abstimmungs- und fehlgeschlagene Transaktionen. Das ArrayaccountInclude beschränkt die Ergebnisse auf Aktivitäten, die die Jupiter-Programm-ID betreffen.
filters, createdAt und einen transaction-Zweig, der zwei untergeordnete Elemente enthält:
transaction.transaction.transaction→ die signierte Messagetransaction.transaction.meta→ die Meta der Ausführung
Uint8Array aussieht, bleibt vorerst undurchsichtig.
Wenn du das Skript mit der Decodierungsfunktion ausführst, siehst du die tatsächliche verschachtelte Struktur mit lesbaren Adressen:
Binärdaten decodieren
Warum decodieren? Laserstream-Rohdaten enthalten Signaturen, Kontoschlüssel und Hashes als binäreUint8Array-Objekte, die nicht lesbar sind. Du musst sie in base58-Strings umwandeln, um die Transaktion nachvollziehen zu können.
Die Lösung: Laserstream verwendet Yellowstone gRPC mit integrierten Hilfsfunktionen zur Decodierung. Statt für jeden Feldtyp einen eigenen Decoder zu schreiben, verwenden wir eine rekursive Funktion, die alle Binärdaten in ein menschenlesbares Format umwandelt.
Die Transaktionsstruktur verstehen
Da wir die decodierten Daten jetzt sehen können, sehen wir uns die beiden Hauptbestandteile jedes Laserstream-Transaktionsupdates an. Wie im ersten Beispiel gezeigt, enthält jede Transaktion zwei zentrale Objekte:- Message (Vorschlag) →
transaction.transaction.transaction→ die signierte Message (Vorschlag des Benutzers) - Meta (Ausführung) →
transaction.transaction.meta→ die Metadaten der Ausführung (response des Validators)
Der Vorschlag: alles innerhalb von message
Der Benutzer erstellt eine Message, die festlegt, was, wer und bis wann. So decodierst du die einzelnen Teile:Transaktions-Header
numRequiredSignatures teilt dem Validator mit, wie viele Signaturen er verifizieren muss. Die beiden numReadonly*-Werte kennzeichnen Konten, die die Laufzeitumgebung als schreibgeschützt behandeln kann. Das ermöglicht eine parallele Ausführung.
Wörterbuch der Kontoschlüssel
accountKeys ist eine einfache Liste öffentlicher Schlüssel, die als Nachschlagetabelle dient. Jede spätere Ganzzahl in der Transaktion – programIdIndex sowie jedes Element im accounts-Array einer Anweisung – verweist per Index auf diese Liste. Dadurch spart jede Message mehr als ein Kilobyte.
Schutz vor Wiederholung
recentBlockhash läuft ab, sobald er nicht mehr zu den letzten 150 Block-Hashes gehört. Das dauert im Mainnet ungefähr 90 Sekunden.
Anweisungen: die eigentlichen Befehle
- Programm-ID (
programIdIndex): Verweist auf eine Adresse imaccountKeys-Array (z. B. Index 10 =ComputeBudget111111111111111111111111111111) - Konten (
accounts): Ein base58-codierter String, der angibt, auf welche Kontoindizes diese Anweisung zugreift - Daten (
data): Die eigentlichen, als base58 codierten Anweisungsdaten
convertBuffers erscheinen Konten als base58, enthalten tatsächlich aber Kontoindizes. Beispielsweise wird "3vtmrQMafzDoG2CBz1iqgXPTnC" zu den Indizes [21, 19, 12, 17, 2, 6, 1, 22] decodiert.
Dank dieses Designs verweist jede Anweisung nur auf Positionen in der Nachschlagetabelle, statt vollständige 32-Byte-Adressen zu wiederholen.
Signaturen: Autorisierungsnachweis
signatures enthält die kryptografischen Signaturen, die belegen, dass die erforderlichen Konten diese Transaktion autorisiert haben. Die Anzahl der Signaturen muss mit header.numRequiredSignatures übereinstimmen.
Nachschlagen in Adresstabellen
versioned den Wert true hat, erscheint addressTableLookups mit einer On-Chain-Tabelle und zwei Indexlisten. Nachschlagetabellen erhöhen die feste Obergrenze für die Anzahl der Adressen auf mehrere Dutzend und halten das Paket zugleich unter der MTU von 1.232 Byte.
Transaktion v1: Compute-Budget im Header
Transaktion v1 (SIMD-0385, Agave 4.2) fügt der Message ein weiteres Feld hinzu:transactionConfig.
instructions-Array einer v1-Transaktion nie einen ComputeBudget111111111111111111111111111111-Eintrag. priorityFee ist die Gesamtgebühr in Lamports für die gesamte Transaktion und nicht die Anzahl der Mikro-Lamports pro Compute-Einheit. Ein null-Feld bedeutet, dass der Sender keinen Wert festgelegt hat. Legacy- und v0-Messages haben kein transactionConfig. Daher kennzeichnet die Anwesenheit dieses Felds eine v1-Transaktion.
Prüfe in deinem Decoder zwei Dinge:
- Prioritätsgebühr extrahieren. Lies
transactionConfig.priorityFee, wenn es vorhanden ist. Durchsuche ComputeBudget-Anweisungen nur bei Legacy- und v0-Transaktionen als Fallback. Code, der ausschließlich Anweisungen durchsucht, interpretiert jede v1-Transaktion so, als würde sie keine Prioritätsgebühr zahlen. - Proto-Version.
yellowstone-grpc-proto12.6.0 ist die erste Version, die die v1-Felder überträgt.helius-laserstream0.8.4 (JavaScript), 0.6.3 (Rust) und 0.2.0 (Go) sind die ersten darauf basierenden SDK-Versionen. Ältere Versionen verwerfentransactionConfigohne Hinweis.
So hängt alles zusammen: der Ablauf
Ausgehend von den Grundlagen passiert Folgendes:- Nachschlagetabelle erstellen:
accountKeyslistet alle Adressen auf, auf die diese Transaktion zugreift - Regeln festlegen:
headerlegt fest, wie viele Signaturen erforderlich und welche Konten schreibgeschützt sind - Befehle erstellen: Jedes
instructionverweist auf:- Ein Programm (über
programIdIndex→accountKeys[index]) - Die benötigten Konten (über
accounts→ mehrereaccountKeys[index]-Positionen) - Die Anweisungsdaten (codiert in
data)
- Ein Programm (über
- Autorisierung hinzufügen:
signaturesbelegt, dass die erforderlichen Konten diese Transaktion genehmigt haben - Ablaufzeit festlegen:
recentBlockhashverhindert, dass diese Transaktion später erneut abgespielt werden kann
Die Ausführung: alles innerhalb von meta
Die Message zeigt, was der Benutzer tun wollte. Die Meta zeigt dagegen, was tatsächlich passiert ist, als die Validatoren die Transaktion ausgeführt haben.Grundlegende Ausführungsinformationen
Erfolg/Fehlschlagerr: null= Erfolgerr: {...}= Fehlschlag mit Fehlerdetailsfee= für diese Transaktion berechnete Lamports
accountKeys-Array:
- Konto 0: 15000 Lamports verloren (Gebührenzahlung)
- Konto 1: 1461600 Lamports erhalten (neues Konto erstellt)
- Konto 3: 2001231920 Lamports erhalten (Programmkonto)
Erweiterte Ausführungsdetails
Innere AnweisungenPraktische Decodierungsmuster
Hier sind gängige Muster, mit denen du nützliche Informationen aus decodierten Transaktionen extrahierst:Vollständiges Beispiel: Decoder für Jupiter-Swaps
Hier ist ein vollständiges Beispiel, das Jupiter-Swap-Transaktionen decodiert und relevante Informationen extrahiert:Das Wichtigste
- Zweiteilige Struktur: Jede Transaktion hat eine Message (was angefordert wurde) und eine Meta (was tatsächlich passiert ist)
- Binärdaten decodieren: Verwende
bs58.encode(), um Binärfelder in lesbare base58-Strings umzuwandeln - Kontoschlüssel nachschlagen: Anweisungen referenzieren Konten über ihren Index im
accountKeys-Array - Kontostände verfolgen: Vergleiche
preBalancesundpostBalances, um Änderungen zu erkennen - Transaktion v1: Lies das Compute-Budget und die Prioritätsgebühr aus
transactionConfig, wenn es vorhanden ist. v1-Transaktionen enthalten keine ComputeBudget-Anweisungen