Hardhat is a local Ethereum network designed for development and deployment of smart contracts. Hardhat allows you to deploy and run tests using Solidity and replicate Ethereum network environments without using real cryptocurrency or paying gas fees.
What is Metadata?
Metadata is simply data that provides additional data about other data. Metadata of an object stored on Filebase typically includes the owner’s account email, size, ETag, object key or name, time and date last modified, and if the object is stored on an IPFS bucket, the IPFS CID is included in the object’s metadata.
In this guide, we’ll use Hardhat to create a test environment for creating a smart contract, then deploy and mint that contract to be used with an NFT token. Lastly, we’ll edit the contract to include metadata when we mint NFTs, and in this metadata we’ll include an image URL that uses the IPFS CID that we get from uploading an image to a Filebase IPFS bucket.
Prerequisites:
1. First, we need an image to use as an NFT. We’ll start by uploading an image to Filebase for us to use.
To do this, navigate to . If you don’t have an account already, , then log in.
2. Select ‘Buckets’ from the left side bar menu, or navigate to .
Select ‘Create Bucket’ in the top right corner to create a new bucket for your NFTs.
3. Enter a bucket name and choose the IPFS storage network to create the bucket.
Bucket names must be unique across all Filebase users, be between 3 and 63 characters long, and can contain only lowercase characters, numbers, and dashes.
4. Next, select the bucket from your list of buckets, then select ‘Upload’ in the top right corner to upload an image file.
5. Select an image to be uploaded.
Once uploaded, it will be listed in the bucket.
6. Click on your uploaded object to display the metadata for the object.
Take note of the IPFS CID. We will reference this later.
7. Open a command line interface and create a new directory for your project with the commands:
mkdir nft-tutorial
cd nft-tutorial
8. Initialize your npm configuration if you have not previously done so with the command:
npm init
9. Install npm dependencies for this project with the commands:
21. Now let’s compile the contract with the command:
npx hardhat compile
22. Now that the contract is compiled, we need to deploy it.
To do this, we’ll need to create a file in the scripts directory called deploy.js. Enter the following content in this new file:
async function main() {
// Get our account (as deployer) to verify that a minimum wallet balance is available
const [deployer] = await ethers.getSigners();
console.log(`Deploying contracts with the account: ${deployer.address}`);
console.log(`Account balance: ${(await deployer.getBalance()).toString()}`);
// Fetch the compiled contract using ethers.js
const NFT = await ethers.getContractFactory("NFT");
// calling deploy() will return an async Promise that we can await on
const nft = await NFT.deploy();
console.log(`Contract deployed to address: ${nft.address}`);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
23. We can now deploy the contract with the following command:
npx hardhat run scripts/deploy.js --network rinkeby
Take note of the value that gets returned. This is your contract address, we’ll reference this later. You’ve successfully deployed your contract! Now let’s move onto minting our new contract.
24. Edit the existing deploy.js script so it reflects the following:
const { task } = require("hardhat/config");
const { getAccount } = require("./helpers");
task("check-balance", "Prints out the balance of your account").setAction(async function (taskArguments, hre) {
const account = getAccount();
console.log(`Account balance for ${account.address}: ${await account.getBalance()}`);
});
task("deploy", "Deploys the NFT.sol contract").setAction(async function (taskArguments, hre) {
const nftContractFactory = await hre.ethers.getContractFactory("NFT", getAccount());
const nft = await nftContractFactory.deploy();
console.log(`Contract deployed to address: ${nft.address}`);
});
25. We also need to create a new script in our scripts directory called helpers.js.
In this script, enter the following content:
const { ethers } = require("ethers");
// Helper method for fetching environment variables from .env
function getEnvVariable(key, defaultValue) {
if (process.env[key]) {
return process.env[key];
}
if (!defaultValue) {
throw `${key} is not defined and no default value was provided`;
}
return defaultValue;
}
// Helper method for fetching a connection provider to the Ethereum network
function getProvider() {
return ethers.getDefaultProvider(getEnvVariable("NETWORK", "ropsten"), {
alchemy: getEnvVariable("ALCHEMY_KEY"),
});
}
// Helper method for fetching a wallet account using an environment variable for the PK
function getAccount() {
return new ethers.Wallet(getEnvVariable("ACCOUNT_PRIVATE_KEY"), getProvider());
}
module.exports = {
getEnvVariable,
getProvider,
getAccount,
}
26. We also need to edit our hardhat.config.js configuration file to reflect the newly defined tasks we created in the scripts above.
Edit your hardhat.confg.js file to reflect the following configuration:
27. Now let’s add a minting task. Create a new script in the scripts directory called mint.js and enter the following content to create your minting task:
const { task } = require("hardhat/config");
const { getContract } = require("./helpers");
task("mint", "Mints from the NFT contract")
.addParam("address", "The address to receive a token")
.setAction(async function (taskArguments, hre) {
const contract = await getContract("NFT", hre);
const transactionResponse = await contract.mintTo(taskArguments.address, {
gasLimit: 500_000,
});
console.log(`Transaction Hash: ${transactionResponse.hash}`);
});
28. Now we need to edit our .env file to reflect our NFT contract address that we took note of when we deployed our contract.
Edit your .env file so it resembles the following:
ALCHEMY_KEY = "alchemy-api-key"
ACCOUNT_PRIVATE_KEY = "private-key"
NETWORK="ropsten"
NFT_CONTRACT_ADDRESS="nft-contract-address"
29. Let’s edit our helpers.js script to include a helper function called getContract() to read the new environment variable we just added to our .env file.
Edit your helpers.js script so it looks like the following:
const { ethers } = require("ethers");
const { getContractAt } = require("@nomiclabs/hardhat-ethers/internal/helpers");
// Helper method for fetching environment variables from .env
function getEnvVariable(key, defaultValue) {
if (process.env[key]) {
return process.env[key];
}
if (!defaultValue) {
throw `${key} is not defined and no default value was provided`;
}
return defaultValue;
}
// Helper method for fetching a connection provider to the Ethereum network
function getProvider() {
return ethers.getDefaultProvider(getEnvVariable("NETWORK", "ropsten"), {
alchemy: getEnvVariable("ALCHEMY_KEY"),
});
}
// Helper method for fetching a wallet account using an environment variable for the PK
function getAccount() {
return new ethers.Wallet(getEnvVariable("ACCOUNT_PRIVATE_KEY"), getProvider());
}
// Helper method for fetching a contract instance at a given address
function getContract(contractName, hre) {
const account = getAccount();
return getContractAt(hre, contractName, getEnvVariable("NFT_CONTRACT_ADDRESS"), account);
}
module.exports = {
getEnvVariable,
getProvider,
getAccount,
getContract,
}
We also need to edit our hardhat.config.js configuration file again so that our new mint.js script is included:
Use the following command with your NFT contract address to mint your deployed contract:
npx hardhat mint --address [NFT-CONTRACT-ADDRESS]
31. Lastly, let’s add some metadata to our NFTs.
This includes the name, description, and image that we uploaded to our IPFS Filebase bucket in the beginning of this guide. To do this, we’ll need to edit our NFT.sol contract to resemble the following:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract NFT is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private currentTokenId;
/// @dev Base token URI used as a prefix by tokenURI().
string public baseTokenURI;
constructor() ERC721("NFTTutorial", "NFT") {
baseTokenURI = "";
}
function mintTo(address recipient) public returns (uint256) {
currentTokenId.increment();
uint256 newItemId = currentTokenId.current();
_safeMint(recipient, newItemId);
return newItemId;
}
/// @dev Returns an URI for a given token ID
function _baseURI() internal view virtual override returns (string memory) {
return baseTokenURI;
}
/// @dev Sets the base token URI prefix.
function setBaseTokenURI(string memory _baseTokenURI) public {
baseTokenURI = _baseTokenURI;
}
}
32. Then, create a metadata file for our NFT.
We’ll call this file 1, which does not require a file extension. In this file, paste the following content:
{
"name" : "Filebase Robot"
"description" : "The cute, little Filebase robot!",
"image" : "https://ipfs.filebase.io/ipfs/[IPFS-CID]",
"external_url": "https://example.com/?token_id=1",
}
Edit the values in this file to reflect your desired configuration. Replace the name, description, and IPFS-CID values. The IPFS CID value is the value we took note of after uploading our image file to our Filebase bucket at the start of this guide.
You will need to upload this metadata file to your Filebase IPFS bucket in the same way that we used to upload our image file to our Filebase bucket. Copy the IPFS CID for the metadata file once uploaded to Filebase.
33. Now we need to edit our mint.js script to include a function that sets this metadata.
Edit your mint.js file to reflect the following:
const { task } = require("hardhat/config");
const { getContract } = require("./helpers");
const fetch = require("node-fetch");
task("mint", "Mints from the NFT contract")
.addParam("address", "The address to receive a token")
.setAction(async function (taskArguments, hre) {
const contract = await getContract("NFT", hre);
const transactionResponse = await contract.mintTo(taskArguments.address, {
gasLimit: 500_000,
});
console.log(`Transaction Hash: ${transactionResponse.hash}`);
});
task("set-base-token-uri", "Sets the base token URI for the deployed smart contract")
.addParam("baseUrl", "The base of the tokenURI endpoint to set")
.setAction(async function (taskArguments, hre) {
const contract = await getContract("NFT", hre);
const transactionResponse = await contract.setBaseTokenURI(taskArguments.baseUrl, {
gasLimit: 500_000,
});
console.log(`Transaction Hash: ${transactionResponse.hash}`);
});
task("token-uri", "Fetches the token metadata for the given token ID")
.addParam("tokenId", "The tokenID to fetch metadata for")
.setAction(async function (taskArguments, hre) {
const contract = await getContract("NFT", hre);
const response = await contract.tokenURI(taskArguments.tokenId, {
gasLimit: 500_000,
});
const metadata_url = response;
console.log(`Metadata URL: ${metadata_url}`);
const metadata = await fetch(metadata_url).then(res => res.json());
console.log(`Metadata fetch response: ${JSON.stringify(metadata, null, 2)}`);
});
34. Finally, we’re ready to put it all together. Let’s start by compiling our project:
npx hardhat compile
Then deploy our contract:
npx hardhat deploy
Then set your NFT_CONTRACT_ADDRESS env variable in your .env file. Next, set your metadata file:
Replace the METADATA-IPFS-CID value with the IPFS CID you copied after uploading your metadata file to your IPFS Filebase bucket.
Then mint your contract:
npx hardhat mint --address [NFT-CONTRACT-ADDRESS]
Then lastly, retrieve your NFT token and it’s metadata:
npx hardhat token-uri --token-id 1
From here, you can move this configuration to a live network such as the Ethereum network by simply creating a new Alchemy app on the Ethereum network and updating your .env file with the new Alchemy API key and changing the network value to ethereum instead of rinkeby.
Keep in mind that once you move to the Ethereum network, you’ll be working with real cryptocurrency and you’ll be charged gas fees for transactions.
13. Now, let’s head over to Alchemy and either for an account or .
For this, we’ll need a Metamask account. You can sign up for one for free . For additional information on how Ethereum transactions work, check out the Ethereum foundation’s information page .
To do this, we’ll need to go to the , enter our wallet address, and click ‘Send Ropsten Eth’.
For instructions on getting your Metamask private key, see .
For instructions on how to get your Alchemy API URL, see .