ウォレットのトークン残高 API:ERC-20 資産と送金履歴

残高がゼロでない ERC-20 トークン、トークン送金履歴、一括取得したメタデータを使ってウォレット資産ページを構築します。チェーンの対応範囲を確認し、結果をページ分割して取得し、decimals に応じて整数の数量を換算します。

ブロックチェーンのウォレットデータ API を使ってウォレット資産ページを構築します。残高がゼロでない ERC-20 保有資産にはトークン残高 API、ウォレットの履歴にはトークン送金 APIを使用します。開発者と AI エージェントは同じ認証付きリクエストを使用します。クエリを実行する前に GET /v1/status を取得し、選択したチェーンの data_features と data_status を確認してください。残高データの対応範囲はチェーンによって異なります。リクエストパラメータとレスポンススキーマは Data API リファレンスに記載されています。

このガイドでできること

ウォレット資産ページに必要な 3 種類のデータ

ウォレット資産ページには、アドレスの ERC-20 トークン残高、トークン送金履歴、トークンのメタデータを表示できます。Data API では、それぞれに対応するエンドポイントを提供しています。

  • 残高:GET /{chain}/addresses/{address}/balances は、そのアドレスの残高がゼロでない ERC-20 トークンを、token アドレスの昇順で返します。トークンの symbol と decimals は取得できる場合に含まれます。残高がないアドレスには、data: [] を含む 200 を返します。
  • 送金:GET /{chain}/addresses/{address}/transfers は、必須のブロック範囲内でそのアドレスが関わったトークン送金を、(block_number, log_index) の降順で返します。
  • トークンのメタデータ:GET /{chain}/tokens/{token} は、コントラクトアドレスを使って 1 つのトークンの名前、シンボル、小数桁数、総供給量を取得します。POST /{chain}/tokens:batch は、同じメタデータを 1 回のリクエストで最大 100 アドレス分取得します。

3 つともベース URL に https://api.blockvectra.com/v1/data、リクエストヘッダーに x-api-key を使用し、チェーンの例として robinhood_mainnet を使います。それぞれ balances、transfers、token_metadata の機能に属します。各機能を提供するチェーンについては、対応チェーンページを参照してください。その機能に対応していないチェーンでは、エンドポイントは 422 no_coverage を返します。

リクエスト 1:アドレスの残高

このエンドポイントはパラメータが少ないため、ページで最初に行うリクエストに適しています。

  • {chain}(パスパラメータ、必須):チェーン識別子です。GET /chains のエントリーの chain 値(例:robinhood_mainnet)を指定します。完全一致で、大文字と小文字を区別します。別名や数値の Chain ID は受け付けません。
  • {address}(パスパラメータ、必須):20 バイトのアドレスです。0x プレフィックスは任意で、大文字と小文字のどちらも受け付けます。
  • limit(クエリパラメータ、任意):ページサイズです。デフォルトは 50 で、500 を超える値は 500 に切り詰められます。0 または整数以外の値は 400 bad_request を返します。
  • cursor(クエリパラメータ、任意):前のレスポンスの next_cursor を変更せずに渡し、次のページを取得します。カーソルは、それを発行したチェーン、エンドポイント、クエリパラメータに対してのみ有効です。別の条件で再利用すると 400 bad_request を返します。

キーセット方式のページネーションを使用します。next_cursor は次のページがある場合にのみ含まれます。最終ページではキー自体が存在せず、null になることはありません。

export BLOCKVECTRA_API_KEY=rgw_your_api_key

curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances?limit=50" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

レスポンスのエンベロープは AddressBalanceListEnvelope で、data と meta を含みます。data の各項目は AddressBalance です。

フィールド型説明
tokenstring(アドレス)トークンのコントラクトアドレス。正規形は 0x に小文字の 16 進数 40 桁を続けた形式です。
balancestring(10 進数)生の整数残高。2^53 を超える場合があり、通常の 10 進数文字列として返されます。JSON 数値、指数表記、16 進数では返されません。
symbolstring または nullトークンのシンボル。取得できない場合は null です。
decimalsinteger または nullトークンの小数桁数。0–255 の値で、取得できない場合は null です。

リクエスト 2:アドレスの送金

