В нашей практике мы сталкивались с десятками интеграций NFT-membership и видели, как ошибочная архитектура контракта приводит к утечке привилегий. Самая частая ошибка: разработчики реализуют проверку ownerOf(tokenId) == msg.sender и считают задачу решённой. Но NFT можно одолжить, flash loan-нуть (на один блок) или выставить на маркетплейс, сохранив доступ через делегирование. Правильная membership система требует понимания этих векторов и явного выбора модели доверия. Наш опыт — 5 лет в DeFi и NFT, более 20 успешных проектов — гарантирует надёжное решение.
Контрактная архитектура
Базовая модель: владение токеном
Для простых кейсов (доступ к контенту, Discord-верификация) достаточно ERC-721 с функцией проверки balanceOf(user) > 0. balanceOf дешевле ownerOf при множественных токенах и устойчивее к edge cases. Но она не защищает от listing: владелец может выставить NFT на OpenSea, получить доступ к закрытому контенту, и снять листинг после.
Тиерная membership через ERC-1155
Для нескольких уровней доступа (Bronze/Silver/Gold, или месяц/год/lifetime) ERC-1155 нативно подходит лучше ERC-721. Каждый tokenId — тир:
uint256 public constant TIER_BRONZE = 1; uint256 public constant TIER_SILVER = 2; uint256 public constant TIER_GOLD = 3; function getMemberTier(address user) external view returns (uint256) { if (balanceOf(user, TIER_GOLD) > 0) return TIER_GOLD; if (balanceOf(user, TIER_SILVER) > 0) return TIER_SILVER; if (balanceOf(user, TIER_BRONZE) > 0) return TIER_BRONZE; return 0; // не участник } Тиры с накопленным доступом: Gold включает всё что есть в Silver и Bronze. Проверяем сверху вниз.
Почему стоит использовать soulbound токены для membership?
Soulbound токены (EIP-5192) лишены проблемы переносимости: они привязаны к адресу владельца навсегда. Если цель — привязать доступ к конкретному человеку, а не кошельку, используем EIP-5192 или просто переопределяем transfer функции:
function _beforeTokenTransfer( address from, address to, uint256 tokenId, uint256 batchSize ) internal override { require(from == address(0) || to == address(0), "Soulbound: non-transferable"); super._beforeTokenTransfer(from, to, tokenId, batchSize); } from == address(0) — mint, to == address(0) — burn. Всё остальное запрещено. Проблема: потеря ключей = потеря membership. Решение: предусмотреть recovery механизм через мультисиг или социальное восстановление (ERC-4337 account abstraction).
Как реализовать временные подписки в ERC-721?
Expiring membership требует хранения дат. Два подхода:
| Подход | On-chain timestamp | Signature-based off-chain |
|---|---|---|
| Хранение | Маппинг tokenId → expiresAt | Подписанные JWT с expiry (на бэкенде) |
| Газ на проверку | Запись в хранилище | Без записей, дешевле |
| Доверие | Полное (всё on-chain) | Требуется доверие к сервису подписей |
| Подходит для | Fully on-chain систем | Web2-hybrid и интеграций с API |
Для fully on-chain — первый подход. ERC-5643 — черновик стандарта для subscription NFT с renewSubscription(uint256 tokenId, uint64 duration).
Сравнение моделей membership
| Характеристика | ERC-721 | ERC-1155 | Soulbound (EIP-5192) |
|---|---|---|---|
| Тиры | 1 тип | Множество тиров | 1 тип (если один) |
| Передача | Да | Да | Нет |
| Gas на проверку | balanceOf ~30k |
balanceOfBatch ~40k |
balanceOf ~30k |
| Сложность | Низкая | Средняя | Средняя |
| Применение | Простые доступы | Tiered membership | Персональные подписки |
Интеграция с off-chain системами
Верификация через EIP-1271
Для проверки membership в бэкенде без транзакций: пользователь подписывает сообщение (EIP-191 или EIP-712), бэкенд верифицирует через eth_call к isValidSignature(bytes32 hash, bytes signature) для смарт-кошельков или через ecrecover для EOA. Это стандартный смарт-контракт членства вне цепи.
Делегирование через delegate.xyz
delegate.cash (де-факто стандарт) позволяет владельцу NFT делегировать cold wallet → hot wallet. Для membership систем это важно: держатели хранят дорогой NFT в cold wallet, а взаимодействуют через горячий. Интеграция:
IDelegationRegistry constant DELEGATION_REGISTRY = IDelegationRegistry(0x00000000000076A84feF008CDAbe6409d2FE638B); function isMember(address user) public view returns (bool) { if (balanceOf(user) > 0) return true; // Проверяем делегирование address[] memory delegators = DELEGATION_REGISTRY.getDelegationsByDelegate(user); for (uint i = 0; i < delegators.length; i++) { if (balanceOf(delegators[i]) > 0) return true; } return false; } Это реальная потребность: Moonbirds, Doodles и другие крупные коллекции интегрировали delegate.cash именно для этого.
Mint механизм и ценообразование
Allowlist через Merkle Tree — стандарт для presale. Экономия газа достигает 70% по сравнению с хранением списка на контракте:
bytes32 public merkleRoot; function allowlistMint(bytes32[] calldata proof) external payable { bytes32 leaf = keccak256(abi.encodePacked(msg.sender)); require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof"); require(msg.value >= PRICE, "Insufficient payment"); _safeMint(msg.sender, _nextTokenId()); } Proof генерируется off-chain (merkletreejs), root загружается в контракт. Список на 10,000 адресов — proof из ~14 хэшей, calldata ~450 байт.
Пример расчёта Merkle proof для 10,000 адресов
Дерево глубины 14 содержит 16384 листа. Для proof из 14 хэшей (32 байта каждый) calldata составляет 14*32 + 4 (смещение) ≈ 452 байта. При gas price 100 gwei экономия относительно хранения списка на контракте — около 0.005 ETH.
Что входит в работу
- Аудит текущей архитектуры и постановка требований
- Разработка смарт-контрактов (Solidity 0.8.x, OpenZeppelin)
- Написание unit-тестов (Foundry/Hardhat) с покрытием edge cases
- Интеграция с бэкендом (EIP-1271, delegate.cash) и фронтендом (wagmi, RainbowKit)
- Документация контрактов (NatSpec) и инструкции по развертыванию
- Развертывание в mainnet/testnet, настройка верификации в Etherscan
- Поддержка после запуска (опционально)
Ориентиры по срокам
ERC-721 membership с тирами и Merkle allowlist — от 2 дней. Добавление временной подписки (ERC-5643 стиль) + бэкенд верификация — ещё 1-2 дня. Полная система с делегированием, soulbound recovery и фронтендом — 4-5 дней. Свяжитесь с нами для точной оценки вашего проекта. Закажите разработку системы и получите консультацию нашего инженера.







