This EIP proposes a standard for creating economically sustainable NFT governance for collections built on collaborative models based on ERC-721. It introduces dynamic minting fees, role-based access control, and a donation-based engagement model to enhance creator-community interactions. These mechanisms aim to balance scarcity, incentivize meaningful participation, and ensure sustainable growth for both creators and contributors.
The model defines "economically sustainable" as tokens whose minting value, creator subscription fees, and token quantity within each progressive discount cycle can only be adjusted once every 30 days from the last update by an ADMIN
user. These mechanisms prevent excessive administrative modifications that could disrupt market stability, ensuring consistent price discovery and maintaining participant confidence. By aligning incentives and fostering predictability, the model creates a robust framework for engagement and value creation.
As the NFT market matures, one of the recurring challenges faced by both creators and users is the inflationary nature of supply and the lack of effective mechanisms to engage the community meaningfully. NFT collections built on collaborative models require governance systems that empower all stakeholders—creators, contributors, and collectors—while also maintaining long-term economic sustainability. The introduction of this proposal aims to solve these issues by fostering a more dynamic, flexible, and transparent system for NFT collections. This EIP addresses these gaps by introducing:
- Role-Based Access: Empowering creators while ensuring transparent governance by admins.
- Dynamic Minting Fees: To align token costs with user activity and ownership.
- Donation-Based Engagement: Encouraging contributions to creators.
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
The following interface MUST be implemented.
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;
interface IERC7832 is IERC721 {
// Events
event CreatorTermsUpdated(
uint256 mintBaseFee,
uint256 creatorSignatureFee,
uint256 maxMintsPerUserInCycle);
event DonationReceived(address from, address to, uint256 amount);
// Function to get the current token ID
function currentTokenId() external view returns (uint256);
// Function to get the current mint base fee
function mintBaseFee() external view returns (uint256);
// Function to get the creator signature fee
function creatorSignatureFee() external view returns (uint256);
// Function to get the maximum mints a user can perform during his progressive discount cycle per mint
function maxMintsPerUserInCycle() external view returns (uint256);
// Function to get the last update timestamp
function lastUpdateTimestamp() external view returns (uint256);
// Function to get the update interval constant (30 days)
function UPDATE_INTERVAL() external pure returns (uint256);
// Function to get the CREATOR_ROLE
function getCreatorSignature() external payable;
// Function to get the CONTRIBUTOR_ROLE identifier
function CONTRIBUTOR_ROLE() external pure returns (bytes32);
// Function to get CREATOR_ROLE identifier
function CREATOR_ROLE() external pure returns (bytes32);
// Function to get ADMIN_ROLE identifier
function ADMIN_ROLE() external pure returns (bytes32);
// Function to check the number of mints a user has performed in their current cycle of progressive discounts
function mintsPerUserInCycle(address user) external view returns (uint256);
// Allow users to donate ETH to a specific creator in the system.
function donate(address creator) external payable;
// Allow users to check their mint fee
function mintFee() external view returns (uint256);
// Allow token owners to burn their tokens
function burn(uint256 tokenId) external;
// Allow ADMIN_ROLE pause the contract
function pause() external;
// Allow ADMIN_ROLE unpause the contract
function unpause() external;
// Allow CREATOR_ROLE to mint
function safeMint(string memory uri) external payable;
// Allow ADMIN_ROLE to update contract terms
function updateTerms(
uint256 mintBaseFee,
uint256 creatorSignatureFee,
uint256 maxMintsPerUserInCycle
) external;
// Allow ADMIN_ROLE to withdraw funds from the contract
function withdraw(uint256 amount) external;
}
currentTokenId()
Description:
Tracks the current token ID in the system. Can also be used to determine how many tokens have been minted in the system.
mintBaseFee()
Description:
The base fee for minting a token, paid by the user to create a new token.
creatorSignatureFee()
Description:
The fee required for a user to acquire a creator's signature, allowing them to become a creator in the system.
maxMintsPerUserInCycle()
Description:
The maximum number of mints a user can perform during their current cycle of progressive discounts. Once the limit is exceeded, the user's minting count is reset to zero.
lastUpdateTimestamp()
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()
Description:
The time interval between contract terms updates. MUST be fixed to 30 days.
ADMIN_ROLE()
Description:
The role identifier for admins in the system.
Requirements:
- MUST be assigned inside the constructor to the msg.sender
.
CREATOR_ROLE()
Description:
The role identifier for creators in the system.
Requirements:
- MUST be assigned inside the constructor to the msg.sender
.
CONTRIBUTOR_ROLE()
Description:
The role identifier for contributors in the system.
mintsPerUserInCycle(address user)
Description:
Tracks the number of mints a user has performed in their current cycle of progressive discounts. It is used to enforce the maximum minting limit per user.
CreatorTermsUpdated(uint256 mintBaseFee, uint256 creatorSignatureFee, uint256 maxMintsPerUserInCycle)
Description:
Emitted when the contract terms related to minting are updated by the 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.
safeMint(string memory uri)
Description:
Allows the caller to mint a new token to their address with a provided URI.
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 to zero.
- Is RECOMMENDED to require that the contract is not paused before using this function.
mintFee()
Description:
Calculates and returns the current minting fee that the caller MUST pay, based on the number of mints performed during his current discount per mint cycle. Is RECOMMENDED that the fee use a logarithmic reduction to adjust the fee smoothly.
Formula:
\text{mintFee} =
\begin{cases}
0, & \text{if msg.sender has the ADMIN\_ROLE} \\
\frac{\text{mintBaseFee}}{\log_x(\text{userMints})}, & \text{if } \text{userMints} > 1 \\
\frac{\text{mintBaseFee}}{1}, & \text{if } \text{userMints} <= 1
\end{cases}
Note: Please note that the returned logarithm is always rounded to integers due to the characteristics of Solidity with floating-point numbers.
Requirements:
- The minting fee MUST be paid by the caller.
donate(address creator)
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.sender
MUST NOT be the same as the creator
.
- The donation amount MUST be greater than zero.
- MUST emit a DonationReceived
event after the donation is processed.
getCreatorSignature()
Description:
Allows a user to acquire a creator's signature by paying the required fee.
Requirements:
- The caller MUST pay the creator signature fee.
- After the payment, the caller SHALL be granted the CREATOR_ROLE.
- Is RECOMMENDED to require that the contract is not paused before using this function.
updateTerms(uint256 mintBaseFee, uint256 creatorSignatureFee, uint256 maxMintsPerUserInCycle)
Description:
Allows the admin to update the minting fee, creator signature fee, and the maximum mints per user in a cycle of progressive discounts.
Requirements:
- Only the ADMIN_ROLE
MUST call this function.
- MUST be called in the contract constructor as the first update of the contract terms.
- The update interval period SHALL be respected before another update can occur.
- MUST emit a CreatorTermsUpdated
event after the contract terms are updated.
withdraw(uint256 amount)
Description:
Allows the ADMIN_ROLE
to withdraw ETH from the contract.
Requirements:
- Only the ADMIN_ROLE
MUST call this function.
burn(uint256 tokenId)
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.
pause()
Description:
Allows the ADMIN_ROLE
to pause the contract, disabling certain functions.
Requirements:
- Only the ADMIN_ROLE
SHOULD call this function to pause the contract.
unpause()
Description:
Allows the ADMIN_ROLE
to unpause the contract, re-enabling functionality.
Requirements:
- Only the ADMIN_ROLE
SHOULD call this function to unpause the contract.
Below are the key considerations and justifications for the design choices:
Solution: By introducing role-based access control, this standard ensures that only trusted actors can perform sensitive actions. Admins are responsible for updating contract terms, pausing or unpausing the contract, and withdrawing funds, while creators can manage their own collections and get donations. This prevents arbitrary changes that might harm market stability or erode community trust.
Dynamic Minting Fees
Solution: By dynamically adjusting minting fees based on user activity within defined minting cycles, we ensure that users are incentivized to engage with the platform by receiving gradual discounts as they mint. Using a logarithmic reduction in minting fees ensures that the process is gradual, preventing market manipulation and maintaining scarcity over time.
Donation-Based Engagement
This EIP is fully compatible with ERC-721. Extensions like dynamic minting fees, donation systems are modular and do not impact existing NFT token functionalities.
mintFee()
function mintFee() public view returns (uint256) {
if (hasRole(ADMIN_ROLE, msg.sender)) return 0;
uint256 userMints = mintsPerUserInCycle(msg.sender);
uint256 divisor = userMints <= 1 ? 1 : Math.log2(userMints);
return mintBaseFee / divisor;
}
safeMint(string memory 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)++;
}
safeMint
, withdraw
and donate
are protected against reentrancy attacks.Copyright and related rights waived via CC0.