지갑 토큰 잔액 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 레퍼런스에 있습니다.
이 가이드를 통해 완료할 수 있는 작업
- 키를 사용하여 지갑 토큰 잔액 조회 및 0이 아닌 ERC-20 보유 자산 페이지네이션.
- 고정된 블록 윈도우 내에서 지갑 전송 내역 조회 및 선택한 주소에 대한 커서 추적.
- 토큰 메타데이터 보완을 통해 누락된 필드를 보존하면서 원시 정수 잔액과 함께 이름과 심볼 표시.
지갑 자산 페이지에 필요한 세 가지 데이터
지갑 자산 페이지는 주소의 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입니다:
| 필드 | 타입 | 설명 |
|---|---|---|
token | string (address) | 토큰 컨트랙트 주소입니다. 정규 형식은 0x와 40자리의 소문자 16진수 문자입니다. |
balance | string (decimal) | 2^53을 초과할 수 있는 원시 정수 잔액으로, JSON 숫자가 아닌 일반 10진수 문자열로 반환되며 지수 표기법이나 16진수 표기법을 사용하지 않습니다. |
symbol | string 또는 null | 토큰 심볼이며, 제공되지 않는 경우 null입니다. |
decimals | integer 또는 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입니다:
| 필드 | 타입 | 설명 |
|---|---|---|
address | string (address) | 토큰 컨트랙트 주소입니다. |
standard | string | erc20, erc721, 또는 unknown. |
name | string 또는 null | 토큰 이름이며, 제공되지 않는 경우 null입니다. |
symbol | string 또는 null | 토큰 심볼이며, 제공되지 않는 경우 null입니다. |
decimals | integer 또는 null | 토큰 소수점 자릿수로 0~255 사이이며, 제공되지 않는 경우 null입니다. |
total_supply | string 또는 null | 원시 총 공급량입니다. API는 decimals 배율을 적용하지 않습니다. 제공되지 않는 경우 null입니다. |
first_seen_block | integer (int64) | 토큰이 처음 발견된 블록 높이입니다. |
metadata_updated_at | string (timestamp) | 메타데이터가 마지막으로 업데이트된 UTC 시간입니다. |
metadata_block | integer (int64) | 메타데이터를 읽어온 블록 높이입니다. |
metadata_status | string | ok, partial, 또는 unavailable. |
metadata_issues | object | name, 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_balances | 25 |
data.address_transfers | 25 |
data.tokens_batch | 10 |
1회 페이지 로드(예상)
잔액 요청 1회 + 전송 3페이지 + tokens:batch 요청 1회, 총 5회 호출로 약 110 CU입니다. 실제 사용량은 페이지 수와 토큰 수에 따라 달라집니다.
과금 결정 및 비과금 오류 응답에 대해서는 과금 규칙을 참조하세요. 인덱싱된 전송 내역이 아닌 가장 최근 블록의 로그가 필요한 경우, eth_getLogs로 전환할지 결정하기 전에 먼저 최신 노드 데이터 vs 인덱싱된 이력을 확인하세요.
다음 단계
- 데이터셋 디렉터리 살펴보기: BlockVectra가 인덱싱하는 모든 데이터셋을 확인하세요.
- 무료 플랜 및 요금 확인: 계정에 포함된 혜택을 확인하세요.
- 콘솔에 로그인: API key를 생성하세요.
최종 수정일: