API балансів токенів гаманця: активи ERC-20 та історія переказів

Створіть сторінку активів гаманця з ненульовими балансами токенів ERC-20, історією переказів токенів та пакетними метаданими. Перевіряйте покриття мереж, пагінуйте результати та масштабуйте цілочисельні суми за кількістю десяткових знаків.

Створіть сторінку активів гаманця за допомогою API блокчейн-даних гаманця: використовуйте API балансів токенів для ненульових активів ERC-20 та API переказів токенів для історії гаманця. Розробники та AI-агенти використовують ті самі автентифіковані запити. Перед виконанням запитів виконайте 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} зчитує назву, символ, кількість десяткових знаків та загальну пропозицію одного токена за адресою контракту; 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} (параметр шляху, обов'язковий): ідентифікатор мережі, значення chain запису в GET /chains (наприклад, 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 плюс 40 малих шістнадцяткових цифр.
balancestring (десятковий)Необроблений цілочисельний баланс, який може перевищувати 2^53, повертається як звичайний десятковий рядок — ніколи не як число JSON, наукова чи шістнадцяткова нотація.
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.

Межі вікна та фінальність: явний to_block, вищий за as_of_block, повертає 409 not_indexed_yet, якщо тільки clamp=true не скоротить його до as_of_block; вікно, ширше за ліміт мережі (limits.max_window_blocks із GET /chains), повертає 409 window_too_large, якщо тільки clamp=true не скоротить його зі старішого кінця (збільшуючи from_block і залишаючи to_block фіксованим). Якщо сам from_block уже перевищує as_of_block, запит залишається суворою помилкою 409 навіть із clamp=true. Коли вікно обрізано або частково покрите, поле 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) Зчитайте as_of_block із відповіді balances.
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 скорочує занадто широке вікно або to_block вище as_of_block замість повернення 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 обрізає зі старішого кінця
  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; // відсутнє на останній сторінці
} while (cursor);

Запит 3: метадані токенів та tokens:batch

Зчитайте один токен за допомогою GET /{chain}/tokens/{token}; шлях приймає лише {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_issuesobjectЗаписи проблем для кожного поля з ключами name, 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": [...]} максимум зі 100 адресами на запит; понад 100 елементів або елемент, який не є дійсною 20-байтною адресою, повертає 400 bad_request (помилка виникає на першій недійсній адресі, до якої дійде обробка).
  • Адреси, які не знайдено, не викликають помилки; вони перелічені в data.missing, тоді як data.tokens містить лише ті токени, метадані яких було знайдено.
  • Повторювані адреси дедуплікуються як у tokens, так і в missing, кожна в порядку першої появи в запиті.
export BLOCKVECTRA_API_KEY=rgw_your_api_key

# Один токен
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

# Пакетний запит: до 100 адрес на запит
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 є необробленими цілими числами, представленими як десяткові рядки (UInt256String); загальна пропозиція токена total_supply також є необробленим ончейн-цілим числом без застосування масштабування decimals. Щоб показати зручну для читання кількість, розділіть її на 10^decimals відповідного токена.

  • decimals береться з власних полів symbol/decimals елемента балансу або з GET /{chain}/tokens/{token} і POST /{chain}/tokens:batch; воно може дорівнювати null.
  • Ці значення можуть перевищувати 2^53, тому не виконуйте арифметичні дії зі стандартними числами JSON: використовуйте BigInt у TypeScript та Decimal у Python, парсячи десятковий рядок як є, щоб уникнути втрати точності.
function toDisplayAmount(raw: string, decimals: number | null): string {
  if (decimals === null) return raw; // метадані decimals відсутні: зберігаємо необроблене ціле число
  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 є необробленим десятковим рядком; decimals надходить із того самого елемента або 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". Перекази адрес та подібні ендпоінти повідомляють про "partial", коли clamp звузив обслуговуване вікно або коли вікно починається до першого проіндексованого блоку мережі.
  • 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_balances25
data.address_transfers25
data.tokens_batch10

Одне завантаження сторінки (оцінка)

1 запит balances + 3 сторінок transfer + 1 запит(ів) tokens:batch, загалом 5 викликів, близько 110 CU. Фактичне використання залежить від кількості сторінок і токенів.

Щодо рішень про білінг та незатарифікованих відповідей із помилками звертайтеся до правил білінгу. Якщо вам потрібна не індексована історія переказів, а логи з найновіших блоків, спершу прочитайте статтю Нещодавні дані вузла проти індексованої історії, перш ніж вирішувати, чи переходити на eth_getLogs.

Наступні кроки

Востаннє оновлено:

На цій сторінці