送金エンドポイントでは、ブロック範囲を明示する必要があります。from_block と to_block はどちらも必須で、from_block <= to_block を満たす必要があります。次のパラメータも使用します。

  • standard(クエリパラメータ、必須):erc20 または erc721。アドレス単位のクエリは erc1155 に対応していません。指定すると 422 no_coverage を返します。
  • direction(クエリパラメータ、任意):in、out、any のいずれか。デフォルトは any で、アドレスから見た送金方向でフィルタリングします。
  • token(クエリパラメータ、任意):結果を 1 つのトークンコントラクトに限定します。
  • clamp(クエリパラメータ、任意):文字列 true をそのまま指定した場合にのみ有効になります。それ以外の値は false として扱われます。

範囲の境界とファイナリティ:明示した to_block が as_of_block を超えると 409 not_indexed_yet を返します。ただし、clamp=true の場合は as_of_block まで切り詰められます。範囲がチェーンの上限(GET /chains の limits.max_window_blocks)を超えると 409 window_too_large を返します。ただし、clamp=true の場合は古い側から切り詰められます(from_block を引き上げ、to_block は固定します)。from_block 自体がすでに as_of_block を超えている場合は、clamp=true でも必ず 409 を返します。範囲が切り詰められた場合や一部のみが対応範囲内にある場合、レスポンスの meta.coverage は "partial" になります。それ以外の場合は "full" です。

送金レコードでは、ERC-20 の項目には amount、ERC-721 の項目には token_id が追加されます。どちらも token、standard、from、to、block_number、block_timestamp、tx_hash、tx_index、log_index を含みます。

export BLOCKVECTRA_API_KEY=rgw_your_api_key

