4EVERLAND Tutorial: NFT Contract Deployment
This article mainly introduces how to deploy your NFT (ERC-721 Token) to a decentralized network using smart contracts.
Initialize the Project
// Create an NFT for the ocean
mkdir nft-ocean
// Enter the directory
cd nft-ocean
// Initialize the project, fill in as prompted. You can fill in the package name and description.
npm init
// Add hardhat dependency
npm install --save-dev hardhat
/* Using scaffolding to set up the project, we choose "Create a basic sample project." This will help us create a demo project and install the necessary dependencies. */
npx hardhat
The code structure will look like this after initialization:
contracts directory is for storing smart contract code.
scripts directory is for storing scripts, such as those for contract deployment.
test directory is for storing test code for smart contracts.
hardhat.config.js contains some configurations about the hardhat framework, such as the Solidity version, etc.
package.json contains npm related configurations.
Writing the Smart Contract
You can now start writing the contract code. This tutorial mainly introduces NFT issuance, so let’s implement a simple NFT smart contract.
// Install the openzeppelin/contracts dependency, which has built-in implementations for many contract protocols and utility code.
npm install @openzeppelin/contracts
Create a file named NFT_OCEAN.sol in the contracts directory
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract NFT_OCEAN is ERC721, ERC721Enumerable, Ownable {
string private _baseURIextended;
//Set mint limit
uint256 public constant MAX_SUPPLY = 5;
//0.01eth per mint
uint256 public constant PRICE_PER_TOKEN = 0.01 ether;
constructor() ERC721("nft_ocean", "NFT_OCEAN") {
}
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
return super.supportsInterface(interfaceId);
}
function setBaseURI(string memory baseURI_) external onlyOwner() {
_baseURIextended = baseURI_;
}
function _baseURI() internal view virtual override returns (string memory) {
return _baseURIextended;
}
function mint(uint numberOfTokens) public payable {
uint256 ts = totalSupply();
require(ts + numberOfTokens <= MAX_SUPPLY, "Purchase would exceed max tokens"); require(PRICE_PER_TOKEN * numberOfTokens <= msg.value, "Ether value sent is not correct");
for (uint256 i = 0; i < numberOfTokens; i++) {
_safeMint(msg.sender, ts + i);
}
}
function withdraw() public onlyOwner {
uint balance = address(this).balance;
payable(msg.sender).transfer(balance);
}
}
This code does not meet production standards as it lacks features like whitelisting and limits on how many tokens each address can mint. It’s just for demonstration purposes. It’s recommended to design your contract carefully before issuing NFTs.
Compile the Contract
// Compile the contract
npx hardhat compile
Deploy the Contract
Modify the auto-generated script in the scripts directory, rename it to deploy.js, and change its internal code.
const hre = require("hardhat");
async function main() {
// Hardhat always runs the compile task when running scripts with its command
// line interface.
//
// If this script is run directly using `node` you may want to call compile
// manually to make sure everything is compiled
// await hre.run('compile');
// We get the contract to deploy
const Nft_ocean = await hre.ethers.getContractFactory("NFT_OCEAN");
const nft_ocean = await Nft_ocean.deploy();
await nft_ocean.deployed();
console.log("NFT_OCEAN deployed to:", nft_ocean.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Select node service proxy
To interact with the Ethereum blockchain, you have various options. To keep things simple, we will use a free account on Alchemy, a blockchain developer platform and API that allows us to interact with the Ethereum chain without running our own node. Refer to the documentation to create an Ethereum Mainnet and an Ethereum Sepolia App.
Configuring the Testnet Environment
First off, let’s get our contract onto a test network to see it in action. For this tutorial, we’re picking Sepolia as our go-to testnet.
Now, a quick heads-up: when we’re executing our code, we’re going to be dealing with sensitive stuff like public and private keys, along with API data. It’s a no-go to hard-code these directly into our codebase for obvious security reasons. So, we’ll use the dotenv package to securely manage all the key data we'll need for both deploying and interacting with our contract.
// Add dotenv dependency
npm install dotenv
// Create a file to store variables at the root of the project.
touch .env
Add content to .env:
PRIVATE_KEY= <Your MetaMask private key here>
API= <Alchemy APP HTTPS here>
PUBLIC_KEY= <MetaMask address here>
NETWORK=sepolia
API_KEY=<Alchemy API KEY here>
Exporting MetaMask Private Key: Open wallet -> Account Details -> Export Private Key.
API, API_KEY: Open Alchemy's Ethereum Sepolia API KEY, copy both the HTTPS and API_KEY.
Modify hardhat.config.js
/**
* @type import('hardhat/config').HardhatUserConfig
*/
require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-ethers"); //require("@nomiclabs/hardhat-waffle");
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
require('dotenv').config();
const { API, PRIVATE_KEY } = process.env;
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.19",
defaultNetwork: "sepolia",
networks: {
hardhat: {},
sepolia: {
url: API,
accounts: [`0x${PRIVATE_KEY}`]
}
},
};
To perform contract tests, ETH is required. Obtain test ETH by visiting https://sepoliafaucet.com/ and entering the appropriate wallet address.
After switching to the Sepolia test network, it’s possible to observe that the wallet now contains some ETH.
Execute Contract Deployment
First, install ETHERS.JS.
npm install --save-dev @nomiclabs/hardhat-ethers ethers@^5.7.2
Deploy the contract.
npx hardhat --network sepolia run scripts/deploy.js
// After execution is complete, a prompt will appear, indicating that the contract has been successfully deployed
NFT_WEB3_EXPOLRER deployed to: {Contract Address}
At this point, the contract information can be found by searching for the contract address on https://sepolia.etherscan.io/.
Setting Resources for NFTs
The current NFTs only contain a tokenID, and resources need to be set for them. In the implementation of ERC721, we can see the implementation of the tokenURI method. Various marketplaces use this method to read NFT resource information.
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
As we can see, the method concatenates BaseURI and tokenID to read the tokenURI. Resources should be prepared in this format.
Preparing Resources
// Create a resources folder
mkdir res cd res
// Create a folder for storing images
mkdir nfts
// Create a folder for storing metadata information
mkdir metadata
Since this is a sample project and no specialized designer has been hired, five images were sourced online. Place these five images in the img directory and name them sequentially from 1.jpeg to 5.jpeg.
Similar to the Ethereum network, resource files are uploaded to IPFS. There’s no need to run a local IPFS node; instead, a node service provider can be used for uploading. For this tutorial, use 4everland’s storage dashboard to upload the nfts folder.
Select the image folder and perform snapshots to generate a root CID. The result when accessed through a gateway is shown below:
Next, prepare the metadata files. Create five new files in the metadata directory and name them sequentially from 0 to 4. Below is the information for file 0:
{
"name": "nft-ocean",
"attributes": [
{
"trait_type": "tokenID",
"value": "0"
}
],
"description": "nft-ocean image",
// Include the recently uploaded image URL here
"image": "ipfs://bafybeief75v57gu3khb5ftjkgldyzyea4x3r6sesekcia6lk2pijgl5idm/01.jpeg"
}
After uploading the metadata folder and taking snapshots, publish it once you have obtained the folder's CID.
Setting the BaseURI
It’s necessary to set the contract’s baseURI to the metadata file address that was just uploaded. This way, various platforms can access resource information using the tokenURI method. Create a new file under the scripts directory called setBaseURI.js.
setBaseURI.js
require("dotenv").config()
const hre = require("hardhat");
const PRIVATE_KEY = process.env.PRIVATE_KEY
const NETWORK = process.env.NETWORK
const API_KEY = process.env.API_KEY
const provider = new hre.ethers.providers.AlchemyProvider("sepolia",API_KEY);
// The contract will be automatically generated upon compilation
const abi = require("../artifacts/contracts/NFT_OCEAN.sol/NFT_OCEAN.json").abi
const contractAddress = "the actual contract address"
const contract = new hre.ethers.Contract(contractAddress, abi, provider)
const wallet = new hre.ethers.Wallet(PRIVATE_KEY, provider)
const baseURI = "ipfs://{ the actual IPFS root CID where your metadata is stored}/"
async function main() {
const contractWithSigner = contract.connect(wallet);
// Call the setBaseURI method
const tx = await contractWithSigner.setBaseURI(baseURI)
console.log(tx.hash);
await tx.wait();
console.log("setBaseURL success");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Execute the setBaseURI Script
npx hardhat --network sepolia run scripts/setBaseURI.js
Mint Testing
Only minted tokenIDs will appear on the marketplace, so a minting test is needed. This is usually done via a frontend interface in real scenarios.
mint.js
require("dotenv").config()
const hre = require("hardhat");
const PRIVATE_KEY = process.env.PRIVATE_KEY
const NETWORK = process.env.NETWORK
const API_KEY = process.env.API_KEY
const provider = new hre.ethers.providers.AlchemyProvider("sepolia",API_KEY);
const abi = require("../artifacts/contracts/NFT_OCEAN.sol/NFT_OCEAN.json").abi
const contractAddress = "0x531D6B8fBe5FAC349D05642E17F5E998A4DfEd68"
const contract = new hre.ethers.Contract(contractAddress, abi, provider)
const wallet = new hre.ethers.Wallet(PRIVATE_KEY, provider)
async function main() {
const contractWithSigner = contract.connect(wallet);
/// Determine how much ETH is needed to mint
const price = await contract.PRICE_PER_TOKEN();
console.log("price is" + price);
// Call the mint method and pay the minting fee
const tx = await contractWithSigner.mint(1, { value: price});
console.log(tx.hash);
await tx.wait();
console.log("mint success");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Execute the Mint Script
npx hardhat --network sepolia run scripts/mint.js
Mint Successful
Viewing the NFT
At this point, you can go to the OpenSea testnet at https://testnets.opensea.io/ to view the NFT you have minted.
About 4EVERLAND
4EVERLAND is a Web3 infrastructure powered by blockchain technology that provides developers with convenient, efficient, and low-cost storage, network, and computing capabilities. It is committed to helping developers smoothly transition from Web2.0 to Web3.0 and building a Web3.0 cloud computing platform friendly to Web2.0.
Website | Twitter | Telegram | Discord | Reddit | Medium | Email