Introduction
Welcome, dear reader, to Part 2 of our exciting journey into the world of fundraising decentralized applications (dApps). In this section, we will delve into the fascinating realm of conjugating our powerful smart contract with a cutting-edge frontend framework - none other than React!
Prepare yourself for an immersive and exhilarating experience as we combine the solidity of our smart contract with the dynamic capabilities of React. Get ready to unleash your technical prowess and embark on a thrilling adventure in building a seamless user interface for our fundraising dApp. With React by our side, we’ll create a visually stunning and user-friendly frontend that will leave users in awe.
Prerequisites
Building this dapp, you would need:
- Solid understanding of Javascript
- Basic grasp of solidity
Requirements
- VSCode or any other editor
- A terminal
- Remix
- React
- Celo Extension Wallet
Getting Started
Clone the github repository here to follow along.
This is the structure you should see in your code editor.
├── node_modules
├── public
├── src
│ ├── components
│ ├── contracts
│ │ ├── ierc.abi.json
│ │ ├── donater.abi.json
│ │ ├── donater.sol
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
├── setupTests.js
├── .env
├── .gitignore
├── package-lock.json
├── package.json
└── README.md
Run npm install
in your terminal to install all dependencies needed to make the dapp work properly.
Smart Contract Preview
Before we begin, let’s see the contract that we developed from the part 1 of the tutorial.
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
interface IERC20Token {
function transfer(address, uint256) external returns (bool);
function approve(address, uint256) external returns (bool);
function transferFrom(
address,
address,
uint256
) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address) external view returns (uint256);
function allowance(address, address) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
contract Donater{
struct Donate{
address payable owner;
string title;
string description;
string image;
uint goal;
uint amountDonated;
bool goalReached;
}
mapping(uint256=>Donate) internal donations;
uint256 donateLength = 0;
address internal cUsdTokenAddress =
0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;
address internal adminAddress = 0xE2a0411465fd913502A8390Fe22DC7004AC47A04;
function addDonation(
string memory _title,
string memory _description,
string memory _image,
uint _goal
)public payable{
require(
IERC20Token(cUsdTokenAddress).transferFrom(
msg.sender,
adminAddress,
1e18
),
"Transaction could not be performed"
);
donations[donateLength] = Donate(
payable(msg.sender),
_title,
_description,
_image,
_goal,
0,
false
);
donateLength++;
}
function getDonation(uint _index)public view returns(
address payable,
string memory,
string memory,
string memory,
uint,
uint,
bool
){
Donate storage _donations = donations[_index];
return(
_donations.owner,
_donations.title,
_donations.description,
_donations.image,
_donations.goal,
_donations.amountDonated,
_donations.goalReached
);
}
function donate(uint _index, uint amount)public payable {
require(donations[_index].amountDonated < donations[_index].goal);
require(
IERC20Token(cUsdTokenAddress).transferFrom(
msg.sender,
donations[_index].owner,
amount
),
"Transaction could not be performed"
);
donations[_index].amountDonated+=amount;
if(donations[_index].amountDonated >= donations[_index].goal){
donations[_index].goalReached = true;
}
}
function getDonationLength() public view returns (uint){
return donateLength;
}
}
App.js
In React, the App.js
file serves as the main entry point and central component of the application. It acts as a container for other components and holds the overall structure and logic of the application. The App.js
file allows us to define the layout of the application, handle routing, and manage the state of the application if needed. It acts as a bridge between different components and provides a cohesive structure for building your react dApp.
For the first section, we are importing various dependencies and components for our React application.
import { useState, useEffect } from "react";
import Footer from "./components/Footer";
import Header from "./components/Header";
import Main from "./components/Main";
import Web3 from "web3";
import { newKitFromWeb3 } from "@celo/contractkit";
import BigNumber from "bignumber.js";
import donater from "./contracts/donater.abi.json";
import ierc from "./contracts/ierc.abi.json";
We import useState
and useEffect
hooks from React, which allow us to manage state and perform side effects in functional components.
Next, we import Footer
, Header
, and Main
components from their respective files. These components are responsible for rendering the footer, header, and main content of our application.
We import the Web3
library, which is a JavaScript library for interacting with the Celo blockchain. It provides functions to connect to a Celo node, send transactions, and interact with smart contracts.
Using the newKitFromWeb3
function from @celo/contractkit
, we create a Celo-specific instance of the ContractKit library using a Web3 provider.
The BigNumber
library is imported to handle large numerical values, such as token amounts or balances, in a precise manner.
We then import the ABI JSON files for the donater
and ierc
smart contracts. These files contain the necessary information to interact with the smart contracts, including function signatures and data types.
Moving forward, we define the App
function, which represents the main component of our React application.
function App() {
const [contract, setcontract] = useState(null);
const [address, setAddress] = useState(null);
const [kit, setKit] = useState(null);
const [cUSDBalance, setcUSDBalance] = useState(0);
const [donations, setDonations] = useState([]);
const ERC20_DECIMALS = 18;
const contractAddress = "0xf9b0A2ffeaCC51f94eE24d33304d26c8Fd3777cf";
const cUSDContractAddress = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1";
Inside the App
function, we use the useState
hook to define several state variables:
contract
represents the state variable for our smart contract instance.address
represents the state variable for the address associated with the user’s account.kit
represents the state variable for the ContractKit instance.cUSDBalance
represents the state variable for the balance of cUSD (Celo Dollar) tokens.donations
represents the state variable for storing the list of donations.
We have also declared a constant variable named ERC20_DECIMALS
, which is set to 18. This variable is used to represent the number of decimal places for token amounts in the ERC20 standard.
We then define two constant variables: contractAddress
and cUSDContractAddress
. These variables hold the addresses of the smart contracts we will be interacting with.
By utilizing the useState
hook and declaring these variables, we are setting up the initial state of our application and providing placeholders for the contract instance, user address, ContractKit instance, cUSD balance, and donations list. These state variables will be updated and used throughout the application as we interact with the smart contracts and retrieve data from the blockchain.
Up next, we utilize useEffect
hooks, we can perform certain actions and fetch data from the blockchain at specific points in the component’s lifecycle. For example, when the component mounts, we establish a connection with the blockchain. When the kit
and address
values are available, we retrieve the cUSD balance. And when the contract
value is set, we fetch the list of donations.
useEffect(() => {
celoConnect();
}, []);
useEffect(() => {
if (kit && address) {
getBalance();
} else {
console.log("no kit");
}
}, [kit, address]);
useEffect(() => {
if (contract) {
getDonations();
}
}, [contract]);
The first useEffect
hook is called when the component mounts, as indicated by the empty dependency array []
. It invokes the celoConnect
function, which is responsible for establishing a connection with the Celo blockchain.
The second useEffect
hook is triggered whenever there are changes to the kit
or address
variables. It checks if both kit
and address
have values and then calls the getBalance
function. This function retrieves the cUSD balance associated with the user’s address from the Celo blockchain. If either kit
or address
is null, it logs a message indicating that the ContractKit instance is not available.
The third useEffect
hook is executed whenever there are changes to the contract
variable. It checks if the contract
has a valid value and then calls the getDonations
function. This function retrieves the list of donations made using the smart contract.
Following this, we demonstrate the celoConnect function, which is an asynchronous function responsible for establishing a connection with the Celo blockchain.
const celoConnect = async () => {
if (window.celo) {
try {
await window.celo.enable();
const web3 = new Web3(window.celo);
let kit = newKitFromWeb3(web3);
const accounts = await kit.web3.eth.getAccounts();
const user_address = accounts[0];
kit.defaultAccount = user_address;
await setAddress(user_address);
await setKit(kit);
console.log(user_address);
} catch (error) {
console.log(error);
}
} else {
console.log("Error");
}
};
Here’s a breakdown of what the function does:
-
It checks if the
window.celo
object is available, which indicates that the Celo extension or wallet is installed in the user’s browser. -
If the Celo extension is available, it proceeds with the connection process. It first enables the Celo extension by calling
window.celo.enable()
, which prompts the user to authorize the connection. -
Once the extension is enabled, it creates a new instance of
Web3
usingnew Web3(window.celo)
. This instance will be used for interacting with the Celo blockchain. -
It initializes a
kit
(ContractKit) object using thenewKitFromWeb3
function, passing theweb3
instance as a parameter. -
The function then retrieves the user’s accounts from the Celo blockchain using
kit.web3.eth.getAccounts()
. The first account in the returned array is considered the user’s address. -
It sets the
defaultAccount
of thekit
object to the user’s address, allowing subsequent interactions with the Celo blockchain to use this address by default. -
The user’s address is set using the
setAddress
function, updating theaddress
state variable in the React component. -
The
kit
object is set using thesetKit
function, updating thekit
state variable in the React component. -
The user’s address is logged to the console for verification and debugging purposes.
In case the Celo extension or wallet is not available (window.celo
is falsy), an error message is logged to the console.
Next, we create the getBalance
function, which is an asynchronous function responsible for retrieving the user’s balance from the Celo blockchain.
const getBalance = async () => {
try {
const balance = await kit.getTotalBalance(address);
const USDBalance = balance.cUSD.shiftedBy(-ERC20_DECIMALS).toFixed(2);
const contract = new kit.web3.eth.Contract(donater, contractAddress);
setcontract(contract);
setcUSDBalance(USDBalance);
} catch (error) {
console.log(error);
}
};
Here’s an explanation of what the function does:
-
It first attempts to retrieve the total balance of the user’s address using
kit.getTotalBalance(address)
. This function returns abalance
object containing the user’s balances across different Celo assets. -
The
balance
object includes the balance of thecUSD
asset, which represents the Celo equivalent of the U.S. Dollar. TheUSDBalance
variable is calculated by shifting the decimal places of thecUSD
balance by-ERC20_DECIMALS
(18 in this case) and rounding it to 2 decimal places. -
Next, it creates a new instance of the
Contract
object from thedonater
ABI (Application Binary Interface) and thecontractAddress
. This allows interaction with the deployed smart contract on the Celo blockchain. -
The
contract
object is set using thesetcontract
function, updating thecontract
state variable in the React component. -
The calculated
USDBalance
is set using thesetcUSDBalance
function, updating thecUSDBalance
state variable in the React component. -
If any error occurs during the process, it is caught in the
catch
block, and an error message is logged to the console.
Next Up, we define our next function, the getDonations
function, which is an asynchronous function responsible for retrieving the list of donations from the smart contract deployed on the Celo blockchain.
const getDonations = async () => {
const donationLength = await contract.methods.getDonationLength().call();
const _donations = [];
for (let index = 0; index < donationLength; index++) {
let _donate = new Promise(async (resolve, reject) => {
let donate = await contract.methods.getDonation(index).call();
resolve({
index: index,
owner: donate[0],
title: donate[1],
description: donate[2],
image: donate[3],
goal: donate[4],
amountDonated: donate[5],
isGoalReached: donate[6],
});
});
_donations.push(_donate);
}
const donations = await Promise.all(_donations);
setDonations(donations);
};
-
It first calls the smart contract method
getDonationLength()
usingcontract.methods.getDonationLength().call()
. This function retrieves the total number of donations stored in the smart contract. -
The returned
donationLength
represents the number of donations present in the smart contract. -
An empty array
_donations
is initialized to store the promises of individual donation objects. -
A
for
loop is used to iterate over each donation index from 0 todonationLength - 1
. -
Inside the loop, a new promise
_donate
is created. This promise awaits the result of calling the smart contract methodgetDonation(index).call()
, which retrieves the details of a specific donation at the given index. -
The resolved value of the promise is an object containing the donation details, including the index, owner address, title, description, image, goal amount, donated amount, and a boolean indicating if the goal has been reached.
-
The resolved promise
_donate
is pushed into the_donations
array. -
After the loop,
Promise.all(_donations)
is called to wait for all the promises in the array to resolve. This returns an array of donation objects. -
The
setDonations
function is used to update thedonations
state variable in the React component with the retrieved list of donations.
After this function, The donate
function enables users to make a donation to a specific donation campaign.
const donate = async (index, _amount) => {
const cUSDContract = new kit.web3.eth.Contract(ierc, cUSDContractAddress);
try {
const amount = new BigNumber(_amount).shiftedBy(ERC20_DECIMALS).toString();
await cUSDContract.methods
.approve(contractAddress, amount)
.send({ from: address });
await contract.methods.donate(index, amount).send({ from: address });
getBalance();
getDonations();
} catch (error) {
console.log(error);
}
};
-
First, a new instance of the cUSD contract is created using the
cUSDContractAddress
. This contract represents the Celo Dollar (cUSD) token. -
Inside a try-catch block, the function performs the following steps:
-
The
amount
parameter passed to the function is converted into the appropriate format. It is multiplied by 10^18 (the decimal places of cUSD) and converted to a string usingshiftedBy()
andtoString()
from theBigNumber
library. -
The
approve()
method of the cUSD contract is called to approve the smart contract atcontractAddress
to spend the specifiedamount
of cUSD tokens on behalf of the user. This approval is necessary for the subsequent donation transaction. -
The
send()
function is used to send the approval transaction, specifying the user’s address as thefrom
parameter. -
After the approval is successful, the
donate()
method of the smart contract atcontractAddress
is called to make the actual donation. Theindex
parameter indicates the index of the donation campaign, and theamount
parameter represents the donation amount in cUSD tokens. -
Again, the
send()
function is used to send the donation transaction, specifying the user’s address as thefrom
parameter. -
After the donation is successful, the
getBalance()
andgetDonations()
functions are called to update the user’s balance and the list of donations.
-
-
If any error occurs during the donation process, it is caught in the catch block, and an error message is logged to the console.
Up Next, we have a new function the addDonations
function enables users to create a new donation campaign.
const addDonations = async (title, description, image, _goal) => {
const cUSDContract = new kit.web3.eth.Contract(ierc, cUSDContractAddress);
const amount = new BigNumber(1).shiftedBy(ERC20_DECIMALS).toString();
const goal = new BigNumber(_goal).shiftedBy(ERC20_DECIMALS).toString();
try {
await cUSDContract.methods
.approve(contractAddress, amount)
.send({ from: address });
await contract.methods
.addDonation(title, description, image, goal)
.send({ from: address });
} catch (error) {
console.log(error);
}
getDonations();
};
The function begins by creating a new instance of the cUSD contract using the cUSDContractAddress
, which represents the Celo Dollar (cUSD) token.
Next, the function sets the amount
variable to 1 cUSD token and the goal
variable to the specified fundraising goal amount. These amounts are adjusted to the appropriate decimal places using the shiftedBy()
and toString()
functions from the BigNumber
library.
Inside a try-catch block, the function performs the following steps:
-
Calls the
approve()
method of the cUSD contract to approve the smart contract atcontractAddress
to spend the specifiedamount
of cUSD tokens on behalf of the user. This approval is required for the subsequent transaction to add a new donation campaign. -
Sends the approval transaction using the
send()
function, specifying the user’s address as thefrom
parameter. -
Upon successful approval, calls the
addDonation()
method of the smart contract atcontractAddress
to create a new donation campaign. Thetitle
,description
,image
, andgoal
parameters are provided to set the details of the campaign. -
Sends the transaction to add the new donation campaign using the
send()
function, specifying the user’s address as thefrom
parameter.
If any errors occur during the process, they are caught in the catch block, and an error message is logged to the console.
The getDonations()
function is called to update the list of donations and reflect the newly added campaign.
Following this, we write the App
component in React. It defines the structure and composition of the user interface (UI) for the fundraising dApp.
return (
<>
<div>
<Header balance={cUSDBalance} />
<Main
addDonations={addDonations}
donations={donations}
donate={donate}
/>
<Footer />
</div>
</>
);
-
The
<>
and</>
tags are React fragments, which allow grouping multiple elements without adding an additional parent element to the HTML structure. -
The
<div>
element wraps the entire UI content. -
The
<Header>
component is rendered and receives thecUSDBalance
as a prop. This component represents the header section of the dApp UI, which typically displays information such as the user’s cUSD balance. -
The
<Main>
component is rendered and receives three props:addDonations
,donations
, anddonate
. This component represents the main content area of the dApp UI, where users can view existing donations, create new donation campaigns, and make donations to existing campaigns. These props are passed to enable communication and interaction between theMain
component and other parts of the application. -
The
<Footer>
component is rendered and represents the footer section of the dApp UI.
Main.js
The Main
component sets up several state variables to manage user input and data. It defines event handlers (submitHandler
and donateHandler
) that interact with the parent component by calling specific functions from the props (addDonations
and donate
). These functions facilitate the creation of new donation campaigns and the process of making donations.
import { useState } from "react";
const Main = (props) => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [image, setImage] = useState("");
const [goal, setGoal] = useState(0);
const [amount, setAmount] = useState(0);
const submitHandler = (event) => {
event.preventDefault();
props.addDonations(title, description, image, goal);
};
const donateHandler = (index) => {
if (!amount) {
alert("Amount is not valid");
return;
}
props.donate(index, amount);
};
-
The
import { useState } from "react";
statement imports theuseState
hook from the React library. This hook allows functional components to manage state. -
The
Main
component is defined as a function that takesprops
as its parameter. It represents the main content area of the fundraising dApp UI. -
Inside the
Main
component, several state variables are defined using theuseState
hook. These variables (title
,description
,image
,goal
,amount
) are used to store and manage user input and data within the component. -
The
submitHandler
function is defined and triggered when a form submission event occurs. It prevents the default form submission behavior, and then calls theaddDonations
function from the props, passing thetitle
,description
,image
, andgoal
values as arguments. This function is typically used to create new donation campaigns. -
The
donateHandler
function is defined and triggered when a donate button is clicked. It checks if theamount
is valid (not empty or falsy), and then calls thedonate
function from the props, passing theindex
of the donation and theamount
value as arguments. This function is used to make donations to existing campaigns.
You can follow the remaining part of the code to see how the frontend is built. I wont go into the details because this is a celo tutorial.
return (
<>
<main>
<section className="py-5 text-center container">
<div className="row py-lg-5">
<div className="col-lg-6 col-md-8 mx-auto">
<h1 className="fw-light">Welcome to Animal Donater</h1>
<p className="lead text-muted">
We are a community dedicated to helping animals and we raise
money for certain wild animals who need our help. Join us today
make the world a better place
</p>
<p>
<a href="#" className="btn btn-primary my-2">
Donate Today
</a>
</p>
</div>
</div>
</section>
<div className="album py-5 bg-light">
<div className="container">
<div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
{props.donations.map((donation) => (
<div className="col">
<div className="card shadow-sm">
<img
src={donation.image}
className="bd-placeholder-img card-img-top"
width="100%"
height={225}
aria-label="Placeholder: Thumbnail"
preserveAspectRatio="xMidYMid slice"
focusable="false"
/>
<div className="card-body">
<h3>{donation.title}</h3>
<p className="card-text">{donation.description}</p>
<div className="d-flex justify-content-between align-items-center">
<h6>Goal: {donation.goal / 10 ** 18}cUSD</h6>
<h6>
Amount Donated: {donation.amountDonated / 10 ** 18}
cUSD
</h6>
</div>
{!donation.isGoalReached && <div className="mb-3">
<label>Donate Amount</label>
<input
type="text"
className="form-control"
onChange={(e) => setAmount(e.target.value)}
/>
</div>}
<div className="btn-group">
{!donation.isGoalReached ? (
<button
type="button"
className="btn btn-sm btn-outline-primary"
onClick={() => donateHandler(donation.index)}
>
Donate
</button>
) : (
<button
type="button"
className="btn btn-sm btn-outline-primary"
disabled
>
Goal Reached
</button>
)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</main>
<div
className="modal modal-signin position-static d-block bg-secondary py-5"
tabIndex={-1}
role="dialog"
id="modalSignin"
>
<div className="modal-dialog" role="document">
<div className="modal-content rounded-5 shadow">
<div className="modal-header p-5 pb-4 border-bottom-0">
{/* <h5 class="modal-title">Modal title</h5> */}
<h2 className="fw-bold mb-0">Add an animal</h2>
</div>
<div className="modal-body p-5 pt-0">
<form onSubmit={submitHandler} className>
<div className="form-floating mb-3">
<input
onChange={(e) => setTitle(e.target.value)}
type="text"
required
className="form-control rounded-4"
id="floatingInput"
placeholder=""
/>
<label htmlFor="floatingInput">Name of animal</label>
</div>
<div className="form-floating mb-3">
<input
onChange={(e) => setDescription(e.target.value)}
type="text"
required
className="form-control rounded-4"
id="floatingInput"
placeholder=""
/>
<label htmlFor="floatingInput">Description</label>
</div>
<div className="form-floating mb-3">
<input
type="text"
onChange={(e) => setImage(e.target.value)}
required
className="form-control rounded-4"
id="floatingInput"
placeholder=""
/>
<label htmlFor="floatingInput">Image of animal</label>
</div>
<div className="form-floating mb-3">
<input
type="text"
onChange={(e) => setGoal(e.target.value)}
required
className="form-control rounded-4"
id="floatingInput"
placeholder=""
/>
<label htmlFor="floatingInput">Goal to be reached</label>
</div>
<button
className="w-100 mb-2 btn btn-lg rounded-4 btn-primary"
type="submit"
>
Add
</button>
</form>
</div>
</div>
</div>
</div>
</>
);
};
export default Main;
Header.js
const Header = (props) => {
return (
<>
<div className="collapse bg-dark" id="navbarHeader">
<div className="container">
<div className="row">
<div className="col-sm-8 col-md-7 py-4">
<h4 className="text-white">Balance: {props.balance}cUSD</h4>
<p className="text-muted">
We are a community dedicated to helping animals and we raise
money for certain wild animals who need our help. Join us today
make the world a better place
</p>
</div>
<div className="col-sm-4 offset-md-1 py-4">
<h4 className="text-white">Contact</h4>
<ul className="list-unstyled">
<li>
<a href="#" className="text-white">
Follow on Twitter
</a>
</li>
<li>
<a href="#" className="text-white">
Like on Facebook
</a>
</li>
<li>
<a href="#" className="text-white">
Email me
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="navbar navbar-dark bg-dark shadow-sm">
<div className="container">
<a href="#" className="navbar-brand d-flex align-items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width={20}
height={20}
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
aria-hidden="true"
class="bi bi-cash"
viewBox="0 0 24 24"
>
<path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
<path d="M0 4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V4zm3 0a2 2 0 0 1-2 2v4a2 2 0 0 1 2 2h10a2 2 0 0 1 2-2V6a2 2 0 0 1-2-2H3z" />
</svg>
{/* <svg
xmlns="http://www.w3.org/2000/svg"
className="me-2"
>
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
<circle cx={12} cy={13} r={4} />
</svg> */}
<strong>Donater</strong>
</a>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarHeader"
aria-controls="navbarHeader"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon" />
</button>
</div>
</div>
</>
);
};
export default Header;
Footer.js
const Footer = props=>{
return (
<>
<footer className="text-muted py-5">
<div className="container">
<p className="float-end mb-1">
<a href="#">Back to top</a>
</p>
<p className="mb-1">
Donater is © A licensed mock application, use at your own risk
</p>
</div>
</footer>
</>
)
}
export default Footer;
Conclusion
Throughout the tutorial, we learned about important concepts such as smart contracts, web3 integration, state management with React’s useState hook, and interacting with the Celo blockchain using the ContractKit library. We also gained insights into the functionalities of different functions and their roles in the dApp.
By combining our frontend knowledge with blockchain technology, we created a user-friendly interface that allows users to create donation campaigns, donate to existing campaigns, and view campaign details. We demonstrated how to fetch data from the smart contract and update the UI dynamically.
Next Steps
Remember to continue experimenting, exploring, and improving your skills as you delve deeper into the world of blockchain development. Happy coding!
About the Author
Daniel Ogbuti is a web3 developer with a passion for teaching as well as learning. I would love to connect on Twitter @daniel_ogbuti and linkedin @ Daniel Ogbuti
See you soon!