지갑 토큰 잔액 API: ERC-20 자산 및 전송 내역

0이 아닌 ERC-20 토큰 잔액, 토큰 전송 내역 및 배치 메타데이터로 지갑 자산 페이지를 구축하세요. 체인 지원 범위를 확인하고, 결과를 페이지네이션하며, 소수점 자릿수(decimals)로 정수 금액을 변환합니다.

블록체인 지갑 데이터 API로 지갑 자산 페이지를 구축하세요: 0이 아닌 ERC-20 보유 자산은 Token Balances API를 사용하고, 지갑 이력은 Token Transfers API를 사용합니다. 개발자와 AI 에이전트는 동일한 인증 요청을 사용합니다. 쿼리하기 전에 GET /v1/status를 확인하고 선택한 체인의 data_features와 data_status를 확인하세요. 잔액 지원 범위는 체인마다 다릅니다. 요청 파라미터와 응답 스키마는 Data API 레퍼런스에 있습니다.

이 가이드를 통해 완료할 수 있는 작업

지갑 자산 페이지에 필요한 세 가지 데이터

지갑 자산 페이지는 주소의 ERC-20 토큰 잔액, 토큰 전송 내역 및 토큰 메타데이터를 표시할 수 있습니다. Data API는 각각에 대한 엔드포인트를 제공합니다:

  • 잔액: GET /{chain}/addresses/{address}/balances는 주소의 0이 아닌 ERC-20 잔액을 token 주소 오름차순으로 반환하며, 가능한 경우 토큰 symbol과 decimals를 포함합니다. 잔액이 없는 주소는 data: []와 함께 200을 반환합니다.
  • 전송 내역: GET /{chain}/addresses/{address}/transfers는 필수 블록 윈도우 내에서 해당 주소가 관련된 토큰 전송을 (block_number, log_index) 내림차순으로 반환합니다.
  • 토큰 메타데이터: GET /{chain}/tokens/{token}은 컨트랙트 주소별로 단일 토큰의 이름, 심볼, 소수점 자릿수(decimals) 및 총 공급량을 읽습니다. POST /{chain}/tokens:batch는 한 번의 요청으로 최대 100개 주소에 대해 동일한 메타데이터를 읽습니다.

세 가지 엔드포인트 모두 https://api.blockvectra.com/v1/data을 기본 URL로 사용하고 x-api-key 요청 헤더를 사용하며, 예제 체인으로는 robinhood_mainnet을 사용합니다. 이들은 각각 balances, transfers, token_metadata 기능에 속합니다. 각 기능을 제공하는 체인은 지원 체인 페이지를 참조하세요. 해당 기능이 없는 체인에서 엔드포인트를 호출하면 422 no_coverage를 반환합니다.

요청 1: 주소 잔액

이 엔드포인트는 필요한 파라미터가 적기 때문에 페이지의 첫 번째 요청으로 적합합니다:

  • {chain} (경로 파라미터, 필수): 체인 식별자, GET /chains 항목의 chain 값 (예: robinhood_mainnet). 대소문자를 구분하여 정확히 일치해야 하며, 별칭이나 숫자 체인 ID는 허용되지 않습니다.
  • {address} (경로 파라미터, 필수): 20바이트 주소. 0x 접두사는 선택 사항이며 대소문자 모두 허용됩니다.
  • limit (쿼리 파라미터, 선택): 페이지 크기입니다. 기본값은 50이며, 500을 초과하는 값은 500으로 제한됩니다. 0 또는 정수가 아닌 값은 400 bad_request를 반환합니다.
  • cursor (쿼리 파라미터, 선택): 이전 응답의 next_cursor 값으로, 다음 페이지를 가져오기 위해 변경 없이 전달합니다. 커서는 이를 발급한 체인, 엔드포인트 및 쿼리 파라미터에 대해서만 유효하며, 다른 곳에서 재사용하면 400 bad_request를 반환합니다.

키셋(keyset) 기반으로 페이지네이션됩니다: 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 (address)토큰 컨트랙트 주소입니다. 정규 형식은 0x와 40자리의 소문자 16진수 문자입니다.
balancestring (decimal)2^53을 초과할 수 있는 원시 정수 잔액으로, JSON 숫자가 아닌 일반 10진수 문자열로 반환되며 지수 표기법이나 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 (쿼리 파라미터, 선택): 결과를 특정 토큰 컨트랙트로 제한합니다.
  • clamp (쿼리 파라미터, 선택): 리터럴 문자열 true만 활성화하며, 다른 모든 값은 false로 처리됩니다.

윈도우 경계 및 최종 확정성: as_of_block보다 큰 명시적 to_block은 clamp=true로 as_of_block까지 잘라내지 않는 한 409 not_indexed_yet을 반환합니다. 체인의 한도(GET /chains의 limits.max_window_blocks)보다 넓은 윈도우는 clamp=true로 이전 블록부터 잘라내지 않는 한(from_block을 높이고 to_block을 고정) 409 window_too_large를 반환합니다. 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}으로 읽습니다. 이 경로는 페이지네이션 없이 {chain}과 {token}만 받습니다. 응답 외층 구조는 TokenEnvelope이며, data는 Token입니다:

필드타입설명
addressstring (address)토큰 컨트랙트 주소입니다.
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 (timestamp)메타데이터가 마지막으로 업데이트된 UTC 시간입니다.
metadata_blockinteger (int64)메타데이터를 읽어온 블록 높이입니다.
metadata_statusstringok, partial, 또는 unavailable.
metadata_issuesobjectname, symbol, decimals, total_supply를 키로 하고 reverted, no_data, invalid_encoding, 또는 temporarily_unavailable을 값으로 갖는 필드별 이슈 레코드입니다.

유효한 20바이트 주소가 아닌 {token}은 400 bad_request를 반환합니다. 알 수 없는 {token}은 404 not_found를 반환하고, 알 수 없는 {chain}은 404 unknown_chain을 반환합니다.

잔액 엔드포인트는 가능한 경우 이미 symbol과 decimals를 포함하지만 둘 다 null일 수 있습니다. 지갑에 있는 모든 토큰의 이름과 소수점 자릿수를 채우려면 POST /{chain}/tokens:batch를 사용하세요:

  • 요청 본문은 요청당 최대 100개의 주소를 갖는 {"addresses": [...]}입니다. 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"]}'

소수점 자릿수에 따른 금액 변환

잔액 필드 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회 페이지 로드(예상)

잔액 요청 1회 + 전송 3페이지 + tokens:batch 요청 1회, 총 5회 호출로 약 110 CU입니다. 실제 사용량은 페이지 수와 토큰 수에 따라 달라집니다.

과금 결정 및 비과금 오류 응답에 대해서는 과금 규칙을 참조하세요. 인덱싱된 전송 내역이 아닌 가장 최근 블록의 로그가 필요한 경우, eth_getLogs로 전환할지 결정하기 전에 먼저 최신 노드 데이터 vs 인덱싱된 이력을 확인하세요.

다음 단계

최종 수정일:

이 페이지의 내용