DEV Community

Cover image for The Complete Guide to Full Stack Ethereum Development
Nader Dabit
Nader Dabit

Posted on • Updated on

The Complete Guide to Full Stack Ethereum Development

Building Full Stack dApps with React, Ethers.js, Solidity, and Hardhat

In this tutorial, you'll learn a web3 tech stack that will allow you to build full stack apps on dozens of blockchain networks including Ethereum, Polygon, Avalanche, Celo, and many others by leveraging the Ethereum Virtual Machine (EVM).

The code for this project is located here. The video course for this tutorial is located here. Also check out Defining the web3 stack

I recently joined Edge & Node as a Developer Relations Engineer and have been diving deeper into smart contract development with Ethereum. I have settled upon what I think is the best stack for building full stack dApps with Solidity:

▶︎ Client Framework - React
▶︎ Ethereum development environment - Hardhat
▶︎ Ethereum Web Client Library - Ethers.js
▶︎ API layer - The Graph Protocol

The problem that I ran into though while learning this was that while there was fairly good documentation out there for each of these things individually, there was nothing really out there for how to put all of these things together and understand how they worked with each other. There are some really good boilerplates out there like scaffold-eth (which also includes Ethers, Hardhat, and The Graph), but may be too much to pick up for people just getting started.

I wanted an end to end guide to show me how to build full stack Ethereum apps using the most up to date resources, libraries, and tooling.

The things I was interested in were this:

  1. How to create, deploy, and test Ethereum smart contracts to local, test, and mainnet
  2. How to switch between local, test, and production environments / networks
  3. How to connect to and interact with the contracts using various environments from a front end like React, Vue, Svelte, or Angular

After spending some time figuring all of this out and getting going with the stack that I felt really happy with, I thought it would be nice to write up how to build and test a full stack Ethereum app using this stack not only for other people out there who may be interested in this stack, but also for myself for future reference. This is that reference.

The pieces

Let's go over the main pieces we will be using and how they fit into the stack.

1. Ethereum development environment

When building smart contracts, you will need a way to deploy your contracts, run tests, and debug Solidity code without dealing with live environments.

You will also need a way to compile your Solidity code into code that can be run in a client-side application – in our case, a React app. We'll learn more about how this works a little later.

Hardhat is an Ethereum development environment and framework designed for full stack development and is the framework that I will be using for this tutorial.

Other similar tools in the ecosystem are Ganache, Truffle, and Foundry.

2. Ethereum Web Client Library

In our React app, we will need a way to interact with the smart contracts that have been deployed. We will need a way to read for data as well as send new transactions.

ethers.js aims to be a complete and compact library for interacting with the Ethereum Blockchain and its ecosystem from client-side JavaScript applications like React, Vue, Angular, or Svelte. It is the library we'll be using.

Another popular option in the ecosystem is web3.js

3. Metamask

Metamask helps to handle account management and connecting the current user to the blockchain. MetaMask enables users to manage their accounts and keys in a few different ways while isolating them from the site context.

Once a user has connected their MetaMask wallet, you as a developer can interact with the globally available Ethereum API (window.ethereum) that identifies the users of web3-compatible browsers (like MetaMask users), and whenever you request a transaction signature, MetaMask will prompt the user in as comprehensible a way as possible.

4. React

React is a front end JavaScript library for building web applications, user interfaces, and UI components. It's maintained by Facebook and many many individual developers and companies.

React and its large ecosystem of metaframeworks like Next.js, Gatsby, Redwood, Blitz.js, and others enable all types of deployment targets including traditional SPAs, static site generators, server-side rendering, and a combination of all three. React continues to be seemingly dominating the front-end space and I think will continue to do so for at least the near future.

5. The Graph

For most apps built on blockchains like Ethereum, it's hard and time-intensive to read data directly from the chain, so you used to see people and companies building their own centralized indexing server and serving API requests from these servers. This requires a lot of engineering and hardware resources and breaks the security properties required for decentralization.

The Graph is an indexing protocol for querying blockchain data that enables the creation of fully decentralized applications and solves this problem, exposing a rich GraphQL query layer that apps can consume. In this guide we won't be building a subgraph for our app but will do so in a future tutorial.

To learn how to build blockchain APIs using The Graph, check out Building GraphQL APIs on Ethereum.

What we will be building

In this tutorial, we'll be building, deploying, and connecting to a couple of basic smart contracts:

  1. A contract for creating and updating a message on the Ethereum blockchain
  2. A contract for minting tokens, then allowing the owner of the contract to send tokens to others and to read the token balances, and for owners of the new tokens to also send them to others.

We will also build out a React front end that will allow a user to:

  1. Read the greeting from the contract deployed to the blockchain
  2. Update the greeting
  3. Send the newly minted tokens from their address to another address
  4. Once someone has received tokens, allow them to also send their tokens to someone else
  5. Read the token balance from the contract deployed to the blockchain

Prerequisites

  1. Node.js installed on your local machine
  2. MetaMask Chrome extension installed in your browser

You do not need to own any Ethereum for this guide as we will be using fake / test Ether on a test network for the entire tutorial.

Getting started

To get started, we'll create a new React application:

npx create-react-app react-dapp
Enter fullscreen mode Exit fullscreen mode

Next, change into the new directory and install ethers.js and hardhat using either NPM or Yarn:

npm install ethers hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers
Enter fullscreen mode Exit fullscreen mode

