Decentralized applications (dApps) use a blockchain or on-chain smart contracts to store and reference data, rather than relying on traditional centralized databases. A common, simple dApp structure generally consists of a React.js or Vue.js front-end using Web3.js or Ethers.js to interact with smart contracts deployed to an EVM-compatible blockchain.
In this tutorial, we'll develop a simple dApp using React.js and Ethers.js that stores data in a smart contract on the Core blockchain and displays it to users. The dApp's full code is available on GitHub in the dApp-tutorial repository.
This tutorial will help you gain knowledge on the following learning points:
- MetaMask Wallet connectivity to Core Testnet;
- Smart contract development and deployment on Core Testnet;
- Front-end integration with the smart contract using Ethers.js library;
- Read data from a smart contract;
- Write data to a smart contract;
- Git v2.44.0
- Node.js v20.11.1
- npm v10.2.4
- Hardhat v10.2.4
- MetaMask Web Wallet Extension
- Create a new directory for the project and navigate into it
mkdir dapp-tutorial
cd dapp-tutorial
npm init --yes
npm install --save-dev hardhat
npm install --save-dev chai @nomiclabs/hardhat-waffle
- Initialize Hardhat project by running the following command
npx hardhat
:::note As we will using Waffle for this project and make sure to select No for the option "Do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)? (Y/n)" :::
- Once this project is initialized, you'll find the following project structure:
dapp-tutorial.
| .gitignore
| hardhat-config.js (HardHat configuration file.)
| package-lock.json
| package.json
| README.md
|
+---contracts (For Solidity Smart Contracts)
| Lock.sol
|
+---ignition (Scripts in previous versions, contains config files that specify how smart contracts should be deployed)
| \---modules
| Lock.js
|
+---node_modules
|
+---test (For writing and Running Tests)
| Lock.js
|
-
Install and configure MetaMask Chrome Extension to use with Core Testnet. Refer here for a detailed guide.
-
Create a secret.json file in the root folder and store the private key of your MetaMask wallet in it. Refer here for details on how to get MetaMask account's private key.
{"PrivateKey":"you private key, do not leak this file, do keep it absolutely safe"}
:::caution
Do not forget to add this file to the .gitignore
file in the root folder of your project so that you don't accidentally check your private keys/secret phrases into a public repository. Make sure you keep this file in an absolutely safe place!
:::
- Copy the following into your
hardhat.config.js
file
/**
* @type import('hardhat/config').HardhatUserConfig
*/
require('@nomiclabs/hardhat-ethers');
require("@nomiclabs/hardhat-waffle");
const { PrivateKey } = require('./secret.json');
module.exports = {
defaultNetwork: 'testnet',
networks: {
hardhat: {
},
testnet: {
url: 'https://rpc.test.btcs.network',
accounts: [PrivateKey],
chainId: 1115,
}
},
solidity: {
compilers: [
{
version: '0.8.9',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
paths: {
sources: './contracts',
cache: './cache',
artifacts: './artifacts',
},
mocha: {
timeout: 20000,
},
};
- Navigate to the
contracts
folder in the root directory of your project. - Delete the
Lock.sol
file; create a new fileStorage.sol
and paste the following contents into it.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
*/
contract Storage {
uint256 number;
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}
The Storage
contract is a simple example that demonstrates how to store and retrieve a value using a Solidity smart contract. It consists of a state variable to hold the value and two functions to update and read this value. The store
function allows any user to set the value, while the retrieve
function allows any user to read the current value. This contract can be useful for understanding the basics of state variables and function visibility in Solidity. This Solidity smart contract, named Storage
, is a simple contract that allows storing and retrieving a single uint256
value. Here’s a detailed breakdown of its components and functionality:
- State Variable:
number
: Auint256
variable that is used to store the value.
-
Store Function:
store(uint256 num) public
: A function that allows users to store a new value in thenumber
variable. This function takes a single parameter,num
, which is the value to be stored. The function updates thenumber
variable with the provided value.- Visibility: The function is marked as
public
, meaning it can be called by any user or contract. - State Change: This function modifies the state of the contract by updating the
number
variable.
-
Retrieve Function:
retrieve() public view returns (uint256)
: A function that returns the current value stored in thenumber
variable. This function does not take any parameters and returns auint256
value.- Visibility: The function is marked as
public
, meaning it can be called by any user or contract. - View: The function is marked as
view
, indicating that it does not modify the state of the contract. It only reads the state. - Return Value: The function returns the value of the
number
variable.
- To compile the
Storage
smart contract defined in theStorage.sol
, from the root directory run the following command
npx hardhat compile
-
Before deploying your smart contract on the Core Chain, it is best adviced to first run a series of tests making sure that the smart contract is working as desired. Refer to the detailed guide here for more details.
-
Create a
scripts
folder in the root directory of your project. Inside this folder, create a filedeploy-and-call.js
; paste the following script into it.
const hre = require("hardhat");
async function main() {
const Storage = await hre.ethers.getContractFactory("Storage");
const storage = await Storage.deploy();
await storage.deployed();
console.log("Storage contract deployed to:", storage.address);
console.log("call retrieve():", await storage.retrieve())
console.log("call store(), set value to 100")
const tx = await storage.store(100)
await tx.wait()
console.log("call retrieve() again:", await storage.retrieve())
}
// 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;
});
-
Make sure your MetaMask wallet has tCORE test tokens for the Core Testnet. Refer here for details on how to get tCORE tokens from Core Faucet.
-
Run the following command from the root directory of your project, to deploy your smart contract on the Core Chain.
npx hardhat run scripts/deploy-and-call.js
If succesfully deployed, you will get the following output
>npx hardhat run scripts/deploy-and-call.js
Storage contract deployed to: 0x48F68BF4A1b1fE6589B9D0a5ad0dF0520582edA2
call retrieve(): BigNumber { value: "0" }
call store(), set value to 100
call retrieve() again: BigNumber { value: "100" }
- Make sure to save the Address of Storage Contract at which is deployed, as obtained above, this will be used for interacting with smart contract from the dApp's frontend.
🎉 Congratulations! You have successfully learned how to create, compile, and deploy a smart contract on the Core Chain Testnet using the Hardhat.
⚡️ Let's create a frontend interface for interacting with the smart contract.
- Clone the dApp-tutorial repository from GitHub using the following command.
git clone https://github.com/coredao-org/dapp-tutorial.git
- Rename this folder from
dapp-tutorial
tofrontend
. Navigate into thefrontend
folder
cd frontend
- Install all the dependencies, i.e., node modules.
npm install
- To test if things are working fine, run the application by using the following command. This will serve applciation with hot reload feature at http://localhost:5173
npm run dev
The application's key blockchain logic is implemented in App.tsx
- App.tsx (Wallet): logic for conencting to application to MetaMask wallet.
- App.tsx (Store): logic to write data to the Storage smart contract.
- App.tsx (Retrieve): logic to read data from the Storage smart contract.
- Copy the
Storage.sol
file from thecontracts
folder in the root of oyur project and paste it into thefrontend/src/contracts
folder. - Copy the address of the Storage smart contract as obtained in the section above.
- Paste this into Line 9 of App.tsx.
const contractAddress = '0x48F68BF4A1b1fE6589B9D0a5ad0dF0520582edA2'
- Additionally, we'll need the ABI metadata to interact with the contract from our dApp. From the
artifacts/contracts
folder in the root of your project. Copy theStorage.json
file and save it to the/src/contracts
folder.
-
Run the command
npm run dev
from teh root of the project to start the application. This will serve applciation with at http://localhost:5173 -
Make sure that your MetaMask wallet is correctly installed and switched to Core Testnet as described in our Core Testnet user guide. You'll also need to connect your MetaMask wallet to the local site.
- Enter a number in the input field and click the store button to save it to the contract. A write action on the smart contract invokes the MetaMask wallet. Click the Confirm button to sign the transaction and wait for confirmation on the blockchain.
- After the transaction is confirmed on the blockchain, click the retrieve button to read the value from the smart contract. You will notice the value has been updated.
🎉 Congratulations! You've just interacted with your newly-deployed contract using your dApp's front end! You can build on the codebase by deploying and interacting with different contracts, and by adding new UI components to the website for your users.