Чтение из контракта: ABI и view-функции

Читаем данные из контракта: что такое ABI, как фронт «знает» функции контракта и как вызвать view-функцию.

ABI (Application Binary Interface) — JSON-описание функций и событий контракта: их имена, аргументы и типы. По нему ethers.js понимает, как кодировать вызовы и декодировать ответы.

Блокчейн хранит контракт как байткод — нечитаемые байты. Чтобы вызвать balanceOf, фронту нужно знать сигнатуру функции. Эту «карту» даёт ABI. Без ABI контракт для фронта — чёрный ящик.

Как выглядит ABI

ABI — это массив объектов, по одному на функцию/событие. Часто его берут целиком (из артефакта компиляции), но для фронта достаточно тех функций, что вы реально зовёте. Минимальный ABI для чтения баланса ERC-20:

[
  {
    "name": "balanceOf",
    "type": "function",
    "stateMutability": "view",
    "inputs": [{ "name": "owner", "type": "address" }],
    "outputs": [{ "name": "", "type": "uint256" }]
  },
  {
    "name": "decimals",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [{ "name": "", "type": "uint8" }]
  }
]

Есть и компактный «человекочитаемый» формат ABI, который ethers тоже понимает — удобно для пары функций:

const abi = [
  "function balanceOf(address owner) view returns (uint256)",
  "function decimals() view returns (uint8)",
];

Вызов view-функции

view- и pure-функции не меняют состояние, поэтому читаются бесплатно и без подписи — достаточно Provider:

import { Contract, BrowserProvider } from "ethers";

const provider = new BrowserProvider(window.ethereum);
const token = new Contract(tokenAddress, abi, provider);

const raw = await token.balanceOf(userAddress); // bigint
const dec = await token.decimals();             // напр. 18
console.log("сырой баланс:", raw.toString());

Обратите внимание: balanceOf возвращает сырое число в минимальных единицах токена (как копейки). Превратить его в человекочитаемое — отдельная задача (урок про числа).

Как работает под капотом

Вызов token.balanceOf(addr) ethers превращает в eth_call: берёт из ABI тип аргумента (address), кодирует адрес в 32 байта, добавляет 4-байтовый селектор функции и шлёт ноде. Нода исполняет функцию на EVM «как бы», ничего не записывая, и возвращает 32-байтовый результат. ethers по типу uint256 из ABI декодирует его в bigint. Поэтому правильный ABI критичен: ошибётесь в типе — получите мусор или ошибку декодирования.

Частые ошибки

  • Неполный или неверный ABI. Если функции нет в ABI или у неё неверный тип — вызов упадёт.
  • Показывать сырой баланс пользователю. balanceOf отдаёт минимальные единицы; делите на 10^decimals.
  • Звать view-функцию на контракте без подключённого провайдера. Нужен рабочий Provider (или Signer).

Итоги

  • ABI — карта функций контракта; без неё фронт не знает, как его звать.
  • view/pure читаются бесплатно через Provider, без подписи.
  • Возвращаются сырые числа в минимальных единицах — их надо форматировать.
Проверьте себя
1. Зачем фронту нужен ABI контракта?
AЧтобы хранить приватный ключ
BЧтобы знать имена, аргументы и типы функций для кодирования вызовов
CЧтобы оплатить газ
DЧтобы выбрать сеть
2. Что возвращает balanceOf у ERC-20?
AБаланс в долларах
BСырое число в минимальных единицах токена
CУже человекочитаемое число
DАдрес владельца
3. Что нужно для вызова view-функции?
ASigner и газ
BТолько Provider, без подписи и газа
CПриватный ключ в коде
DПодпись сообщения