Building a decentralized application (DApp) on the Celo Blockchain for funding/supporting an orphanage

Introducton.

Welcome to this comprehensive technical tutorial on building a decentralized application (Dapp) on the Celo blockchain for donating to an orphanage. In this tutorial, we will walk you through the process of creating a fully functional Dapp that allows users to make donations to support orphanages in a transparent and secure manner.

Orphanages play a crucial role in providing care, shelter, and education to vulnerable children who have lost their parents or are in need of special assistance. However, traditional donation systems for orphanages often face challenges, such as lack of transparency, high transaction costs, and delays in fund disbursement. These issues can hinder the efficient flow of resources to those in need and may discourage potential donors from contributing.

To address these challenges, we will leverage the power of blockchain technology, specifically the Celo blockchain. Celo is a fast-growing, open-source blockchain platform that prioritizes financial inclusion and social impact. With its focus on mobile accessibility and low-cost transactions, Celo is an ideal choice for building applications that aim to empower underprivileged communities.

Throughout this tutorial, we will cover each step of the development process, from setting up the development environment to deploying the smart contract and building the front-end interface. By the end of this tutorial, you will have a fully functional Dapp that allows users to make donations to an orphanage of their choice, with full transparency and traceability of funds on the Celo blockchain.

Let’s get started on this exciting journey of building a Dapp that can make a positive impact on the lives of orphaned children. Together, we can create a transparent and efficient donation platform that encourages more people to contribute and support these deserving individuals. So, without further ado, let’s dive into the world of blockchain and Dapp development on the Celo platform!

Prerequisites.

  • A basic understanding of the block chain technology
  • A basic understanding of Celo and smart contracts.
  • A knowledge of JavaScript programming language.

Requirements.

  • Node.js should be installed along with a node package manager(npm)
  • A Celo Wallet address.
  • To create Alfajores wallet, go to Alfajores wallet
  • To get testnest funds, go to Celo faucet
  • A text editor such as Visual Studio Code installed on your computer
  • A text editor such as the Remix IDE
  • Metamask
  • A Pinata account

Now let’s begin!


What our DApp will look like!

Contract development.

Step 1: Creating a New Smart Contract File

  • Open Remix IDE in your web browser.
  • Click on the “+” icon at the top left corner to create a new file.
  • Name the file “OrphanageDonation.sol” and click “OK” to create the file.

Step 2: Writing the Smart Contract

In the “OrphanageDonation.sol” file, define the necessary contract and data structures:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;


contract OrphanageDonation {
    address public owner;
    uint256 public totalDonations;
    mapping(address => uint256) public donations;

    event DonationReceived(address indexed donor, uint256 amount);
    event DonationWithdrawn(address indexed orphanage, uint256 amount);

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the contract owner can call this function");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function donate() external payable {
        require(msg.value > 0, "Donation amount should be greater than 0");
        totalDonations += msg.value;
        donations[msg.sender] += msg.value;
        emit DonationReceived(msg.sender, msg.value);
    }

    function withdrawDonations() external onlyOwner {
        require(totalDonations > 0, "No donations available to withdraw");
        uint256 balance = address(this).balance;
        (bool success, ) = owner.call{value: balance}("");
        require(success, "Withdrawal failed");
        totalDonations = 0;
        emit DonationWithdrawn(owner, balance);
    }
}

Smart contract breakdown.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;


contract OrphanageDonation {
    address public owner;
    uint256 public totalDonations;
    mapping(address => uint256) public donations;

    event DonationReceived(address indexed donor, uint256 amount);
    event DonationWithdrawn(address indexed orphanage, uint256 amount);

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the contract owner can call this function");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

Contract Structure:
The smart contract starts with the SPDX License Identifier and the Solidity version pragma. It defines the OrphanageDonation contract, which allows users to donate to orphanages and the owner of the contract to withdraw the collected donations.

State Variables:

  • owner: The address of the contract owner (the one who deployed the contract).
  • totalDonations: The total amount of donations received by the contract.
  • donations: A mapping that stores the donations made by individual donors. The keys are the addresses of the donors, and the values are the corresponding donation amounts.

Events:

  • DonationReceived: This event is emitted whenever a donation is received. It records the address of the donor and the amount donated.
  • DonationWithdrawn: This event is emitted when the contract owner withdraws the collected donations. It records the address of the orphanage (owner) and the amount withdrawn.

Modifiers:

  • onlyOwner: A custom modifier that restricts access to certain functions only to the contract owner. It requires the caller to be the owner; otherwise, the transaction will revert.

Constructor:
The constructor is executed only once during the contract deployment. It initializes the owner variable with the address of the deployer.

Add the donate function to allow users donate to the contract.

  function donate() external payable {
        require(msg.value > 0, "Donation amount should be greater than 0");
        totalDonations += msg.value;
        donations[msg.sender] += msg.value;
        emit DonationReceived(msg.sender, msg.value);
    }

donate function: This function allows anyone to donate to the contract. It requires the donation amount to be greater than zero. When a donation is made, the totalDonations and the specific donor’s donations mapping are updated accordingly. The DonationReceived event is emitted to log the donation.

Add the withdrawDonations function to allow the owner to withdraw the collected donations.

 function withdrawDonations() external onlyOwner {
        require(totalDonations > 0, "No donations available to withdraw");
        uint256 balance = address(this).balance;
        (bool success, ) = owner.call{value: balance}("");
        require(success, "Withdrawal failed");
        totalDonations = 0;
        emit DonationWithdrawn(owner, balance);
    }
}

