Setting Up a Simple Hardhat Project for Ethereum Development #ethereum #hardhat #blockchain #docker

Hardhat is a popular development environment for building and deploying Ethereum smart contracts. In this guide, we’ll walk you through the steps to set up a simple Hardhat project, create a Dockerfile for containerization, and demonstrate how to launch the project using Docker Compose. Additionally, we’ll show you how to deploy a contract to the Goerli test network.

Step 1: Create a Hardhat Project

To create a new Hardhat project, follow these steps:

  1. Open your terminal and create a new directory for your project:
mkdir my-hardhat-project
cd my-hardhat-project
  1. Initialize a new Node.js project and install Hardhat as a development dependency:
npm init -y
npm install --save-dev hardhat
  1. Initialize Hardhat in your project:
npx hardhat

Follow the prompts to create a new Hardhat project configuration. You can choose to create a sample project or customize it according to your requirements.

Config the hardhat.config.js file and select the node (infura, manged, own, …), network, private key,…

require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.20",
  defaultNetwork: "goerli",
  networks: {
    goerli: {
      url: "https://goerli.infura.io/v3/<API>",
      accounts: ["<PRIVATEKEY>"]
    }
  }
};

Write the main api.js code with the endopints:

const express = require('express')
require("dotenv").config()
const contract = require("./artifacts/contracts/CredentialNFT.sol/CredentialNFT.json");
var axios = require('axios');
const fs = require("fs");
const bodyParser = require('body-parser');
const { json } = require('body-parser');
const app = express()
const Web3 = require("web3")
const { ethers, JsonRpcProvider, BigNumber } = require('ethers');
app.use(bodyParser.json());
app.use(express.json());
const port = process.env.PORT || 5000

//:::::::::::::::::::SETUP NETWORK CONFIG:::::::::::::::::::::::::::::::::::::

const contractAddress = process.env.CONTRACT_ADDRESS
const contractAddressProfile = process.env.CONTRACT_ADDRESS_PROFILE
const abi = contract.abi


const hre = require("hardhat");

//:::::::::::::::::::API:::::::::::::::::::::::::::::::::::::

app.post("/mintToken", async (req, res) => {
    
    if (!req.body.metadata || !req.body.publicKey) {
        return res.status(400).json({ error: 'Missing parameter' });
      }
    
      try {
          const [deployer] = await hre.ethers.getSigners();
          const contract = new hre.ethers.Contract(contractAddress, abi, deployer);
          let result = await contract.Mint(req.body.publicKey, req.body.metadata);
          let receipt = await result.wait(); 
          console.log(receipt)
          let tokenID = await contract.getCount();
          res.json({ tokenId: tokenID.toString(), txsHash: receipt.hash});
  
      } catch (e) {
          console.error(e);
          res.writeHead(500);
          res.end("internal problem with contract invocation");
          return;
      }
})

app.post("/mintTokenProfile", async (req, res) => {
    if (!req.body.metadata || !req.body.publicKey) {
      return res.status(400).json({ error: 'Missing parameter' });
    }
  
    try {
        const [deployer] = await hre.ethers.getSigners();
        const contract = new hre.ethers.Contract(contractAddressProfile, abi, deployer);
        let result = await contract.Mint(req.body.publicKey, req.body.metadata);
        let receipt = await result.wait(); // Attendiamo la conferma della transazione
        console.log(receipt)
        let tokenID = await contract.getCount();
        res.json({ tokenId: tokenID.toString(), txsHash: receipt.hash});

    } catch (e) {
        console.error(e);
        res.writeHead(500);
        res.end("internal problem with contract invocation");
        return;
    }
   
})

app.post("/listNFT", async (req, res) => {
    console.log(req)
    if (!req.body.publicKey) {
      return res.status(400).json({ error: 'Missing parameter' });
    }
  
    try {
        const [deployer] = await hre.ethers.getSigners();
        const contract = new hre.ethers.Contract(contractAddressProfile, abi, deployer);
        let result = await contract.ListNFT(req.body.publicKey);
        console.log(result)
        res.send(result);

    } catch (e) {
        console.error(e);
        res.writeHead(500);
        res.end("internal problem with contract invocation");
        return;
    }
   
})
  
app.post("/burnToken", async (req, res) => {
    if (!req.body.token) {
      return res.status(400).json({ error: 'Missing parameter' });
    }
    
    try {
        let result = await myNftContract.burnNFT(Web3.utils.toBigInt(req.body.token));
        let receipt = await result.wait(); // Attendiamo la conferma della transazione
        res.json({ message: "token burn"});

    } catch (e) {
        console.error(e);
        res.writeHead(500);
        res.end("internal problem with contract invocation");
        return;
    }
   
})
  

