Extension Usage
Inherit ERC721Envious
To begin minting an ERC721Envious NFT Collection, it is recommended to follow the OpenZeppelin ERC721 standard for actual EIP-721 implementation. Follow the steps outlined below to mint an ERC721Envious NFT Collection.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./contracts/extension/ERC721Envious.sol";
contract YourCollectionName is ERC721Envious {
constructor (string memory name, string memory symbol) ERC721(name, symbol) {}
}
Basic Customization
Many NFT collections prefer to use IPFS, which is an excellent tool for Web3 developers. Let’s assume that every new NFT is minted after the asset is designed and uploaded onto IPFS. This way, the NFT collection will have the creator role.
While creating a unique IPFS hash for every new NFT is not a standard way of implementing NFT, it is still feasible by overriding the tokenURI
function logic.
Hint
This is a simplified method to incorporate smart contract admin. Make sure to incorporate any logic that suits the project requirements.
Sending native coins to a smart contract by mistake can lead to the permanent loss of those coins. The issue can be resolved by implementing a receive
function and rerouting the mistake.
It is recommended to use ERC721Enumerable to improve the search capabilities in the collection.
ERC721Envious standard enables developers to create their own distribution logic for the _disperse
function, which is responsible for distributing funds across assets in a collection. The standard also offers customizable templates for this function. However, by default, the _disperse
function splits the contract’s balance equally among all NFTs in the collection.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./contracts/extension/ERC721Envious.sol";
import "@openzeppelin/token/ERC721/extensions/ERC721Enumerable.sol";
contract YourCollectionName is ERC721Envious, ERC721Enumerable {
address private immutable _creator;
uint256 private _tokenNumber;
mapping(uint256 => string) private _tokenURIs;
/* --snip-- */
receive() external payable {
_disperseTokenCollateral(msg.value, address(0));
}
function mint(address who, string memory hash) public override {
require(_msgSender() == address(this), "only for itself");
_tokenNumber += 1;
_safeMint(who, _tokenNumber);
}
function creatorMint(address who, string memory hash) external {
require(_msgSender() == _creator(), "only for creator");
mint(who);
_tokenURIs[_tokenNumber] = hash;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
_requireMinted(tokenId);
return _tokenURIs[tokenId];
}
}
Defining Disperse Function
In most cases the default dispersion method will follow its classic definition in which the entire balance splits equality among all NFTs in the collection. However, the _disperse
function is left undefined so that developers can define their own custom dispersion logic tailored to their specific project needs.
Hint
When the _disperse
function is executed the collateral amount is not transferred under the corresponding tokenId. To keep track of this intermediate step, it is necessary to store both the total disperse balance and the disperse balance for each tokenId.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./contracts/extension/ERC721Envious.sol";
import "@openzeppelin/token/ERC721/extensions/ERC721Enumerable.sol";
contract YourCollectionName is ERC721Envious, ERC721Enumerable {
/* --snip-- */
function _disperse(address tokenAddress, uint256 tokenId) internal virtual override {
uint256 balance = disperseBalance[tokenAddress] / totalSupply();
if (disperseTotalTaken[tokenAddress] + balance > disperseBalance[tokenAddress]) {
balance = disperseBalance[tokenAddress] - disperseTotalTaken[tokenAddress];
}
if (balance > disperseTaken[tokenId][tokenAddress]) {
uint256 amount = balance - disperseTaken[tokenId][tokenAddress];
disperseTaken[tokenId][tokenAddress] += amount;
(bool shouldAppend,) = _arrayContains(tokenAddress, collateralTokens[tokenId]);
if (shouldAppend) {
collateralTokens[tokenId].push(tokenAddress);
}
collateralBalances[tokenId][tokenAddress] += amount;
disperseTotalTaken[tokenAddress] += amount;
}
}
}
Clean Up
Since both ERC721Enumerable and ERC721Envious inherit from the ERC721 standard, there may be some intersections that can cause errors during compilation. All intersections should be overridden to compile without issues.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./contracts/extension/ERC721Envious.sol";
import "@openzeppelin/token/ERC721/extensions/ERC721Enumerable.sol";
contract YourCollectionName is ERC721Envious, ERC721Enumerable {
/* --snip-- */
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721Envious, ERC721Enumerable, ERC721)
returns (bool)
{
return ERC721Envious.supportsInterface(interfaceId) || ERC721Enumerable.supportsInterface(interfaceId);
}
function _disperse(address tokenAddress, uint256 tokenId) internal virtual override {
uint256 balance = disperseBalance[tokenAddress] / totalSupply();
if (disperseTotalTaken[tokenAddress] + balance > disperseBalance[tokenAddress]) {
balance = disperseBalance[tokenAddress] - disperseTotalTaken[tokenAddress];
}
if (balance > disperseTaken[tokenId][tokenAddress]) {
uint256 amount = balance - disperseTaken[tokenId][tokenAddress];
disperseTaken[tokenId][tokenAddress] += amount;
(bool shouldAppend,) = _arrayContains(tokenAddress, collateralTokens[tokenId]);
if (shouldAppend) {
collateralTokens[tokenId].push(tokenAddress);
}
collateralBalances[tokenId][tokenAddress] += amount;
disperseTotalTaken[tokenAddress] += amount;
}
}
}
Final Code
Code can now be deployed to any EVM-compatible blockchain network and let the code handle the rest. Developers have the freedom to explore and integrate gamification features around collateralization
and/or dispersion
as they see fit. More advanced Solidity developers will find this powerful tool inspiring to design creative and diverse solutions that push the boundaries of both NFT and DeFi ecosystems.
Please find the final version of the code below:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./contracts/extension/ERC721Envious.sol";
import "@openzeppelin/token/ERC721/extensions/ERC721Enumerable.sol";
contract YourCollectionName is ERC721Envious, ERC721Enumerable {
address private immutable _creator;
uint256 private _tokenNumber;
mapping(uint256 => string) private _tokenURIs;
constructor (string memory name, string memory symbol) ERC721(name, symbol) {
_creator = _msgSender();
}
receive() external payable {
_disperseTokenCollateral(msg.value, address(0));
}
function mint(address who, string memory hash) public override {
require(_msgSender() == address(this), "only for itself");
_tokenNumber += 1;
_safeMint(who, _tokenNumber);
}
function creatorMint(address who, string memory hash) external {
require(_msgSender() == _creator(), "only for creator");
mint(who);
_tokenURIs[_tokenNumber] = hash;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
_requireMinted(tokenId);
return _tokenURIs[tokenId];
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721Envious, ERC721Enumerable, ERC721)
returns (bool)
{
return ERC721Envious.supportsInterface(interfaceId) || ERC721Enumerable.supportsInterface(interfaceId);
}
}