Разработка системы buyback-and-distribute
Buyback-and-distribute — механизм, при котором протокол использует часть доходов для выкупа собственного токена с открытого рынка и распределения выкупленных токенов (или сожжения) среди стейкеров или холдеров. Это альтернатива прямому распределению дивидендов в stablecoin и более дружественная к налогообложению схема в ряде юрисдикций.
GMX реализовал версию этой механики (esGMX rewards + buyback), Curve используется как базовый прецедент. Но реализации сильно отличаются по деталям, и именно детали определяют, работает ли механика экономически или создаёт только sell pressure.
Экономическая логика
Прямое распределение fee в USDC понятно холдерам, но не влияет на цену токена. Buyback теоретически создаёт покупное давление. На практике эффект зависит от нескольких факторов:
- Размер buyback относительно ежедневного объёма торгов: если buyback = 0.1% суточного объёма, влияние на цену минимально
- Способ выполнения: крупный разовый buyback легко front-run-нят MEV-боты; равномерный TWAP-buyback эффективнее
- Что делается с выкупленными токенами: burn снижает supply (дефляционный эффект), distribute стейкерам создаёт incentive для стейкинга
Честный ответ: buyback-and-distribute — это инструмент для вознаграждения committed holders, а не механизм управления ценой.
Архитектура системы
Система состоит из четырёх компонентов:
FeeCollector — аккумулирует доходы протокола (trading fees, interest, protocol fees) в одном контракте.
BuybackExecutor — получает stablecoin из FeeCollector, выполняет swap на DEX, возвращает купленные токены.
RewardDistributor — получает выкупленные токены и распределяет их среди стейкеров пропорционально стейку.
Staking контракт — хранит информацию о стейках и является источником истины для RewardDistributor.
contract BuybackExecutor {
ISwapRouter public immutable router; // Uniswap V3 Router
IERC20 public immutable revenueToken; // USDC/WETH
IERC20 public immutable protocolToken; // токен протокола
address public immutable distributor;
uint24 public poolFee = 3000; // 0.3% пул
uint256 public minBuybackInterval = 24 hours;
uint256 public lastBuybackTime;
// Максимальный slippage: 1% в basis points
uint256 public maxSlippageBps = 100;
event BuybackExecuted(uint256 revenueSpent, uint256 tokensBought);
function executeBuyback(uint256 amountIn) external onlyRole(EXECUTOR_ROLE) {
require(block.timestamp >= lastBuybackTime + minBuybackInterval, "Too soon");
require(amountIn <= revenueToken.balanceOf(address(this)), "Insufficient revenue");
// Получаем котировку через Quoter перед исполнением
uint256 expectedOut = _getQuote(amountIn);
uint256 minOut = expectedOut * (10000 - maxSlippageBps) / 10000;
revenueToken.approve(address(router), amountIn);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: address(revenueToken),
tokenOut: address(protocolToken),
fee: poolFee,
recipient: distributor,
deadline: block.timestamp + 15 minutes,
amountIn: amountIn,
amountOutMinimum: minOut,
sqrtPriceLimitX96: 0
});
uint256 amountOut = router.exactInputSingle(params);
lastBuybackTime = block.timestamp;
emit BuybackExecuted(amountIn, amountOut);
}
}
TWAP vs instant buyback
Instant buyback (весь объём за одну транзакцию) уязвим: MEV-бот видит транзакцию в mempool, sandwich-атакует (купить перед, продать после). Потери могут достигать 2-5% от объёма.
TWAP buyback разбивает объём на части по времени:
contract TWAPBuyback {
uint256 public totalAllocation; // сколько всего потратить
uint256 public spentAmount; // потрачено
uint256 public tranchSize; // размер одной транши
uint256 public tranchInterval; // интервал между траншами
function executeNextTranch() external {
require(block.timestamp >= lastExecution + tranchInterval, "Too soon");
require(spentAmount < totalAllocation, "Allocation exhausted");
uint256 amount = min(tranchSize, totalAllocation - spentAmount);
spentAmount += amount;
lastExecution = block.timestamp;
_swap(amount);
}
}
Для небольших протоколов (buyback < $10K/месяц) instant buyback через private mempool (Flashbots protect) достаточно. TWAP нужен при суммах, сопоставимых с глубиной пула.
RewardDistributor: механика распределения
Два основных подхода:
Push: контракт итерирует по всем стейкерам и переводит каждому. Простой, но ограничен газом при большом числе стейкеров.
Pull (reward-per-token): контракт хранит накопленное reward-per-token, стейкер забирает награду сам. Масштабируется на любое количество стейкеров.
contract RewardDistributor {
uint256 public rewardPerTokenStored;
uint256 public totalStaked;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) public stakes;
// Вызывается при каждом изменении стейка или наград
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) return rewardPerTokenStored;
return rewardPerTokenStored + (pendingRewards * 1e18 / totalStaked);
}
function earned(address account) public view returns (uint256) {
return stakes[account]
* (rewardPerToken() - userRewardPerTokenPaid[account])
/ 1e18
+ rewards[account];
}
// Вызывается BuybackExecutor при поступлении новых токенов
function notifyRewardAmount(uint256 amount) external onlyBuybackExecutor {
rewardPerTokenStored = rewardPerToken();
pendingRewards = amount;
}
function claimReward() external updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
require(reward > 0, "Nothing to claim");
rewards[msg.sender] = 0;
protocolToken.safeTransfer(msg.sender, reward);
}
}
Это классическая Synthetix staking rewards модель — battle-tested, используется в сотнях протоколов.
Автоматизация: Chainlink Keepers vs Gelato
Buyback нельзя оставить на ручное выполнение — это централизация и риск манипуляции (owner выбирает момент покупки). Автоматизация решает это:
Chainlink Automation (бывшие Keepers): on-chain условия (checkUpkeep) вызывают performUpkeep. Децентрализовано, надёжно, есть gas limit. Стоимость: LINK токены для funded задач.
Gelato Network: web3 automation с более гибкими триггерами (время, on-chain условие, off-chain событие). Проще в настройке, поддерживает ERC-4337. Стоимость: 1BALANCE prepaid.
Для большинства протоколов достаточен Gelato с time-based trigger: раз в 24 часа проверить accumulated fees, если > threshold — запустить buyback.
Параметры и governance
Критические параметры, которые должны управляться через governance, а не храниться у owner:
| Параметр | Описание | Рекомендуемый диапазон |
|---|---|---|
| buybackRatio | % дохода на buyback | 20-50% |
| burnRatio | % выкупленных токенов на burn | 0-50% |
| tranchSize | размер одного buyback (USD) | зависит от ликвидности |
| maxSlippageBps | максимальный slippage | 50-200 bps |
| minBuybackInterval | минимальный интервал | 12-48 часов |
Изменение этих параметров через timelock (48-72 часа задержка) — обязательно. Иначе owner может манипулировать buyback в своих интересах.
Процесс работы
Аналитика (3-5 дней). Источники дохода протокола, объём и частота, целевой DEX и ликвидность токена, желаемая механика (burn vs distribute), governance модель.
Разработка (3-4 недели). FeeCollector → BuybackExecutor → TWAP логика → RewardDistributor → Staking контракт → автоматизация через Gelato/Chainlink.
Тестирование (1 неделя). Fork-тесты на Foundry с реальными DEX пулами, симуляция MEV атак.
Аудит. Контракты управляют treasury протокола. Внешний аудит обязателен.
Стоимость и сроки — после уточнения параметров.