withdrawDonations function: This function can only be called by the contract owner using the onlyOwner modifier. It allows the owner to withdraw the collected donations. The contract’s balance is transferred to the owner’s address, and the totalDonations is reset to zero. The DonationWithdrawn event is emitted to log the withdrawal.

Compiling and deploying the Smart Contract

  • In Remix IDE, click on the “Solidity Compiler” tab on the left-hand side.
  • Select the appropriate compiler version.
  • Click on the “Compile OrphanageDonation.sol” button to compile the smart contract.
  • Switch to the “Deploy & Run Transactions” tab in Remix IDE.
  • Select the Alfajores TestNet MetaMask prop in your Environment dropdown menu.
  • Click on the “Deploy” button to deploy the smart contract to the selected Celo network.


What our deployed smart contract looks like!

Now let’s build the Frontend.

Now Let’s Code the Frontend:
● Step 2: Set up your new react project.
Run this on your terminal

npx create-react-app orphange

This should take a few minutes to complete depending on your internet connection. Navigate
to the directory created Open the directory using your code editor. In this case, it would be
vscode and you would run this on your terminal

Code orphanage

This should open a vscode window with the orphanage directory.
● Step 3: Update package.json file.
Now, update the content of the package.json by copying and pasting this;

{
"name": "orphanage",
"version": "0.1.0",
"private": true,
"dependencies": {
"@celo-tools/use-contractkit": "^3.1.0",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@web3uikit/core": "^0.2.45",
"@web3uikit/web3": "^0.2.2",
"react": "^18.2.0",
"react-bootstrap": "^2.5.0",
"react-dom": "^18.2.0",
"react-error-overlay": "6.0.9",
"react-scripts": "4.0.3",
"web-streams-polyfill": "^3.2.1",
"web-vitals": "^2.1.4",
"web3": "^1.7.0",
"web3uikit": "0.1.159"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": ["react-app", "react-app/jest"]
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"resolutions": {
"react-error-overlay": "6.0.9"
}
}
  • Install all dependencies
npm install
  • Start development server
npm start

Update the App.js file
This is the file that will contain codes that interact with our smart contract and also house all
the components of the frontend. The App.js file should look like this:

import React, { useState, useEffect, useCallback } from "react";
import Web3 from "web3";
import "./App.css";
import { contractABI, contractAddress } from "./const";
const App = () => {
const [donationAmount, setDonationAmount] = useState("");
const [connectedAccount, setConnectedAccount] = useState("");
const [contract, setContract] = useState(null);
const [totalDonations, setTotalDonations] = useState(0);
const connectWallet = async () => {
try {
// Check if the current provider is connected
await window.ethereum.enable();
// Get the user"s account address
const accounts = await window.ethereum.request({
method: "eth_accounts",
});
const account = accounts[0];
// Set the connected account
setConnectedAccount(account);
} catch (error) {
console.error(error);
alert("An error occurred while connecting the wallet.");
}
};
const disconnectWallet = () => {
setConnectedAccount("");
setContract(null);
};
useEffect(() => {
if (connectedAccount && window.ethereum) {
// Create a web3 instance
const web3 = new Web3(window.ethereum);
// Create contract instance
const contract = new web3.eth.Contract(contractABI, contractAddress);
// Set the contract in state
setContract(contract);
}
}, [connectedAccount]);
// eslint-disable-next-line
const donate = async () => {
try {
// Check if the contract is loaded
if (!contract) {
console.log("Contract is not loaded");
return;
}
const donationAmountInWei = Web3.utils.toWei(donationAmount, "ether");
// Send the donation transaction to the contract
await contract.methods
.donate()
.send({ from: connectedAccount, value: donationAmountInWei });
// Display success message
alert("Donation successful!");
setDonationAmount("");
setTotalDonations(totalDonations);
} catch (error) {
console.error(error);
alert("An error occurred during donation.");
}
};
const fetchTotalDonations = useCallback(async () => {
if (contract) {
const total = await contract.methods.totalDonations().call();
setTotalDonations(Web3.utils.fromWei(total, "ether"));
}
}, [contract]);
useEffect(() => {
fetchTotalDonations();
}, [fetchTotalDonations, donate]);
const withdraw = async () => {
try {
// Check if the contract is loaded
if (!contract) {
console.log("Contract is not loaded");
return;
}
// Send the withdrawal transaction to the contract
await contract.methods
.withdrawDonations()
.send({ from: connectedAccount });
// Display success message
alert("Withdrawal successful!");
setTotalDonations(totalDonations);
} catch (error) {
console.error(error);
alert("An error occurred during withdrawal.");
}
};
return (
<div>
<h1>Orphanage Donation DApp</h1>
<button
className="connectButton"
onClick={connectedAccount ? disconnectWallet : connectWallet}
>
{connectedAccount ? "Disconnect Wallet" : "Connect Wallet"}
</button>
<div className="connect">Connected Account: {connectedAccount}</div>
<div className="input">
<div className="totalDonations">
<h2>Total Donations:</h2>
<p>{totalDonations} CELO</p>
</div>
<p>Please enter the amount of Celo:</p>
<input
type="number"
value={donationAmount}
step={1}
onChange={(e) => setDonationAmount(e.target.value)}
/>
</div>
<div>
<button className="donate" onClick={donate} disabled={!donationAmount}>
Donate
</button>
<div />
</div>
<div>
<button className="withdraw" onClick={withdraw}>
Withdraw Donations
</button>
</div>
</div>
);
};
export default App;

Now let’s break this down;

  • Import all dependencies
import React, { useState, useEffect, useCallback } from "react";
import Web3 from "web3";
import "./App.css";
import { contractABI, contractAddress } from "./const";
  • Let’s use useState to keep track of some variables throughout the dapp.
const App = () => {
const [donationAmount, setDonationAmount] = useState("");
const [connectedAccount, setConnectedAccount] = useState("");
const [contract, setContract] = useState(null);
const [totalDonations, setTotalDonations] = useState(0);
  • Connect wallet function
const connectWallet = async () => {
try {
// Check if the current provider is connected
await window.ethereum.enable();
// Get the user"s account address
const accounts = await window.ethereum.request({
method: "eth_accounts",
});
const account = accounts[0];
// Set the connected account
setConnectedAccount(account);
} catch (error) {
console.error(error);
alert("An error occurred while connecting the wallet.");
}
};
const disconnectWallet = () => {
setConnectedAccount("");
setContract(null);
};

It uses the window.ethereum object (provided by an Ethereum browser extension like
MetaMask) to connect to the user’s Ethereum wallet. It first asks for permission with enable(),
then requests the user’s Ethereum accounts using window.ethereum.request(). The first
account is selected and stored via setConnectedAccount(account). If an error occurs, it’s
logged and the user is alerted.
disconnectWallet function disconnects from the wallet by resetting the state variables
associated with the connected account and contract using setConnectedAccount("") and
setContract(null).

  • Create Contract Instance
useEffect(() => {
if (connectedAccount && window.ethereum) {
// Create a web3 instance
const web3 = new Web3(window.ethereum);
// Create contract instance
const contract = new web3.eth.Contract(contractABI, contractAddress);
// Set the contract in state
setContract(contract);
}
}, [connectedAccount]);

The useEffect hook is triggered when connectedAccount changes. If Ethereum provider is
available, it creates a Web3 instance and a contract instance using the contract’s ABI and
address. This contract instance is then stored in the state via setContract(contract), enabling
interaction with the contract elsewhere in the application. The hook ensures the contract
instance aligns with the currently connected account.

  • The donate Function
const donate = async () => {
try {
// Check if the contract is loaded
if (!contract) {
console.log("Contract is not loaded");
return;
}
const donationAmountInWei = Web3.utils.toWei(donationAmount, "ether");
// Send the donation transaction to the contract
await contract.methods
.donate()
.send({ from: connectedAccount, value: donationAmountInWei });
// Display success message
alert("Donation successful!");
setDonationAmount("");
setTotalDonations(totalDonations);
} catch (error) {
console.error(error);
alert("An error occurred during donation.");
}
};

It checks if a contract instance is available in state. If not, it logs a message and ends the
function.
It converts the donation amount from Celo to Wei (the smallest unit of Celo) using
Web3.utils.toWei().
It calls the donate function on the contract instance and sends the transaction from the
connected account, attaching the donation amount as the value.
If successful, it displays a success message, resets the donation amount in state, and
presumably updates the total donations.
If an error occurs during this process, it logs the error and alerts the user.
The function also update the state of totalDonations which calls the fetchTotalDonations.

  • The withdraw Function
const withdraw = async () => {
try {
// Check if the contract is loaded
if (!contract) {
console.log("Contract is not loaded");
return;
}
// Send the withdrawal transaction to the contract
await contract.methods
.withdrawDonations()
.send({ from: connectedAccount });
// Display success message
alert("Withdrawal successful!");
setTotalDonations(totalDonations);
} catch (error) {
console.error(error);
alert("An error occurred during withdrawal.");
}
};

This checks if the contract instance has been properly initiated. If no, it aborts and throws an
error but if yes, it goes on to perform the withdraw logic. Any errors in the next stage of the
function would still throw an error.
Afterwards our setTotalDonations updates the state oof totalDonation to reflect the new
after withdrawal.

  • The fetchTotalDonations Function
const fetchTotalDonations = useCallback(async () => {
if (contract) {
const total = await contract.methods.totalDonations().call();
setTotalDonations(Web3.utils.fromWei(total, "ether"));
}
}, [contract]);
useEffect(() => {
fetchTotalDonations();
}, [fetchTotalDonations, donate]);

The fetchTotalDonations function is wrapped in a useCallback hook to memorize the
function and prevent unnecessary re-renderings. The function checks if the contract instance
exists and then calls the totalDonations method of the contract. The result is converted from
Wei to Celo and stored in state. Also remember that our withdraw function also affects the
state of totalDonation too.
The useEffect hook runs every time the fetchTotalDonations function or donate function
changes. It calls the fetchTotalDonations function to get the updated total donations each
time a donation is made. The donate function is included in the dependency array to ensure
that the total donations are fetched again after a donation is made.

  • Renders the UI of our DApp
return (
<div>
<h1>Orphanage Donation DApp</h1>
<button
className="connectButton"
onClick={connectedAccount ? disconnectWallet : connectWallet}
>
{connectedAccount ? "Disconnect Wallet" : "Connect Wallet"}
</button>
<div className="connect">Connected Account: {connectedAccount}</div>
<div className="input">
<div className="totalDonations">
<h2>Total Donations:</h2>
<p>{totalDonations} CELO</p>
</div>
<p>Please enter the amount of Celo:</p>
<input
type="number"
value={donationAmount}
step={1}
onChange={(e) => setDonationAmount(e.target.value)}
/>
</div>
<div>
<button className="donate" onClick={donate} disabled={!donationAmount}>
Donate
</button>
<div />
</div>
<div>
<button className="withdraw" onClick={withdraw}>
Withdraw Donations
</button>
</div>
</div>
);
};
export default App;

Styling can be effected in the App.css file.

Conclusion

Congratulations on completing this comprehensive tutorial on building a decentralized application (Dapp) on the Celo blockchain for donating to an orphanage. Throughout this tutorial, we have explored the process of creating a fully functional Dapp that enables users to make transparent and secure donations to support orphanages.

By leveraging the power of the Celo blockchain, we have addressed the challenges faced by traditional donation systems, such as lack of transparency, high transaction costs, and delays in fund disbursement. The Celo blockchain’s focus on financial inclusion and social impact makes it an ideal platform for building applications that empower underprivileged communities.

Through the development process, we have covered key aspects, including setting up the development environment, deploying the smart contract, and building the front-end interface. By implementing a smart contract on the Celo blockchain, we have ensured the transparency and traceability of funds, allowing donors to have full visibility into how their contributions are utilized.

By building this Dapp, we have taken a significant step toward creating a more efficient and accessible donation platform for orphanages. Donors can now make contributions with confidence, knowing that their funds are being utilized for the intended purpose and that the entire process is transparent and secure.

This tutorial also highlights the potential of blockchain technology, particularly the Celo blockchain, to revolutionize philanthropic efforts and create a positive impact in society. The transparency and immutability provided by blockchain technology foster trust and accountability, making it an excellent choice for applications involving donations and social causes.

Next steps

As you continue your journey in blockchain development and exploration, consider expanding upon this Dapp by incorporating additional features such as donor verification, real-time fund utilization tracking, and enhanced user engagement. With the tools and knowledge gained from this tutorial, you are well-equipped to take on more complex projects and contribute to the advancement of decentralized applications on the Celo blockchain.

Remember, the success of this Dapp lies not only in its technical implementation but also in its ability to make a real difference in the lives of orphaned children. By building a transparent and efficient donation platform, you are empowering donors to make meaningful contributions and providing support to those in need.

About the Author

Okoro Samuel Ogheneyole is a Web3 technical writer who has burning passion for technology and loves to share his knowledge for the benefit of other Web3 enthusiasts.

References

GitHub Repo
Solidity Documentation

7 Likes

@Celo_Academy Announcing This Week's Top Proposals and Celebrating this Week's Leading Contributors | 06/08/23

3 Likes

Congratulations on being our top contributor this week - your insights are incredibly valued! We’re excitedly looking forward to your upcoming tutorial. Keep up the fantastic work! :mortar_board: :seedling:

2 Likes

I will be reviewing this

2 Likes

Okay. Thanks so much. I await your feedback.

2 Likes