Installing & configuring an Ethereum development environment

Next, initialize a new Ethereum Development Environment with Hardhat:

npx hardhat

? What do you want to do? Create a sample project
? Hardhat project root: <Choose default path>
Enter fullscreen mode Exit fullscreen mode

Now you should see the following artifacts created for you in your root directory:

hardhat.config.js - The entirety of your Hardhat setup (i.e. your config, plugins, and custom tasks) is contained in this file.
scripts - A folder containing a script named sample-script.js that will deploy your smart contract when executed
test - A folder containing an example testing script
contracts - A folder holding an example Solidity smart contract

Because of a MetaMask configuration issue, we need to update the chain ID on our HardHat configuration to be 1337. We also need to update the location for the artifacts for our compiled contracts to be in the src directory of our React app.

To make these updates, open hardhat.config.js and update the module.exports to look like this:

module.exports = {
  solidity: "0.8.4",
  paths: {
    artifacts: './src/artifacts',
  },
  networks: {
    hardhat: {
      chainId: 1337
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Our smart contract

Next, let's have a look at the example contract given to us at contracts/Greeter.sol:

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

import "hardhat/console.sol";


contract Greeter {
  string 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a very basic smart contract. When deployed, it sets a Greeting variable and exposes a function (greet) that can be called to return the greeting.

It also exposes a function that allows a user to update the greeting (setGreeting). When deployed to the Ethereum blockchain, these methods will be available for a user to interact with.

Reading and writing to the Ethereum blockchain

There are two types of ways to interact with a smart contract, reading or writing / transactions. In our contract, greet can be considered reading, and setGreeting can be considered writing / transactional.

When writing or initializing a transaction, you have to pay for the transaction to be written to the blockchain. To make this work, you need to pay gas which is the fee, or price, required to successfully conduct a transaction and execute a contract on the Ethereum blockchain.

As long as you are only reading from the blockchain and not changing or updating anything, you don't need to carry out a transaction and there will be no gas or cost to do so. The function you call is then carried out only by the node you are connected to, so you don't need to pay any gas and the read is free.

From our React app, the way that we will interact with the smart contract is using a combination of the ethers.js library, the contract address, and the ABI that will be created from the contract by hardhat.

What is an ABI? ABI stands for application binary interface. You can think of it as the interface between your client-side application and the Ethereum blockchain where the smart contract you are going to be interacting with is deployed.

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

Compiling the ABI

Now that we have gone over the basic smart contract and know what ABIs are, let's compile an ABI for our project.

To do so, go to the command line and run the following command:

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

Now, you should see a new folder named artifacts in the src directory. The artifacts/contracts/Greeter.json file contains the ABI as one of the properties. When we need to use the ABI, we can import it from our JavaScript file:

import Greeter from './artifacts/contracts/Greeter.sol/Greeter.json'
Enter fullscreen mode Exit fullscreen mode

We can then reference the ABI like this:

console.log("Greeter ABI: ", Greeter.abi)
Enter fullscreen mode Exit fullscreen mode

Note that Ethers.js also enables human readable ABIs, but will will not be going into this during this tutorial.

Deploying and using a local network / blockchain

Next, let's deploy our smart contract to a local blockchain so that we can test it out.

To deploy to the local network, you first need to start the local test node. To do so, open the CLI and run the following command:

npx hardhat node
Enter fullscreen mode Exit fullscreen mode

When we run this command, you should see a list of addresses and private keys.

Hardhat node addresses

These are 20 test accounts and addresses created for us that we can use to deploy and test our smart contracts. Each account is also loaded up with 10,000 fake Ether. In a moment, we'll learn how to import the test account into MetaMask so that we can use it.

Next, we need to deploy the contract to the test network. First update the name of scripts/sample-script.js to scripts/deploy.js.

Now we can run the deploy script and give a flag to the CLI that we would like to deploy to our local network:

npx hardhat run scripts/deploy.js --network localhost
Enter fullscreen mode Exit fullscreen mode

Once this script is executed, the smart contract should be deployed to the local test network and we should be then able to start interacting with it.

When the contract was deployed, it used the first account that was created when we started the local network.

If you look at the output from the CLI, you should be able to see something like this:

Greeter deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
Enter fullscreen mode Exit fullscreen mode

This address is what we will use in our client application to talk to the smart contract. Keep this address available as we will need to use it when connecting to it from the client application.

To send transactions to the smart contract, we will need to connect our MetaMask wallet using one of the accounts created when we ran npx hardhat node. In the list of contracts that the CLI logs out, you should see both an Account number as well as a Private Key:

➜  react-dapp git:(main) npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

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

...
Enter fullscreen mode Exit fullscreen mode

We can import this account into MetaMask in order to start using some of the fake Eth available there.

To do so, first open MetaMask and enable test networks:

Test Networks

Next, update the network to be Localhost 8545:

Choose network

Next, in MetaMask click on Import Account from the accounts menu:

Import account

Copy then paste one of the Private Keys logged out by the CLI and click Import. Once the account is imported, you should see the Eth in the account:

Imported account

Now that we have a smart contract deployed and an account ready to use, we can start interacting with it from the React app.

Connecting the React client

In this tutorial we are not going to be worrying about building a beautiful UI with CSS and all of that, we are focused 100% on the core functionality to get you up and running. From there, you can take it and make it look good if you'd like.

With that being said, let's review the two objectives that we want from our React application:

  1. Fetch the current value of greeting from the smart contract
  2. Allow a user to update the value of the greeting

With those things being understood, how do we accomplish this? Here are the things we need to do to make this happen:

  1. Create an input field and some local state to manage the value of the input (to update the greeting)
  2. Allow the application to connect to the user's MetaMask account to sign transactions
  3. Create functions for reading and writing to the smart contract

To do this, open src/App.js and update it with the following code, setting the value of greeterAddress to the address of your smart contract.:

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

// Update with the contract address logged out to the CLI when it was deployed 
const greeterAddress = "your-contract-address"

function App() {
  // store greeting in local state
  const [greeting, setGreetingValue] = useState()

  // request access to the user's MetaMask account
  async function requestAccount() {
    await window.ethereum.request({ method: 'eth_requestAccounts' });
  }

  // call the smart contract, read the current greeting value
  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()
        console.log('data: ', data)
      } catch (err) {
        console.log("Error: ", err)
      }
    }    
  }

  // call the smart contract, send an update
  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 className="App">
      <header className="App-header">
        <button onClick={fetchGreeting}>Fetch Greeting</button>
        <button onClick={setGreeting}>Set Greeting</button>
        <input onChange={e => setGreetingValue(e.target.value)} placeholder="Set greeting" />
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

To test it out, start the React server:

npm start
Enter fullscreen mode Exit fullscreen mode

When the app loads, you should be able to fetch the current greeting and log it out to the console. You should also be able to make updates to the greeting by signing the contract with your MetaMask wallet and spending the fake Ether.

Setting and getting the greeting value

Deploying and using a live test network

There are several Ethereum test networks like Ropsten, Rinkeby, or Kovan that we can also deploy to in order to have a publicly accessible version of our contract available without having to deploy it to mainnet. In this tutorial we'll be deploying to the Ropsten test network.

To start off, first update your MetaMask wallet to connect to the Ropsten network.

Ropsten network

Next, send yourself some test Ether to use during the rest of this tutorial by visiting this test faucet.

We can get access to Ropsten (or any of the other test networks) by signing up with a service like Infura or Alchemy (I'm using Infura for this tutorial).

Once you've created the app in Infura or Alchemy, you will be given an endpoint that looks something like this:

https://ropsten.infura.io/v3/your-project-id
Enter fullscreen mode Exit fullscreen mode

Be sure to set the ALLOWLIST ETHEREUM ADDRESSES in the Infura or Alchemy app configuration to include the wallet address of the account you will be deploying from.

To deploy to the test network we need to update our hardhat config with some additional network information. One of the things we need to set is the private key of the wallet we will be deploying from.

To get the private key, you can export it from MetaMask.

Exporting private key

I'd suggest not hardcoding this value in your app but instead setting it as something like an environment variable.

Next, add a networks property with the following configuration:

module.exports = {
  defaultNetwork: "hardhat",
  paths: {
    artifacts: './src/artifacts',
  },
  networks: {
    hardhat: {},
    ropsten: {
      url: "https://ropsten.infura.io/v3/your-project-id",
      accounts: [`0x${your-private-key}`]
    }
  },
  solidity: "0.8.4",
};
Enter fullscreen mode Exit fullscreen mode

To deploy, run the following script:

npx hardhat run scripts/deploy.js --network ropsten
Enter fullscreen mode Exit fullscreen mode

Once your contract is deployed you should be able to start interacting with it. You should be now able to view the live contract on Etherscan Ropsten Testnet Explorer

Minting tokens

One of the most common use cases of smart contracts is creating tokens, let's look at how we can do that. Since we know a little more about how all of this works, we'll be going a little faster.

In the main contracts directory create a new file named Token.sol.

Next, update Token.sol with the following smart contract:

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

import "hardhat/console.sol";

contract Token {
  string public name = "Nader Dabit Token";
  string public symbol = "NDT";
  uint public totalSupply = 1000000;
  mapping(address => uint) balances;

  constructor() {
    balances[msg.sender] = totalSupply;
  }

  function transfer(address to, uint amount) external {
    require(balances[msg.sender] >= amount, "Not enough tokens");
    balances[msg.sender] -= amount;
    balances[to] += amount;
  }

  function balanceOf(address account) external view returns (uint) {
    return balances[account];
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that this token contract is for demo purposes only and is not ERC20 compliant. We will be covering ERC20 tokens here

This contract will create a new token called "Nader Dabit Token" and set the supply to 1000000.

Next, compile this contract:

npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

Now, update the deploy script at scripts/deploy.js to include this new Token contract:

const hre = require("hardhat");

async function main() {
  const [deployer] = await hre.ethers.getSigners();

  console.log(
    "Deploying contracts with the account:",
    deployer.address
  );

  const Greeter = await hre.ethers.getContractFactory("Greeter");
  const greeter = await Greeter.deploy("Hello, World!");

  const Token = await hre.ethers.getContractFactory("Token");
  const token = await Token.deploy();

  await greeter.deployed();
  await token.deployed();

  console.log("Greeter deployed to:", greeter.address);
  console.log("Token deployed to:", token.address);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });
Enter fullscreen mode Exit fullscreen mode

Now, we can deploy this new contract to the local or Ropsten network:

npx hardhat run scripts/deploy.js --network localhost
Enter fullscreen mode Exit fullscreen mode

Once the contract is deployed, you can start sending these tokens to other addresses.

To do so, let's update the client code we will need in order to make this work:

import './App.css';
import { useState } from 'react';
import { ethers } from 'ethers'
import Greeter from './artifacts/contracts/Greeter.sol/Greeter.json'
import Token from './artifacts/contracts/Token.sol/Token.json'

const greeterAddress = "your-contract-address"
const tokenAddress = "your-contract-address"

function App() {
  const [greeting, setGreetingValue] = useState()
  const [userAccount, setUserAccount] = useState()
  const [amount, setAmount] = 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)
      console.log({ provider })
      const contract = new ethers.Contract(greeterAddress, Greeter.abi, provider)
      try {
        const data = await contract.greet()
        console.log('data: ', data)
      } catch (err) {
        console.log("Error: ", err)
      }
    }    
  }

  async function getBalance() {
    if (typeof window.ethereum !== 'undefined') {
      const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const contract = new ethers.Contract(tokenAddress, Token.abi, provider)
      const balance = await contract.balanceOf(account);
      console.log("Balance: ", balance.toString());
    }
  }

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

  async function sendCoins() {
    if (typeof window.ethereum !== 'undefined') {
      await requestAccount()
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const signer = provider.getSigner();
      const contract = new ethers.Contract(tokenAddress, Token.abi, signer);
      const transation = await contract.transfer(userAccount, amount);
      await transation.wait();
      console.log(`${amount} Coins successfully sent to ${userAccount}`);
    }
  }

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={fetchGreeting}>Fetch Greeting</button>
        <button onClick={setGreeting}>Set Greeting</button>
        <input onChange={e => setGreetingValue(e.target.value)} placeholder="Set greeting" />

        <br />
        <button onClick={getBalance}>Get Balance</button>
        <button onClick={sendCoins}>Send Coins</button>
        <input onChange={e => setUserAccount(e.target.value)} placeholder="Account ID" />
        <input onChange={e => setAmount(e.target.value)} placeholder="Amount" />
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Next, run the app:

npm start
Enter fullscreen mode Exit fullscreen mode

We should be able to click on Get Balance and see that we have 1,000,000 coins in our account logged out to the console.

You should also be able to view them in MetaMask by clicking on import tokens:

Import Tokens

Next click on Custom Token and enter the token contract address and then Add Custom Token. (if asked for token decimals, choose 0) Now the tokens should be available in your wallet:

NDT

Next, let's try to send those coins to another address.

To do so, copy the address of another account and send them to that address using the updated React UI. When you check the token amount, it should be equal to the original amount minus the amount you sent to the address.

ERC20 Token

The ERC20 Token Standard defines a set of rules that apply to all ERC20 tokens which allow them to easily interact with each other. ERC20 makes it really easy for someone to mint their own tokens that will have interoperability with others on the Ethereum blockchain.

Let's look at how we may build our own token using the ERC20 standard.

First, install the OpenZepplin smart contract library where we will be importing the base ERC20 Token:

npm install @openzeppelin/contracts
Enter fullscreen mode Exit fullscreen mode

Next, we'll create our token by extending (or inheriting from) the ERC20 contract:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract NDToken is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _mint(msg.sender, 100000 * (10 ** 18));
    }
}
Enter fullscreen mode Exit fullscreen mode

