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} (параметр пути, обязательный): идентификатор сети, значение chain элемента в GET /chains (например, robinhood_mainnet). Сопоставление точное и чувствительное к регистру; псевдонимы и числовые Chain 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 шестнадцатеричных символов в нижнем регистре.
balancestring (decimal)Исходный целочисленный баланс, который может превышать 2^53, возвращаемый в виде обычной десятичной строки — никогда не число JSON, экспоненциальная запись или hex.
symbolstring or nullСимвол токена или null, если он недоступен.
decimalsinteger or 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 из ответа балансов.
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 (address)Адрес контракта токена.
standardstringerc20, erc721 или unknown.
namestring or nullНазвание токена или null, если оно недоступно.
symbolstring or nullСимвол токена или null, если он недоступен.
decimalsinteger or nullКоличество десятичных знаков токена, от 0 до 255, или null, если оно недоступно.
total_supplystring or nullИсходное общее предложение; API не применяет масштабирование по decimals. null, если недоступно.
first_seen_blockinteger (int64)Высота блока, в котором токен был впервые обнаружен.
metadata_updated_atstring (timestamp)Время 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. Чтобы заполнить название и 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: используйте BigInt в TypeScript и Decimal в Python, выполняя парсинг десятичной строки как есть во избежание потери точности.
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". Эндпоинты переводов адреса и аналогичные сообщают "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.

Следующие шаги

Последнее обновление:

На этой странице