Create a dapp using Truffle and React
In this tutorial, we'll deploy a simple smart contract and build a Web3 frontend using React.
The Web3 frontend integrates with the smart contract to allow you to read from and write to the Ethereum blockchain.
Prerequisites
- An Ethereum project on Infura
- Node.js installed
- An Ethereum wallet for testing purposes
Use MetaMask or similar to create an Ethereum wallet for testing purposes.
Deploy the smart contract
1. Install Truffle
Truffle is a smart contract development tool and testing framework for blockchain networks.
Install Truffle globally using the Node.js package manager:
npm install -g truffle
2. Fund your Ethereum account
Use a faucet to load testnet ETH on your Ethereum account for the Sepolia network.
You have to create an account to get to the faucet.
3. Create a project directory
Create a new project directory from the command line:
mkdir simple-storage
cd
into the new directory:
cd simple-storage
4. Create a Truffle project
Create a bare Truffle project which generates the required directory structure to test and deploy contracts:
truffle init
Truffle creates the following directory structure for your project:
contracts/
: directory for your Solidity contracts.migrations/
: directory for the scriptable deployment files.test/
: directory for files that test your application and contracts.truffle-config.js
: the Truffle configuration file.
5. Install hdwallet-provider
hdwallet-provider
is a separate package that signs transactions for addresses derived from a 12 or 24-word mnemonic.
By default, the hdwallet-provider
uses the first address generated from the mnemonic. However, this is configurable.
Infura does not manage your private keys, meaning it cannot sign transactions on your behalf.
Run the following command to install hdwallet-provider
:
npm install @truffle/hdwallet-provider
For more information, refer to the Truffle hdwallet-provider
repository.
You can also use the Truffle Dashboard to allow MetaMask to sign your transactions.
6. Create the .env file
Install the dotenv
package.
npm install dotenv
Create a file named .env
in your project directory to store the project and Ethereum account details.
The following example .env
file includes the MetaMask secret recovery phrase. Refer to the MetaMask
instructions on how to reveal a secret recovery phrase.
If using a network other than Sepolia, ensure you update INFURA_API_KEY
accordingly.
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>"
MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
Ensure you replace the following values in the .env
file:
<Your-API-Key>
with the API key of the Ethereum project.<Your-MetaMask-Secret-Recovery-Phrase>
with the mnemonic of your MetaMask wallet. This phrase is used by the Trufflehdwallet-provider
to sign transactions.
Never disclose your secret recovery phrase. Anyone with your recovery phrase can steal any assets held in your wallet.
7. Create a smart contract
Using an editor, create a smart contract in the contracts/
directory. In this example, we'll create a basic contract
called SimpleStorage.sol
.
pragma solidity >=0.5.8;
contract SimpleStorage {
uint256 storedData;
function set(uint256 x) public {
storedData = x;
}
function get() public view returns (uint256) {
return storedData;
}
}
8. Configure the Truffle settings
Configure the truffle-config.js
file to use the HDWalletProvider
and include the required configuration to deploy to
the network (Sepolia in this example).
Find the truffle-config.js
file in your project's root directory. Delete the contents of the file, and replace it with
the following:
require("dotenv").config();
const HDWalletProvider = require("@truffle/hdwallet-provider");
const { INFURA_API_KEY, MNEMONIC } = process.env;
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*",
},
sepolia: {
provider: () => new HDWalletProvider(MNEMONIC, INFURA_API_KEY),
network_id: "11155111",
gas: 4465030,
},
},
};
Refer to the Truffle documentation for more information about configuring the Truffle settings.
9. Compile the smart contract
Navigate to the project's root directory and run the following:
truffle compile
If successful, you'll see output similar to:
Compiling your contracts...
===========================
> Compiling ./contracts/SimpleStorage.sol
> Artifacts written to /Users/myuser/simple-storage/build/contracts
> Compiled successfully using:
- solc: 0.5.16+commit.9c3226ce.Emscripten.clang
Compiled files go in the build/contracts/
directory, relative to your project root. Refer to the
Truffle documentation
for more information about the compilation process.
10. Create the deployment script
Scripts that deploy smart contracts are located in the migrations/
directory and are numbered. In the migrations/
directory, create a file called 1_deploy_contract.js
.
Add the following code to the 1_deploy_contract.js
file:
const SimpleStorage = artifacts.require("SimpleStorage.sol");
module.exports = function (deployer) {
deployer.deploy(SimpleStorage);
};
11. Deploy the smart contract
In the root folder of the project, run the migration command to deploy the smart contract to the Ethereum network:
truffle migrate --network sepolia
If successful you'll see a response similar to the following:
Response
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Migrations dry-run (simulation)
===============================
> Network name: 'sepolia-fork'
> Network id: 11155111
> Block gas limit: 30000000 (0x1c9c380)
1_deploy_contract.js
====================
Deploying 'SimpleStorage'
-------------------------
> block number: 7588631
> block timestamp: 1663134656
> account: 0x9cE564c7d09f88E7d8233Cdd3A4d7AC42aBFf3aC
> balance: 0.087955581025643905
> gas used: 96189 (0x177bd)
> gas price: 4.570417781 gwei
> value sent: 0 ETH
> total cost: 0.000439623915936609 ETH
-------------------------------------
> Total cost: 0.000439623915936609 ETH
Summary
=======
> Total deployments: 1
> Final cost: 0.000439623915936609 ETH
Starting migrations...
======================
> Network name: 'sepolia'
> Network id: 5
> Block gas limit: 30000000 (0x1c9c380)
1_deploy_contract.js
====================
Deploying 'SimpleStorage'
-------------------------
> transaction hash: 0x4a5e6d6621294909fe21ace8373bd96af3337f9808dac8331eaabb69c88eecab
> Blocks: 2 Seconds: 22
> contract address: 0x116Ff2B4f980635FA87B68260199E314bCc9D239
> block number: 7588638
> block timestamp: 1663134684
> account: 0x9cE564c7d09f88E7d8233Cdd3A4d7AC42aBFf3aC
> balance: 0.087973778088173959
> gas used: 96189 (0x177bd)
> gas price: 4.381237495 gwei
> value sent: 0 ETH
> total cost: 0.000421426853406555 ETH
> Saving artifacts
-------------------------------------
> Total cost: 0.000421426853406555 ETH
Summary
=======
> Total deployments: 1
> Final cost: 0.000421426853406555 ETH
Use a block explorer like Etherscan to view transaction details by searching for the
transaction hash
or contract address
for example.
Build the Web3 frontend
We'll use React to build the Web3 frontend that will communicate with the deployed smart contract.
1. Create a project directory
Use the npx
command to create a React project boilerplate and a directory named storage-lab
:
npx create-react-app storage-lab
The command sets up your development environment with the latest JavaScript features, and optimizes your app for production.
cd
into the new directory:
cd storage-lab
2. Install Web3.js
The Web3.js
library interacts with the Ethereum network.
You can also use the ether.js
library to interact with the Ethereum network.
npm install web3
3. Copy your smart contract into the new directory
Copy the smart contract project directory you created previously (simple-storage
) into the React project directory.
The project directory should now look as follows:
storage-lab
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
├── simple-storage
└── src
4. Create a file containing the ABI information
The Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem.
We'll create a JavaScript file to enable the Web3 frontend to communicate with the smart contract deployed earlier via the smart contract's ABI.
While there are more elegant ways to get the frontend and contract to communicate, we'll do it manually in this tutorial for educational purposes.
When we compiled our smart contract earlier, it created a JSON file named SimpleStorage.json
inside of the Truffle
project's build/contracts/
directory. Copy the abi
object from the JSON file and reformat it so that we can import
it into our app.
In the /src
directory, create a folder named /abi
, which will contain our abi.js
file. The /src
directory should
look as follows:
src
├── App.css
├── App.js
├── App.test.js
├── abi
│ └── abi.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
Copy the ABI data from the JSON file into the abi.js
file and reformat it as follows:
export const SimpleStorage = [
{
inputs: [
{
internalType: "uint256",
name: "x",
type: "uint256",
},
],
name: "set",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "get",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
constant: true,
},
];
5. Update the App.js file
The src/App.js
file is the highest level component in the React application structure.
Update the file to include the app logic and interface components.
Replace <CONTRACT-ADDRESS>
with your smart contract address. To locate the contract address, view the address
field
in the SimpleStorage.json
file inside of the build/contracts/
directory.
import React, { useState } from "react";
import { SimpleStorage } from "./abi/abi";
import Web3 from "web3";
import "./App.css";
// Access our wallet inside of our dapp
const web3 = new Web3(Web3.givenProvider);
// Contract address of the deployed smart contract
const contractAddress = "<CONTRACT-ADDRESS>";
const storageContract = new web3.eth.Contract(SimpleStorage, contractAddress);
function App() {
// Hold variables that will interact with our contract and frontend
const [number, setUint] = useState(0);
const [getNumber, setGet] = useState("0");
const numberSet = async (t) => {
t.preventDefault();
const accounts = await window.ethereum.enable();
const account = accounts[0];
// Get permission to access user funds to pay for gas fees
const gas = await storageContract.methods.set(number).estimateGas();
const post = await storageContract.methods.set(number).send({
from: account,
gas,
});
};
const numberGet = async (t) => {
t.preventDefault();
const post = await storageContract.methods.get().call();
setGet(post);
};
return (
<div className="main">
<div className="card">
<form className="form" onSubmit={numberSet}>
<label>
Set your uint256:
<input
className="input"
type="text"
name="name"
onChange={(t) => setUint(t.target.value)}
/>
</label>
<button className="button" type="submit" value="Confirm">
Confirm
</button>
</form>
<br />
<button className="button" onClick={numberGet} type="button">
Get your uint256
</button>
{getNumber}
</div>
</div>
);
}
export default App;
6. Update the stylesheet
In the App.css
file, delete the boilerplate stylesheet and add the following custom stylesheet:
.main {
text-align: center;
display: flex;
justify-content: center;
background-color: #f2f1f5;
height: 100vh;
}
.card {
min-height: 50vh;
width: 50vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.form {
height: 20vh;
width: 20vw;
display: flex;
justify-content: space-evenly;
flex-direction: column;
}
.button {
width: 20vw;
height: 5vh;
}
7. Run the Web3 frontend
If you are using create-react-app version >=5 you may run into issues building, such as:
Module not found: Error: Can't resolve 'crypto' in 'C:\Users\Username\Projects\testProject\client\node_modules\eth-lib\lib'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
This is because NodeJS polyfills are not included in the latest version of create-react-app.
Run the Web3 frontend from the command line.
yarn start
8. Use the dapp
In your dapp, enter a value in the Set your uint256 field and click Confirm.
The MetaMask app should open to allow you to pay the required gas fee.
Click Get your uint256 to retrieve the value.
Note that the read operation does not require you to pay gas. This is because it's not a state-changing operation.