The constructor allows you to set the token name and symbol, and the _mint function allows you to mint the tokens and set the amount.

By default, ERC20 sets the number of decimals to 18, so in our _mint function we multiply 100,000 by 10 to the 18 power to mint a total of 100,000 tokens, each with 18 decimal places (similarly to how 1 Eth is made up of 10 to the 18 wei.

To deploy, we need to pass in the constructor values (name and symbol), so we might do something like this in our deploy script:

const NDToken = await hre.ethers.getContractFactory("NDToken");
const ndToken = await NDToken.deploy("Nader Dabit Token", "NDT");
Enter fullscreen mode Exit fullscreen mode

By extending the original ERC20 token, your token will inherit all of the following functions and functionality:

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
Enter fullscreen mode Exit fullscreen mode

Once deployed, you can use any of these functions to interact with the new smart contract. For another example of an ERC20 token, check out [Solidity by example)(https://solidity-by-example.org/app/erc20/)

Conclusion

Ok, we covered a lot here but for me this is kind of the bread and butter / core of getting started with this stack and is kind of what I wanted to have not only as someone who was learning all of this stuff, but also in the future if I ever need to reference anything I may need in the future. I hope you learned a lot.

If you want to support multiple wallets in addition to MetaMask, check out Web3Modal which makes it easy to implement support for multiple providers in your app with a fairly simple and customizable configuration.

In my future tutorials and guides I'll be diving into more complex smart contract development and also how to deploy them as subgraphs to expose a GraphQL API on top of them and implement things like pagination and full text search.

I'll also be going into how to use technologies like IPFS and Web3 databases to store data in a decentralized way.

If you have any questions or suggestions for future tutorials, drop some comments here and let me know.

Discussion (111)

Collapse
horaceshmorace profile image
Horace Nelson

Any idea why getBalance in App.js would throw

Error: call revert exception (method="balanceOf(address)", errorSignature=null, errorArgs=[null], reason=null, code=CALL_EXCEPTION, version=abi/5.1.0)

sendCoins works fine.

Collapse
niroshans profile image
Niroshan Sooriyakumar • Edited on

Not sure if this will help but I had the same error while working on my own project. I used this blog post as a starting point and my code is slightly different but I was also getting this error on a read-only transaction. The code was working the day before and I was really consued.

After some failed googling, I saw that metamask was still pointing to mainnet (I had switched away from localhost to mainnet later that day). After switching back to localhost things just started working!

Not sure if this will help your particular issue but thought I'd post this here if someone else is also googling this error. Hopes this helps someone out there!

Collapse
horaceshmorace profile image
Horace Nelson • Edited on

Here's the deploy output:

Starting: scripts/deploy.js --network localhost
Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deploying a Greeter with greeting: Hello, World!
Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Token deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Enter fullscreen mode Exit fullscreen mode

And here's my App.js:

import './App.css';
import { useState } from 'react';
import { ethers } from 'ethers'
import Greeter from './artifacts/contracts/Greeter.sol/Greeter.json'
import Token from './artifacts/contracts/Token.sol/Token.json'
const greeterAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
const tokenAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"

function App() {
  const [greeting, setGreetingValue] = useState()
  const [userAccount, setUserAccount] = useState()
  const [amount, setAmount] = 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)
      console.log({ provider })
      const contract = new ethers.Contract(greeterAddress, Greeter.abi, provider)
      try {
        const data = await contract.greet()
        console.log('data: ', data)
      } catch (err) {
        console.log("Error: ", err)
      }
    }    
  }

  async function getBalance() {
    if (typeof window.ethereum !== 'undefined') {
      const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })
      console.log({ account }) // outputs { account: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const signer = provider.getSigner()
      const contract = new ethers.Contract(tokenAddress, Token.abi, signer)

      // THIS THROWS
      contract.balanceOf(account)
        .then(data => {
          console.log("data: ", data.toString())
        })
    }
  }

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

  async function sendCoins() {
    if (typeof window.ethereum !== 'undefined') {
      await requestAccount()
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const signer = provider.getSigner()
      const contract = new ethers.Contract(tokenAddress, Token.abi, signer)
      contract.transfer(userAccount, amount).then(data => console.log({ data }))
    }
  }

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={fetchGreeting}>Fetch Greeting</button>
        <button onClick={setGreeting}>Set Greeting</button>
        <input onChange={e => setGreetingValue(e.target.value)} placeholder="Set greeting" />

        <br />
        <button onClick={getBalance}>Get Balance</button>
        <button onClick={sendCoins}>Send Coins</button>
        <input onChange={e => setUserAccount(e.target.value)} placeholder="Account ID" />
        <input onChange={e => setAmount(e.target.value)} placeholder="Amount" />
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
Collapse
sprtd profile image
Dav • Edited on

