Разработка NFT-доступа для членов DAO
NFT-членство — альтернатива токен-взвешенному governance. Вместо «1 токен = 1 голос» работает «1 NFT = 1 голос» или «владение NFT = доступ». Это решает часть проблем plutocracy: крупный холдер не имеет автоматического доминирования. MolochDAO, Guild.xyz, Friends With Benefits — примеры систем, где membership определяется NFT или whitelist, а не просто балансом токенов.
Технически задача делится на два блока: сам NFT-membership контракт и система контроля доступа, которая проверяет владение NFT при каждой защищённой операции.
Membership NFT контракт
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract DAOMembershipNFT is ERC721, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
// Soulbound: NFT нельзя передавать
bool public isSoulbound;
// Merkle root для whitelist-минтинга
bytes32 public merkleRoot;
uint256 private _tokenIdCounter;
uint256 public maxSupply;
// Tier системa: 1 = Member, 2 = Core, 3 = Founder
mapping(uint256 => uint8) public memberTier;
mapping(address => bool) public hasMinted;
event MemberAdded(address indexed member, uint256 tokenId, uint8 tier);
event MemberRevoked(uint256 indexed tokenId);
constructor(
string memory name,
string memory symbol,
uint256 _maxSupply,
bool _soulbound
) ERC721(name, symbol) {
maxSupply = _maxSupply;
isSoulbound = _soulbound;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// Mintинг через Merkle proof (для whitelist launch)
function mintWithProof(
bytes32[] calldata proof,
uint8 tier
) external {
require(!hasMinted[msg.sender], "Already minted");
require(_tokenIdCounter < maxSupply, "Max supply reached");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, tier));
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
hasMinted[msg.sender] = true;
_mintMember(msg.sender, tier);
}
// Минтинг через governance решение
function mintByGovernance(
address to,
uint8 tier
) external onlyRole(MINTER_ROLE) {
require(!hasMinted[to], "Already has membership");
_mintMember(to, tier);
}
function _mintMember(address to, uint8 tier) internal {
uint256 tokenId = ++_tokenIdCounter;
memberTier[tokenId] = tier;
_safeMint(to, tokenId);
emit MemberAdded(to, tokenId, tier);
}
// Soulbound: блокировка transfer
function _beforeTokenTransfer(
address from, address to, uint256 tokenId, uint256 batchSize
) internal override {
super._beforeTokenTransfer(from, to, tokenId, batchSize);
if (isSoulbound && from != address(0) && to != address(0)) {
revert("Soulbound: non-transferable");
}
}
// Отзыв членства через governance (burn)
function revoke(uint256 tokenId) external onlyRole(MINTER_ROLE) {
address owner = ownerOf(tokenId);
hasMinted[owner] = false;
_burn(tokenId);
emit MemberRevoked(tokenId);
}
}
Soulbound vs передаваемые NFT
Soulbound (ERC-5192) — NFT нельзя продать или передать. Членство привязано к личности, не к капиталу. Подходит для DAO, где важна реальная идентичность участников (contributor DAO, professional guilds). Минус: если ключ потерян — нет способа перенести членство без governance голосования.
Передаваемые membership NFT — работают как коллекционные токены доступа (например, Nouns DAO). Можно продать место в DAO. Более ликвидны, создают market price для членства. Риск: спекулятивный рынок может исказить состав DAO.
Tier-система
Одного «да/нет» недостаточно для сложных DAO. Tier-уровни дают гибкость:
| Tier | Название | Права | Условия получения |
|---|---|---|---|
| 1 | Observer | Чтение закрытых обсуждений | Whitelist mint |
| 2 | Member | Голосование, proposals | Активность 30+ дней |
| 3 | Core | Grants committee, veto | Выбор голосованием |
| 4 | Founder | Treasury multi-sig | Только founding team |
Повышение tier через governance proposal: любой член Tier 2 может номинировать другого на Tier 3. Governor голосует, по итогам MINTER_ROLE минтует upgrade.
On-chain верификация владения
Контроль доступа к DAO функциям через балансовую проверку:
contract DAOGovernanceWithNFT {
DAOMembershipNFT public membershipNFT;
modifier onlyMember() {
require(membershipNFT.balanceOf(msg.sender) > 0, "Not a member");
_;
}
modifier onlyTier(uint8 minTier) {
uint256 balance = membershipNFT.balanceOf(msg.sender);
require(balance > 0, "Not a member");
// Находим максимальный tier владельца
uint8 highestTier = _getHighestTier(msg.sender);
require(highestTier >= minTier, "Insufficient tier");
_;
}
function createProposal(...) external onlyMember() { ... }
function accessTreasury(...) external onlyTier(3) { ... }
function _getHighestTier(address member) internal view returns (uint8) {
uint256 balance = membershipNFT.balanceOf(member);
uint8 highest = 0;
// Для небольших DAO (< 1000 членов) можно итерировать
for (uint256 i = 0; i < balance; i++) {
uint256 tokenId = membershipNFT.tokenOfOwnerByIndex(member, i);
uint8 tier = membershipNFT.memberTier(tokenId);
if (tier > highest) highest = tier;
}
return highest;
}
}
Для DAO с тысячами членов итерация в on-chain функции — проблема. Альтернатива: хранить mapping address => uint8 tier в контракте, обновляемый при минт/burn.
Off-chain верификация через подпись
Для доступа к закрытым Discord каналам, внутренним сайтам или off-chain ресурсам — верификация через wallet signature без транзакции:
// Frontend: пользователь подписывает сообщение
const message = `Verify DAO membership\nTimestamp: ${Date.now()}\nAddress: ${address}`;
const signature = await signer.signMessage(message);
// Backend: проверка подписи + ownership
async function verifyMembership(address: string, signature: string): Promise<boolean> {
// Восстанавливаем адрес из подписи
const recovered = ethers.verifyMessage(message, signature);
if (recovered.toLowerCase() !== address.toLowerCase()) return false;
// Проверяем баланс NFT через RPC
const nft = new ethers.Contract(NFT_ADDRESS, ABI, provider);
const balance = await nft.balanceOf(address);
return balance.gt(0);
}
Этот паттерн используется в Guild.xyz и Collab.Land для Discord gate-inга. Пользователь не платит gas, просто подписывает — и получает роль в Discord при наличии NFT.
Процесс работы
Проектирование (3-5 дней). Tier структура, soulbound vs transferable, launch механика (Merkle whitelist, публичный mint, только governance), интеграция с существующим Governor или новый.
Разработка контрактов (1,5-2 недели). Membership NFT + governance integration + тесты.
Off-chain интеграция (1 неделя). Backend верификация, Discord/Telegram bot через Collab.Land или кастомный.
Аудит (1 неделя). NFT контракты с access control функциями требуют аудита, особенно revoke логика.
Сроки и стоимость — после детализации требований.







