錢包代幣餘額 API:ERC-20 資產與轉帳歷史
用非零 ERC-20 代幣餘額、代幣轉帳歷史與批次中繼資料建置錢包資產頁。檢查鏈涵蓋範圍、分頁結果,並依 decimals 換算整數金額。
用區塊鏈錢包資料 API 建置錢包資產頁:使用代幣餘額 API 取得非零 ERC-20 持倉,使用代幣轉帳 API 取得錢包歷史。開發者與 AI Agent 使用同一套認證請求。查詢前請讀取 GET /v1/status,並檢查所選鏈的 data_features 與 data_status;餘額涵蓋範圍因鏈而異。請求參數與回應結構定義見 Data API 參考。
本指南協助你完成的任務
錢包資產頁需要的三種資料
錢包資產頁可以顯示地址的 ERC-20 代幣餘額、代幣轉帳歷史與代幣中繼資料。Data API 為每個項目提供一個端點:
- 餘額:
GET /{chain}/addresses/{address}/balances回傳地址的非零 ERC-20 餘額,按token地址遞增排序,並在可用時包含代幣symbol與decimals。沒有餘額的地址回傳200與data: []。 - 轉帳:
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)。比對為精確且區分大小寫;不接受別名與數字 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:
| 欄位 | 型別 | 說明 |
|---|---|---|
token | string (address) | 代幣合約地址;標準形式為 0x 加上 40 個小寫十六進位字元。 |
balance | string (decimal) | 原始整數餘額,可能超過 2^53,以純十進位字串回傳——絕不是 JSON 數字、科學記號或十六進位。 |
symbol | string or null | 代幣符號,或在無法取得時為 null。 |
decimals | integer or null | 代幣 decimals,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。
視窗邊界與最終性:明確指定的 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} 讀取單一代幣;路徑只接受 {chain} 與 {token},沒有分頁。回應結構是 TokenEnvelope,data 是 Token:
| 欄位 | 型別 | 說明 |
|---|---|---|
address | string (address) | 代幣合約地址。 |
standard | string | erc20、erc721 或 unknown。 |
name | string or null | 代幣名稱,或在無法取得時為 null。 |
symbol | string or null | 代幣符號,或在無法取得時為 null。 |
decimals | integer or null | 代幣 decimals,0–255,或在無法取得時為 null。 |
total_supply | string or 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。若要補齊錢包中每個代幣的名稱與 decimals,請使用 POST /{chain}/tokens:batch:
- 請求主體為
{"addresses": [...]},每個請求最多 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 是以十進位字串呈現的原始整數(UInt256String);代幣的 total_supply 也是未套用 decimals 換算的原始鏈上整數。若要顯示人類可讀的數量,請除以該代幣的 decimals。
decimals來自餘額項目本身的symbol/decimals,或來自GET /{chain}/tokens/{token}與POST /{chain}/tokens:batch;它可以是null。- 這些值可能超過
2^53,因此不要用 JSON 數字進行運算:在 TypeScript 中使用BigInt,在 Python 中使用Decimal,並原樣解析十進位字串以避免精度損失。
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。
單次頁面載入的 CU 估算
每個方法都依其 CU 權重計費,權重從平台方案 API 讀取:
單次呼叫的 CU 權重
| 方法 | 單次呼叫 CU |
|---|---|
data.address_balances | 25 |
data.address_transfers | 25 |
data.tokens_batch | 10 |
一次頁面載入(估算)
1 次餘額請求 + 3 頁轉帳請求 + 1 次 tokens:batch 請求,共 5 次呼叫,合計約 110 CU。實際用量取決於翻頁次數與代幣數量。
計費決策與不計費的錯誤回應,請參閱計費規則。如果你需要的不是已索引的轉帳歷史,而是最近區塊的日誌,請先閱讀最新節點資料與已索引歷史,再決定是否改用 eth_getLogs。
下一步
最後更新: