Solana Stream SDKはオープンソースで提供しています。詳細は以下のGitHubリポジトリをご覧ください。
gRPCフィルターの概要
Solana Geyser gRPCでは、特定のアカウントやプログラム、トランザクション、スロット、ブロックなど、関心のあるデータのみを効率的に取得するためフィルターを活用します。
以下では、Solana Stream SDKを用いたTypeScriptのコード例を示し、各フィルターの具体的な役割を解説します。Rustを使用する場合もフィルターの構造や意味は同一です。
各フィルターの役割と例
特定のアカウントを購読
特定のアカウントの更新をリアルタイムに購読します。
以下は、SOL-USDCペアのOpenBookアカウントをConfirmedレベルで購読する例です。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {
'wsol/usdc': {
account: ['8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'],
},
},
transactions: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.CONFIRMED,
}
"wsol/usdc" はクライアントが自由に設定できるラベルです。
- 複数のアカウントやプログラム、ブロック、スロットを同時に購読する場合、JSON内で複数のフィルターを組み合わせることができます。
account_data_sliceで特定範囲のデータのみを取得
アカウントデータの一部だけを取得する例です。
USDCトークンアカウントの全データ(165バイト)ではなく、40バイト(オフセット32から)を取得します。
この範囲には所有者情報とlamports残高が含まれています。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {},
accounts: {
usdc: {
owner: ['TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'],
filters: [
{
tokenAccountState: true,
},
{
memcmp: {
offset: 0,
data: {
base58: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
},
},
},
],
},
},
transactions: {},
blocks: {},
blocksMeta: {},
entry: {},
commitment: CommitmentLevel.CONFIRMED,
accountsDataSlice: [{ offset: 32, length: 40 }],
}
特定のプログラムを購読
特定のプログラムに関連するアカウントの更新を購読する例です。
以下の例では、Solendプログラムが所有するアカウントの更新をProcessedレベルで購読しています。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {
solend: {
owner: ['So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'],
},
},
transactions: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
"solend" はクライアントが自由に設定できるラベルです。
- 複数のプログラムを購読する場合は、次の「複数のプログラムを購読」のセクションをご参照ください。
複数のプログラムを購読
複数のプログラムが所有するアカウントの更新を一括して購読する場合の例です。
以下の例では、SolendとSerumの両プログラムに関連するアカウントの更新を取得します。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {
programs: {
owner: [
'So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo',
'9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin',
],
},
},
transactions: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
また、異なるプログラムの更新にそれぞれ個別のラベルを付けたい場合は、以下のような指定も可能です。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {
solend: {
owner: ['So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'],
},
serum: {
owner: ['9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'],
},
},
transactions: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
全てのFinalizedかつ非投票・非失敗トランザクションを購読
Finalizedレベルで、投票トランザクションや失敗したトランザクションを除いたすべてのトランザクションを購読する例です。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {},
transactions: {
alltxs: {
vote: false,
failed: false,
},
},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.FINALIZED,
}
vote: false は、投票トランザクションを除外します。
failed: false は、失敗したトランザクションを除外します。
- フィールドが空の場合、すべてのトランザクションが取得されます。
- フィールドが指定された場合はAND条件で動作します。
特定のアカウントを含む非投票トランザクションを購読
特定のアカウントに関連し、かつ投票トランザクションではないトランザクションを購読する例です。
以下の例では、Serumプログラムのアカウントを含む非投票トランザクションを購読します。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {},
transactions: {
serum: {
vote: false,
accountInclude: ['9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'],
},
},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
特定のアカウントを除外したトランザクションを購読
特定のアカウントを除外して、それらが関わらないトランザクションだけを購読します。
以下の例では、SerumおよびTokenkegプログラムのアカウントを除外したトランザクションを取得します。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {},
transactions: {
serum: {
accountExclude: [
'9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin',
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
],
},
},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
特定アカウントを含み、別のアカウントを除外するトランザクションを購読
特定のアカウントが関わるトランザクションを取得しつつ、その中でさらに別の特定アカウントを除外する例です。
以下の例では、Serumアカウントが含まれるトランザクションを購読しますが、指定したアカウントを除外します。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
slots: {},
},
accounts: {},
transactions: {
serum: {
accountInclude: ['9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'],
accountExclude: ['9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
},
},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
特定のトランザクション署名を購読
特定のトランザクション署名(signature)を指定して、そのトランザクションがConfirmedやFinalizedの状態に移行するまでの更新をリアルタイムで購読する例です。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {},
accounts: {},
transactions: {
sign: {
signature:
'5rp2hL9b6kexex11Mugfs3vfU9GhieKruj4CkFFSnu52WLxiGn4VcLLwsB62XURhMmT1j4CZiXT6FFtYbXsLq2Zs',
},
},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
スロットを購読
スロットの通知を購読します。スロット情報を取得するためには追加の詳細は不要で、タグ付け用の名前のみ設定します。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {
incoming_slots: {},
},
accounts: {},
transactions: {},
blocks: {},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
ブロックを購読
生成されるすべてのブロック情報をリアルタイムで取得します。
デフォルトではブロックに含まれるすべてのトランザクションが取得されます。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {},
accounts: {},
transactions: {},
blocks: {
blocks: {},
},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
トランザクションを除外し、更新されたアカウント情報のみを含む場合
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {},
accounts: {},
transactions: {},
blocks: {
blocks: {
includeTransactions: false,
includeAccounts: true,
},
},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
特定のアカウントが関連するトランザクション/アカウント情報のみを取得する場合
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {},
accounts: {},
transactions: {},
blocks: {
blocks: {
accountInclude: ['So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'],
},
},
blocksMeta: {},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
ブロックメタデータを購読
ブロックが処理された際の通知(メタデータ)のみを取得します。
トランザクションの詳細情報は含まれません。
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
const request = {
slots: {},
accounts: {},
transactions: {},
blocks: {},
blocksMeta: {
blockmetadata: {},
},
accountsDataSlice: [],
commitment: CommitmentLevel.PROCESSED,
}
Commitmentレベルの管理
Solana Geyser gRPCのストリームでは、デフォルトでProcessedのCommitmentレベルを使用します。
ConfirmedやFinalizedレベルを指定することも可能です。その場合、Geyser側がデータをバッファリングし、指定したCommitmentレベルに到達した時点で通知を送信します。
パフォーマンスを最大限に引き出すためには、クライアント側でCommitmentレベルの処理を管理することを推奨します。
Commitmentレベルの指定方法は以下の通りです:
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'
enum CommitmentLevel {
PROCESSED = 0,
CONFIRMED = 1,
FINALIZED = 2,
}
PROCESSED: 処理された直後のデータ。高速に取得可能ですが、まだ確定ではありません。
CONFIRMED: クラスターによって確認されたデータ。確度が高まります。
FINALIZED: 完全に確定したデータ。再編成(reorg)の可能性はありません。
Processedレベルで処理する利点
Processedレベルを使用する最大の利点は、トランザクションをすぐに取得し、クライアント側で迅速に処理できることです。
クライアント側で後からConfirmedやFinalizedへの移行を検知することにより、迅速なレスポンスを提供できます。
ConfirmedやFinalizedレベルの管理方法
ConfirmedまたはFinalizedレベルを扱う場合、スロットごとにイベント(トランザクションやアカウント更新)をバッファリングする必要があります。
イベントをスロット単位でバッファリングし、スロット通知を購読して特定のスロットがConfirmedまたはFinalizedになった段階でバッファからリリースします。
イベントの通知は、スロットがConfirmedやFinalizedになる前に受け取ります。
Finalizedレベルの特別な注意点
SolanaのGeyserの仕様上、すべてのスロットのFinalized通知が発行されるわけではありません。そのため、Finalized通知を受け取ったスロットの祖先スロットも、通知を受けていなくてもFinalizedとして扱う必要があります。
具体的には、Finalized通知を受け取った場合、その祖先スロットを遡及的にすべてFinalizedとして処理します。