# 1) Read as_of_block from the balances response.
AS_OF_BLOCK=$(curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" | grep -o '"as_of_block":[0-9]*' | cut -d: -f2)

# 2) clamp=true truncates a too-wide window, or a to_block above as_of_block, instead of returning 409.
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/transfers?standard=erc20&from_block=0&to_block=$AS_OF_BLOCK&direction=any&clamp=true" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

ページネーションで全送金を取得する

アドレス送金エンドポイントの next_cursor は、次のページの存在を確約しません。返された行数がちょうど limit の場合にのみ含まれるため、next_cursor があるページでも、実際には最終ページである可能性があります。ページが空でも処理を止めず、キーがなくなるまで next_cursor をたどってください。

次のコードは、指定範囲内のすべての送金を取得します。

const address = "0x1111111111111111111111111111111111111111";
const head = await fetch(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());
const asOfBlock = head.meta.as_of_block;
const transfers: unknown[] = [];
let cursor: string | undefined;

do {
  const url = new URL(
    `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/transfers`,
  );
  url.searchParams.set("standard", "erc20");
  url.searchParams.set("from_block", "0");
  url.searchParams.set("to_block", String(asOfBlock));
  url.searchParams.set("limit", "500");
  // clamp truncates from the older end
  url.searchParams.set("clamp", "true");
  if (cursor) url.searchParams.set("cursor", cursor);

  const res = await fetch(url, {
    headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
  });
  const page = await res.json();
  transfers.push(...page.data);
  cursor = page.next_cursor; // absent on the last page
} while (cursor);

リクエスト 3:トークンのメタデータと tokens:batch

GET /{chain}/tokens/{token} で 1 つのトークンを取得します。パスには {chain} と {token} のみを指定し、ページネーションはありません。レスポンスのエンベロープは TokenEnvelope で、data は Token です。

フィールド型説明
addressstring(アドレス)トークンのコントラクトアドレス。
standardstringerc20、erc721、unknown のいずれか。
namestring または nullトークン名。取得できない場合は null です。
symbolstring または nullトークンのシンボル。取得できない場合は null です。
decimalsinteger または nullトークンの小数桁数。0–255 の値で、取得できない場合は null です。
total_supplystring または null生の総供給量。API は decimals に応じた換算を行いません。取得できない場合は null です。
first_seen_blockinteger (int64)トークンが最初に検出されたブロック高。
metadata_updated_atstring(タイムスタンプ)メタデータが最後に更新された UTC 時刻。
metadata_blockinteger (int64)メタデータを読み取ったブロック高。
metadata_statusstringok、partial、unavailable のいずれか。
metadata_issuesobjectname、symbol、decimals、total_supply をキーとする、フィールドごとの問題の記録。値は reverted、no_data、invalid_encoding、temporarily_unavailable のいずれかです。

{token} が有効な 20 バイトのアドレスでない場合は 400 bad_request、未知の {token} は 404 not_found、未知の {chain} は 404 unknown_chain を返します。

残高エンドポイントには、取得できる場合はすでに symbol と decimals が含まれていますが、どちらも null になる可能性があります。ウォレット内のすべてのトークンの名前と小数桁数を補完するには、POST /{chain}/tokens:batch を使用します。

  • リクエストボディは {"addresses": [...]} で、1 回のリクエストにつき最大 100 アドレスを指定できます。100 件を超える場合や、有効な 20 バイトのアドレスでない項目がある場合は、400 bad_request を返します(順番に検証し、最初に見つかった無効なアドレスで失敗します)。
  • 見つからないアドレスはエラーにならず、data.missing に列挙されます。data.tokens には、メタデータが見つかったトークンのみが含まれます。
  • 重複するアドレスは tokens と missing の両方で重複排除され、それぞれリクエスト内で最初に出現した順序で並びます。
export BLOCKVECTRA_API_KEY=rgw_your_api_key

# Single token
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

# Batch: up to 100 addresses per request
curl -s -X POST "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"addresses":["0x1111111111111111111111111111111111111111","0x2222222222222222222222222222222222222222"]}'

decimals に応じて数量を換算する

残高フィールド balance と ERC-20 送金フィールド amount は、10 進数文字列(UInt256String)で表された生の整数です。トークンの total_supply も、decimals に応じた換算が適用されていない、生のオンチェーン整数です。人が読みやすい数量を表示するには、そのトークンの decimals に応じて割り算を行います。

  • decimals は、残高項目自体の symbol/decimals、または GET /{chain}/tokens/{token} と POST /{chain}/tokens:batch から取得します。null の場合もあります。
  • これらの値は 2^53 を超える可能性があるため、JSON 数値で計算しないでください。TypeScript では BigInt、Python では Decimal を使い、精度の損失を防ぐために 10 進数文字列をそのまま解析します。
function toDisplayAmount(raw: string, decimals: number | null): string {
  if (decimals === null) return raw; // no decimals metadata: keep the raw integer
  const value = BigInt(raw);
  const base = 10n ** BigInt(decimals);
  const whole = value / base;
  const fraction = (value % base)
    .toString()
    .padStart(decimals, "0")
    .replace(/0+$/, "");
  return fraction ? `${whole}.${fraction}` : whole.toString();
}

// balance.balance is a raw decimal string; decimals comes from the same item or tokens:batch.
const display = toDisplayAmount(balance.balance, balance.decimals);

データの鮮度

チェーン単位の成功レスポンスには、すべて meta が含まれます。

  • as_of_block:チェーンの、書き込みが完全に完了した最新ブロックです。ブロック単位のエンドポイントは、この高さまでのデータを返します。
  • safe_block:ノードのコンセンサス上の safe ブロックタグを示す指標です(不明な間は null)。finalized_block を下回ることはなく、レスポンスを切り詰めたり、拒否したり、遅延させたりすることもありません。
  • finalized_block:ノードのコンセンサス上の finalized ブロックタグを示す指標です(不明な間は null)。レスポンスを切り詰めたり、拒否したり、遅延させたりすることはありません。この指標に基づいてどの程度の安全性が必要か(確認状況など)をクライアント側で判断します。
  • coverage:"full" または "partial"。アドレス送金などのエンドポイントは、clamp により返す範囲が狭められた場合や、範囲の開始がチェーンの最初のインデックス済みブロックより前の場合に "partial" を返します。
  • refreshed_at:レスポンスの元となるデータが最後に更新された時刻(UTC)です。null の場合もあります。null はデータの更新時刻が不明であることを意味し、古いデータとして扱う必要があります。ブロック単位のエンドポイントは常に値を返します。
  • chain、chain_slug、chain_external_id も繰り返し含まれます。

よく使われる方法は、最初のレスポンスから meta.as_of_block を読み取って最新のインデックス済みブロックまで取得し、確認済みの状態を表示したい場合は meta.safe_block / meta.finalized_block を確認することです。

ページ読み込み 1 回あたりの CU 見積もり

各メソッドは、プラットフォームのプラン API から取得した CU 重み付けに基づいて課金されます。

呼び出しあたりの CU 重み付け

メソッド呼び出しあたりの CU
data.address_balances25
data.address_transfers25
data.tokens_batch10

ページを一回読み込む場合(概算)

残高リクエスト 1 回 + 転送履歴 3 ページ + tokens:batch リクエスト 1 回、合計 5 回の呼び出しで約 110 CU です。実際の利用量はページ数とトークン数によって変わります。

課金の判断基準と課金対象外のエラーレスポンスについては、課金ルールを参照してください。必要なものがインデックス済みの送金履歴ではなく最新ブロックのログである場合は、eth_getLogs に切り替えるか判断する前に、最新のノードデータとインデックス済み履歴の比較を読んでください。

次のステップ

最終更新:

このページの目次