I faced the same issue too and figured it out. First, make sure you've selected the right MetaMask network. If you deployed to a local node, make sure the selected MetaMask network on your browser is Localhost: 8545. If you deployed to ropsten, make sure you've selected Ropsten Test Network. Secondly, each time you successfully deploy your contract, make sure you correctly copy and reference the generated contract address if you intend to interact with the newly deployed contract without errors. Just like @richardmelko advised, make sure that your tokenAddress is pointing the Token contract address you copied after deployment.

Collapse
smrnjeet222 profile image
Simranjeet Singh


const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner(); // remove this
const contract = new ethers.Contract(tokenAddress, Token.abi, signer); // replace signer with provider

signing is not required here!

Thread Thread
jamiescript profile image
Jamiebones

Signing is required.

Collapse
dabit3 profile image
Nader Dabit Author

Hey, not sure why you would run into that, did you figure it out?

Thread Thread
dominicwong profile image
Dominic Wong

I am running into the same issue

Thread Thread
dominicwong profile image
Dominic Wong

It worked when I called balanceOf inside deploy.js, just not when calling from App.js

Thread Thread
enemycnt profile image
Nikolay

Stuck with the same issue. After some deep dive into hardhat docs finally, I've found the cause.
It looks like instead

npx run scripts/deploy.js --network localhost
Enter fullscreen mode Exit fullscreen mode

