Deploying an NFT with Hardhat
In this guide, we’ll harness the power of Hardhat to deploy a basic NFT smart contract on Soneium Minato. This tutorial will walk you through the setup, deployment, and testing process, ensuring you're equipped to launch your first NFT project with ease. If you're new to Hardhat, check out our Hardhat guide to get familiar with the tool.
Prerequisites
Ensure you have the following installed:
- Node.js
- pnpm (optional, but recommended)
Example Code Repository
You can find the example code we used for this tutorial here.
Step 1: Create a Hardhat Project
To kickstart your Hardhat project, follow these steps:
-
Create your project directory:
$ mkdir nft-contract
$ cd ./nft-contract -
Initialize a pnpm project:
$ pnpm init
-
Install Hardhat:
$ pnpm i -D hardhat
-
Create a sample Hardhat project:
$ npx hardhat init
-
Choose TypeScript for this project:
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.22.13 👷
? What do you want to do? …
Create a JavaScript project
❯ Create a TypeScript project
Create a TypeScript project (with Viem)
Create an empty hardhat.config.js
Quit
Step 2: Configure Hardhat and Set Up Private Key
-
Set up the deploy configuration:
In the
hardhat.config.ts
file, configure your Soneium Minato network information:hardhat.config.tsimport { vars, type HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const PK = vars.get("PRIVATE_KEY");
const config: HardhatUserConfig = {
solidity: "0.8.27",
defaultNetwork: "minato",
networks: {
hardhat: {},
minato: {
url: "https://rpc.minato.soneium.org",
accounts: [PK],
},
},
etherscan: {
apiKey: {
minato: "NO API KEY",
},
customChains: [
{
network: "minato",
chainId: 1946,
urls: {
apiURL: "https://explorer-testnet.soneium.org/api",
browserURL: "https://explorer-testnet.soneium.org",
},
},
],
},
};
export default config; -
Set up the deployer’s private key:
We'll use Hardhat’s built-in configuration variables manager to assign the private key for the deployment account.
$ npx hardhat vars set PRIVATE_KEY
Step 3: Create an NFT Contract with OpenZeppelin
The OpenZeppelin Wizard offers developers the ability to generate smart contracts in no time. We used this tool to generate an ERC721 NFT contract. Suppose you wanted to create a Mintable
, Enumerable
ERC721 token.
- Select the
ERC721
tab. - Name your NFT and give it a symbol by filling the
Name
andSymbol
fields. - Use the check-boxes on the left to select features of your token:
- Put a tick on the
Mintable
check-box - Put a tick on the
Auto Increment Ids
check-box, this ensures uniqueness of each minted NFT - Put a tick on the
Enumerable
check-box
- Put a tick on the
Now click the Copy to Clipboard
button to copy the smart contract code.
Step 4: Update the NFT Contract
-
Replace the sample contract:
Delete the sample
contracts/Lock.sol
and create a new filecontracts/NFT.sol
. Paste the contract code you copied from the OpenZeppelin Wizard. -
Customize the contract:
Remove the
onlyOwner
modifier from thesafeMint
function so that anyone can mint an NFT.contracts/NFT.sol// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract DemoNFT is ERC721, ERC721Enumerable, Ownable {
uint256 private _nextTokenId;
constructor(
address initialOwner
) ERC721("DemoNFT", "DNFT") Ownable(initialOwner) {}
/// @dev the function can be called by anyone
function safeMint(address to) public {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
}
// The following functions are overrides required by Solidity.
function _update(
address to,
uint256 tokenId,
address auth
) internal override(ERC721, ERC721Enumerable) returns (address) {
return super._update(to, tokenId, auth);
}
function _increaseBalance(
address account,
uint128 value
) internal override(ERC721, ERC721Enumerable) {
super._increaseBalance(account, value);
}
function supportsInterface(
bytes4 interfaceId
) public view override(ERC721, ERC721Enumerable) returns (bool) {
return super.supportsInterface(interfaceId);
}
} -
Install openzeppelin/contracts package
Install the necessary OpenZeppelin package.
$ pnpm i -D @openzeppelin/contracts
Step 5: Update the Tests
-
Write test cases:
Delete the sample
test/Lock.ts
and create a newtest/NFT.ts
file:test/NFT.solimport { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
import hre from "hardhat";
describe("NFT", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployFixture() {
const [owner, otherAccount] = await hre.ethers.getSigners();
const Nft = await hre.ethers.getContractFactory("DemoNFT");
const nft = await Nft.deploy(owner);
return { nft, owner, otherAccount };
}
describe("Deployment", function () {
it("Should set the right owner", async function () {
const { nft, owner } = await loadFixture(deployFixture);
expect(await nft.owner()).to.equal(owner.address);
});
});
}); -
Run the tests:
Execute the test suite using Hardhat’s network, a build-in network that can run the test features.
$ npx hardhat test --network hardhat
NFT
Deployment
✔ Should set the right owner (1448ms)
1 passing (1s)
Step 6: Deploy the Contract
-
Update the Ignition deployment script:
Delete the sample
ignition/Lock.ts
and create a newignition/NFT.ts
file:ignition/NFT.sol// This setup uses Hardhat Ignition to manage smart contract deployments.
// Learn more about it at https://hardhat.org/ignition
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
const NftModule = buildModule("NftModule", (m) => {
const adminOwner = m.getAccount(0);
const nft = m.contract("DemoNFT", [adminOwner]);
return { nft };
});
export default NftModule; -
Compile the Contract:
Compile the contract (this will show
Nothing to compile
in the output since the contract was already compiled when we ran the test above):$ npx hardhat compile
-
Deploy the contract:
Deploy the contract to Soneium Minato network. The contract will output an address once deployed:
$ npx hardhat ignition deploy ./ignition/modules/NFT.ts
✔ Confirm deploy to network minato (1946)? … yes
Hardhat Ignition 🚀
Deploying [ NftModule ]
Batch #1
Executed NftModule#DemoNFT
[ NftModule ] successfully deployed 🚀
Deployed Addresses
NftModule#DemoNFT - 0x509020Ac6410142F3146f0CdFF25701010073b7f
Step 7: Verify the Contract
To verify the contract, input the contract address and the parameters used during deployment:
$ npx hardhat verify [DEPLOYED_CONTRACT_ADDRESS] [DEPLOYER_WALLET_ADDRESS]
For this example:
$ npx hardhat verify 0x509020Ac6410142F3146f0CdFF25701010073b7f 0xdef39Bf39EE8F392D434a0d98B6BA8C471Eb26d6
Congratulations! You’ve successfully deployed and verified your NFT contract on Soneium Minato! 🎉 You can find the verified contract page in Soneium Minato Explorer: Example Link
In the next chapter, we’ll dive into creating an interactive app to interact with this deployed contract.