Skip to main content
Laserstreamからトランザクションデータを受け取ったとき、重要なことが2つあります。
  • Message → ユーザーがやりたかったこと(署名済みの提案)
  • Meta → 実際に起きたこと(実行結果)
課題: 生のトランザクションデータは、読みやすいアドレスや署名の代わりにバイナリのバイト配列(<Buffer 00 bf a0 e8...>のような形式)で提供されます。 このガイドは、そのバイナリデータを人間が読みやすい形式にデコードし、意味のある情報を抽出し、提案から実行までの完全なトランザクションストーリーを理解する方法を示します。

ライブストリーム、デコードなし

以下の最小クライアントを実行します。フィルターフラグは投票および失敗したトランザクションをドロップし、accountInclude配列はJupiterプログラムIDに触れる活動の結果を制限します。
コンソールには、ラッパー(filters, createdAt)と、2つの子を隠すtransactionブランチが表示されます。
  • transaction.transaction.transaction → 署名済みのメッセージ
  • transaction.transaction.meta → 実行メタ
Uint8Arrayのように見えるものは、その時点では不透明なままです。 デコード関数を使ってスクリプトを実行すると、読みやすいアドレスを持つ実際のネスト構造が見られます。

バイナリデータのデコード

デコードの理由: 生のLaserstreamデータには、署名、アカウントキー、ハッシュがバイナリUint8Arrayオブジェクトとして含まれており、読み取りができません。これらをbase58文字列に変換する必要があります。 解決策: Laserstreamは、Yellowstone gRPCを使用しており、組み込みのデコードユーティリティを提供しています。各フィールドタイプのデコーダーを書く代わりに、再帰関数を使ってすべてのバイナリデータを人間が読みやすい形式に変換します。
このアプローチは、組み込みのデコードを活用しながら、手動変換が必要なバイナリフィールドを処理します。トランザクション構造はすでに解析されているので、バイナリフィールドを人間が読みやすい形式に変換するだけです。

トランザクション構造の理解

デコードされたデータが見えるようになったので、Laserstreamトランザクションアップデートの主な2つの部分を探ります。最初の例から、各トランザクションには2つのキーオブジェクトが含まれていることを思い出してください。
  • Message (Proposal) → transaction.transaction.transaction→ 署名されたメッセージ(ユーザーの提案)
  • Meta (Execution) → transaction.transaction.meta→ 実行メタデータ(バリデーターの応答)
この2部構成は、ユーザーの要求と実際に起こったことの完全なストーリーを伝えます。各部分を詳細に検討していきましょう。

提案: メッセージ内部のすべて

ユーザーは何を、誰が、そしていつまでを指定するメッセージを作成します。以下に各部分をデコードする方法を示します。

トランザクションヘッダー

numRequiredSignaturesは、バリデーターにどれだけの署名を確認するかを伝え、2つのnumReadonly*の値は、ランタイムが読み取り専用として扱えるアカウントをラベル付けし、並列実行を可能にします。

アカウントキー辞書

accountKeysは公開鍵の単純なリストで、検索テーブルとして機能します。トランザクション内の後の整数(programIdIndexや命令のaccounts配列内の各要素)がこのリストにインデックスを使用して戻ります。これによりメッセージあたり1キロバイト以上を節約できます。

リプレイ防止

recentBlockhashは、最後の150のブロックハッシュをスクロールすると期限切れになります。メインネットで約90秒です。

命令: 実際のコマンド

各命令には3つの主要な部分があります。
  • Program ID (programIdIndex): accountKeys配列内のアドレスにポイントします(例: インデックス10 = ComputeBudget111111111111111111111111111111)
  • Accounts (accounts): この命令がどのアカウントインデックスに触れるのかを表すbase58エンコードされた文字列
  • Data (data): base58でエンコードされた実際の命令データ
convertBuffers関数により、アカウントはbase58として表示されますが、実際にはアカウントインデックスを含んでいます(例: "3vtmrQMafzDoG2CBz1iqgXPTnC"はインデックス[21, 19, 12, 17, 2, 6, 1, 22]にデコードされます) この設計により、フルの32バイトのアドレスを繰り返す代わりに、各命令はルックアップテーブル内の位置を参照するだけで済みます。

署名: 認証の証明

signaturesには必要なアカウントがこのトランザクションを承認したことを証明する暗号署名が含まれています。署名の数はheader.numRequiredSignaturesと一致する必要があります。

アドレスタブルックアップ

versionedがtrueの場合、addressTableLookupsはオンチェーンテーブルと2つのインデックスリストと共に表示されます。ルックアップテーブルにより、パケットを1,232バイトのMTU以下に保ちながら、アドレスカウントのハードキャップを数十に引き上げます。

