Розробка NFT-membership системи: смарт-контракти та архітектура

У нашій практиці ми стикалися з десятками інтеграцій NFT-membership і бачили, як помилкова архітектура контракту призводить до витоку привілеїв. Найпоширеніша помилка: розробники реалізують перевірку `ownerOf(tokenId) == msg.sender` і вважають задачу вирішеною. Але NFT можна позичити, flash loan-нут

Напрямки блокчейн-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1450
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1309
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1003
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1269
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    719
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1009

У нашій практиці ми стикалися з десятками інтеграцій 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.

Що входить в роботу

  1. Аудит поточної архітектури та постановка вимог
  2. Розробка смарт-контрактів (Solidity 0.8.x, OpenZeppelin)
  3. Написання unit-тестів (Foundry/Hardhat) з покриттям edge cases
  4. Інтеграція з бекендом (EIP-1271, delegate.cash) та фронтендом (wagmi, RainbowKit)
  5. Документація контрактів (NatSpec) та інструкції з розгортання
  6. Розгортання в mainnet/testnet, налаштування верифікації в Etherscan
  7. Підтримка після запуску (опціонально)

Орієнтири за термінами

ERC-721 membership з тирами та Merkle allowlist — від 2 днів. Додавання тимчасової підписки (ERC-5643 стиль) + бекенд верифікація — ще 1-2 дні. Повна система з делегуванням, soulbound recovery та фронтендом — 4-5 днів. Зв'яжіться з нами для точної оцінки вашого проєкту. Замовте розробку системи та отримайте консультацію нашого інженера.