Стандарт ERC-20: что такое токен
ERC-20 — это набор из шести функций и двух событий, согласившись на который любой токен становится понятен всем кошелькам и биржам.
Токен — это не «монета внутри сети». Это просто число в маппинге одного контракта. Магия в том, что все контракты договорились о едином интерфейсе.
ERC-20 — стандарт взаимозаменяемых (fungible) токенов: каждая единица равна любой другой, как рубли. Стандарт описывает интерфейс: totalSupply(), balanceOf(address), transfer(to, amount), approve(spender, amount), allowance(owner, spender), transferFrom(from, to, amount), плюс события Transfer и Approval.
Балансы хранятся в маппинге внутри контракта токена. «Перевести токены» — значит уменьшить число у одного адреса и увеличить у другого. Никакого отдельного «кошелька с монетами» нет.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
ERC-20: балансы — это просто маппинг
===================================
contract Token {
balanceOf: alice -> 100
bob -> 30
carol -> 0
}
transfer(bob, 40) от alice:
alice: 100 -> 60 bob: 30 -> 70
(никаких «монет», только числа в одном контракте)
Про decimals
У большинства токенов decimals = 18. Это значит, что «1 токен» в интерфейсе — это 1 * 10^18 базовых единиц в контракте. Solidity не знает дробей, поэтому все суммы хранят в наименьших единицах, а decimals — лишь подсказка кошелькам, где рисовать запятую.
Как работает под капотом (EVM/газ)
Совместимость держится на ABI: кошелёк знает селекторы функций ERC-20 и формирует вызовы по ним. Поскольку интерфейс одинаков у всех токенов, MetaMask, Uniswap и эксплореры работают с любым ERC-20 без доработок. Событие Transfer с indexed from/to позволяет кошелькам строить историю переводов, не читая весь storage. decimals — обычная view-функция, она ничего не меняет, лишь сообщает масштаб.
# Та же логика на Python: ядро ERC-20 — баланс как dict
DECIMALS = 18
ONE = 10 ** DECIMALS # 1 «токен» в базовых единицах
balance_of = {"alice": 100 * ONE, "bob": 30 * ONE}
total_supply = sum(balance_of.values())
def transfer(frm, to, amount):
assert balance_of.get(frm, 0) >= amount, "REVERT: low balance"
balance_of[frm] -= amount
balance_of[to] = balance_of.get(to, 0) + amount
print(f"Transfer {frm}->{to}: {amount // ONE} токенов")
transfer("alice", "bob", 40 * ONE)
print("alice:", balance_of["alice"] // ONE, "| bob:", balance_of["bob"] // ONE)
print("total supply неизменен:", total_supply == sum(balance_of.values()))
«Та же логика на Python ▶». Перевод не создаёт и не уничтожает токены — общий сапплай сохраняется, меняются лишь числа в «маппинге».
Частые ошибки
- Думать, что «1 токен» равно числу 1 — почти всегда это
10^18единиц из-заdecimals. - Считать, что у токена есть собственный баланс ETH — нет, это отдельная сущность.
- Отправлять токены на адрес контракта, не поддерживающего их — токены «застрянут» (для безопасных переводов есть отдельные паттерны).
Best practices
- Не пишите ERC-20 с нуля для продакшена — берите аудированный
ERC20от OpenZeppelin. - Всегда излучайте
TransferиApproval— без них кошельки не увидят движения. - Работайте с суммами в базовых единицах и конвертируйте по
decimalsтолько в интерфейсе.
Итоги
ERC-20 — это договорённость об интерфейсе, благодаря которой токен-число в маппинге понятен всей экосистеме. decimals задаёт масштаб, события строят историю. Дальше разберём связку approve + transferFrom.