In the world of web3, developers are the architects of the decentralized future. Join us for our Ur-Hackathon 2.0 and build the future of the decentralized web.
Register & win upto 250k INR: https://ur-hackathon-2.devfolio.co/
As part of hackathon, we are publishing a blog which was focused on building a simple Dapp from scratch.
Decentralized applications (dApps) are one of the most promising applications of blockchain technology. They open up new possibilities for consumer and business-focused products with never-before-seen capabilities.
This blog will teach you how to build a simple favourite number storing Dapp on polygon and shardeum testnets.
Let's start building Dapp. we are going to start this from a smart contract and then we will build front end for that contract.
We will be using Hardhat to bootstrap our local development environment. Make sure you have NodeJS installed!
Hardhat Setup:
Create a new folder called Dapp(use your preferred name) and create one more folder inside Dapp called contracts.
mkdir Dapp && cd Dapp && cd contracts
Open this folder in the terminal and run the following code.
npx hardhat
you can see the above image it will show like this choose javascript if you prefer typescript go with that but we will be following javascript in this tutorial. Once that's done, install the following dependencies.
npm install --save-dev "hardhat@^2.12.5" "@nomicfoundation/hardhat-toolbox@^2.0.0"
In this demo, we are going to create a simple favourite number setter and getter smart contract in solidity. Now open this folder in VSCode and create a new solidity file called FavNumber.sol inside the contracts folder.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract FavNumber {
// stores the favorite integer of specific address.
mapping(address => uint) addressToNumber ;
// funtion used to set the favourite number to a address
function setNumber(uint number) external {
addressToNumber[msg.sender] = number;
}
//function used to get the favorite number of the msg sender
function getNumber() external view returns(uint){
return addressToNumber[msg.sender];
}
}
Writing tests
Delete the sample-test.js
file in your test
directory and create a new file called test.js
. Let's code our tests for our contract.
const { expect } = require("chai");
describe("Favourite Number", function () {
let favNumber;
let address;
// before starting testing the contract we need to deploy it first
// below before code will deploy the contract and store an instance of contract in favNumber for us to interact with
before(async () => {
const accounts = await ethers.getSigners();
address = await accounts[0].getAddress();
const FavNumber = await ethers.getContractFactory("FavNumber");
favNumber = await FavNumber.deploy();
await favNumber.deployed();
});
//
it("set and get the numer", async function () {
// set 42 as favorite number of msg sender
const tx = await favNumber.setNumber(42);
await tx.wait();
// get the favorite number of msg sender
const number = await favNumber.getNumber();
// expects return number was 42 otherwise returns an error.
expect(number).to.equal(42);
});
});
Now run npx hardhat test
in the contracts directory.
And...of course, it does! We just tested the Favorite Number Contract. Woohoo! ๐Let's now try deploying our smart contract to Polygon Mumbai and Shardeum Liberty 2.1
Deploying
We will be deploying our contract to the Polygon Mumbai Testnet and Shardeum Liberty 2.1.
Before deploying we are going to add one more plugin called hardhat-secure-accounts. I have seen so many devs out there tweeting that their wallets got compromised what they are doing was adding a private key in .env and forgetting to add .env in gitignore file. They will commit that to GitHub that's it smash smash smash wallet got hacked. To avoid this I recommend using the hardhat-secure-accounts plugin.
A few reasons why you might want to use this plugin:
You don't want to store your private keys in plain text on a .env file or
secrets.json
You don't want to update your keys when switching between accounts
You accidentally committed your private keys to a public repository and don't want it to happen again...
Let's set up hardhat secure accounts.
npm install hardhat-secure-accounts
And add the following statement to your hardhat.config.js:
require("hardhat-secure-accounts");
Let's add an account to hardhat:
npx hardhat accounts add
It will ask for the name(you can choose any name) and mnemonic of the wallet.
At last, it asks for a password. Basically what it does was it will encrypt the mnemonic with the password using cryptographic functions.
you can see the .keysore file in the directory this was the encrypted file. Now add .keystore in gitignore. By doing this you will get two-level security, if you committed code to GitHub by adding the .keystore in gitignore no one knows about the account but if you accidentally committed the .keystore password is there to save you from getting compromised. So setting a good password was recommended.
Need to configure the hardhat before deploying:
Open hardhat.config.js
:
require("@nomicfoundation/hardhat-toolbox");
require("hardhat-secure-accounts");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.17",
networks: {
mumbai: {
// public rpc url..
url: "https://rpc-mumbai.maticvigil.com",
},
shardeum: {
// public rpc url..
url: "https://liberty20.shardeum.org",
},
},
etherscan: {
apiKey: "get api key",
},
};
For enabling networks in the hardhat cli tool you need to add the required networks in the networks section of the hardhat config file. Also, I am going to show how to verify the contract in a polygon scan for that you need to get an api key. For getting an api key head to polygonscan.com and create an account and head to API keys and create one API key and paste it there in the config file. By using this API key you can verify both polygon mainnet and testnet contracts.
The last step needs to write a deploy script :
Open scripts/deploy.js
:
Before deploying the contract make sure you have enough testnet tokens for deploying a contract in that account. we need to unlock the account and then will deploy the contract to the network from that account.
This line of code const signer = await hre.accounts.getSigner();
will get all available accounts and asks you to choose one and the password of it. it unlocks the account.
const hre = require("hardhat");
async function main() {
// In cli it will ask you to enter the password for unlocking
const signer = await hre.accounts.getSigner();
console.log(`Account ${signer.address} unlocked!`);
// this will get the contract and wrap the signer with that
const FavNumber = await hre.ethers.getContractFactory("FavNumber", signer);
// this will deploy the contract
const favNumber = await FavNumber.deploy();
// this will wait until the contract deployed
await favNumber.deployed();
console.log("FavNumber deployed to:", favNumber.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Let's deploy:
Run this below command to deploy the contract to Mumbai testnet.
npx hardhat run scripts/deploy.js --network mumbai
Run this below command to deploy the contract to Shardeum Lliberty 2.1
npx hardhat run scripts/deploy.js --network shardeum
Contract verification:
You can verify your contracts in Mumbai testnet but you can't verify them in shardeum. By running this command you can able to verify the FavNumber contract in Mumbai.
npx hardhat verify CONTRACT_ADDRESS --network mumbai
Save this contract addresses in some files or don't clear the terminal. Now let's start building frontend for this simple Dapp.
Frontend :
We are going to build a simple user interface for our favourite number Dapp. The technologies and Development tools we'll use are :
Next js
Tailwind CSS
Rainbow kit
Wagmi
Ethers
Setting up frontend:
Earlier we created a Dapp folder now open this Dapp directory in the terminal and enter the following commands.
npx create-next-app frontend
choose the options shown below for questions and also for import alias choose "@" Let's add TailwindCSS to the next app
cd frontend
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configure your tailwind.config.js
file:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Add these three lines to the top of the globals.css
file:
@tailwind base;
@tailwind components;
@tailwind utilities;
Now start the server :
npm run dev
Let's add a wallet connection in this Dapp using RainbowKit. Run the following commands to install RainbowKit and wagmi, ethers.
npm install @rainbow-me/rainbowkit wagmi ethers
Now open pages/_app.js
there will be an App function out there. Here we need to configure RainbowKit and wagmi client.
import RainbowKit, wagmi, and ethers.
import '@rainbow-me/rainbowkit/styles.css';
import {
getDefaultWallets,
RainbowKitProvider,
} from '@rainbow-me/rainbowkit';
import { configureChains, createClient, WagmiConfig } from 'wagmi';
// you can import the other chains
import { polygonMumbai } from 'wagmi/chains';
import { publicProvider } from 'wagmi/providers/public';
wagmi does not have shardeum chain in their chains list so we need to create our own custom shardeum chain object. The below code creates a chain object by taking name, rpcUrls and block explorer links.
const shardeum = {
id: 8081,
name: "Shardeum",
network: "Shardeum Liberty 2.X",
nativeCurrency: {
decimals: 18,
name: "Shardeum",
symbol: "SHM",
},
rpcUrls: {
default: { http: ["https://liberty20.shardeum.org"] },
},
blockExplorers: {
default: {
name: "explorer-liberty20",
url: "https://explorer-liberty20.shardeum.org",
},
},
};
Configure your desired chains and generate the required connectors. You will also need to set up a wagmi
client.
const { chains, provider } = configureChains(
[polygonMumbai,shardeum],
[
publicProvider()
]
);
const { connectors } = getDefaultWallets({
appName: 'Favourite Number app',
chains
});
const wagmiClient = createClient({
autoConnect: true,
connectors,
provider
})
Wrap your application with RainbowKitProvider
and WagmiConfig
.
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains}>
<Component {...pageProps} />
</RainbowKitProvider>
</WagmiConfig>
The app function will look like this:
Let's add ConnectButton
. Now open index.js
and remove the code of the main tag, import ConnectButton
from the rainbow kit.
import { ConnectButton } from '@rainbow-me/rainbowkit';
Now add this code below the head tag in index.js
:
// Below tailwind classes set the connect button always at right of the page
<div className="flex flex-row-reverse mx-20 my-5">
<ConnectButton />
</div>
We have two functions in the contract which setNumber and getNumber. In which one function was taking input of a number so we have to create an input field in frontend to take input and a button which will execute the function of SetNumber. One more button for getting a favourite number of connected wallet address. Add the following code below the connect button Div.
<div className="flex flex-col items-center gap-10">
<div className="flex flex-col items-center gap-2">
<input
type="number"
className="border-2 w-80 p-1 focus:outline-none"
id="number"
></input>
<button
className="rounded-lg bg-blue-400 px-2 py-1"
onClick={setNumber}
>
Set Number
</button>
</div>
<div className="flex flex-col items-center gap-2">
// will add show code soon
<button
className="rounded-lg bg-blue-400 px-2 py-1"
onClick={getNumber}
>
Get Number
</button>
</div>
</div>
Here we go it looks like this. yet to write the code for interacting with the contract. Right now you can connect your wallet with two different chains. I haven't explained the tailwind CSS code above because our main focus is on the blockchain side, not on styling.
Let's add code to interact with the contract. So will need to create an instance of Contract using ethers. For creating that we need contract abi, contract address and signer instance. we can easily create this using wagmi hooks. Now create a folder called constants inside frontend and create index.js
inside it. Here will add our contract abi, polygon address and shardeum address. You can get the abi from contract/artifacts/contracts
file and get the addresses from the hardhat terminal or where ever you have shared.
export const abi = [
{
inputs: [
{
internalType: "address",
name: "_address",
type: "address",
},
],
name: "getNumber",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "number",
type: "uint256",
},
],
name: "setNumber",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
];
export const polygonAddress = "0x707BdD99A0dF0B8469eCE90c4d7CfF4DE9F5d361";
export const shardeumAddress = "0x7c18eB954E292105e84693ff791196095a8709F1";
import abi and addresses in index.js
:
import { abi, polygonAddress, shardeumAddress } from "@/constants";
import these hooks from wagmi
import { useContract, useSigner, useNetwork, useAccount } from "wagmi";
import { ethers } from "ethers";
Add the below code inside the Home function.
// returns the signer instance
const { data: signer, isError, isLoading } = useSigner();
// returns the address of wallet
const { address, isConnected } = useAccount();
// show helps us to show number
const [show, setShow] = useState(false);
// store the favourite number
const [num, setNum] = useState(0);
//returns the present chain of user
const { chain } = useNetwork();
Contract instance :
useContract was a hook provided by wagmi which will return an instance of ethers contract we can use this to interact with smart contract.
const contract = useContract({
abi,
// checks whether the current network is matic mumbai or shardeum and returns the address of respective network
address: chain?.network === "maticmum" ? polygonAddress : shardeumAddress,
signerOrProvider: signer,
});
setNumber :
You can see an async function in the below code which contains a block of try-and-catch code. In try, we are getting the number entered inside the input tag and storing it in the number variable and calling the contract to set that number as the favourite number of the user. Catch helps us to log the error to the console with a small message so it is easy to debug.
const setNumber = async () => {
try {
const number = document.getElementById("number").value;
const tx = await contract.setNumber(number);
// waits until the tx completed
await tx.wait();
} catch (e) {
console.log(e, "setNumber error");
}
};
getNumber :
In getNumber function will just call the getNumber function of the contract with a wallet address.
const getNumber = async () => {
try{
// address was providev by useAccount
const number = await contract.getNumber(address);
// sets show to true
setShow(true);
// sets num variable with number value
setNum(number);
}
catch(e){
console.log(e, "getNumber error");
}
};
Add the below code above the getNumber button inside the index.js. If the show was true it will show the favourite number of the user and show was set to true inside the getNumber function. Also in EVMs numbers are stored as BigNumber so whenever we are trying to get a number from contract it will be in BigNumber format. So we need to format that before displaying them. Ethers provide the formatUnits function for formatting it to a number.
{show && (
<p>Your favourite number is {ethers.utils.formatUnits(num, 0)}</p>
)}
// <button
//className="rounded-lg bg-blue-400 px-2 py-1"
//onClick={getNumber}
//>
Conclusion
Woohoo! ๐ you have successfully created your first Dapp. The final code repository is available here in Dapp. If you have any issues or bugs in your code created by following this article would love to solve them. you can connect with me on Twitter and Linkedlin.