Чтение из контракта: 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, без подписи.
- Возвращаются сырые числа в минимальных единицах — их надо форматировать.