新着:HeliusがLight Protocolを買収
getTransfersByAddress
ブログ/更新情報

getTransfersByAddress:1回の呼び出しで解析済みの Solana 転送履歴を取得

Helius プロダクト担当XのKiryl MiranovichLinkedInのKiryl Miranovich
読了時間:5分

getTransfersByAddress は Helius 独自の新しい Solana RPC メソッドです。ウォレットアドレスについて、解析済みで人が読みやすいトークンおよび SOL の転送記録を返します。ミント、時刻、数量、スロット、方向、取引相手によるネイティブフィルターも備えています。

これは getTransactionsForAddress(gTFA)を完璧に補完するメソッドです。gTFA が完全なトランザクションペイロードを返すのに対し、getTransfersByAddress は、誰が、何を、誰に、いつ、どれだけ送ったかを示す簡潔な転送オブジェクトを返します。

なぜ転送専用の RPC メソッドが必要なのでしょうか?

ほとんどのウォレット、決済、ポートフォリオ製品には、トランザクションのペイロード全体は必要ありません。必要なのは転送データです。

では、各チームはどうしているのでしょうか?それぞれが同じ転送パーサーを別々に実装していますが、残念ながら、その多くはエッジケースを正しく処理できていません。

これまで、整然とした Solana の転送履歴を構築するには、開発者が以下を行う必要がありました。

  1. getSignaturesForAddress で署名を取得する
  2. getTransaction で各署名を取得する
  3. 処理前後の残高、トークン残高、内部命令を解析する
  4. 転送を再構築し、SPL Token と Token-2022 の手数料セマンティクスの違いに対応し、WSOL のラップ/アンラップによるノイズを整理する
  5. 複数ページにわたって処理を繰り返し、再試行に対応して、結果を保存する

getTransactionsForAddress メソッドを使用してステップ1と2を1回の呼び出しにまとめても、ステップ3〜5は依然として開発者が対応する必要があります。

今では、getTransfersByAddress がこの処理を代行し、構造化されたリストとして結果を返します。

getTransfersByAddress のレスポンス

各転送オブジェクトには、署名、スロット、ブロック時刻、転送タイプ、送信者、受信者、ミント、数量(raw と UI)、小数点以下の桁数、確認ステータス、正確な命令インデックスが含まれます。そのため、各転送を元のトランザクションに対応付けられます。

コード
{
  "signature": "<TX_SIGNATURE>",
  "slot": 315073428,
  "blockTime": 1736159420,
  "type": "transfer",
  "fromUserAccount": "<SENDER_WALLET>",
  "toUserAccount": "<RECIPIENT_WALLET>",
  "fromTokenAccount": "<SENDER_TOKEN_ACCOUNT>",
  "toTokenAccount": "<RECIPIENT_TOKEN_ACCOUNT>",
  "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "amount": "2500000",
  "decimals": 6,
  "uiAmount": "2.5",
  "confirmationStatus": "finalized",
  "transactionIdx": 35,
  "instructionIdx": 1,
  "innerInstructionIdx": 0
}

type フィールドから、何が起きたのかを正確に把握できます。transfer、transferFee、mint、burn、wrap、unwrap、changeAccountOwner、withdrawWithheldFee のいずれかが示されるため、プログラムの生データから挙動を推測する必要はありません。

Solana の転送解析が難しいのはなぜでしょうか?

Solana トランザクションにおける転送は、単一の概念ではありません。

これは6種類ほどのエッジケースを内包するカテゴリーであり、いずれか1つでも誤って処理するとデータが破損します。

SOL と WSOL

ネイティブ SOL と Wrapped SOL は、ユーザーには同じ資産に見えますが、トランザクション内では異なる部分に存在します。

ネイティブ SOL は、システムアカウントの処理前後の lamport 残高を通じて移動します。WSOL は、トークンアカウントの SPL トークン残高を通じて移動します。

Jupiter でスワップするユーザーは、SOL を WSOL にラップしてから WSOL を USDC に交換し、その後アンラップしないことがあります。この場合、WSOL トークンアカウントが残ります。

ユーザーの視点では SOL を使用しただけですが、ネットワークの視点では3件の転送と1件のラップが発生しています。

さらに、ラップ自体は別の所有者への転送ではありません。同じウォレットが、自身のトークンアカウントへ lamport を移動しているだけです。これを転送として数えると、ユーザーのアクティビティが二重に計上されます。

Token-2022 の転送手数料

Token-2022 では TransferCheckedWithFee が導入され、送信者の引き落とし額と受信者の受取額が一致しない場合があります。

その差額は手数料として受信者のトークンアカウントに保留され、後から withdrawWithheldFee を介して手数料権限者に支払われます。

