Вызов view-функций — read
Как читать данные из смарт-контракта через ethers v6: подключить ABI, вызвать view-функции и разобрать результат без транзакции.
Что такое read-вызов
Read-вызов читает состояние контракта и не меняет State Ethereum.
Для view и pure функций не нужен private key, signer или ETH на балансе. Достаточно RPC-провайдера, адреса контракта и ABI.
Читаем данные ERC-20 контракта
Создайте проект:
mkdir call-view-functions-example
cd call-view-functions-example
npm init -y
npm pkg set type="module"
npm install ethers tsx typescript @types/node
mkdir scriptsСоздайте файл scripts/read-usdc.ts:
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");
const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
const erc20Abi = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address account) view returns (uint256)",
];
const usdc = new ethers.Contract(usdcAddress, erc20Abi, provider);
const [name, symbol, decimals, totalSupply, balance] = await Promise.all([
usdc.name(),
usdc.symbol(),
usdc.decimals(),
usdc.totalSupply(),
usdc.balanceOf(vitalikAddress),
]);
console.log("Token:", name);
console.log("Symbol:", symbol);
console.log("Decimals:", decimals.toString());
console.log("Total supply:", ethers.formatUnits(totalSupply, decimals));
console.log("Vitalik balance:", ethers.formatUnits(balance, decimals), symbol);Запустите скрипт:
npx tsx scripts/read-usdc.tsКод прочитает состояние реального USDC-контракта в Ethereum mainnet. Транзакция не отправляется, gas не списывается.
Как это работает
new ethers.JsonRpcProvider() — подключается к RPC-ноде Ethereum.
erc20Abi — минимальный ABI. В нём только функции, которые мы вызываем.
new ethers.Contract(usdcAddress, erc20Abi, provider) — создаёт read-only объект контракта.
usdc.name() — вызывает view функцию name() через RPC.
usdc.balanceOf(vitalikAddress) — кодирует аргумент address, отправляет eth_call и возвращает uint256.
ethers.formatUnits(balance, decimals) — переводит raw значение токена в читаемый формат с учётом decimals.
Promise.all([...]) — запускает независимые read-вызовы параллельно.
Что происходит под капотом
Когда вы вызываете usdc.balanceOf(...), ethers не отправляет транзакцию.
Он собирает RPC-запрос eth_call:
to: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
data: 0x70a08231...Нода выполняет функцию локально на текущем State и возвращает результат. Блокчейн от этого не меняется.
Read vs write
Один и тот же контракт можно подключить по-разному:
const readOnlyUsdc = new ethers.Contract(usdcAddress, erc20Abi, provider);
const writableUsdc = new ethers.Contract(usdcAddress, erc20Abi, signer);readOnlyUsdc.balanceOf(user) — бесплатный read-вызов через eth_call.
writableUsdc.transfer(to, amount) — транзакция, которая требует подпись и gas.
Частые ошибки
Искать gas fee у view-функции → обычный read-вызов не попадает в блок и не тратит gas. Gas нужен только для транзакций.
Подключать signer для чтения → для view функций достаточно provider. Signer нужен, когда функция меняет State.
Передавать неправильный ABI → адрес может быть верным, но ethers не сможет закодировать вызов, если ABI не содержит нужную функцию.
Забывать про decimals у токенов → balanceOf() возвращает raw uint256, а не человекочитаемое число.
Что дальше
- Что такое ABI — понять, как ethers кодирует вызовы функций
- Провайдер vs Сайнер — разделить чтение сети и подпись транзакций
- Деплой контракта — создать свой контракт и прочитать его состояние