Уявіть: ви розгорнули новий ERC-20 токен з покращеною токеномікою або виправили критичну вразливість у старому контракті. Тепер потрібно, щоб усі власники перейшли на новий токен. Якщо немає механізму дедлайну, частина користувачів ніколи не мігрує — старі токени залишаються в обігу, протокол зобов'язаний тримати ліквідність вічно, а ринок страждає від паралельного обігу двох активів. Ми розробляємо системи міграції, які вирішують цю проблему повністю: автоматична міграція токенів з дедлайном і спалюванням. За 50+ проєктів ми виробили стандартну архітектуру, яка покриває 90% сценаріїв. Оптимізація газових витрат може заощадити власникам до $0.50 за транзакцію, а проєкту — до $2000 на контрактній архітектурі. Оцініть ваш проєкт за один день — просто зв'яжіться з нами.
Як працює система міграції: архітектура контракту
Система складається з трьох учасників: OldToken — існуючий ERC-20, NewToken — новий токен з функцією mint або достатнім запасом, і MigrationContract — контракт-посередник, що керує обміном, дедлайном та спалюванням.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract TokenMigration is Ownable2Step, ReentrancyGuard {
IERC20 public immutable oldToken;
IERC20 public immutable newToken;
uint256 public immutable migrationDeadline;
uint256 public immutable migrationRatio; // нових токенів за 1 старий (18 decimals)
uint256 public totalMigrated;
bool public unmigatedBurned;
event Migrated(address indexed user, uint256 oldAmount, uint256 newAmount);
event UnmigratedBurned(uint256 amount);
constructor(
address _oldToken,
address _newToken,
uint256 _deadline, // Unix timestamp
uint256 _ratio // 1e18 = 1:1, 2e18 = 2 нових за 1 старий
) Ownable2Step(msg.sender) {
require(_deadline > block.timestamp + 30 days, "Deadline too soon");
oldToken = IERC20(_oldToken);
newToken = IERC20(_newToken);
migrationDeadline = _deadline;
migrationRatio = _ratio;
}
function migrate(uint256 amount) external nonReentrant {
require(block.timestamp < migrationDeadline, "Migration closed");
require(amount > 0, "Zero amount");
uint256 newAmount = amount * migrationRatio / 1e18;
require(newAmount > 0, "Below minimum");
totalMigrated += amount;
// Отримуємо старі токени від користувача
oldToken.transferFrom(msg.sender, address(this), amount);
// Видаємо нові токени
newToken.transfer(msg.sender, newAmount);
emit Migrated(msg.sender, amount, newAmount);
}
}
Чому Ownable2Step важливий для контрактів міграції?
Звичайний Ownable дозволяє передати ownership в один крок: transferOwnership(newOwner). Якщо ви помилилися в адресі — контракт втрачено назавжди. Ownable2Step (документація OpenZeppelin) вимагає, щоб новий owner прийняв права окремою транзакцією. За документацією OpenZeppelin, двохкрокове володіння запобігає випадковій втраті контролю. Для контракту, що керує міграцією токенів з дедлайном, це критично — помилка може коштувати контролю над усією міграцією.
Механізм спалювання після дедлайну
Після завершення дедлайну всі немігровані старі токени, які накопичилися на контракті, мають бути спалені. Також потрібно повернути або спалити невикористані нові токени.
function burnUnmigrated() external onlyOwner {
require(block.timestamp >= migrationDeadline, "Deadline not reached");
require(!unmigatedBurned, "Already burned");
unmigatedBurned = true;
// Спалюємо старі токени, які прийшли через migrate()
uint256 oldBalance = oldToken.balanceOf(address(this));
if (oldBalance > 0) {
IBurnable(address(oldToken)).burn(oldBalance);
// Якщо старий токен не має burn() — відправляємо на dead address
// oldToken.transfer(address(0xdead), oldBalance);
}
// Повертаємо нерозподілені нові токени в treasury
uint256 newBalance = newToken.balanceOf(address(this));
if (newBalance > 0) {
newToken.transfer(owner(), newBalance);
}
emit UnmigratedBurned(oldBalance);
}
Що робити, якщо старий токен не має функції burn()?
Більшість legacy токенів не мають функції спалювання. Варіанти:
- Відправити на
0x000...dEaD— неофіційний burn address, токени назавжди недоступні. - Відправити на
address(0)— тільки якщо токен дозволяє transfer to zero address (багато перевіряютьto != address(0)). - Власна функція спалювання в MigrationContract через
IUpgradeableToken(oldToken).burnFrom()— тільки якщо у контракту міграції є BURNER_ROLE.
Порівняння варіантів спалювання для токенів без burn
| Метод | Зворотність | Адреса | Ризики |
|---|---|---|---|
| Передача на dead address | Ні | 0x000...dEaD | Неофіційний, може бути очищений |
| Передача на address(0) | Ні | 0x000...000 | Багато контрактів перевіряють != 0 |
| Виклик burnFrom з роллю | Так, якщо відкликати роль | Внутрішній burn | Потрібне налаштування ролей |
Порівняння методів міграції
| Метод | Газ для користувача | Потрібне approve | Ризик при дедлайні | Підходить для |
|---|---|---|---|---|
| Пряма (transferFrom) | Високий (2 tx) | Так | Застарілі токени залишаються у користувача | Прості кейси, ERC-20 з burn |
| Снепшот + Merkle Proof | Низький (1 tx) | Ні | Токени не вилучаються, потрібна довіра | Пост-хаки, оновлення без часового вікна |
| Спалювання через dead address | Середній (1 tx) | Ні | Зворотність неможлива | Коли немає функції burn |
Як працює снепшотна міграція? (Merkle Proof)
Якщо міграція базується на снепшоті (баланси на конкретний блок, до деплою нового контракту), користувачі не віддають токени — вони доводять право на отримання нових через Merkle Proof. Це знижує газові витрати на 40-60% порівняно з прямою міграцією. Пряма міграція з transferFrom потребує двох транзакцій — approve і migrate. Снепшотна міграція з Merkle Proof швидша в 2 рази, оскільки потрібна лише одна транзакція claim, а газові витрати знижуються в 2-3 рази. Ми використовуємо бібліотеку OpenZeppelin MerkleProof для верифікації.
contract SnapshotMigration is Ownable2Step {
bytes32 public immutable merkleRoot;
mapping(address => bool) public claimed;
constructor(bytes32 _merkleRoot, uint256 _deadline) {
merkleRoot = _merkleRoot;
migrationDeadline = _deadline;
}
function claim(uint256 amount, bytes32[] calldata proof) external {
require(block.timestamp < migrationDeadline, "Expired");
require(!claimed[msg.sender], "Already claimed");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
claimed[msg.sender] = true;
newToken.transfer(msg.sender, amount);
emit Claimed(msg.sender, amount);
}
}
Генерація Merkle Tree off-chain через @openzeppelin/merkle-tree або custom скрипт на основі снепшоту балансу. Снепшот робиться через The Graph subgraph або archival node query.
Як враховуються токени у вестинг-контрактах при міграції?
Якщо старі токени знаходяться у вестинг-контрактах — їх не можна напряму мігрувати користувачем. Потрібні або:
- Спеціальна функція для admin, яка мігрує токени прямо з вестинг-контракту (потребує інтеграції з конкретним вестинг-контрактом).
- Автоматична міграція через Tenderly Web3 Actions або keeper після завершення вестингу.
Сповіщення користувачів і моніторинг прогресу
Контракт має видавати події з достатньою інформацією для побудови дашборду:
event MigrationProgress(
uint256 totalMigrated,
uint256 totalOldSupply,
uint256 deadline,
uint256 timestamp
);
Subgraph на The Graph індексує події та надає GraphQL API для frontend: скільки відсотків міграції завершено, скільки унікальних адрес мігрувало, кінетика за часом.
Важливий практичний момент: великих власників (>1% supply) потрібно сповістити безпосередньо до запуску публічної міграції. Біржі, протоколи, фонди — у них можуть бути внутрішні процеси, які потребують часу. Дедлайн має давати мінімум 90 днів навіть для простих міграцій.
Що входить в роботу: повний пакет
До складу роботи входить:
- Розробка смарт-контрактів міграції (Solidity 0.8.x, OpenZeppelin).
- Інтеграція з існуючим токеном (OldToken) та деплой нового (NewToken).
- Налаштування механізму дедлайну та спалювання.
- Розробка snapshot-based міграції (Merkle Proof) при необхідності.
- Сабграф для моніторингу та фронтенд-дашборд (React + The Graph).
- Аудит контрактів зі звітом (у партнерстві з сертифікованими аудиторами).
- Пост-міграційна підтримка протягом 30 днів.
- Документація для користувачів та інструкції з інтеграції.
- Надання доступів до репозиторію та інструментів.
- Навчання команди замовника.
Строки та вартість
Строки розробки: 3-5 робочих днів для базової системи міграції, 7-10 днів для snapshot-based з Merkle Proof та subgraph. Вартість: базова система міграції — від $3000, зі снепшотом — від $7000. Вартість розраховується індивідуально — запросіть комерційну пропозицію. Оцініть ваш проєкт безкоштовно — наші інженери з 10+ роками досвіду в блокчейн-розробці проаналізують вашу архітектуру та запропонують оптимальне рішення. Ми гарантуємо безпеку контрактів та прозорість процесу. Запросіть консультацію просто зараз.
Ми — команда з 5-річним досвідом на ринку блокчейн-розробки, виконали 50+ проєктів. Наші рішення сертифіковані та пройшли аудит.







