docs
About

Collectible

License: Apache 2.0

Simple Summary

The Collectible contract implements economically sustainable NFTs using features derived from the ERC721 standard. It integrates dynamic minting fees, role-based access control, and donation-based engagement. These mechanisms align tokenomics with principles of scarcity and value attribution while fostering community engagement between creators and contributors.

Abstract

The Collectible contract enables creators to mint non-fungible tokens (NFTs) and interact with contributors through donations and creator signature functionalities. Minting fees dynamically adjust based on user activity and a decay model, encouraging sustained engagement while ensuring economic sustainability. Contributors can support creators directly, and users can gain access to special creator roles by paying a fee.

Motivation

Traditional NFT systems often encounter challenges such as inflationary supply and limited engagement incentives. The Collectible contract addresses these challenges by:

  1. Implementation of dynamic minting rate and controlled adjustments to and encourage engagement.
  2. Introducing donation mechanics to foster community-driven contributions.
  3. Defining clear role-based privileges to support structured interactions between creators and contributors.

Specification

  1. Variables and Constants

    mintBaseFee

    Type: uint256
    Description:
    The base fee for minting a token, paid by the user to create a new token.

    creatorSignatureFee

    Type: uint256
    Description:
    The fee required for a user to acquire a creator's signature, allowing them to become a creator in the system.

    maxMintsPerUserInCycle

    Type: uint256
    Description:
    The maximum number of mints a user can perform in a cycle. Once the limit is exceeded, the user's minting count is reset.

    lastUpdateTimestamp

    Type: uint256
    Description:
    Timestamp of the last time the contract terms were updated (e.g., minting fees and creator signature fees). It is used to determine when the contract's terms can be updated again.

    UPDATE_INTERVAL

    Type: uint256 (constant)
    Description:
    The time interval between contract terms updates. MUST be fixed to 30 days.

    CREATOR_ROLE

    Type: bytes32 (constant)
    Description:
    The role identifier for creators in the system.

    CONTRIBUTOR_ROLE

    Type: bytes32 (constant)
    Description:
    The role identifier for contributors in the system.

    mintsPerUserInCycle

    Type: mapping(address => uint256)
    Description:
    A mapping that tracks the number of mints a user has performed in the current cycle. It is used to enforce the maximum minting limit per user.


  2. Events

    CreatorTermsUpdated(uint256 mintBaseFee, uint256 creatorSignatureFee, uint256 maxMintsPerUserInCycle)

    Description:
    Emitted when the contract terms related to minting are updated by the DEFAULT_ADMIN_ROLE.


    DonationReceived(address from, address to, uint256 amount)

    Description:
    Emitted when a user donates ETH to a creator. This event tracks the details of the donation, including the donor's address, the recipient's address, and the donation amount.

    Parameters:

    • from: The address of the user making the donation.
    • to: The address of the creator receiving the donation.
    • amount: The amount of ETH donated.

  3. Modifiers

    onlyIfNotPaused

    Description:
    Ensures that the contract MUST NOT be paused when executing a function.

    onlyTokenOwner(uint256 tokenId)

    Description:
    Ensures that the caller is the owner of the specified token. This is checked by comparing the caller's address with the owner of the token identified by tokenId.

    updateCooldown

    Description:
    Ensures that the function can only be executed after the update interval has passed since the last contract term update.

    modifier updateCooldown() {
        require(
            block.timestamp >= lastUpdateTimestamp + UPDATE_INTERVAL, 
            "Updates not available until contract update interval is reached."
        );
        _;
    }

  4. Functions

    function safeMint(string memory uri)

    Type: Public and payable function
    Description:
    Allows the caller to mint a new token to their address with a provided URI.

    function safeMint(string memory uri)
    public
    payable
    override 
    onlyIfNotPaused
    nonReentrant
    onlyRole(CREATOR_ROLE)
    {
        bool userMintsExceeded = mintsPerUserInCycle[msg.sender] + 1 > maxMintsPerUserInCycle;
     
        require(msg.value >= mintFee(), "Not enough ETH!");
     
        uint256 tokenId = currentTokenId++;
        _safeMint(msg.sender, tokenId);
        _setTokenURI(tokenId, uri);
     
        if(userMintsExceeded){
            mintsPerUserInCycle[msg.sender] = 0;
        }
        mintsPerUserInCycle[msg.sender]++;
    }

    Requirements:

    • The caller MUST have the CREATOR_ROLE.
    • The user MUST pay the minting fee, which is dynamic based on their previous minting activity.
    • If the minting limit is exceeded, the user's mint count SHALL be reset.
    • The modifier nonReentrant is REQUIRED to prevent reentrancy attacks.
    • The modifier onlyIfNotPaused is RECOMMENDED.

    mintFee

    Type: Public view function
    Description:
    Calculates and returns the current minting fee that the caller MUST pay, based on the number of mints performed during his current cycle. Is RECOMMENDED that the fee use a logarithmic reduction to adjust the fee smoothly.

    function mintFee() public view returns (uint256) {
        if (hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) return 0;
        uint256 userMints = mintsPerUserInCycle[msg.sender];
        uint256 divisor = userMints == 0 ? 1 : Math.log2(userMints + 2);
        return mintBaseFee / divisor;
    }

    Requirements:

    • If the caller has the DEFAULT_ADMIN_ROLE, the fee can be OPTIONAL. Otherwise, the minting fee MUST be paid by the caller.

    donate(address creator)

    Type: Public and payable function
    Description:
    Allows users to donate ETH to a creator, helping fund their activities. After making a donation, the donor SHALL receive the CONTRIBUTOR_ROLE.

    Requirements:

    • The provided address MUST be a valid creator (having the CREATOR_ROLE).
    • The msg.caller MUST NOT be the same as the creator.
    • The donation amount MUST be greater than zero.

    Events:

    • MUST emit a DonationReceived event after the donation is processed.

    getCreatorSignature

    Type: Public and payable function
    Description:
    Allows a user to acquire a creator's signature by paying the required fee.

    function getCreatorSignature() public payable onlyIfNotPaused;

    Requirements:

    • It's RECOMMENDED to use the modifier onlyIfNotPaused to pause during emergencies.
    • The caller MUST pay the creator signature fee.
    • After the payment, the caller SHALL be granted the CREATOR_ROLE.
    • The modifier onlyIfNotPaused is RECOMMENDED.

    updateTerms(uint256 mintBaseFee, uint256 creatorSignatureFee, uint256 maxMintsPerUserInCycle)

    Type: Public function
    Description:
    Allows the admin to update the minting fee, creator signature fee, and the maximum mints per user in a cycle. The update MUST only be executed after the updateCooldown period has passed.

    Requirements:

    • Only the DEFAULT_ADMIN_ROLE MUST call this function.
    • The updateCooldown period SHALL be respected before another update can occur.

    Events:

    • MUST emit a CreatorTermsUpdated event after the contract terms are updated.

    withdraw(uint256 amount)

    Type: Public function
    Description:
    Allows the DEFAULT_ADMIN_ROLE to withdraw ETH from the contract.

    function withdraw(uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) nonReentrant;

    Requirements:

    • Only the DEFAULT_ADMIN_ROLE MUST call this function.
    • The nonReentrant modifier is REQUIRED.

    burn(uint256 tokenId)

    Type: Public function
    Description:
    Allows the owner of a token to burn (destroy) the token specified by tokenId.

    Requirements:

    • The caller MUST be the owner of the token according with onlyTokenOwner(tokenId) modifier.

    pause

    Type: Public function
    Description:
    Allows the DEFAULT_ADMIN_ROLE to pause the contract, disabling certain functions.

    Requirements:

    • Only the DEFAULT_ADMIN_ROLE SHOULD call this function to pause the contract.

    unpause

    Type: Public function
    Description:
    Allows the DEFAULT_ADMIN_ROLE to unpause the contract, re-enabling functionality.

    Requirements:

    • Only the DEFAULT_ADMIN_ROLE SHOULD call this function to unpause the contract.

Rationale

The design encourages:

  • Engagement: Dynamic fees incentivize long-term interaction.
  • Community Support: Donations allow contributors to support their favorite creators directly.
  • Sustainability: Role-based access and adjustable fees ensure an economically balanced system.

Backward Compatibility

The contract is fully compatible with the ERC721 standard and integrates seamlessly with OpenZeppelin extensions such as ERC721URIStorage, ERC721Pausable, and AccessControl. Its modular design allows for future scalability and adaptation.

References

License

Copyright [2024] [gfLobo]

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:

http://www.apache.org/licenses/LICENSE-2.0 (opens in a new tab)

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.