単純なパーサーはこれを1件の転送として認識し、数量を誤って処理します。慎重に設計されたパーサーは手数料拡張を検出し、命令を転送と保留手数料の発生に分割して、手数料アカウントを個別に追跡します。

ミントとバーン

アカウントにミントされたトークンには送信者がいません。バーンされたトークンには受信者がいません。どちらも処理前後の残高差では「転送」のように見えますが、ウォレット間の転送と混同すると取引相手の分析が歪みます。ウォレットがゼロアドレスから資金を「受け取り」、何もない場所へ資金を「送信」したように見えてしまいます。

getTransfersByAddress は、これらを mint および burn タイプとして表現し、fromUserAccount または toUserAccount を null に設定します。そのため、構築するものに応じて含めるか除外するかを選択できます。

getTransfersByAddress のメリット

getTransfersByAddress メソッドは、これまでクライアント側でトランザクション履歴全体を取得して解析しなければ使えなかったフィルターを受け付けます。 

ミントで検索

特定のトークンの転送のみを返します。

コード
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "getTransfersByAddress",
  "params": [
    "<WALLET_ADDRESS>",
    { "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" }
  ]
}

数量で検索

gt、gte、lt、lte の比較演算子を使い、raw 数量でフィルタリングできます。大口保有者の抽出、ダスト(つまり、トークン数量がごくわずかなアカウント)の除外、異常なアクティビティの検出に役立ちます。

コード
{
  "params": [
    "<WALLET_ADDRESS>",
    {
      "mint": "So11111111111111111111111111111111111111112",
      "filters": {
        "amount": { "gte": 1000000000, "lt": 10000000000 }
      }
    }
  ]
}

時刻で検索

ブロック時刻は Unix タイムスタンプの範囲として指定できます。スロット範囲も同様に機能し、スロット単位で正確にクエリできます。

コード
{
  "params": [
    "<WALLET_ADDRESS>",
    {
      "filters": {
        "blockTime": { "gte": 1735718400, "lt": 1738396800 }
      }
    }
  ]
}

取引相手で検索

with と direction パラメータを組み合わせると、特定の2つのウォレット間の転送を双方向でクエリできます。

コード
{
  "params": [
    "<WALLET_ADDRESS>",
    {
      "with": "<COUNTERPARTY_WALLET>",
      "direction": "in"
    }
  ]
}

SOL モード

ネイティブ SOL と WSOL は Solana 上では異なる形で表現されますが、ユーザーにとっては通常同じものを意味します。そのため、getTransfersByAddress メソッドでは solMode パラメータを利用できます。

merged(デフォルト)

WSOL はネイティブ SOL として扱われます。

ラップとアンラップの行は除外され、ネイティブ SOL のミントでクエリすると、ネイティブ SOL と WSOL の両方の転送が返されます。

separate

このモードでは、WSOL は独立したミントとして保持され、完全な監査可能性を確保するため、ラップとアンラップのライフサイクル行も含まれます。

ほとんどの製品ユースケースでは、通常 merged が適しています。照合、会計、プロトコルレベルの分析では、多くの場合 separate が適しています。

ページネーションと並び順

paginationToken を使用した標準的なカーソルベースのページネーションに対応し、1ページあたり最大100件のレコードを取得できます。sortOrder には asc と desc を指定できます。

コード
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "getTransfersByAddress",
  "params": [
    "<WALLET_ADDRESS>",
    { "limit": 50, "paginationToken": "315069220:308:2:1" }
  ]
}

getTransfersByAddress を使用する場面

getTransfersByAddress と getTransactionsForAddress は似ていますが、それぞれ異なる目的に対応します。 

ニーズメソッド
フィルター付きの解析済みトークンおよび SOL 転送getTransfersByAddress
完全なトランザクションペイロードまたは転送以外のアクティビティgetTransactionsForAddress
任意の署名またはアドレスについてデコードされた命令解析済みイベント API
署名のみtransactionDetails: 'signatures' を指定した getTransactionsForAddress
転送のリアルタイムストリーミングLaserStream

利用を開始する

getTransfersByAddress メソッドは現在、Developer プラン以上のすべての有料プランで利用できます。リクエストごとに10クレジットが必要で、標準の RPC レート制限グループに含まれます。

既存の Helius RPC URL で使用できます。

コード
const response = await fetch("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "1",
    method: "getTransfersByAddress",
    params: ["<WALLET_ADDRESS>"]
  })
});

const data = await response.json();
console.log(data.result.data);

パラメータとレスポンスの詳細については、API リファレンスをご覧ください。

Heliusを購読

Solana開発の最新情報や新しい記事の公開通知を受け取れます