should be

npx run hardhat scripts/deploy.js --network localhost
Enter fullscreen mode Exit fullscreen mode

@dabit3 could you please correct it?

Collapse
richardmelko profile image
Richard Melkonian

I had this error after switching to live test nets then back to local host. The error originates from these line of code in your react components.

const greeterAddress = "{address that you copied in from the local node instance}"

const tokenAddress = "{address that you copied in from the local node instance}"

If you're deploying on Ropsten for example, you will need to manually update this line of code, changing the address to the deployed address that hardhat will supply in the terminal after successfully deploying. So when switching from local to test net, this line needs to be changed!

Collapse
gautham profile image
Gautham 🌶

Hi Horace, I was getting this same error - my issue was that I had re-deployed my contracts and their addresses had changed, so I had to copy-paste the new addresses to the top of App.js.

Collapse
nmassi profile image
Nico

Nice run Nader – thanks for this.
I am a newbie in Ethereum and I got this error after make some tx with the same account:

Nonce too high. Expected nonce to be 0 but got 4. Note that transactions can't be queued when automining.

If you got this, I figured that there is a limit (I think it's 4 per block) on the number transactions the same address can have on the transaction pool, so just by adding another account in metamask works again :)

Collapse
dabit3 profile image
Nader Dabit Author

Good call out. You can also go to advanced settings and reset the account which will fix this as well.

Collapse
lpatipat profile image
lpatipat

Hi Nader,

Likewise, thanks for this great post. I had the same issue and did resolve it using the methods above. Could you elaborate further on the problem here? Is this just a quirk for the hardhat local network or is this something related to ethereum in general?

Thanks in advance.

Thread Thread
dabit3 profile image
Nader Dabit Author

I believe it has something to do with the nonce being out of sync, but I don't know a lot more than that.

Collapse
emanuel_hodl profile image
Emanuel • Edited on

If you have this problem at the beginning:

**Is because you're using an account address instead of the contract address that was prompted in the terminal when you deployed it.

Error:  Error: network does not support ENS (operation="ENS", network="unknown", code=UNSUPPORTED_OPERATION, version=providers/5.1.0)
    at Logger.makeError (index.ts:205)
    at Logger.throwError (index.ts:217)
    at Web3Provider.<anonymous> (base-provider.ts:1407)
    at Generator.next (<anonymous>)
    at fulfilled (base-provider.ts:2)```


Enter fullscreen mode Exit fullscreen mode
Collapse
wschwab profile image
wschwab

I usually get that when I put in a signer instead of the signer's address. The basic idea is that you're putting in something other than a hex address, so the compiler's first thought is that it must be an ENS address (ENS addresses are a way to attach a url-like identifier to an address, like vitalik.eth).

Collapse
nmassi profile image
Nico • Edited on

or if you leave "your-contract-address" as I forgot to change the const :)
Looks like any string (if it isn't a contract address) assume it's an ens domain.

Collapse
mayassalman profile image
mayas salman

thank you Nader
I will consider this as my first step to blockchain domain
all the best

Collapse
preciouschicken profile image
Precious Chicken

Great run though, thank you. Do you have any particular reason for preferring hardhat over ganache / truffle? I think the latter is probably more popular (?), not of course that is any reason to prefer it. I hadn't heard of hardhat previously, so will give it a whirl.

I too prefer ethers to web3; I liked the documentation more. Often with these choices though, you just have to choose and go. Otherwise you can spend your entire time choosing rather than actually coding...

Collapse
dabit3 profile image
Nader Dabit Author

I think either are good choices, but after talking with a few people in the space and hearing that some projects like Aave, Decentraland, PoolTogether, Synthetix, and others were now using it I decided to go with Hardhat.

Collapse
preciouschicken profile image
Precious Chicken

Hmmm, interesting. I will defo take a look next time.

Collapse
nirajkamdar profile image
Niraj Kamdar • Edited on

I am a long-time truffle user but planning to switch over hardhat for a couple of reasons. In hardhat, you have support for multiple solidity compilers simultaneously, it's really helpful say if you have a 0.5 and 0.8 contract in the same codebase. Truffle will refuse to compile and you have to perform some hacks to make it work. Hardhat also has this nice collection of extensions like: console.log, auto compile & deployment while solidity code changes, etc. which are super useful while developing. I also found some minor problems in the truffle development chain like they set a hard block limit which is lesser than the main net limit which may result in your transaction being reverted.

Collapse
preciouschicken profile image
Precious Chicken

console.log would be very useful...

Collapse
bdougieyo profile image
Brian Douglas

This is such a great run-through on such a complex topic. I was aware of a lot of these tools but never leveraged them because of how dense the docs and content usually are. I am looking forward to learning more about this space from you. Keep it up!

Collapse
dabit3 profile image
Nader Dabit Author

Thanks Brian, happy to hear this 🙏

Collapse
nickytonline profile image
Nick Taylor

I went to the faucet site, but it contains a certificate error. Maybe that faucet is no longer valid?

Ropsten Faucet Site has an invalid certificate

If you continue to the site it returns the following JSON response.

{
  code: "ResourceNotFound",
  message: "/ does not exist"
}
Enter fullscreen mode Exit fullscreen mode

I'm going to try out the one mentioned here

The Ropsten Ethereum Faucet said I was spamming, so I just ended up using this one instead -- faucet.dimensions.network/

Collapse
nickytonline profile image
Nick Taylor

Looks like the test faucet in the tutorial is working again. I guess they updated their certificate. 😎

Collapse
tanyeun profile image
tanyeun • Edited on

Help! I got stuck at this step:

After I import private key, I got the following error message:

Expected private key to be an Uint8Array with length 32

Expected private key to be an Uint8Array with length 32

Collapse
lhultqvist profile image
Lucas

I have the same error... Have you found a solution yet?

Collapse
ockhamsrazor profile image
OckhamsRazor • Edited on

Maybe you copied this contract address,

you should choose one from these 20 accounts' keys

Collapse
robertosnap profile image
RobertoSnap
Collapse
arielbk profile image
arielbk

I used NextJS as the React framework, added some styling and refactored the code a bit.

Check it out: react-eth.netlify.app/

All of the code for this is on GitHub also.

The token on that extends the ERC20 standard, and I ran into some issues here. Since there are 18 decimal places for the token I would try to transfer something like 500 * 10 ** 18 and would get an overflow error.

To resolve this I just had to put it into a JavaScript BigInt like so:
const wholeTokens = BigInt(amount * 10 ** 18)

Hope this helps someone!

Collapse
chiranz profile image
Chiranjibi Poudyal

I am doing this project in typescript, when I run "npx hardhat node" it throws an error saying

An unexpected error occurred:

/home/chiranz/programming/blockchain/smartcontracts/tutorials/react-dapp/hardhat.config.ts:12
export default config;

SyntaxError: Unexpected token 'export'
Enter fullscreen mode Exit fullscreen mode

What am I missing here ?

import { HardhatUserConfig } from "hardhat/types";
const config: HardhatUserConfig = {
  solidity: {
    compilers: [{ version: "0.8.3", settings: {} }],
  },
  paths: { artifacts: "./src/artifacts" },
  networks: {
    hardhat: {
      chainId: 1337,
    },
  },
};

export default config;
Enter fullscreen mode Exit fullscreen mode
Collapse
mmatila profile image
Manu Matila

Also ran into this issue. create-react-app Typescript template had created a tsconfig file for me, that had "module": "esnext". Changing it to "module": "commonjs" ended up fixing the issue for me.

Collapse
lpatipat profile image
lpatipat

I also ran into this with typescript for hardhat. Did you ever find a solution?

Collapse
lpatipat profile image
lpatipat

Update:

just found a fix:

update the hardhat.config.ts file with

"module": "commonjs"

Collapse
horaceshmorace profile image
Horace Nelson • Edited on

Wow. This is the best full-stack dApp tutorial I've seen. I wish I had this a few months ago (before I already learned it). The only thing I think is really missing for a starter tutorial (in a future post maybe?) is to touch on ERC standards. But really, well done.

Collapse
dabit3 profile image
Nader Dabit Author

Thanks Horace, I now plan on updating this with more info based on feedback I've received and also doing a part two that goes into more depth, I appreciate your feedback 🙏

Collapse
thasquirrie profile image
Zulu™#July25th

Can't wait for it. Thanks Nader. One more question tho. I'm a backend dev with Node.js and I'd love to get started with blockchain how and what resources should I get started with. Thanks

Collapse
fwalker profile image
Francine Walker

Almost a year old and this is still the best tutorial out there, IMO.
But I am stuck in a VERY frustrating aspect. When I run:
npx hardhat run scripts/deploy.js --network localhost
It returns with no error, but also no console output, and no console output on the terminal with the hardhart node running.
I don't know what to do, I can't find an answer online.
Has anyone encounter this frustrating problem.
I am new to this, but have been at it for several months with different projects and i think I got the basics down.
But this one is stumping me ! :(
I am running on a Linux machine on AWS.

Collapse
drinkius profile image
Alexander Telegin • Edited on

Same here actually. Execution just stops on getContractFactory, no output, try/catch doesn't help, no info on the web - just your comment. JS works just fine, TS has such unexpected undebuggable issues

UPD: ok, as usual it turned out to be a simple missing await but one level higher that I've forgotten to look at :)

Collapse
brianfakhoury profile image
Brian Fakhoury

This was great -- really enjoyed the walkthrough.

I'm running into an issue where the deployed contract addresses have already been used previously. What's a straightforward way to force the contract to deploy to another address? I tried changing the solidity files slightly and re-compiling them, but no luck.

Collapse
dominicwong profile image
Dominic Wong

I did the same thing and no luck as well. Tried restarting the node and the contracts were deployed to the same address.

Collapse
tangonan profile image
Tango (he/him)

When I run the React server to test the greeting, the "Fetch greeting" button does not prompt Metamask to sign. The "Set greeting" also does not prompt MM.

I've started the tutorial from scratch three times with the same result.

What could I have done incorrectly?

Collapse
codybreene profile image
Cody Breene

I ran into the same issue - you'll just need to reconnect MetaMask: ethereum.stackexchange.com/questio...

Collapse
ockhamsrazor profile image
OckhamsRazor • Edited on

Maybe you need to keep "npx hardhat node" running when you are using react-app. If I don't keep it running, there will be no sign in a moment, after that, errors happen.

Collapse
ramvi profile image
Jon Ramvi

Thanks for the perfectly detailed introduction to dapp development!

How do you feel about the hardhat-react plugin? Is that something that could be included in this guide? npmjs.com/package/@symfoni/hardhat...

It autogenerates typed React hooks integrated into the Hardhat smart contract watch pipeline and gives you hot reloading while developing contracts

Collapse
elm3nt0r profile image
elm3nt0r

So symfoni makes it easier to connect the back-end dapp code (eg. the smart contract) to the front-end? So for example, wanted to make a website the will result in exchanging an NFT for ether, symfoni would make it easier?

Collapse
ramvi profile image
Jon Ramvi

That's right. Symfoni, together with Hardhat, does all the heavy lifting and repetitive tasks for you.

Collapse
nicebardo profile image
niceBardo

This is n awesome tutorial, thanks!
I am stuck while trying to set the greeting, I have this error in my console:

Object { code: -32603, message: "Error: [ethjs-query] while formatting outputs from RPC '{\"value\":{\"code\":-32603,\"data\":{\"code\":-32602,\"message\":\"Trying to send an incompatible EIP-155 transaction, signed for another chain.\"}}}'" }

and this in my terminal:

eth_blockNumber (2)
eth_sendRawTransaction

Trying to send an incompatible EIP-155 transaction, signed for another chain.

Can anybody help?
Thanks!

Collapse
a_regularjeff profile image
Jeff • Edited on

Verify your hardhat.config.js, in my case my issue was a typo around network

Collapse
charles_lukes profile image
Son DotCom 🥑💙

I had this error too, I resolved it by connecting my meta mask, you'll see the connect button on meta mask click on it.

Collapse
iamjasonlevin profile image
Jason LΞvin

I'm getting this error. Source "hardhat/console.sol" not found: File import callback not supported And when I do npx hardhat compile it says there's nothing to compile. Any idea how to fix this?

Collapse
colnnn profile image
Colnnn

hi have you figured it out? I'm getting the same error too

Collapse
palodean profile image
PaloDean
Collapse
pureblack profile image
Pure Black

This is the problem of my newspaper. Why is it like this? Can it solve this problem? I have followed the steps in the tutorial and refactored it several times. The errors are the same. thank you

./src/App.js
Module not found: Can't resolve './artifacts/contracts/Greeter.sol/Greeter.json' in '/Users/liuminghui/Desktop/react/react-dapp/src'

img

Collapse
braifz profile image
Braian Fernandez

Verify your hardhat.config.js, in my case my issue was a typo in paths :D

Collapse
nickytonline profile image
Nick Taylor

I'm going through the post building things out and when I compiled via npx hardhat compile, I had a look at the outputted JSON and it got me thinking about ASTs and then I discovered AST Explorer support Solidity for anyone interested.

A Solidity file in AST Explorer

It also got me thinking that you could probably write codemods for Solidity files. Anyways, small brainfart lol.

Collapse
steadylearner profile image
Steadylearner • Edited on

I wrote a blog post "How to make a fullstack dapp with React, Hardhat and Ethers js" following this.

You will have the React frontend examaple along with it.

cover

Collapse
tysonwynne_ profile image
Tyson Wynne

The Ropsten Ethereum Faucet said I was spamming, so I just ended up using this one instead -- faucet.dimensions.network/

Collapse
ricardo85x profile image
Ricardo T

With truffle the contract address was inside the artifact file on networks.
Are there something like that on hardhat?

I'm asking that, because it is easier to import the address direct from the json file.

Collapse
huudyy profile image
PabloHoudini • Edited on

Great job!

I am also trying to deploy such full stack react application to fleek, but the problem I have is that artifacts folder is in .gitignore so I do not have access to it like I do in the local environment:

import Election from '../../artifacts/contracts/Election.sol/Election.json'; 
Enter fullscreen mode Exit fullscreen mode

From hardhat docs it is recommended NOT to add that folder as it might get HUGE...
Has someone tried that, maybe ?
any help appreciated;)
~~~~~~~~~!!!UPDATE!!!!~~~~~~~
So, on fleek.co you can actually run 'npx hardhat compile' to create artifact directory for you and make the above import work correctly again, something like this:
alt text

Collapse
kacemlight profile image
Kacem AIT OUAL

Hello,
I got this error when I update my app.tsx :
Property 'ethereum' does not exist on type 'Window & typeof globalThis'

And I cannot even compile my code:
Failed to compile.

/workspace/template-typescript-react/src/App.tsx
TypeScript error in /workspace/template-typescript-react/src/App.tsx(15,18):
Property 'ethereum' does not exist on type 'Window & typeof globalThis'. TS2339

13 |   // request access to the user's MetaMask account
14 |   async function requestAccount() {
Enter fullscreen mode Exit fullscreen mode

15 | await window.ethereum.request({ method: 'eth_requestAccounts' });
| ^
16 | }
17 |
18 | // call the smart contract, read the current greeting value

I'm working on a running node on gitpod.

Collapse
marlonaesparza profile image
Marlon

Is it just me, or are none of the ropsten faucets working at the moment? Should I adjust and just switch test networks, or what should i do? Thanks. :)

Will improvise on a new branch until I figure it out lol...

Collapse
deer26 profile image
deer

Hi all, any thoughts on the following error? Happening when first trying to set the greeting.
Error: Transaction reverted: function selector was not recognized and there's no fallback function
at Greeter. (contracts/Greeter.sol:6)

Thought I would jump into the web3 world with what seemed like a popular tutorial, but sadly not making it very far.

Collapse
jyt profile image
JT

I am getting the same issue. Have you been able to resolve this?

Collapse
deer26 profile image
deer

Nope. I moved on, although I would like to revisit if someone knows what to do. Maybe I'll learn enough with other tutorials to unblock myself here. Will comment if I figure it out.

Collapse
cleveroscar profile image
Oscar Ortiz

As of today, this article is still amazing.

Just managed to complete this guide and feel super confident with using the docs now that I know how to work around the baiscs.

Thank you so much for this guide my dude! I been bouncing through so many docs and trying to do it all on my own.

Having this guide helped out a lot!

Much thanks!

Collapse
atish_kadam_f9074b3182262 profile image
Atish Kadam

Hey, I am running into vulnerability error when I am trying to install

npm install ethers hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers
Error- found 3089 vulnerabilities (11 low, 968 moderate, 2110 high)
run npm audit fix to fix them, or npm audit for details
I tried 'npm audit fix' but still facing same issue.

Collapse
greent3 profile image
greent3

To anyone receiving an error when importing "hardhat/console.sol" in VS code.. this error is caused by the solidity extension in VS code (version 0.0.136). In order to fix the error, simply go to your extensions manager in VS code, click on the solidity extension (should be Juan Blanco), click the down-arrow on the "uninstall" button, click "install another version", and then click 0.0.135.
After restarting your VS code application, the error should be gone. Anything Blockchain explains it in the video below.
youtube.com/watch?v=5qTdQNCMwk8