Сделать токен в Ethereum — значит развернуть смарт-контракт по стандарту ERC-20. Писать Solidity с нуля не нужно: мастер OpenZeppelin Contracts Wizard собирает контракт по выбранным параметрам, а Remix компилирует его и отправляет в сеть. После одной транзакции у токена появляется адрес, балансы и правила, которые уже нельзя изменить.
Токен в Ethereum — это контракт, а не «монета»
Стандарт ERC-20 задаёт набор функций и событий: name, symbol, decimals, totalSupply, balanceOf, transfer, transferFrom, approve, allowance, а также события Transfer и Approval. Кошельки, обозреватели и пулы работают с любым контрактом, который реализует этот интерфейс.
Развёртывание — обычная транзакция, которая создаёт контракт с собственным адресом. Дальше всё решают правила внутри контракта: он ведёт учёт токенов, но не умеет сам их продавать, не формирует цену и не создаёт ликвидность. Это отдельные задачи.
Всё ниже — только про Ethereum и ERC-20: в других сетях свои стандарты, инструменты и комиссии.
Что выбрать до развёртывания
Параметры конструктора определяют правила для всех держателей, поэтому это не галочки, а решения.
| Параметр | Типичный выбор | Что это меняет |
|---|---|---|
| Name и Symbol | Gold и GLD | Задаются один раз. Опечатка в символе останется навсегда. |
| Decimals | 18, как у ETH; 0 — если делить токен не нужно | Только формат отображения. При 0 отправить 1,5 токена нельзя. После деплоя не меняется. |
| Premint (начальная эмиссия) | 1 000 000 | Сумма сразу зачисляется адресу деплоера и входит в totalSupply. |
| Mint (допэмиссия) | Выключен, если предложение должно быть фиксированным | Владелец может выпускать новые токены и увеличивать предложение. |
| Burnable | По желанию | Держатель может сжечь свои токены, totalSupply уменьшается. |
| Pausable | Только если нужен рубильник | Владелец может остановить все переводы, включая вывод средств держателями. |
| Freeze или blacklist (доп. код) | Только если действительно нужно | Владелец блокирует исходящие переводы конкретного адреса. Галочки в мастере нет — функцию добавляют вручную. |
| ERC20Votes | Для голосований | Добавляет делегирование голосов и учёт истории балансов. |
| Access Control | Ownable для одного владельца; роли — если прав несколько | Определяет, кто распоряжается административными функциями. |
| Upgradability | None | Контракт неизменяем. Прокси-обновления усложняют проверку и добавляют админа. |
Premint вместе с mint означает, что предложение контролируете вы: сначала выпускаете 1 000 000, потом можете добавить ещё. Pause и freeze дают власть над чужими переводами, и чем больше таких прав, тем сильнее держатели зависят от адреса, который ими управляет. Для токена, который должен жить долго, права владельца разумнее передать мультисигу: тогда для mint или pause нужно согласие нескольких людей. Список прав владельца публикуйте вместе с адресом токена — именно его будут проверять покупатели.
Развёртывание в Remix по шагам
Откройте OpenZeppelin Contracts Wizard и выберите ERC20.
Заполните Name и Symbol (Gold и GLD), укажите Premint 1 000 000 в целых токенах. В Features снимите Permit; если предложение должно быть фиксированным, снимите и Mintable. Access Control — Ownable, Upgradability — None.
Нажмите Open in Remix (или Download, если работаете в другой среде). Контракт и зависимости появятся в проекте.
Скомпилируйте контракт в Remix.
На вкладке развёртывания выберите сеть и аккаунт, на котором есть ETH для газа, и подтвердите транзакцию в кошельке.
Ожидаемый результат: адрес контракта и 1 000 000 GLD на адресе деплоера. Функции
balanceOfиtotalSupplyвернут 10²⁴ минимальных единиц.Если транзакция отклонена, чаще всего не хватает ETH на газ или выбрана не та сеть. Контракт, развёрнутый в тестовой сети, остаётся там: в основную сеть он не «переедет».
Сразу после успешного деплоя опубликуйте исходник и версию компилятора в обозревателе — без этого контракт никто не сможет проверить.
Пример: 1 000 000 GLD и перевод 1,5 GLD
Параметры примера: Gold, GLD, decimals 18, premint 1 000 000, владелец — Ownable, без Permit. Solidity и EVM не хранят дробные числа, поэтому внутри контракта количество записывается целым числом минимальных единиц: 1 000 000 × 10¹⁸ = 10²⁴. Именно это вернёт balanceOf деплоера, а кошелёк покажет 1 000 000 GLD, разделив значение на 10¹⁸.
Чтобы отправить 1,5 GLD, контракт вызывают с суммой 1,5 × 10¹⁸ = 1 500 000 000 000 000 000. Decimals на арифметику не влияют: все переводы идут целыми числами, а интерфейсы только корректируют отображение. В документации OpenZeppelin тот же механизм показан на 1000 токенов: balanceOf вернёт 10²¹ единиц, а перевод 300 токенов — это transfer(recipient, 300000000000000000000).
Защитные проверки, чтобы держатели не теряли токены
Стандарт ERC-20 не сообщает контракту-получателю о входящем переводе. Если контракт не умеет обрабатывать токены, они остаются на его адресе: по оценке ethereum.org на 20 июня 2024 так потеряно не менее $83,7 млн. Чистая реализация стандарта к этому склонна, если не добавить ограничения сверху.
Два частых случая ошибки — перевод на адрес самого токен-контракта и на пустой адрес, который никому не принадлежит. В OpenZeppelin v5 любое изменение баланса — перевод, выпуск и сжигание — проходит через внутреннюю функцию _update. Её переопределяют в туториале ethereum.org, чтобы проверки срабатывали до изменения балансов:
function _update(address from, address to, uint256 value) internal virtual override(ERC20) {
// Сжигание отправляет токены на address(0), его не блокируем
if (to != address(0)) {
require(to != address(this), "Can't send tokens to the contract address");
bool isToContract;
assembly { isToContract := gt(extcodesize(to), 0) }
require(to.balance != 0 || isToContract, "Can't send tokens to an empty address");
}
super._update(from, to, value);
}
Что это даёт: перевод на адрес контракта и на адрес, у которого нет ни ETH, ни кода, отклоняется. Второе правило — эвристика, и у неё есть цена: новый кошелёк, на который ещё не приходило ETH, тоже будет отклонён, пока получатель не пополнит его небольшим количеством ETH. После EIP-7702 код может быть и у обычного аккаунта, поэтому проверка определяет наличие кода, а не строго тип аккаунта.
Блокировать переводы на все контракты нельзя: это ломает совместимость с пулами ликвидности, которые принимают токены через approve и transferFrom. Поэтому пополняйте контракты связкой approve + transferFrom, а не прямым transfer.
Второй элемент защиты — функция cleanupERC20 под правами владельца: она возвращает ERC-20, случайно отправленные на адрес вашего контракта. Перевод, ушедший на чужой ошибочный адрес, она не отменяет. Эти проверки снижают потери от ошибок, но не устраняют их полностью.
Публикация исходника и проверка чужого токена
Публикация исходника — не формальность: обозреватель компилирует предоставленный код независимо и отклоняет его, если байткод не совпал с развёрнутым. Совпавший исходник — единственный способ для стороннего человека убедиться, что контракт делает заявленное.
Дальше смотрят список функций владельца (mint, burn, pause, freeze, cleanup) и события: при переводе, выпуске и сжигании должен эмититься Transfer, при одобрении — Approval. Кошельки узнают о движении токенов именно по событиям.
В разборе ethereum.org на примере wARB, который выдавал себя за ARB, видны типичные приёмы: событие Transfer без реального изменения балансов, функция-двойник Approve с заглавной буквы, которая сжигает балансы держателей, и выпуск токенов владельцу с событием о зачислении другому адресу.
Автоматическая проверка событий помогает, но даёт ложные отрицания: совершенно нормальный ERC-20 может не представлять ничего реального. Поэтому адрес настоящего токена берите из документации эмитента, а не из объявления или сообщения.
Что дальше
После деплоя у вас есть адрес контракта и возможность переводить токены — этого достаточно для внутренней экономики, голосований и тестов. Для обращения на рынке понадобятся пул ликвидности и раскрытие прав владельца; это отдельная работа, и развёрнутый контракт её не выполняет.



