Реалізація DAO-портала (голосування, пропозиції) на сайті
DAO-портал — веб-інтерфейс для управління децентралізованою організацією: створення пропозицій, голосування токенами, делегування голосів, виконання прийнятих рішень через смарт-контракти.
Архітектура DAO
Governance Token (ERC-20 + EIP-2612 для делегації)
|
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 місяці







