Skip to main content

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:

  1. Create your project directory:

    $ mkdir nft-contract
    $ cd ./nft-contract
  2. Initialize a pnpm project:

    $ pnpm init
  3. Install Hardhat:

    $ pnpm i -D hardhat
  4. Create a sample Hardhat project:

    $ npx hardhat init
  5. 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

  1. Set up the deploy configuration:

    In the hardhat.config.ts file, configure your Soneium Minato network information:

    hardhat.config.ts
    import { 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;
  2. 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.

  1. Select the ERC721 tab.
  2. Name your NFT and give it a symbol by filling the Name and Symbol fields.
  3. Use the check-boxes on the left to select features of your token:
    1. Put a tick on the Mintable check-box
    2. Put a tick on the Auto Increment Ids check-box, this ensures uniqueness of each minted NFT
    3. Put a tick on the Enumerable check-box

Now click the Copy to Clipboard button to copy the smart contract code.


Step 4: Update the NFT Contract

  1. Replace the sample contract:

    Delete the sample contracts/Lock.sol and create a new file contracts/NFT.sol. Paste the contract code you copied from the OpenZeppelin Wizard.

  2. Customize the contract:

    Remove the onlyOwner modifier from the safeMint 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);
    }
    }
  3. Install openzeppelin/contracts package

    Install the necessary OpenZeppelin package.

    $ pnpm i -D @openzeppelin/contracts

Step 5: Update the Tests

  1. Write test cases:

    Delete the sample test/Lock.ts and create a new test/NFT.ts file:

    test/NFT.sol
    import { 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);
    });
    });
    });
  2. 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

  1. Update the Ignition deployment script:

    Delete the sample ignition/Lock.ts and create a new ignition/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;
  2. 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
  3. 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.