Collectible
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:
- Implementation of dynamic minting rate and controlled adjustments to and encourage engagement.
- Introducing donation mechanics to foster community-driven contributions.
- Defining clear role-based privileges to support structured interactions between creators and contributors.
Specification
-
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.
-
Events
CreatorTermsUpdated(uint256 mintBaseFee, uint256 creatorSignatureFee, uint256 maxMintsPerUserInCycle)
Description:
Emitted when the contract terms related to minting are updated by theDEFAULT_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.
-
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 bytokenId
.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." ); _; }
-
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 thecreator
. - 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 theupdateCooldown
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 theDEFAULT_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 bytokenId
.Requirements:
- The caller MUST be the owner of the token according with
onlyTokenOwner(tokenId)
modifier.
pause
Type: Public function
Description:
Allows theDEFAULT_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 theDEFAULT_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
- ERC721 - Non-Fungible Token Standard (opens in a new tab)
- OpenZeppelin Contracts (opens in a new tab)
- OpenZeppelin Math (opens in a new tab)
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.