Разработка deflationary-токена (с сжиганием)
Deflationary токен — это токен, total supply которого уменьшается со временем. Механизм сжигания встроен в сам контракт: часть каждого transfer автоматически уходит на нулевой адрес (address(0)). Звучит просто, но дьявол в деталях реализации — особенно когда токен должен работать с DeFi протоколами.
Два подхода к burn
1. Fee-on-transfer (автоматическое сжигание при трансфере)
При каждом transfer автоматически сжигается X% от суммы:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract DeflationaryToken is ERC20, Ownable2Step {
uint256 public burnBps; // базисные пункты, 100 = 1%
uint256 public constant MAX_BURN_BPS = 1000; // 10% максимум
// Адреса исключённые из налога (LP пары, роутеры)
mapping(address => bool) public isBurnExempt;
event BurnBpsUpdated(uint256 oldBps, uint256 newBps);
constructor(
string memory name,
string memory symbol,
uint256 initialSupply,
uint256 _burnBps
) ERC20(name, symbol) Ownable2Step() {
require(_burnBps <= MAX_BURN_BPS, "Burn too high");
burnBps = _burnBps;
_mint(msg.sender, initialSupply);
}
function _transfer(
address from,
address to,
uint256 amount
) internal override {
if (burnBps > 0 && !isBurnExempt[from] && !isBurnExempt[to]) {
uint256 burnAmount = (amount * burnBps) / 10000;
uint256 sendAmount = amount - burnAmount;
super._transfer(from, address(0), burnAmount); // burn
super._transfer(from, to, sendAmount); // transfer
} else {
super._transfer(from, to, amount);
}
}
function setBurnBps(uint256 _burnBps) external onlyOwner {
require(_burnBps <= MAX_BURN_BPS, "Burn too high");
emit BurnBpsUpdated(burnBps, _burnBps);
burnBps = _burnBps;
}
function setBurnExempt(address account, bool exempt) external onlyOwner {
isBurnExempt[account] = exempt;
}
}
Критическая проблема с fee-on-transfer в DeFi: Uniswap V2 и большинство AMM не поддерживают токены с transfer tax корректно из коробки. Роутер отправляет amountIn в пул, но пул получает amountIn - burnAmount. Это вызывает revert с ошибкой UniswapV2: INSUFFICIENT_INPUT_AMOUNT или некорректный расчёт.
Решение — использовать Uniswap V2 функции с суффиксом SupportingFeeOnTransferTokens:
// Вызов с фронтенда или из контракта
IUniswapV2Router02(router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
amountIn,
amountOutMin,
path,
to,
deadline
);
Но это ответственность фронтенда и интеграторов — ваш токен должен явно документировать что он fee-on-transfer.
2. Manual burn через buyback-and-burn
Более предсказуемый механизм: часть дохода протокола периодически используется для покупки токена на рынке и его сжигания. Нет проблем с DeFi совместимостью, более прозрачная экономика.
contract BuybackBurnVault is Ownable2Step {
IERC20 public immutable token;
IUniswapV2Router02 public immutable router;
uint256 public totalBurned;
event BuybackExecuted(uint256 bnbSpent, uint256 tokensBurned);
constructor(address _token, address _router) Ownable2Step() {
token = IERC20(_token);
router = IUniswapV2Router02(_router);
}
// Принимает BNB от протокола
receive() external payable {}
function executeBuyback(
uint256 bnbAmount,
uint256 minTokensOut,
uint256 deadline
) external onlyOwner {
require(address(this).balance >= bnbAmount, "Insufficient BNB");
address[] memory path = new address[](2);
path[0] = router.WETH(); // WBNB на BSC
path[1] = address(token);
uint256[] memory amounts = router.swapExactETHForTokens{value: bnbAmount}(
minTokensOut,
path,
address(this),
deadline
);
uint256 tokensBought = amounts[amounts.length - 1];
// Сжигаем купленные токены
token.transfer(address(0), tokensBought);
totalBurned += tokensBought;
emit BuybackExecuted(bnbAmount, tokensBought);
}
}
Дефляционная модель: важные вопросы
Перед выбором механизма нужно ответить на несколько вопросов, которые определяют архитектуру:
Процент burn: 1–2% — это агрессивно для high-frequency трейдинга. Каждый свап в Uniswap = buy + sell = 2 transfer + AMM fee. При 1% burn токен теряет 2% за одну торговую операцию плюс 0.3% LP fee. Это отпугивает трейдеров. Для utility токенов с нечастыми трансферами — приемлемо.
Фиксированный vs динамический burn: динамический (например, выше burn при большом объёме) создаёт сложную токеномику, но позволяет настраивать давление под рыночные условия.
Burn cap: при достаточно большом burn рано или поздно supply упадёт до неликвидных уровней. Разумно задать минимальный порог: если totalSupply < MIN_SUPPLY, burn отключается.
uint256 public constant MIN_SUPPLY = 1_000_000 * 10**18; // 1M токенов — минимум
function _transfer(address from, address to, uint256 amount) internal override {
if (burnBps > 0 && !isBurnExempt[from] && !isBurnExempt[to]) {
uint256 burnAmount = (amount * burnBps) / 10000;
// Не сжигаем если упадём ниже минимума
uint256 currentSupply = totalSupply();
if (currentSupply > MIN_SUPPLY) {
if (currentSupply - burnAmount < MIN_SUPPLY) {
burnAmount = currentSupply - MIN_SUPPLY;
}
super._transfer(from, address(0), burnAmount);
super._transfer(from, to, amount - burnAmount);
return;
}
}
super._transfer(from, to, amount);
}
Мониторинг и аналитика
Дефляционная механика бессмысленна без прозрачности. Пользователи должны видеть динамику сжигания:
// Событие для трекинга в субграфе
event TokensBurned(address indexed from, address indexed to, uint256 amount, uint256 newTotalSupply);
function _transfer(address from, address to, uint256 amount) internal override {
// ... логика burn ...
if (burnAmount > 0) {
emit TokensBurned(from, address(0), burnAmount, totalSupply());
}
}
Через The Graph subgraph строится дашборд: суточный burn rate, cumulative burned, projected supply через N лет при текущем burn rate.
Безопасность
Два специфичных риска deflationary токенов:
Re-entrancy через approve: если в _transfer есть внешние вызовы (например, автоматический свап части burn в ETH) — классическая re-entrancy атака. Решение: ReentrancyGuard + CEI паттерн.
Манипуляция exempt-списком: если владелец может добавить любой адрес в isBurnExempt, злоумышленник с захваченным owner-ключом может отключить burn и нарушить обещания токеномики. Используйте timelock на изменения exempt-списка для проектов с серьёзным TVL.
// Timelock для критических изменений
uint256 public constant BURN_CHANGE_TIMELOCK = 48 hours;
mapping(bytes32 => uint256) public pendingChanges;
function scheduleBurnBpsChange(uint256 newBps) external onlyOwner {
bytes32 changeId = keccak256(abi.encodePacked("burnBps", newBps));
pendingChanges[changeId] = block.timestamp + BURN_CHANGE_TIMELOCK;
}
function executeBurnBpsChange(uint256 newBps) external onlyOwner {
bytes32 changeId = keccak256(abi.encodePacked("burnBps", newBps));
require(pendingChanges[changeId] != 0, "Not scheduled");
require(block.timestamp >= pendingChanges[changeId], "Timelock active");
burnBps = newBps;
delete pendingChanges[changeId];
}
Сроки и состав работ
Разработка (контракты + тесты) — 5–8 дней. Тестирование совместимости с Uniswap/PancakeSwap — 1–2 дня. Деплой + верификация + настройка LP пары — 1 день. Subgraph для аналитики burn — опционально, 2–3 дня.







