Skip to main content

Deploying and Verifying Contracts - Hardhat

Updated over 2 weeks ago

⚠️ The Hardhat Team released version 3 of their product. The guide below works only with existing version 2 projects. We are working on a new guide that will support both new and version 3 projects.

This article provides a detailed guide on how to leverage Hardhat for smart contract deployment and verification using JavaScript or TypeScript. Learn how to efficiently verify your contracts on Routescan using the hardhat-verify plugin.

Initial Setup

Before creating the project, your environment must be set up correctly.

1. Install Node.js and npm

Hardhat is a JavaScript-based tool, so it requires Node.js to run. Node Package Manager (npm) is installed automatically with Node.js and is used to manage project packages like Hardhat itself.

ℹ️ If you see the error 'npm' is not recognized as the name of a cmdlet..., it means Node.js is not installed correctly or not in your system's PATH. Re-running the installer and choosing system PATH is the best fix.

  • Go to the official Node.js website and download the LTS (Long-Term Support) version.

  • During installation, ensure the 'Add to PATH' option is enabled. This allows you to run node and npm commands from any folder in your terminal.

2. Configure Your Terminal (If Needed)

ℹ️ If you see an error like ...cannot be loaded because running scripts is disabled on this system, this step is the solution.

macOS/Linux Users

This step is generally not required. The default terminal settings on macOS/Linux are typically sufficient for running npm scripts without any changes.

Windows Users (PowerShell)

By default, PowerShell has a security policy that can prevent npm from running scripts. You may need to change it.

  • Open PowerShell as an Administrator (Start Menu > type 'PowerShell' > right-click > Run as administrator).

  • Run the command Set-ExecutionPolicy RemoteSigned

  • When prompted, type Y and press Enter.

3. Prepare Your Crypto Wallet

You will need a browser extension wallet like MetaMask to hold your test funds and sign transactions. Deployment costs gas, even on a Testnet.

Creating the Hardhat Project

1. Initialize the Project

Open your terminal (PowerShell, Terminal, CMD, etc.) and run these commands one by one.

Create a new folder for the project.

mkdir my-hardhat-project

Navigate into it.

cd my-hardhat-project

Initialize a Node.js project.

npm init -y

Install Hardhat.

npm install --save-dev hardhat

Run the Hardhat setup wizard.

npx hardhat --init

During the npx hardhat wizard, answer the prompts as follows:

  • What do you want to do? → Create a JavaScript project OR Create a TypeScript project (you can pick any for your convenience)

  • Hardhat project root: → Press Enter to accept the default

  • Do you want to add a .gitignore? → yes

ℹ️ This creates a .gitignore file. This is crucial for security as it prevents your private .env file from being accidentally uploaded to version control systems like GitHub.

  • Do you want to install this sample project's dependencies...? → yes

ℹ️ It automatically installs essential packages like @nomicfoundation/hardhat-toolbox, saving you a manual step.

Please note that this guide provides code for both JavaScript (.js) and TypeScript (.ts). Choose the option that best fits your needs.

2. Project Structure Overview

Hardhat creates several folders. The most important for us are:

  • /contracts: Where your Solidity source code (.sol files) goes.

  • /ignition/modules: Deployment logic.

  • hardhat.config.js (or .ts): The main configuration file for the project.

Configuring Your Project for Deployment

1. Write Your Smart Contract

  • Go to the /contracts folder and modify the sample Lock.sol file or create a new .sol file.

  • Write your smart contract code inside the file. As example, we will add the following code to Greeter.sol.

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

    contract Greeter {
    string private greeting;

    constructor(string memory _greeting) {
    greeting = _greeting;
    }

    function greet() public view returns (string memory) {
    return greeting;
    }
    }

2. Securely Store Your Private Key

You must provide your private key to deploy. There are two ways to do this: using a persistent .env file or a temporary terminal variable. In MetaMask, click the three dots (⋮) > Account details > Show private key. If you are using other Crypto Wallets look for a corresponding setting to display private key.

⚠️ Important Note: Your private key gives total control over your wallet. Never share it.

Option A. Using a .env File

This method securely stores your key in a file for the project, so you only have to set it up once.

  • Install dotenv. To load the .env file, we first need to install the dotenv package. In your terminal, run the following command from your project's root folder.

    npm install dotenv
  • Create the .env file. In the root of your project folder (my-hardhat-project), create a new file named exactly .env. You can do this manually in your file manager, or directly from your terminal.

    • For macOS/Linux: touch .env

    • For Windows (PowerShell): New-Item .env

ℹ️ Files starting with a dot (.) are hidden by default in macOS/Linux. In the file manager, you can press Cmd + Shift + . to show/hide hidden files.

ℹ️ Windows sometimes hides file extensions. If your file is named .env.txt, go to File Explorer > View tab > check the 'File name extensions' box. Then you can rename the file to remove the .txt part.

  • Add Content to .env. Open the file and add the following, replacing the placeholder.

    PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY"
    SEPOLIA_RPC_URL="RPC_URL"

ℹ️ Visit Routescan RPCs page for detailed information about all supported Chain IDs and it’s RPC URLs.

ℹ️ If you see an error like Error HH8: Invalid account... received undefined, it means this file is misnamed, it’s path or content is incorrect. The log injecting env (0) confirms that zero variables were loaded.

Option B. Using a Temporary Terminal Variable

