A First Look at Ethers and Hardhat
Tags: ethereum, solidity, hardhat, ethers
Published: 2022-03-04 06:01:05

---
canonical_url: "https://ajcwebdev.com/first-look-ethers-and-hardhat/"
description: "This example uses Hardhat and Ethers to compile a smart contract written in Solidity and deploy that contract to an Ethereum test network with Alchemy."
---

## Outline

* [Introduction](#introduction)
* [Create Project](#create-project)
  * [Install Dependencies](#install-dependencies)
  * [Initialize Hardhat Environment](#initialize-hardhat-environment)
  * [Sample Deployment Script](#sample-deployment-script)
  * [Hardhat Configuration](#hardhat-configuration)
  * [Greeter Solidity Contract](#greeter-solidity-contract)
* [Deploy to Testnet](#deploy-to-testnet)
  * [Compile Application Binary Interface](#compile-application-binary-interface)
  * [Start Test Node](#start-test-node)
  * [Run Deployment Script](#run-deployment-script)
* [Connect to MetaMask](#connect-to-metamask)
  * [Import Account into MetaMask](#import-account-into-metamask)
* [Create React App](#create-react-app)
  * [Start Development Server](#start-development-server)
  * [Connect React App to MetaMask](#connect-react-app-to-metamask)
* [Deploy to Ropsten Testnet](#deploy-to-ropsten-testnet)
  * [Connect MetaMask to Ropsten](#connect-metamask-to-ropsten)
  * [Create an Alchemy Account](#create-an-alchemy-account)
  * [Create an Alchemy Application](#create-an-alchemy-application)
  * [Get Alchemy Key](#get-alchemy-key)
  * [Add Alchemy Endpoint to Hardhat Configuration](#add-alchemy-endpoint-to-hardhat-configuration)
  * [Deploy Contract to Ropsten](#deploy-contract-to-ropsten)

> ___All of this project's code can be found in the [First Look monorepo](https://github.com/ajcwebdev/a-first-look/tree/main/web3/ethers/) on my GitHub.___

## Introduction

[ethers.js](https://ethers.org/) is a library that aims to be a complete and compact tool for interacting with the Ethereum Blockchain and its ecosystem. This example uses [Hardhat](https://hardhat.org/), an Ethereum development environment, to compile a smart contract and deploy it to [Ropsten](https://ropsten.etherscan.io/), an Ethereum test network that allows for blockchain development testing.

The contract is written in [Solidity](https://docs.soliditylang.org/), an object-oriented, high-level language for implementing smart contracts. After deploying the contract, we will generate a boilerplate React application with [Vite](https://vitejs.dev/) and connect the application to our smart contract running on Ropsten. [Alchemy](https://www.alchemy.com/) provides a [managed node](https://www.infoq.com/articles/blockchain-as-a-service-get-block/) that enables connecting to various tools in the blockchain ecosystem.

## Create Project

This article is heavily based on Nader Dabit's definitive article, [The Complete Guide to Full Stack Ethereum Development](https://dev.to/dabit3/the-complete-guide-to-full-stack-ethereum-development-3j13). I've made a few alternations including using Vite instead of Create React App and the inclusion of `dotenv` for environment variables but all credit due to Sensei Dabit.

```bash
pnpm create vite ajcwebdev-ethers --template react
cd ajcwebdev-ethers
```

Remove `"type": "module"` from `package.json`.

### Install Dependencies

In addition to Ethers and Hardhat we will also install [Waffle](https://ethereum-waffle.readthedocs.io/) and [Chai](https://www.chaijs.com/) for testing our contracts.

```bash
pnpm add dotenv ethers hardhat chai ethereum-waffle \
   @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle
```

Create a `.env` file to store environment variables later in the tutorial.

```bash
touch .env
```

The file will include the following three variables:

```
ALCHEMY_URL=
ALCHEMY_KEY=
VITE_GREETER_ADDRESS=
```

### Initialize Hardhat Environment

Developing smart contracts requires the ability to deploy your contracts, run tests, and debug Solidity code. We will also need a way to compile Solidity code into code that can be run in a client-side application. Hardhat compiles your contracts and runs them on a development network. This lets you develop without having to deal with live environments.

We'll create the following files/directories:

* `hardhat.config.js` - Entire Hardhat setup including config, plugins, and custom tasks.
* `scripts` - Contains a script named `deploy.js` that deploys your smart contract when executed
* `contracts` - Contains a file called `Greeter.sol` with an example Solidity smart contract

```bash
mkdir scripts contracts
echo > scripts/deploy.js
echo > contracts/Greeter.sol
echo > hardhat.config.js
```

### Sample Deployment Script

In `deploy.js` we use `getContractFactory` to create a `ContractFactory`, an abstraction used to deploy new smart contracts. `Greeter` is a factory for instances of our greeting contract. Calling `deploy()` on a `ContractFactory` will start the deployment, and return a `Promise` that resolves to a `Contract`. This is the object that has a method for each of your smart contract functions. Modify the text to send your own message.

```js
// scripts/deploy.js

const hre = require("hardhat")

async function main() {
  const Greeter = await hre.ethers.getContractFactory("Greeter")
  const greeter = await Greeter.deploy("Hello from ajcwebdev!")
  await greeter.deployed()
  console.log("Greeter deployed to:", greeter.address)
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })
```

I'll also add a few more console logs to the `main` function to log additional pieces of information including the `signer`, `hash`, and `blockHash`.

```js
// scripts/deploy.js

const hre = require("hardhat")

async function main() {
  const Greeter = await hre.ethers.getContractFactory("Greeter")
  const greeter = await Greeter.deploy("Hello from ajcwebdev!")
  await greeter.deployed()

  console.log("Greeter deployed to " + greeter.address + " address")
  console.log("Greeter deployed by " + JSON.stringify(greeter.signer) + " signer")
  console.log("Deploy transaction hash: " + greeter.deployTransaction.hash)
  console.log("Deploy transaction block hash: " + greeter.deployTransaction.blockHash)
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })
```

### Hardhat Configuration

When Hardhat is run, it searches for the closest `hardhat.config.js` file starting from the current working directory. This file normally lives in the root of your project. The entirety of your Hardhat setup, including your config, plugins and custom tasks, is contained in this file.

We need to make a couple changes to our `hardhat.config.js` file. Update the chain ID to [1337](https://hardhat.org/metamask-issue.html) and change the location for the [artifacts](https://hardhat.org/guides/compile-contracts.html#artifacts) of the compiled contracts to the `src` directory of the React app.

```js
// hardhat.config.js

require("@nomiclabs/hardhat-waffle")

task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners()
  for (const account of accounts) {
    console.log(account.address)
  }
})

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.7",
  paths: {
    artifacts: './src/artifacts',
  },
  networks: {
    hardhat: {
      chainId: 1337
    }
  }
}
```

### Greeter Solidity Contract

Solidity files start with a pragma that is used by the Solidity compiler to validate its version. `contract` specifies the main building block for the smart contract, which is named `Greeter`.

```js
// contracts/Greeter.sol

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

import "hardhat/console.sol";

contract Greeter { }
```

The `constructor` is executed only once when the contract is created. The `Greeter` constructor will set a `greeting` variable and expose a function (`greet`) that can be called to return the `greeting`. It also exposes a function called `setGreeting` that allows a user to update the greeting.

```js
// contracts/Greeter.sol

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

import "hardhat/console.sol";

contract Greeter {
  string private greeting;

  constructor(string memory _greeting) {
    console.log("Deploying a Greeter with greeting: ", _greeting);
    greeting = _greeting;
  }

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

  function setGreeting(string memory _greeting) public {
    console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
    greeting = _greeting;
  }
}
```

These methods will be available for a user to interact with when deployed to the Ethereum blockchain. These methods represent the two ways of interacting with a smart contract:
* `greet` reads from the blockchain and does not require money for [gas](https://www.investopedia.com/terms/g/gas-ethereum.asp).
* `setGreeting` writes to the blockchain and requires payment for the transaction if you want to deploy to [mainnet](https://ethereum.org/en/glossary/#mainnet) and not a [testnet](https://ethereum.org/en/glossary/#testnet).

## Deploy to Testnet

To deploy to the local network, you first need to compile your contract and then start a local test node. Our React app will interact with the smart contract using a combination of the Ethers library, the contract address, and the ABI that will be created from the contract by `hardhat`.

### Compile Application Binary Interface

ABI stands for application binary interface. It is the interface between your client-side application and the Ethereum blockchain where the smart contract you are going to be interacting with is deployed.

```bash
pnpm hardhat compile
```

ABIs are typically compiled from Solidity smart contracts by a development framework like Hardhat. You can also often find the ABIs for a smart contract on [Etherscan](https://etherscan.io/).

### Start Test Node

```bash
pnpm hardhat node
```

This will return a list of addresses and private keys.

```
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Accounts
========
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

...
```

We will need to pick an account and corresponding private key. I will use Account #11:

```
Account #11: 0x71be63f3384f5fb98995898a86b02fb2426c5788 (10000 ETH)
Private Key: 0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82
```

Leave this terminal running and open another for the next command.

### Run Deployment Script

Run the deploy script and give a flag to the CLI to let it know that we would like to deploy to our local network:

```bash
pnpm hardhat run scripts/deploy.js \
  --network localhost
```

This will result in the following output in your terminal:

```
Greeter deployed to 0x5FbDB2315678afecb367f032d93F642f64180aa3 address
Greeter deployed by "<SignerWithAddress 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266>" signer
Deploy transaction hash: 0x74b10cb0666183a9f2d5d20dff6cbf12f2d026d39ce7c48db0f9bb02f52f2ee1
Deploy transaction block hash: 0xfb0865cdff882c11ee07521ed2732b4c961c915d88d407953336484e6ffd2395
```

The address returned on the first line (`0x5FbDB2315678afecb367f032d93F642f64180aa3`) is what we will use in our client application to talk to the smart contract. Save this address somewhere that you can access later as we will need to use it when connecting to the contract from the client application.

## Connect to MetaMask

To send transactions to the smart contract, we will need to connect our MetaMask wallet using one of the accounts created when we ran `pnpm hardhat node`. In the terminal running our local node, you will see the following output verifying that the `Greeter` contract was successfully deployed.

```
web3_clientVersion
eth_chainId
eth_accounts
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
  Contract deployment: Greeter
  Contract address:    0x5fbdb2315678afecb367f032d93f642f64180aa3
  Transaction:         0x74b10cb0666183a9f2d5d20dff6cbf12f2d026d39ce7c48db0f9bb02f52f2ee1
  From:                0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
  Value:               0 ETH
  Gas used:            505488 of 505488
  Block #1:            0xfb0865cdff882c11ee07521ed2732b4c961c915d88d407953336484e6ffd2395

  console.log:
    Deploying a Greeter with greeting: Hello from ajcwebdev!

eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
```

In the list of contracts that the CLI logs out, you should see both an Account number as well as a Private Key for the account you originally chose at the beginning of the tutorial. We can import this account into MetaMask in order to start using some of its fake Eth. To do so, first open MetaMask and check the available networks.

![01 - check metamask networks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsuehp9d7vb1mfylez2q.png)

If you only see the Ethereum Mainnet, go to Settings > Advanced and make sure "Show test networks" is set to ON.

![02 - set metamask to localhost 8545](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8bxwvd2v3uz093u2hoe3.png)

Update the network to Localhost 8545.

### Import Account into MetaMask

Once you are connected to Localhost 8545, click the icon at the top right to see your accounts. Select "Import Account."

![03 - import account into metamask](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cskqw7cw2h5g7532zc92.png)

Go back to the list of accounts printed by `pnpm hardhat node` and copy the account's private keys. I'll be using Account #11.

![04 - paste private key to import account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxzhie84g1jqarghftuv.png)

After importing the account you will see ether in the account.

![05 - ether account balance in metamask](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvx212ng4nyagzbt7rge.png)

If you are using MetaMask for other purposes, it will be important to distinguish this account from an account that is actually holding funds. Go to "Account details."

![06 - get account details in metamask](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bowt63izdzaa5anjaccu.png)

Click the pencil icon next to the account name to give it a more descriptive name.

![07 - rename account to hardhat account 11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hst60nomgcxutmt5c0y.png)

## Create React App

Set `greeterAddress` to the contract address logged out to the CLI when it was deployed. I have created an environment variable named `VITE_GREETER_ADDRESS` in a `.env` file. Any environment variables that need to be exposed on the client must be prefixed with `VITE_`.

```jsx
// src/App.js

import { useState } from 'react'
import { ethers } from 'ethers'
import Greeter from './artifacts/contracts/Greeter.sol/Greeter.json'

const greeterAddress = import.meta.env.VITE_GREETER_ADDRESS

function App() { }

export default App
```

We'll import the `useState` hook and set the state to `greeting` with `setGreetingValue`. `fetchGreeting` will call the smart contract and read the current greeting value.

```jsx
// src/App.js

import { useState } from 'react'
import { ethers } from 'ethers'
import Greeter from './artifacts/contracts/Greeter.sol/Greeter.json'

const greeterAddress = import.meta.env.VITE_GREETER_ADDRESS

function App() {
  const [greeting, setGreetingValue] = useState()

  async function requestAccount() {
    await window.ethereum.request({ method: 'eth_requestAccounts' })
  }

  async function fetchGreeting() {
    if (typeof window.ethereum !== 'undefined') {
      const provider = new ethers.providers.Web3Provider(window.ethereum)
      const contract = new ethers.Contract(greeterAddress, Greeter.abi, provider)
      try {
        const data = await contract.greet()
        setGreetingValue(data)
        console.log('Greeting: ', data)
        console.log('Contract Address: ', contract.address)
        console.log('Contract Network: ', contract.provider._network.name)
      } catch (err) {
        console.log("Error: ", err)
      }
    }    
  }

  return (
    <div>
      <header>
        <h1>Ethers.js, Hardhat, Solidity</h1>
        <h2>and React, Alchemy, and MetaMask</h2>
      </header>

      <main>
        <h3>Greeting</h3>

        <button onClick={fetchGreeting}>
          Fetch Greeting
        </button>

        <div>{greeting}</div>
      </main>
    </div>
  )
}

export default App
```

I'll include Water.css in `index.html` for some nice looking CSS defaults.

```html
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
    <title>A First look at Ethers and Hardhat</title>
  </head>

  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
```

### Start Development Server

Start the development server with `pnpm dev`.

```bash
pnpm dev
```

Open [localhost:3000](http://localhost:3000).

![08 - react app on localhost 3000 with fetch greeting button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkoxpx53ht60b3o7ki34.png)

Click the "Fetch Greeting" button to display the greeting from the deployed contract.

![09 - greeting displayed after clicking fetch greeting button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5a26a1vq1urbtvaewprp.png)

`setGreeting` calls the smart contract and sends an update.

```jsx
// src/App.js

import { useState } from 'react'
import { ethers } from 'ethers'
import Greeter from './artifacts/contracts/Greeter.sol/Greeter.json'

const greeterAddress = import.meta.env.VITE_GREETER_ADDRESS

function App() {
  const [greeting, setGreetingValue] = useState()

  async function requestAccount() {
    await window.ethereum.request({ method: 'eth_requestAccounts' })
  }

  async function fetchGreeting() {
    if (typeof window.ethereum !== 'undefined') {
      const provider = new ethers.providers.Web3Provider(window.ethereum)
      const contract = new ethers.Contract(greeterAddress, Greeter.abi, provider)
      try {
        const data = await contract.greet()
        setGreetingValue(data)
        console.log('Greeting: ', data)
        console.log('Contract Address: ', contract.address)
        console.log('Contract Network: ', contract.provider._network.name)
      } catch (err) {
        console.log("Error: ", err)
      }
    }    
  }

  async function setGreeting() {
    if (!greeting) return
    if (typeof window.ethereum !== 'undefined') {
      await requestAccount()
      const provider = new ethers.providers.Web3Provider(window.ethereum)
      const signer = provider.getSigner()
      const contract = new ethers.Contract(greeterAddress, Greeter.abi, signer)
      const transaction = await contract.setGreeting(greeting)
      await transaction.wait()
      fetchGreeting()
    }
  }

  return (
    <div>
      <header>
        <h1>Ethers.js, Hardhat, Solidity</h1>
        <h2>and React, Alchemy, and MetaMask</h2>
      </header>

      <main>
        <h3>Greeting</h3>

        <button onClick={fetchGreeting}>
          Fetch Greeting
        </button>
        <div>{greeting}</div>

        <input
          onChange={e => setGreetingValue(e.target.value)}
          placeholder="Set greeting" 
        />
        <button onClick={setGreeting}>
          Set Greeting
        </button>
      </main>
    </div>
  )
}

export default App
```

### Connect React App to MetaMask

Select account to connect with MetaMask.

![10 - select account to connect with metamask](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rytdc1cj071kk4xcm27v.png)

Connect to the account.

![11 - connect to account with metamask](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qft6ta0lj5xhk4i0knu8.png)

Enter input and click Set Greeting. You will be asked to pay a gas fee.

![12 - pay gas fee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xppbyh6zwtqgacjojjax.png)

The greeting value will be set to the new inputted greeting.

## Deploy to Ropsten Testnet

Ethereum provides test networks like Ropsten, Rinkeby, or Kovan that we can use to deploy a publicly accessible version of our contract without having to deploy it to the mainnet.

### Connect MetaMask to Ropsten

Update your MetaMask wallet to connect to the [Ropsten](https://ropsten.etherscan.io/) network.

![13 - change to ropsten testnet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xeefv78bpy2vj1i1xdi8.png)

We can get access to Ropsten and other test networks by using a service like [Infura](https://infura.io/), [Alchemy](https://www.alchemy.com/), or [QuickNode](https://www.quicknode.com/).

### Create an Alchemy Account

Alchemy Supernode is a blockchain API that can connect to various tools in the blockchain ecosystem such as Ethereum, Polygon, Arbitrum, Optimism, and Flow. It provides common node functionality including JSON-RPC support with built in reliability, data correctness and scalability. After creating an account, you will see your dashboard.

![14 - alchemy dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1w8axkijbifzcfa9lqu7.png)

### Create an Alchemy Application

Click **Create App** and give your app a name and description. Select development for the environment, Ethereum for the chain, and Ropsten for the network.

![15 - create app on alchemy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drxvimw53jqlu4nrmcii.png)

After creating the app you will see it appear in your dashboard.

![16 - alchemy dashboard with ajcwebdev-ethers app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0fpw8akulm90jgp6vup0.png)

### Get Alchemy Key

Click the name of the app to see more details.

![17 - alchemy app view details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjzpqe9zgmo3nzcstkmy.png)

Click **View Key** to see your endpoints.

![18 - alchemy endpoints](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nvxdpozaswc9u4fy4ccc.png)

The https endpoint will look something like this:

```
https://eth-ropsten.alchemyapi.io/v2/PROJECT_ID
```

Click **Edit App** and add the account address from `App.jsx` to the allowed accounts.

![19 - allowed addresses](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ved13q0s5d1lrgnmofu.png)

### Add Alchemy Endpoint to Hardhat Configuration

Open `hardhat.config.js` and add `networks` property.

```js
// hardhat.config.js

require("@nomiclabs/hardhat-waffle")
require('dotenv').config()

const { ALCHEMY_URL, ALCHEMY_KEY } = process.env

task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners()

  for (const account of accounts) {
    console.log(account.address)
  }
})

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.7",
  defaultNetwork: "hardhat",
  paths: {
    artifacts: './src/artifacts',
  },
  networks: {
    hardhat: {},
    ropsten: {
      url: `${ALCHEMY_URL}`,
      accounts: [`0x` + `${ALCHEMY_KEY}`]
    }
  }
}
```

### Deploy Contract to Ropsten

Send yourself test Ether by visiting a test faucet like [faucet.ropsten.be](https://faucet.ropsten.be/) or [faucet.dimensions.network](https://faucet.dimensions.network/). Run the following script to deploy.

```bash
pnpm hardhat run scripts/deploy.js \
  --network ropsten
```

```
Greeter deployed to 0x42839A6cD421B0C29AcD3FB900feAa6a76f34684 address
Greeter deployed by "<SignerWithAddress 0x71bE63f3384f5fb98995898A86B02Fb2426c5788>" signer
Deploy transaction hash: 0x9f5875c939d804501767690d843aefb05d91f4b1f5d34d477dbe6d7327c83ca4
Deploy transaction block hash: null
```

Once your contract is deployed you can view the live contract on [Etherscan Ropsten Testnet Explorer](https://ropsten.etherscan.io/).

![20 - contract on etherscan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcbigmb5felstbcdeiio.png)