app.listen(port, () => {
    console.log(`Server running on port ${port}`)

  })

Step 2: Create a Dockerfile

To containerize your Hardhat project, create a Dockerfile in the project root directory with the following content:

FROM node:18-alpine
COPY package.json package.json
COPY package-lock.json package-lock.json
RUN npm ci
COPY artifacts artifacts 
COPY contracts contracts
COPY scripts scripts
COPY .env .env
COPY api.js api.js
COPY hardhat.config.js hardhat.config.js
CMD ["node", "api.js"]

This Dockerfile sets up a Node.js environment, installs project dependencies, and specifies the command to run when the container starts. You may need to customize it based on your project’s requirements.

Step 3: Create a Docker Compose File

To simplify the deployment of your Hardhat project with Docker, create a docker-compose.yml file in the project root directory with the following content:

version: '3'

services:

  hardhat:
    build: ./hardhat-project
    container_name: sace-hardhat
    network_mode: "host"
    restart: always

This Docker Compose file defines a service named hardhat using the Dockerfile in the current directory. The service name and container name can be customized as needed.

Step 4: Deploy a Contract

To deploy a contract, you can use a deploy script. for example in script/deploy.js:

// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");

async function main() {
  const currentTimestampInSeconds = Math.round(Date.now() / 1000);
  const unlockTime = currentTimestampInSeconds + 60;

  const lockedAmount = hre.ethers.parseEther("0.001");

  const lock = await hre.ethers.deployContract("CredentialNFT", [unlockTime], {
    value: lockedAmount,
  });

  await lock.waitForDeployment();

  console.log(
    `Lock with ${ethers.formatEther(
      lockedAmount
    )}ETH and unlock timestamp ${unlockTime} deployed to ${lock.target}`
  );
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Will deploy the contract in contracts/ folder:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

error notOwner();

contract CredentialNFT is ERC721, ERC721URIStorage {
    // uint256 public mintPrice;
    uint256 private _tokenIdCounter;
    address owner;

    struct token {
        uint256 id;
        string uri;
    }

    constructor(string memory name, string memory symbol) ERC721(name, symbol) {
        owner = msg.sender;
        _tokenIdCounter = 0;
    }

    modifier _onlyOwner() {
        if (msg.sender != owner) {
            revert notOwner();
        }
        _;
    }

    function Mint(
        address to,
        string calldata uri
    ) public returns (uint256) {
        _tokenIdCounter ++;
        uint256 tokenId = _tokenIdCounter;
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);

        return tokenId;
    }


    function burnNFT(uint256 tokenId) public _onlyOwner {

        _burn(tokenId);
    }

    function tokenURI(
        uint256 tokenId
    ) public view override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
    }

    function transfer(
        address from,
        address to,
        uint256 tokenId
    ) public returns (bool) {
        require(ownerOf(tokenId) == from);
        require(to != from);
        safeTransferFrom(from, to, tokenId);
        return true;
    }

    function ListNFT(address addr) public view returns (token[] memory) {
        require(balanceOf(addr)>0);
        token[] memory list = new token[](balanceOf(addr));
        uint count = 0;
        for (uint i = 1; i <= _tokenIdCounter; i++) {
           
            if (addr == ownerOf(i)) {
               
                string memory _uri = tokenURI(i);
                token memory t = token({
                    id: i,
                    uri: _uri
                });
                list[count] = t;
                count++;
            }
        }
        return list;
    }

    function getCount() public view returns (uint256) {
        return _tokenIdCounter;
    }

     function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) {
        return ERC721.supportsInterface(interfaceId) || ERC721URIStorage.supportsInterface(interfaceId);
    }
}

Deploy to the Goerli test network using Hardhat, using the following command:

npx hardhat run scripts/deploy.js --network=goerli

Step 5: Build and Launch with Docker Compose

Now that you have set up the Dockerfile and Docker Compose configuration, you can build and launch your Hardhat project using Docker Compose:

  1. Build the Docker image from your project directory:
docker-compose build
  1. Launch your Hardhat project in a Docker container:
docker-compose up

Your Hardhat project will run inside the Docker container, and you can access it as specified in your Dockerfile.

Conclusion

You’ve successfully set up a simple Hardhat project, created a Dockerfile for containerization, and demonstrated how to deploy a contract to the Goerli test network using Docker Compose. This setup allows you to develop, test, and deploy Ethereum smart contracts in a consistent and reproducible environment.

Articoli consigliati