[ad_1]
Security, libraries, functions, attributes. Let’s build a solid basic mint contract step by step.
Hello strangers, quick reminder to get started, NFTs are just some pieces of code running on a blockchain node somewhere. Smart Contracts are also a piece of code that is fully executed in Ethereum Virtual Machines (EVMs). In this article, I want to explain what this code snippet looks like by going deeper.
Before continuing, I will talk about the basic requirements that I have agreed on as a result of my own research. I’m not claiming that this is the most perfect smart contract or that it is the best I’m going to tell you about.
First, we need to create our structure. What we want from our smart contracts. Let’s define the need. We want to create a safe, uncomplicated mint contract, right?
I will follow these steps:
- Describe the library to import step by step
- Declare smart contract attributes, modifiers, and events
- Create our method
- Deploy our smart contracts
Libraries to import
Every developer needs helpers. There’s no point in writing the same code over and over again. That’s why we’ll be using some very useful OpenZeppelin libraries for our projects.
ERC721
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
A non-fungible token is one that cannot be shared or immediately exchanged with any other ERC-721 token, and the Ethereum Request for Comments (ERC) 721 data standard is used to create these non-fungible tokens.
The ERC721 library provides a basic implementation of the ERC721 standard, including the methods required to create, own, and transfer NFTs, as well as an optional method to query metadata about NFTs. This library can be used to implement basic NFT contracts. I recommend reading these documents for full understanding.
https://eips.ethereum.org/EIPS/eip-721
https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#ERC721
ERC721IEnumarable
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
That ERC721Enumerable the library extends the functionality provided by ERC721 library by implementing IEnumerableERC721 interface, which allows efficient access and manipulation of NFT pools.
It includes methods for querying NFT collection properties, such as the number of tokens in the collection, the owner of a particular token, and the metadata associated with the token. It also includes methods for modifying collections, such as adding and removing tokens.
Counter (Util)
import "@openzeppelin/contracts@4.8.1/utils/Counters.sol";
The Counter utility in the OpenZeppelin library is a simple smart contract that allows you to track event counts. It provides a basic implementation of a counter, with methods to add, subtract, and get the current count. The Counter utility can be used in a variety of situations, such as counting the number of times an event has occurred, tracking the number of tokens in a contract, or calculating some other type of value.
The Counter Utility is designed to be easy to use and implement and provides a safe and reliable counter implementation. It also includes protection against overflow and underflow errors, ensuring that the count never goes below zero or exceeds the maximum value that can be stored in the uint256 variable.
ERC721URI Storage
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
The ERC721URIStorage library from OpenZeppelin is a smart contract implementation for storing the URI (Uniform Resource Identifier) for the ERC-721 non-fungible token (NFT) on the Ethereum blockchain.
This library follows the ERC-721 standard for NFTs and implements the basic functionality for setting and getting the URI for a specific NFT token ID.
The ERC721URIStorage library from OpenZeppelin is capable of the following:
- Stores the URI (Uniform Resource Identifier) for the ERC-721 non-fungible token (NFT) on the Ethereum blockchain.
- Implemented basic functionality to set and retrieve the URI for a specific NFT token ID.
- Provides a centralized storage solution for NFT URIs, enabling NFT owners, developers, and users to easily retrieve information about NFTs.
Ownable (Access)
import "@openzeppelin/contracts/access/Ownable.sol";
The Ownable Library from OpenZeppelin is a smart contract implementation for ownership management on the Ethereum blockchain. It provides a basic set of functions for controlling contractual ownership, including functions for transferring ownership, checking the current owner of the contract, and assigning operators (addresses authorized to perform actions on behalf of the current owner).
The Ownable Library from OpenZeppelin is a smart contract implementation for ownership management on the Ethereum blockchain. It provides a basic set of functions for controlling contractual ownership, including functions for transferring ownership, checking the current owner of the contract, and assigning operators (addresses authorized to perform actions on behalf of the current owner).
The Ownable Libraries from OpenZeppelin are capable of the following:
- Implement basic functionality for ownership management on the Ethereum blockchain.
- Provides functions for transferring ownership, checking the current owner of the contract, and setting the operator (address authorized to perform actions on behalf of the current owner).
- Follow best practices for security and code reuse in the Ethereum ecosystem.
ReentrancyGuard (Security)
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
First of all, I want to give a brief information about reentrancy attacks. A reentrancy attack is a type of attack in blockchain technology that exploits the ability of a contract to invoke another contract. An attacker can repeatedly invoke a vulnerable contract and perform its function many times before the first call finishes, draining its resources or changing its state.
In a reentrancy attack, the attacker invokes a vulnerable contract, which in turn invokes another contract controlled by the attacker. The second contract can then call the first contract again, causing endless call loops and resource consumption. This could result in a drain on the funds of a vulnerable contract or a change in their status, which could result in significant financial or operational losses.
Reentrancy attacks can be prevented using techniques such as mutex locks, state variables, or the ReentrancyGuard library from OpenZeppelin.
ReentrancyGuard is a smart contract security tool in the OpenZeppelin library that helps protect against reentrancy attacks. It works by allowing only one call to the contract at a time, ensuring that any recursive calls are blocked until the first call has finished executing. This helps prevent attackers from repeatedly invoking the contract and draining its resources.
The ReentrancyGuard library from OpenZeppelin is capable of:
- Protects against reentrancy attacks: This ensures that the contract is not vulnerable to attackers who can invoke it repeatedly and drain its resources.
- Allow only one call at a time: This allows only one call to the contract at a time, blocking any recursive calls until the first call has finished executing.
- Provides simple and effective solutions: ReentrancyGuard offers simple solutions to complex security problems, making it easier for developers to implement secure smart contracts.
- Integrates with other OpenZeppelin tools: It can be combined with other security tools from OpenZeppelin to provide a comprehensive security solution.
attribute & Constant
Okay, the theory part is almost done. You need to code it to fully understand what you’re doing. I’ll explain the attributes of our mint contract. Then we go coding after all the parts are done.
bool public publicSaleActive;
The Boolean that we’ll use to control public sales.
address public operator;
The operator is the legal address that appears after the owner. Some methods require authentication and only operators can call them. In large projects, there may be multiple operators.
uint256 public publicSaleStartTime;
We’ll set a public start time when we start the printing process. It may be useful to record the start time of the public sale in a smart contract in Solidity. By logging the start time, we can ensure that the start time is recorded on the blockchain and cannot be changed.
mapping(address => uint256) public mintedPerAddress;
We have a mapping data structure to keep track of which address has how many nft. We will use it to prevent the wallet from printing more than the mint nft amount we set during printing.
uint256 constant public MAX_MINT_PER_ADDRESS;
This is a constant for setting the maximum number of nfts per address during printing.
uint256 constant public MAX_NFTS;
This is a constant for setting the maximum number of NFTs.
In Solidity, a “constant” variable is a state variable that has a fixed value and cannot be changed once assigned. The keyword “constant” is used to declare a constant variable.
Modifier
In Solidity, “modifiers” are ways to change the behavior of a function or add additional conditions that must be met before the function can be executed. Modifiers are defined as separate functions with the “modifier” keyword, and can be applied to other functions.
We will have the following modifiers:
modifier whenPublicSaleActive() {
require(publicSaleActive, "Public sale is not active");
_;
}
Several methods need to be employed during an open sale.
modifier onlyOperator() {
require(operator == msg.sender, "Only operator can call this method");
_;
}
And some methods have to be called only by the operator.
Program
event NFTPublicSaleStart(
uint256 indexed _saleDuration,
uint256 indexed _saleStartTime
);
It would be useful to record the start time of the public sale in a smart contract in Solidity. By logging the start time, you can ensure that the start time is recorded on the blockchain and cannot be changed. We have to declare an event to record the public sale start time in our smart contract.
event NFTPublicSaleStop(
uint256 indexed _currentPrice,
uint256 indexed _timeElapsed
);
When we stop a public auction, it must also be recorded in the blockchain for transparency and accountability to users and to ensure that the terms of the auction are followed as intended.
Method
A mint smart contract on the Ethereum blockchain using the OpenZeppelin library can have the following methods:
- constructor: The method that is called only when applying our contract to the blockchain.
- mint leaves: The mint method allows new tokens to be minted and added to the total supply. Set the token URI and send it to the minter address.
- setOperator: After applying, the owner of the smart contract executes this method and assigns the operator to operate the smart contract. Usually used by large development teams.
- Start Public Sale: This method starts a public auction and emits a public auction start event. Executed only by the operator.
- stop General Sale: This method stops the public auction and emits a public auction termination event. Executed only by the operator.
- interesting: A method to withdraw all the ether in the contract to a certain address. Called only by the contract owner.
- totalSupply: A method that returns the total supply of tokens originating from ERC721Enumerable.
- balanceOf: A method that returns the number of tokens owned by a specified address originating from ERC721.
- owner: The method that returns the address of the contract owner is from Ownable.
More methods come from the interface. When you implement it, you can find everything inside the contract method.
Code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;import "@openzeppelin/contracts@4.8.1/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.8.1/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts@4.8.1/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts@4.8.1/access/Ownable.sol";
import "@openzeppelin/contracts@4.8.1/utils/Counters.sol";
contract MyToken is
ERC721,
ERC721Enumerable,
ERC721URIStorage,
Ownable,
ReentrancyGuard
{
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
//attributes
// Standart for ERC721
bool public publicSaleActive;
address public operator;
uint256 public publicSaleStartTime;
//Mapping for holding which address mint how much nft ?
mapping(address => uint256) public mintedPerAddress;
// constants
uint256 public constant MAX_NFT = 10000;
uint256 public constant MAX_MINT_PER_ADDRESS = 3;
uint256 public constant MINT_PRICE = 0.002 ether;
// modifiers
modifier whenPublicSaleActive() {
require(publicSaleActive, "Public sale is not active");
_;
}
modifier onlyOperator() {
require(operator == msg.sender, "Only operator can call this method");
_;
}
// events
event NFTPublicSaleStart(
uint256 indexed _saleDuration,
uint256 indexed _saleStartTime
);
event NFTPublicSaleStop(
uint256 indexed _currentPrice,
uint256 indexed _timeElapsed
);
function setOperator(address _operator) external onlyOwner {
operator = _operator;
}
function getElapsedSaleTime() private view returns (uint256) {
return
publicSaleStartTime > 0 ? block.timestamp - publicSaleStartTime : 0;
}
function startPublicSale() external onlyOperator {
publicSaleStartTime = block.timestamp;
emit NFTPublicSaleStart(MINT_PRICE, publicSaleStartTime);
publicSaleActive = true;
}
function stopPublicSale() external onlyOperator whenPublicSaleActive {
emit NFTPublicSaleStop(MINT_PRICE, getElapsedSaleTime());
publicSaleActive = false;
}
constructor() ERC721("MyToken", "MTK") {}
function safeMint(address to, string memory uri)
public
payable
whenPublicSaleActive
nonReentrant
{
uint256 tokenId = _tokenIdCounter.current();
uint256 mintIndex = totalSupply();
require(mintIndex < MAX_NFT, " All NFTs are already sold");
require(MINT_PRICE <= msg.value, " Ether value sent is not correct");
//Check for minted addres has more than max mint per address
require(
mintedPerAddress[msg.sender] > MAX_MINT_PER_ADDRESS,
"sender address cannot mint more than maxMintPerAddress"
);
mintedPerAddress[msg.sender] += 1;
_tokenIdCounter.increment();
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
}
// The following functions are overrides required by Solidity.
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function _burn(uint256 tokenId)
internal
override(ERC721, ERC721URIStorage)
{
super._burn(tokenId);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
Spread
If you are using REMIX IDE, set your environment to MetaMask and then apply it.
After deployment you can call your method.
Congratulations, this is a simple secure mint contract for your project.
Thanks for following along, feel free to drop your thoughts in the comments below.
w to trade? Try crypto trading bots or copy trade on the best crypto exchanges
Follow me on Twitter
Cheers!
[ad_2]
Source link