Building a dapp frontend, using React & Network.js, that can connect and request data from the Ethereum mainnet using Infura & Metamask

Developing a service or business on Ethereum can be difficult, due to the intricacies of working with a blockchain and the novel UX / UI issues to solve. This series of guides aims to serve as a quickstart to Ethereum dapp development, with a focus on front-end solutions. Thankfully, you don’t need to reinvent the chain. There are many pre-existing tools and services that can be leveraged to get a dapp running quickly, including:

  • Infura as a JSON-RPC connection to the mainnet or testnets;
  • MetaMask to serve as the user’s wallet and web3 provider;
  • OpenZeppelin for contracts and Network JS, a javascript library for web3 (i.e. blockchain development). This code tutorial is inspired by their dapp creation guide.

Using these products together has the benefit of replacing database setup and user credential management, resulting in a quick-to-start blockchain infrastructure. In this tutorial, we’ll create a React dapp using Network JS that can interact with MetaMask, which uses Infura as its connection to the Ethereum mainnet.

Environment Setup

Before we start you should have Node.js with npm installed. Let’s begin by creating a new project...

mkdir web3-infura && cd web3-infura

npm init -y

...and installing Network JS by OpenZeppelin to get library methods for working with web3:

npm install @openzeppelin/network

Now we’re ready to create a Dapp

Using a common React boilerplate called create-react-app we create the initial version of our dapp. This is easy to setup with npx (npm’s package runner). One line and we’ll have the scaffolding files for our dapp:

npx create-react-app client

In order to use Infura inside your dapp, you’ll need to register to set up an account and create a Project.

In the client/src/App.js file, we find the react project and look for the placeholder code written in App.js. We replace the placeholder code with the following:

import React from 'react';
import './App.css';
import { useWeb3 } from '@openzeppelin/network/react';
const infuraProjectId = '<YOUR_INFURA_PROJECT_ID';
function App() {
const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${infuraProjectId}`);
const { networkId, networkName, providerName } = web3Context;
return (
<div className="App">
	<div>
	<h1>Infura/MetaMask/OpenZeppelin Dapp</h1>
		<div>
    	Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}
		</div>
		<div>
		Provider: {providerName}
		</div>
	</div>
</div>
);
}
export default App;

Our dapp will now reveal which Ethereum network (mainnet or a testnet) it is currently connected to, and the web3 provider it’s using.

Let’s test this out by saving and starting the browser by running npm start from within the /client directory. Test it out by replacing the mainnet in the Infura websocket URL to rinkeby. If MetaMask is already installed, disable the extension here as you will see it takes precedence.

How this works is that in the above code we import useWeb3 from the React implementation of Network JS (@openzeppelin/network/react) in order to get a web3Context. This is a Javascript hook that will attempt to retrieve an injected web3 provider which, by default, is MetaMask. If no provider is injected, it will use the Infura URL set as web3Context.  The term injected means code or data that comes from a user's browser and is available for the website to use.

Please note there are a variety of Ethereum web3 libraries available in many languages, and while the implementation specifics differ, they all implement creating a web3 connection through a ‘provider’ or ‘signer’ so this step is important to understand.

The useWeb3 hook attempts to obtain an injected web3 provider first, before falling back to a network connection. Alternatively use useWeb3Injected for an injected web3 provider or useWeb3Network for a network provider such as Infura or a private node.

Add a React Component

Our next goal is to move the display of the current Ethereum network to a component and see how components are re-rendered when changes are made, such as the network.

To do this, the first step is to select a web3 provider to be injected, and install it. We’re going to use MetaMask as our web3 provider in this tutorial, so head to metamask.io to install it.

In the code below, the Web3Data component expects a web3Context. The networkId, networkName and providerName are obtained from the web3Context and displayed in the component.

We then create a components directory in the client/src directory, and create a Web3Data.js file with the code below:

import React from 'react';
export default function Web3Data(props) {
const { web3Context } = props;
const { networkId, networkName, providerName } = web3Context;
return (
<div>
	<h3> {props.title} </h3>
	<div>
    Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'
	</div>
	<div>
	Provider: {providerName}
	</div>
</div>
);
}

Now back inside App.js, we’ll add the Web3Data component to provide a web3Context. In the client/src/App.js file, add this import for the Web3Data component:

import Web3Data from './components/Web3Data.js';

Then replace the entire contents of the App function in App.js with the following code:

const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${infuraProjectId}`);
return (
<div className="App">
	<div>
	<h1>Infura React Dapp with Components!</h1>
	<Web3Data title="Web3 Data" web3Context={web3Context} />
	</div>
</div>
);

Let's run npm start from within the client directory to start the dapp again to see how React components are re-rendered by Network JS when changes are made to accounts, connection type or network. You can test this out by changing networks in MetaMask.

Requesting Access to Account Address

