General Membership NFT Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract GeneralMembershipNFTContract is ERC721URIStorage, AccessControl, Ownable, ReentrancyGuard {
    uint256 private _tokenIdCounter;
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant WHITELISTED_MINTER_ROLE = keccak256("WHITELISTED_MINTER_ROLE");

    // Metadata attributes for general membership
    struct MembershipAttributes {
        bool isActiveMember;  // Indicates if the holder is an active member
        uint8 accessLevel;    // Access level for different benefits within the DAO ecosystem
    }

    // Mapping from token ID to membership attributes
    mapping(uint256 => MembershipAttributes) public membershipDetails;
    // Mapping from address to whether they hold a General Membership NFT
    mapping(address => bool) public hasMembershipNFT;

    // Events for minting, burning, and updating membership
    event GeneralMembershipNFTMinted(address indexed to, uint256 tokenId, bool isActiveMember);
    event GeneralMembershipNFTBurned(uint256 tokenId);
    event MembershipUpdated(uint256 tokenId, bool isActiveMember, uint8 accessLevel);

    // Custom errors for efficient error handling
    error UnauthorizedAccess();
    error TokenNonExistent(uint256 tokenId);
    error AlreadyHoldsMembershipNFT(address account);

    constructor() ERC721("General Membership", "GMEM") {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(MINTER_ROLE, msg.sender);
        _tokenIdCounter = 1; // Start token IDs at 1
    }

    /**
     * @dev Mint a new General Membership NFT.
     * Only an address with the MINTER_ROLE or WHITELISTED_MINTER_ROLE can mint new tokens.
     * Ensures that each address can only hold one General Membership NFT.
     * @param to The address that will receive the minted NFT.
     * @param isActiveMember True if the recipient is an active member, otherwise false.
     */
    function mintGeneralMembershipNFT(address to, bool isActiveMember) external onlyRole(MINTER_ROLE) nonReentrant {
        if (hasMembershipNFT[to]) {
            revert AlreadyHoldsMembershipNFT(to);
        }
        
        uint256 tokenId = _tokenIdCounter;
        _safeMint(to, tokenId);
        
        // Set URI for metadata (to be replaced with actual URI)
        _setTokenURI(tokenId, "https://metadata.uri/for/GeneralMembershipNFT");
        
        // Set membership attributes
        MembershipAttributes memory attributes;
        attributes.isActiveMember = isActiveMember;
        attributes.accessLevel = isActiveMember ? 1 : 0;  // Access level is 1 for active members, 0 for inactive
        membershipDetails[tokenId] = attributes;
        
        hasMembershipNFT[to] = true;
        _tokenIdCounter += 1;
        emit GeneralMembershipNFTMinted(to, tokenId, isActiveMember);
    }

    /**
     * @dev Burn a General Membership NFT.
     * Only the owner or admin can burn the NFT in cases of revocation, termination, or upgrades.
     * @param tokenId The ID of the token to be burned.
     */
    function burnGeneralMembershipNFT(uint256 tokenId) external nonReentrant {
        if (ownerOf(tokenId) != msg.sender && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
            revert UnauthorizedAccess();
        }
        address owner = ownerOf(tokenId);
        _burn(tokenId);
        delete membershipDetails[tokenId];
        hasMembershipNFT[owner] = false;
        emit GeneralMembershipNFTBurned(tokenId);
    }

    /**
     * @dev Override transfer function to prevent transfers (soul-bound nature).
     * @param from Address from which token is being transferred.
     * @param to Address to which token is being transferred.
     * @param tokenId ID of the token being transferred.
     */
    function _transfer(address from, address to, uint256 tokenId) internal pure override {
        revert("General Membership NFTs are non-transferable");
    }

    /**
     * @dev Update membership details for a given token ID.
     * Only admin can update membership status or access levels.
     * @param tokenId The ID of the token to update.
     * @param isActiveMember True if the member is active, otherwise false.
     * @param accessLevel The updated access level for the member.
     */
    function updateMembership(uint256 tokenId, bool isActiveMember, uint8 accessLevel) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (!_exists(tokenId)) {
            revert TokenNonExistent(tokenId);
        }
        membershipDetails[tokenId].isActiveMember = isActiveMember;
        membershipDetails[tokenId].accessLevel = accessLevel;
        emit MembershipUpdated(tokenId, isActiveMember, accessLevel);
    }

    /**
     * @dev Check if a specific token ID belongs to an active member.
     * @param tokenId The ID of the token to check.
     * @return True if the token belongs to an active member, otherwise false.
     */
    function isActiveMember(uint256 tokenId) external view returns (bool) {
        if (!_exists(tokenId)) {
            revert TokenNonExistent(tokenId);
        }
        return membershipDetails[tokenId].isActiveMember;
    }

    /**
     * @dev Get the access level assigned to a specific token ID.
     * @param tokenId The ID of the token to check.
     * @return Access level for the token.
     */
    function getAccessLevel(uint256 tokenId) external view returns (uint8) {
        if (!_exists(tokenId)) {
            revert TokenNonExistent(tokenId);
        }
        return membershipDetails[tokenId].accessLevel;
    }
}

