Реализация DAO-портала (голосование, предложения) на сайте
DAO-портал — веб-интерфейс для управления децентрализованной организацией: создание предложений, голосование токенами, делегирование голосов, исполнение принятых решений через смарт-контракты.
Архитектура DAO
Governance Token (ERC-20 + EIP-2612 для delегации)
│
Governor Contract (OpenZeppelin Governor)
├── Propose (создать предложение)
├── CastVote (проголосовать)
├── Queue (поставить в очередь Timelock)
└── Execute (исполнить через Timelock)
│
Timelock Controller
└── Target Contracts (Treasury, Protocol)
Governor Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
contract DAOGovernor is
Governor, GovernorSettings, GovernorCountingSimple,
GovernorVotes, GovernorTimelockControl
{
constructor(
IVotes _token,
TimelockController _timelock
)
Governor("DAO Governor")
GovernorSettings(
1 days, // voting delay (1 день до старта голосования)
7 days, // voting period (7 дней голосование)
100_000e18 // proposal threshold (нужно 100k токенов)
)
GovernorVotes(_token)
GovernorTimelockControl(_timelock)
{}
// 4% токенов нужно для прохождения (quorum)
function quorum(uint256 blockNumber) public view override returns (uint256) {
return token.getPastTotalSupply(blockNumber) * 4 / 100;
}
}
Frontend: создание предложения
import { useWriteContract, useAccount } from 'wagmi';
import { encodeFunctionData } from 'viem';
function CreateProposal() {
const { writeContractAsync } = useWriteContract();
const handleSubmit = async (formData: ProposalFormData) => {
// Кодируем вызов целевого контракта
const calldata = encodeFunctionData({
abi: treasuryAbi,
functionName: 'transfer',
args: [formData.recipient, formData.amount]
});
await writeContractAsync({
address: GOVERNOR_ADDRESS,
abi: governorAbi,
functionName: 'propose',
args: [
[TREASURY_ADDRESS], // targets
[0n], // values (ETH)
[calldata], // calldatas
formData.description // description (Markdown)
]
});
};
return <ProposalForm onSubmit={handleSubmit} />;
}
Frontend: голосование
function VoteOnProposal({ proposalId }) {
const { writeContractAsync } = useWriteContract();
const { address } = useAccount();
// Получить вес голоса на момент снепшота
const { data: votingPower } = useReadContract({
address: GOVERNOR_ADDRESS,
abi: governorAbi,
functionName: 'getVotes',
args: [address!, proposalSnapshotBlock]
});
const castVote = async (support: 0 | 1 | 2) => {
// 0 = Against, 1 = For, 2 = Abstain
await writeContractAsync({
address: GOVERNOR_ADDRESS,
abi: governorAbi,
functionName: 'castVoteWithReason',
args: [proposalId, support, reason]
});
};
return (
<div>
<p>Ваш вес голоса: {formatTokens(votingPower)} токенов</p>
<button onClick={() => castVote(1)}>За</button>
<button onClick={() => castVote(0)}>Против</button>
<button onClick={() => castVote(2)}>Воздержаться</button>
</div>
);
}
Индексирование через The Graph
// Получение списка предложений
const PROPOSALS_QUERY = gql`
query GetProposals($state: String, $first: Int, $skip: Int) {
proposals(
where: { state: $state }
orderBy: createdAt
orderDirection: desc
first: $first
skip: $skip
) {
id
proposalId
proposer { id }
description
state
forVotes
againstVotes
abstainVotes
quorum
startBlock
endBlock
createdAt
}
}
`;
// Делегирование голосов
async function delegateVotes(delegatee: string) {
await writeContractAsync({
address: GOVERNANCE_TOKEN,
abi: erc20VotesAbi,
functionName: 'delegate',
args: [delegatee]
});
}
Интеграция Snapshot (off-chain голосование)
Для сигнальных голосований без gas-затрат — Snapshot Protocol:
import snapshot from '@snapshot-labs/snapshot.js';
const client = new snapshot.Client712('https://hub.snapshot.org');
// Создать голосование (подписывается кошельком, без транзакции)
await client.proposal(web3, address, {
space: 'your-dao.eth',
type: 'single-choice',
title: 'Принять новую стратегию токенов?',
body: '## Описание\n\nПолное описание предложения...',
choices: ['За', 'Против', 'Воздержаться'],
start: Math.floor(Date.now() / 1000),
end: Math.floor(Date.now() / 1000) + 7 * 24 * 3600,
snapshot: await web3.eth.getBlockNumber(),
plugins: JSON.stringify({}),
app: 'your-dao-app'
});
Сроки реализации
- Governor + Timelock + Governance Token контракты — 2–3 недели
- Frontend (список предложений, голосование, делегирование) — 2–3 недели
- Индексер + полный портал — 1–2 месяца