Requesting a user’s account address and displaying it is one of the most common patterns among dapps. However, injected web3 providers don’t give the dapp access to a user’s address until that user has given permission. This bit of friction has been intentionally designed to protect user privacy and prevent bad actors from using an account address to track the user around the internet. You can learn more about this communication protocol here.

In our Web3Data.js file, we replace the contents of the component with the following code:

import React, { useCallback } from 'react';
export default function Web3Data(props) {
const { web3Context } = props;
const { networkId, networkName, accounts, providerName } = web3Context;
const requestAuth = async web3Context => {
try {
	await web3Context.requestAuth();
	} catch (e) {
	console.error(e);
	}
};
    
const requestAccess = useCallback(() => requestAuth(web3Context), []);

return (
<div>
<h3> {props.title} </h3>
	<div>
    Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}
	</div>
	<div>
	Your address: {accounts && accounts.length ? accounts[0] : 'Unknown'}
	</div>
	<div>
	Provider: {providerName}
	</div>

	{accounts && accounts.length ? (
	<div>
	Accounts & Signing Status: Access Granted
	</div>
	) : !!networkId && providerName !== 'infura' ? (
	<div>
	<button onClick={requestAccess}>Request Access</button>
	</div>
	) : (
	<div></div>
	)}
</div>
);
}

Here’s what the above code is doing:

  • Accounts are pulled from the web3Context and, if available, the user’s address is displayed in the dapp;
  • If the accounts we have requested are not available, the dapp will produce a button that enables the user to give the dapp access to their address;
  • When the user presses that button, the requestAuth function in web3Context is called and the injected web3 provider can display a dialog to the user to request access. Using React functionality, useCallback is used to set up the callback for account request access.

Now, we start the dapp again by running npm start from within the client directory.

In the browser, we press ‘Request Access’ to request access to the user’s address, and then accept the request in the Metamask popup (or extension if no popup). The user’s address will then be displayed. To restart the process, logging out of Metamask will make the user request access again.

Account Balance

Finally, our third goal is to add to the React component to display the account balance.

In the code below, we get the lib object, which contains the blockchain data we want, from the web3Context, which is an initialized instance of web3.js (a library that Network.js relies upon).

We use lib for the method getBalance to request the balance of an account, which returns the balance as in value in units of wei. It’s easier to work with in units of ether, so we convert it with the method fromWei.

Since we want to track the state of the account balance, we use a React-specific function called useState. We also use useEffect to watch for if the account address or networkId has changed, and update accordingly.

To get our dapp to display the account balance, we replace the contents of the component in our Web3Data.js file with the following code:

import React, { useState, useEffect, useCallback } from 'react';
export default function Web3Data(props) {
const { web3Context } = props;
const { networkId, networkName, accounts, providerName, lib } = web3Context;
const [balance, setBalance] = useState(0);
const getBalance = useCallback(async () => {
	let balance = accounts && accounts.length > 0 ? lib.utils.fromWei(await lib.eth.getBalance(accounts[0]), 'ether') : 'Unknown';
	setBalance(balance);
}, [accounts, lib.eth, lib.utils]);
    
useEffect(() => {
getBalance();
}, [accounts, getBalance, networkId]);

const requestAuth = async web3Context => {
	try {
		await web3Context.requestAuth();
	} catch (e) {
		console.error(e);
	}
};
const requestAccess = useCallback((web3Context) => requestAuth(web3Context), []);
return (
<div>
<h3> {props.title} </h3>
	<div>
    Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}
    </div>
	<div>
    Your address: {accounts && accounts.length ? accounts[0] : 'Unknown'}
    </div>
	<div>
    Your ETH balance: {balance}
    </div>
	<div>
    Provider: {providerName}
    </div>
	{accounts && accounts.length ? (
	<div>
    Accounts & Signing Status: Access Granted
    </div>
	) : !!networkId && providerName !== 'infura' ? (
	<div>
	<button onClick={requestAccess}>Request Access</button>
	</div>
	) : (
	<div></div>
	)}
</div>
);
}

We start the dapp again by running npm start from within the client directory and ta-da! The dapp should now display the account balance.

What you have now built is a dapp frontend (using Network.js) that can connect to the Ethereum mainnet and request data from it (using Infura). Users “login” (using Metamask), and the dapp displays the information the user cares about and reacts to state changes.  

Keep Learning

Our next tutorial will show you how to display the ERC20 balance of a user and let them transfer it. Further web3 development guides will include topics such as building with Ethers.js, using OpenZeppelin’s Starter Kits, and more complex integrations such as setting up Eth Log filters with Truffle’s Drizzle framework to react in real time to blockchain data changes on the frontend.



Never miss a tutorial -- subscribe to our newsletter to stay up to date. Ready to test out Infura? Sign up to start building for free!