Разработка системы делегирования в DAO
Делегирование — решение проблемы voter apathy в on-chain governance. В большинстве DAO реальная явка на голосования составляет 5–15% от circulating supply. Причин несколько: технические барьеры (нужно держать токены задефлированными + платить газ), информационная нагрузка (невозможно вникать в каждый пропозал), отсутствие мотивации для мелких holders.
Система делегирования позволяет token holders передавать своё voting power специализированным делегатам — активным участникам сообщества, которые регулярно голосуют и публично обосновывают позицию. Это ближе к представительной демократии, чем к прямой.
Базовый механизм: ERC-20Votes delegation
Стандарт ERC-20Votes (OpenZeppelin) предоставляет встроенный механизм делегирования через checkpoint-систему.
// ERC-20Votes добавляет:
// - delegate(address delegatee) — делегировать свой voting power
// - delegates(address account) → address — кому делегировал
// - getVotes(address account) → uint256 — текущий voting power
// - getPastVotes(address account, uint256 blockNumber) → uint256
// - getPastTotalSupply(uint256 blockNumber) → uint256
// Пример: holder делегирует весь voting power одному делегату
token.delegate(delegateAddress);
// Self-delegation (нужно для получения собственного voting power)
token.delegate(msg.sender);
Критичный момент: после purchase токенов пользователь имеет нулевой voting power пока не вызовет delegate. Это неочевидное поведение, которое нужно явно показывать в UI. Многие крупные holders Uniswap не участвуют в governance просто потому что никогда не делегировали.
Checkpoint-механизм: delegate записывает snapshot voting power в момент вызова. Governor использует getPastVotes(voter, proposalSnapshot) — voting power на блоке создания пропозала. Покупка токенов после снапшота не даёт voting power в текущем голосовании.
Частичное делегирование
Базовый ERC-20Votes поддерживает делегирование только целиком одному адресу. Для продвинутых систем нужно частичное делегирование: 40% делегату A, 30% делегату B, 30% оставить себе.
ERC-20VotesPartialDelegation (EIP-5805 расширение)
struct Delegation {
address delegatee;
uint96 numerator; // доля в numerator/denominator
}
// Пользователь делегирует дробно
function delegateMulti(Delegation[] calldata delegations) external {
uint256 totalNumerator;
for (uint i = 0; i < delegations.length; i++) {
totalNumerator += delegations[i].numerator;
}
require(totalNumerator <= DENOMINATOR, "Exceeds 100%");
_updateDelegations(msg.sender, delegations);
}
Это существенно усложняет checkpoint логику: при каждом transfer нужно пересчитывать вклад в каждого делегата. Газовая стоимость растёт с числом делегатов.
Готовые реализации: Compound Governor Bravo поддерживает основное делегирование; для fractional delegation — смотреть на OpenZeppelin GovernorCountingFractional + кастомный токен.
Делегатные профили и репутация
Технический механизм делегирования — это одно. Инфраструктура для принятия решений о том, кому делегировать — другое.
On-chain делегатный реестр
contract DelegateRegistry {
struct DelegateProfile {
string name;
string ipfsMetadata; // CIDv1 хеш IPFS документа с полным профилем
string[] interests; // теги: ["defi", "security", "tokenomics"]
uint256 createdAt;
}
mapping(address => DelegateProfile) public profiles;
event ProfileUpdated(address indexed delegate, string ipfsMetadata);
function setProfile(
string calldata name,
string calldata ipfsMetadata,
string[] calldata interests
) external {
profiles[msg.sender] = DelegateProfile({
name: name,
ipfsMetadata: ipfsMetadata,
interests: interests,
createdAt: block.timestamp
});
emit ProfileUpdated(msg.sender, ipfsMetadata);
}
}
IPFS metadata содержит: platform statement (позиция по ключевым вопросам), history голосований с обоснованиями, контактные данные. Хранение полного профиля off-chain (IPFS) снижает газовые расходы, on-chain — только хеш для верификации.
Статистика активности делегата
Реальная ценность делегата — его track record. Субграф должен индексировать:
- Participation rate: % пропозалов проголосован из всех пропозалов в период делегирования
- Alignment: насколько часто позиция делегата совпадает с финальным результатом (не лучший метрик, но показательный)
- Rationale rate: % голосований с опубликованным обоснованием
// The Graph subgraph mapping
export function handleVoteCast(event: VoteCast): void {
let delegate = Delegate.load(event.params.voter.toHex());
if (!delegate) {
delegate = new Delegate(event.params.voter.toHex());
delegate.totalVotes = BigInt.zero();
delegate.participationRate = BigDecimal.zero();
}
delegate.totalVotes = delegate.totalVotes.plus(BigInt.fromI32(1));
delegate.save();
let vote = new Vote(event.transaction.hash.toHex() + '-' + event.logIndex.toString());
vote.delegate = delegate.id;
vote.proposalId = event.params.proposalId;
vote.support = event.params.support;
vote.weight = event.params.weight;
vote.reason = event.params.reason;
vote.save();
}
Gasless delegation через EIP-712
Делегирование требует on-chain транзакцию. При стоимости газа $2–5 это барьер для мелких holders. Решение — delegateBySig: holder подписывает delegation off-chain, relayer отправляет транзакцию и платит газ.
// Встроено в ERC-20Votes (OpenZeppelin)
function delegateBySig(
address delegatee,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(block.timestamp <= expiry, "Expired");
address signer = ECDSA.recover(
_hashTypedDataV4(keccak256(abi.encode(
DELEGATION_TYPEHASH,
delegatee,
nonce,
expiry
))),
v, r, s
);
require(nonce == _useNonce(signer), "Invalid nonce");
_delegate(signer, delegatee);
}
Frontend flow: пользователь подписывает EIP-712 сообщение (MetaMask показывает читаемый текст «Делегировать [адрес] до [дата]»), подпись отправляется на backend, backend вызывает delegateBySig и оплачивает газ.
Batch gasless delegation для onboarding
При onboarding нового пользователя можно запросить одновременно delegate-подпись для нескольких токенов:
const delegations = await Promise.all([
signDelegation(tokenA, delegatee, signer),
signDelegation(tokenB, delegatee, signer),
]);
// Один relayer submit
await Promise.all(delegations.map(d => relayer.submit(d)));
Отзыв делегирования и re-delegation
Делегирование — не постоянно. delegate(address(0)) или delegate(msg.sender) отзывает делегирование. Изменение делегата: delegate(newDelegatee) автоматически переносит весь voting power.
Время вступления в силу: изменение эффективно с следующего блока. Если пропозал уже создан и snapshot зафиксирован — изменение делегирования не влияет на текущее голосование.
UI рекомендации: показывать пользователю текущего делегата, его participation rate за последние 30 дней и deadline ближайших активных голосований. Это снижает отток делегаторов, которые разочарованы неактивным делегатом.
Incentive механики для делегатов
Чистая альтруистическая модель работает только для крупных holders. Для системной работы нужны стимулы.
Delegate rewards. Протокол выплачивает делегатам за активное участие. Расчёт: participation rate × delegated voting power × reward rate. Распределяется из treasury через governance-одобренный контракт.
Streaming payments (Sablier/SuperFluid). Делегат получает постоянный stream оплаты пока его participation rate выше порога. При падении активности — stream автоматически приостанавливается через keeper.
// Keeper проверяет participation rate
function checkAndUpdateDelegateStream(address delegate) external {
uint256 participationRate = stats.getParticipationRate(delegate, 30 days);
if (participationRate < MIN_PARTICIPATION_RATE) {
sablier.cancel(delegateStreams[delegate]);
emit DelegateStreamPaused(delegate, participationRate);
}
}
Стек и сроки
Контракты: ERC-20Votes расширение + DelegateRegistry + reward distributor. Solidity + Foundry + OpenZeppelin 5.x. Индексация: The Graph subgraph для delegate stats. Frontend: React + wagmi + react-query.
| Компонент | Срок |
|---|---|
| ERC-20Votes + gasless delegation | 1 неделя |
| DelegateRegistry on-chain | 3–5 дней |
| The Graph subgraph для статистики | 1 неделя |
| Frontend: поиск делегатов, UI делегирования | 2 недели |
| Reward механика | 1–2 недели |
MVP (базовое делегирование + gasless + UI профилей делегатов): 4–5 недель. Полная система с reward стримингом и аналитикой: 2 месяца.







