Розробка системи 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 знижує пропозицію (дефляційний ефект), distribute стейкерам створює стимул для стейкінгу
Чесна відповідь: 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 через приватний 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 автоматизація з більш гнучкими триггерами (час, on-chain умова, off-chain подія). Простіше у налаштуванні, підтримує ERC-4337. Вартість: 1BALANCE prepaid.
Для більшості протоколів достатньо Gelato з time-based триггером: раз на 24 годин перевірити накопилені 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 протоколу. Зовнішній аудит обов'язковий.
Вартість та терміни — після уточнення параметрів.