トランザクション v1: ヘッダー内の計算予算

トランザクション v1 (SIMD-0385, Agave 4.2) はメッセージにもう一つのフィールドを追加します: transactionConfig。
v1 トランザクションでは計算予算が ComputeBudget プログラムの命令ではなくここに含まれるため、v1 トランザクションの instructions 配列には ComputeBudget111111111111111111111111111111 エントリが含まれることはありません。priorityFee はトランザクション全体の手数料を lamports で表し、計算単位当たりのマイクロ-lamports ではありません。null フィールドは、送信者が設定していないことを意味します。レガシーおよび v0 メッセージには transactionConfig がないため、その存在が v1 トランザクションを識別します。 デコーダーで確認すべき2つのこと:
  • 優先手数料の抽出。 transactionConfig.priorityFee が存在する場合に読み取ります。レガシーおよび v0 トランザクションの場合のみ ComputeBudget 命令をスキャンします。命令のみをスキャンするコードは、すべての v1 トランザクションを優先手数料ゼロとして読み取ります。
  • プロトバージョン。 yellowstone-grpc-proto 12.6.0 は v1 フィールドを含む最初のリリースであり、helius-laserstream 0.8.4 (JavaScript)、0.6.3 (Rust)、0.2.0 (Go) はそれを基に構築された最初の SDK リリースです。古いバージョンでは transactionConfig を静かに破棄します。
完全な変更リストについては、トランザクション v1 サポート を参照してください。

すべての接続: フロー

基本原理から見た流れは次のとおりです:
  1. ルックアップテーブルを構築する: accountKeys はこのトランザクションが触れるすべてのアドレスを列挙します
  2. ルールを設定する: header は必要な署名の数と読み取り専用のアカウントを指定します
  3. コマンドを作成する: 各 instruction は以下を指します:
    • プログラム(programIdIndex → accountKeys[index] 経由)
    • 必要なアカウント(accounts → 複数の accountKeys[index] 位置を通して)
    • 命令データ(data にエンコード)
  4. 承認を追加する: signatures は必要なアカウントがこのトランザクションを承認したことを証明します
  5. 有効期限を設定する: recentBlockhash はこのトランザクションが後で再実行されないようにします

実行: meta 内のすべて

メッセージはユーザーが実行したかったことを示し、meta はバリデーターがトランザクションを実行したときに実際に起こったことを示します。

基本的な実行情報

成功/失敗
  • err: null = 成功
  • err: {...} = エラーディテール付きの失敗
  • fee = このトランザクションで課金された lamports
残高の変化
残高配列はインデックスで accountKeys 配列に対応します:
  • アカウント 0: 15000 lamports 減少(手数料支払い)
  • アカウント 1: 1461600 lamports 増加(新しいアカウント作成)
  • アカウント 3: 2001231920 lamports 増加(プログラムアカウント)
計算使用量
要求された量からどれだけの計算予算が使用されたかを示します。

高度な実行詳細

内部命令
内部命令は、実行中にプログラムが呼び出した追加の命令です。それらは元のトランザクションの一部ではありませんが、主要命令によってトリガーされました。 ログメッセージ
ログメッセージはプログラムの実行を時系列で記録し、呼び出されたプログラムやそれらが出力したカスタムログメッセージを示します。 トークン残高の変化
トークン残高の変化は、SPL トークンアカウントの前後の状態を示し、適切な小数処理を伴う人間に読みやすい量を含みます。

実用的なデコードパターン

デコードされたトランザクションから有用な情報を抽出するための一般的なパターンは次のとおりです:

完全な例: Jupiter スワップデコーダー

Jupiter スワップトランザクションをデコードし、意味のある情報を抽出する完全な例は次のとおりです:
この例は、メッセージデコードをメタ解析と組み合わせ、複雑な DeFi トランザクションからビジネスに関連する情報を抽出する方法を示しています。

主なポイント

  • 二部構成: すべてのトランザクションは、メッセージ(要求された内容)と meta(実際に起こった内容)を持ちます。
  • バイナリデコード: バイナリフィールドを読みやすい base58 文字列に変換するために bs58.encode() を使用します。
  • アカウントキーのルックアップ: 命令は accountKeys 配列のインデックスでアカウントを参照します。
  • 残高追跡: preBalances と postBalances を比較して何が変わったかを確認します。
  • トランザクション v1: transactionConfig が存在する場合に計算予算と優先手数料を読み込みます。v1 トランザクションには ComputeBudget 命令はありません。
Solana トランザクションを理解する鍵は、それらが効率性のために設計されていることを認識することです: アドレスを繰り返す代わりに、ルックアップテーブルとインデックスを使用してトランザクションサイズを最小にし、情報密度を最大にしています。