This method sets your private key for your current terminal session only. If you close the terminal, you must run the command again. You do not need to install dotenv or create a .env file if you use this method.

  1. Set the Environment Variable. Run the appropriate command for your operating system in the terminal.

    • For macOS/Linux

      export PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY"
    • For Windows (PowerShell)

      $env:PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY"
  2. Set the RPC URL (if not in config). You will also need to set the RPC URL variable.

    • For macOS/Linux

      export SEPOLIA_RPC_URL="RPC_URL"
    • For Windows (PowerShell)

      $env:SEPOLIA_RPC_URL="RPC_URL"

3. Configure hardhat.config.js (or .ts)

This file tells Hardhat everything it needs to know. Replace its entire content with the appropriate version below.

JavaScript (hardhat.config.js)

require("@nomicfoundation/hardhat-toolbox");
// IMPORTANT: If you are NOT using a .env file, you should remove the following line
require("dotenv").config();

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.30",

networks: {
routescan: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: [process.env.PRIVATE_KEY],
},
},

etherscan: {
apiKey: {
routescan: "ANY_STRING_WORKS", // API key is not required by Routescan
},
customChains: [
{
network: "routescan",
// Use the end goal Chain ID. In this case we are using Sepolia Chain ID.
chainId: 11155111,
urls: {
// This is the specific API endpoint for Routescan's Sepolia verifier. Make sure you change the request with either Testnet or Mainnet value and use the correct end goal Chain ID.
apiURL: "https://api.routescan.io/v2/network/testnet/evm/11155111/etherscan",
browserURL: "https://11155111.testnet.routescan.io/",
},
},
],
},
};

TypeScript (hardhat.config.ts)

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
// IMPORTANT: If you are NOT using a .env file, you should remove the following line
import "dotenv/config";

const config: HardhatUserConfig = {
solidity: "0.8.30",
networks: {
routescan: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
},
etherscan: {
apiKey: {
routescan: "ANY_STRING_WORKS", // API key is not required by Routescan
},
customChains: [
{
network: "routescan",
chainId: 11155111,
urls: {
apiURL: "https://api.routescan.io/v2/network/testnet/evm/11155111/etherscan",
browserURL: "https://11155111.testnet.routescan.io/",
},
},
],
},
};

export default config;

Deploying and Verifying

1. Write the Deployment Module

  • Navigate to the /ignition/modules folder and modify the sample file (Lock.js or Lock.ts) or create a new file (.js or .ts).

  • Add the appropriate script below.

JavaScript (Deploy.js)

const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

module.exports = buildModule("GreeterModule", (m) => {
const initialGreeting = "Hello World!";

const greeter = m.contract("Greeter", [initialGreeting]);

return { greeter };
});

TypeScript (Deploy.ts)

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

const GreeterModule = buildModule("GreeterModule", (m) => {
const initialGreeting = "Hello World!";
const greeter = m.contract("Greeter", [initialGreeting]);

return { greeter };
});

export default GreeterModule;

2. Deploy the Contract

Run the Ignition deployment command from your terminal, targeting the network configured. Use the correct path for your .js or .ts file.

npx hardhat ignition deploy ./ignition/modules/Counter.js --network routescan "Hello World!"

You should use single quotes (' ') for arguments with special characters on macOS/Linux to prevent the shell from interpreting them incorrectly.

Deploying contract on Routescan via Hardhat

The output will show the deployment status and, upon completion, will log the deployed contract address. Copy the contract address.

3. Verify the Contract

Block explorers might often find a 'Partial Match' automatically. To get an 'Exact

Match,' you must verify manually.

  • The command structure is npx hardhat verify --network <network-name> <contract-address> <constructor-args>.

  • Use the address you copied and the same constructor arguments from your deployment module, if any.

    npx hardhat verify --network routescan <YOUR_DEPLOYED_ADDRESS> "Hello World!"

ℹ️ If you see an error like The contract ... has already been verified, add the --force flag at the end of your command.

npx hardhat verify --network routescan --force <YOUR_DEPLOYED_ADDRESS> "Hello World!"
Successful deployment and verification on Routescan via Hardhat

After a successful run, the status on Routescan Contracts Tab will update to Contract Source Code Verified (Exact Match).

Practical Tips

  • To deploy or verify with complex constructor arguments, create a constructor-arg.js or .ts file in project root directory and add this at the end of your command: --constructor-args constructor-args.js. Example arguments file:

    module.exports = [
    "My Token", // string
    "MTK", // string
    1000000, // number
    "0x1234567890123456789012345678901234567890" // address
    ];
  • Before deploying to a live network, you can always run your tests using npx hardhat test. This ensures your contract logic is correct and can save you from costly mistakes.

  • Mismatch between the network name in the networks block and the etherscan.customChains block can cause verification to fail. Always ensure these names are identical.

  • If you accidentally leave off a closing quote (" or ') when typing a command, your terminal will wait for more input (showing a dquote> or >> prompt). If you get stuck, press Ctrl + C to cancel the command and start over.

  • If verification fails and the error message is unclear, run the command again with the --verbose flag. This provides detailed logs that can help you diagnose the issue.

    npx hardhat verify --verbose --network ...
  • If your project has multiple contracts, Hardhat might not know which one to verify. Use the --contract flag to specify the exact contract path and name.

    npx hardhat verify --contract contracts/MyContract.sol:MyContract ...
  • Hardhat automatically compiles changes to .sol files, but not always to hardhat.config.js or .ts. If you change the compiler version or optimizer settings, force a re-compile to ensure the bytecode is updated.

    npx hardhat compile --force

To achieve even greater automation for your Hardhat and Foundry workflows at scale, explore the capabilities of Catapulta.

Did this answer your question?