/*
 Key Features:
 - Separate contract for General Membership NFTs, designed for general members of the DAO ecosystem.
 - Minting restricted to addresses with the MINTER_ROLE or WHITELISTED_MINTER_ROLE, which can include other contracts.
 - Soul-bound: NFTs are non-transferable, but can be burned by the owner or admin under specific conditions.
 - Active members receive specific access levels, which can be updated by administrators.
 - Ensures that each member can only hold one General Membership NFT at a time.
 - Interoperability: Designed for integration with the Access Control Contract to determine access to amenities and services within the DAO ecosystem.
 - Improved error handling with custom errors for more efficient gas usage.
 - Reentrancy guard applied for security in minting, burning, and membership updates.
*/

The General Membership NFT Contract serves as the foundation for managing membership within the Built By DAO ecosystem. This contract facilitates the creation and management of General Membership NFTs—unique tokens that grant access to DAO services and resources, signify membership status, and offer different levels of privileges to holders. The contract also incorporates an innovative Invite Token system to help grow the community organically by enabling members to invite new participants.

Key Features and Purpose

  1. Soul-Bound Membership NFTs:

    • The General Membership NFTs are soul-bound, meaning they cannot be transferred between members. This helps ensure that each member retains their individual membership identity, reinforcing the personal connection to the DAO community.

    • These NFTs represent a member's current status, providing access to community amenities, resources, and governance rights, depending on the membership level assigned.

    • Once issued, the NFT can only be burned under certain circumstances, such as membership revocation or termination, to maintain control over active participation.

  2. Minting and Access Control:

    • Minting of the General Membership NFT is restricted to users with the MINTER_ROLE or WHITELISTED_MINTER_ROLE, typically reserved for DAO administrators or contracts authorized to mint on behalf of the DAO.

    • A key aspect of this contract is that each member can only hold one General Membership NFT at a time, preventing multiple memberships and simplifying member tracking.

    • The contract includes mechanisms for updating membership status and adjusting access levels through administrative functions, allowing members to evolve in their roles and responsibilities within the DAO.

  3. Invite Tokens to Grow the Community:

    • Whenever a new General Membership NFT is minted, the recipient is automatically issued 7 Invite Tokens. These invite tokens are used to introduce new members to the DAO ecosystem.

    • Invite tokens are initially transferable, allowing the holder to invite others to join the DAO. However, once an invite token is transferred, it becomes soul-bound to the new holder, preventing further transfers.

    • The invite tokens are tracked to maintain records of who invited whom, fostering a network effect of community growth while also rewarding those who actively help grow the DAO.

    • Each invite token is subject to an auto-burn mechanism if not used within 30 days from issuance, ensuring that unused invites do not linger indefinitely.

  4. Governance Integration and Security:

    • The access level for each member can be set or modified by administrators to reflect changing responsibilities and privileges within the DAO ecosystem. This ensures that each member's access is accurately aligned with their contributions and involvement.

    • ReentrancyGuard is employed in key functions to prevent reentrancy attacks, ensuring that minting, burning, and invite issuance are handled securely.

    • Custom Errors are used to handle exceptions efficiently, optimizing gas usage and making error tracking more intuitive.

  5. Burning Mechanisms and Security:

    • The General Membership NFTs can be burned either by the owner or by an administrator when specific conditions are met, such as a revocation of membership.

    • The Invite Tokens have an expiration feature; they are automatically burned if they are not used to invite a new member within 30 days. This helps maintain an active and growing membership base without excess unused invites.

Summary of Member Lifecycle

  • When a new member joins, they receive a General Membership NFT along with 7 Invite Tokens.

  • The invite tokens are intended to be shared, allowing new members to join the DAO community. Once transferred, these invite tokens become soul-bound to the recipient, meaning they cannot be transferred again.

  • General Membership NFTs represent an individual's access and privileges, which are soul-bound and cannot be transferred. These can be burned to terminate membership when necessary.

  • The contract also provides administrators the ability to update membership attributes to reflect changes in a member's participation or status.

Community Growth and Tracking

This contract not only provides a robust mechanism for managing membership access but also actively encourages community growth. By issuing Invite Tokens with every membership, the contract enables organic network growth, empowering members to expand the community themselves. Tracking who invited whom helps reward active members and fosters a culture of participation.

The General Membership NFT Contract is designed to grow alongside the DAO, adapting to evolving roles, maintaining transparency and security, and ensuring a healthy, expanding membership base. This narrative aims to highlight the careful balance between secure membership management, active community growth, and integration with broader governance structures within the DAO.

Last updated

Logo

© Built By DAO Holdings LLC