DEV Community

Cover image for Create a Charity/Donation Platform on the Blockchain (part 1)
Majid Kareem
Majid Kareem

Posted on

Create a Charity/Donation Platform on the Blockchain (part 1)

Introduction

We will be creating the smart contract for a charity donation platform in this post. Yes, something like Gofundme but on the blockchain.
and users can donate to individual campaigns with Ether.

Expectations

This post assumes you are already familiar with the structure of a smart contract and basic types in solidity.

Required tools

  • Remix (a browser-based IDE)
  • Any browser (preferably Chrome)

Overview

Our platform will allow anyone to create a charity/donation campaign and set a time limit or deadline for it, After creating the campaign, a user can donate to any campaign they choose to support and the creator of the campaign can withdraw all the funds donated to the campaign after the deadline is exceeded.

Enough talk, let's get started with the code

First, we'll create our contract file and name it Charity.sol. The we populate it with the code below;

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

import "@openzeppelin/contracts/utils/Counters.sol";

contract Charity {

}

Enter fullscreen mode Exit fullscreen mode

The first and second line in the code above defines the license and the solidity version for compiling the code. Then we import a library from openzeppelin called Counters.sol which provides us with counters that can only be incremented or decremented.
Next, we define a new contract called Charity

To give us a sense of direction, we will create all the functions skeleton we need before implementing them one by one.


contract Charity {
    using Counters for Counters.Counter;

// These are events to be emitted when specific actions are completed
    event CampaignStarted(bytes32 campaignId, address initiator);
    event WithdrawFunds(bytes32 campaignId, address initiator, uint256 amount);
    event FundsDonated(bytes32 campaignId, address donor, uint256 amount);

    // defines a variable to keep track of total number of campaigns created
    Counters.Counter public _campaignCount;

   // Campaign details to be saved
    struct Campaign {
        string title;
        string imgUrl;
        string description;
        uint256 fundsRaised;
        bool isLive;
        address initiator;
        uint256 deadline;
        uint256 balance;
    }

    // allows us to keep track of the campaigns created and it's details using a unique ID
    mapping(bytes32=>Campaign) public _campaigns;

    // allows us to keep track of the who donates to a campaign and the amount they donated
    mapping(address=>mapping(bytes32=>uint256)) public userCampaignDonations;

    // this function generated a unique ID for a campaign
//from it's title, descrition and creator address
    function generateCampaignId(address initiator, string calldata title, string calldata description) public pure returns(bytes32) { }

    // this function will be called by a user to create a new campaign
    function startCampaign( string calldata title, string calldata description, string calldata imgUrl, uint256 deadline ) public { }

    // calling this function allows users to donate to a charity campaign of their choice
    function donateToCampaign(bytes32 campaignId) public payable { }

    // returns the details of a campaign given the campaignId
    function getCampaign(bytes32 campaignId) public view returns(Campaign memory) { }

    // this function allows the creator of the campaign to withdraw all the funds donated to the campaign 
    // after the campaign has ended
    function withdrawCampaignFunds(bytes32 campaignId) public { }

}
Enter fullscreen mode Exit fullscreen mode

Now let's take the functions one after the other and flesh them out. First is the generateCampaignId function, this function will create a unique hash from the campaign title, description, and initiator address


    function generateCampaignId(address initiator, string calldata title, string calldata description) public pure returns(bytes32) {
       bytes32 campaignId = keccak256(abi.encodePacked(title, description, initiator));
       return campaignId;
    }

Enter fullscreen mode Exit fullscreen mode

Next, we create the function startCampaign which allows a user to actually create a campaign,


    function startCampaign( string calldata title, string calldata description, string calldata imgUrl, uint256 deadline ) public {
        // first, we generate a campaignID 
        // using the title, description and the address of the initiator
        bytes32 campaignId = generateCampaignId(msg.sender, title, description);

        // get a reference to the campaign with the generated Id
        Campaign storage campaign = _campaigns[campaignId];
        // require that the campaign is not live yet.
        require(!campaign.isLive, "Campaign exists");
        // require the current time to be less than the campaign deadline
        require(block.timestamp < deadline, "Campaign ended");

        campaign.title = title;
        campaign.description = description;
        campaign.initiator = msg.sender;
        campaign.imgUrl = imgUrl;
        campaign.deadline = deadline;
        campaign.isLive = true;

        // increment the total number of charity campaigns created
        _campaignCount.increment();

        // emit an event to the blockchain
        emit CampaignStarted(campaignId, msg.sender);
    }

Enter fullscreen mode Exit fullscreen mode

After creating the campaign, we need a way to allow users donate to a live campaign. So let's create the donateToCampaign function.

// allows users to donate to a charity campaign of their choice
    function donateToCampaign(bytes32 campaignId) public payable {
        // get campaign details with the given campaign
        Campaign storage campaign = _campaigns[campaignId];

        // end the campaign if the deadline is exceeded
        if(block.timestamp > campaign.deadline){
            campaign.isLive = false;
        }
        // require the campaign has not ended
        require(block.timestamp < campaign.deadline, "Campaign has ended");

        uint256 amountToDonate = msg.value;
        require(amountToDonate > 0, "Wrong ETH value");

        // increase the campaign balance by the amount donated;
        campaign.fundsRaised += amountToDonate;
        campaign.balance += amountToDonate;

        // keep track of users donation history
        userCampaignDonations[msg.sender][campaignId] = amountToDonate;

        // emit FundsDonated event
        emit FundsDonated(campaignId, msg.sender, amountToDonate);
    }

Enter fullscreen mode Exit fullscreen mode

Okay, users can now donate to our cause via the above function. But we still need a way for the creator of the campaign to withdraw the Ether donated to them. So let's complete the withdrawCampaignFunds function.

function withdrawCampaignFunds(bytes32 campaignId) public {
        Campaign storage campaign = _campaigns[campaignId];

        // require the msg.sender is the creator of the campaign
        require(msg.sender == campaign.initiator, "Not campaign initiator");
        // require the campaign has ended
        require(!campaign.isLive, "campaign is still active");
        require(block.timestamp > campaign.deadline, "Campaign is still active");
        // require the campaign has funds to be withdrawn
        require(campaign.balance > 0, "No funds to withdraw");

        uint256 amountToWithdraw = campaign.balance;

        // zero the campaign balance
        campaign.balance = 0;

        // transfer the balance to the initiator address;
        payable(campaign.initiator).transfer(amountToWithdraw);

        // emit an event to the blockchain
        emit WithdrawFunds(campaignId, campaign.initiator, amountToWithdraw);
    }

Enter fullscreen mode Exit fullscreen mode

Cool, we now have complete flow for the donation process.
Below is the full code for this tutorial.

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


import "@openzeppelin/contracts/utils/Counters.sol";

contract Charity {
    using Counters for Counters.Counter;

    event CampaignStarted(bytes32 campaignId, address initiator);
    event WithdrawFunds(bytes32 campaignId, address initiator, uint256 amount);
    event FundsDonated(bytes32 campaignId, address donor, uint256 amount);

    Counters.Counter public _campaignCount;

    struct Campaign {
        string title;
        string imgUrl;
        string description;
        uint256 fundsRaised;
        bool isLive;
        address initiator;
        uint256 deadline;
        uint256 balance;
    }

    mapping(bytes32=>Campaign) public _campaigns;
    mapping(address=>mapping(bytes32=>uint256)) public userCampaignDonations;

    constructor(){

    }

    function generateCampaignId(address initiator, string calldata title, string calldata description) public pure returns(bytes32) {
       bytes32 campaignId = keccak256(abi.encodePacked(title, description, initiator));
       return campaignId;
    }

    function startCampaign( string calldata title, string calldata description, string calldata imgUrl, uint256 deadline ) public {
        // generate a campaignID 
        // using the title, description and the address of the initiator
        bytes32 campaignId = generateCampaignId(msg.sender, title, description);

        // get a reference to the campaign with the generated Id
        Campaign storage campaign = _campaigns[campaignId];
        // require that the campaign is not live yet.
        require(!campaign.isLive, "Campaign exists");
        // require the current time to be less than the campaign deadline
        require(block.timestamp < deadline, "Campaign ended");

        campaign.title = title;
        campaign.description = description;
        campaign.initiator = msg.sender;
        campaign.imgUrl = imgUrl;
        campaign.deadline = deadline;
        campaign.isLive = true;

        // increment the total number of charity campaigns created
        _campaignCount.increment();

        // emit an event to the blockchain
        emit CampaignStarted(campaignId, msg.sender);
    }

    function endCampaign() public {

    }

    // allows users to donate to a charity campaign of their choice
    function donateToCampaign(bytes32 campaignId) public payable {
        // get campaign details with the given campaign
        Campaign storage campaign = _campaigns[campaignId];

        // end the campaign if the deadline is exceeded
        if(block.timestamp > campaign.deadline){
            campaign.isLive = false;
        }
        // require the campaign has not ended
        require(block.timestamp < campaign.deadline, "Campaign has ended");

        uint256 amountToDonate = msg.value;
        require(amountToDonate > 0, "Wrong ETH value");

        // increase the campaign balance by the amount donated;
        campaign.fundsRaised += amountToDonate;
        campaign.balance += amountToDonate;

        // keep track of users donation history
        userCampaignDonations[msg.sender][campaignId] = amountToDonate;

        // emit FundsDonated event
        emit FundsDonated(campaignId, msg.sender, amountToDonate);
    }

    // returns the details of a campaign given the campaignId
    function getCampaign(bytes32 campaignId) public view returns(Campaign memory) {
        return _campaigns[campaignId];
    }

    function withdrawCampaignFunds(bytes32 campaignId) public {
        Campaign storage campaign = _campaigns[campaignId];

        // require the msg.sender is the creator of the campaign
        require(msg.sender == campaign.initiator, "Not campaign initiator");
        // require the campaign has ended
        require(!campaign.isLive, "campaign is still active");
        require(block.timestamp > campaign.deadline, "Campaign is still active");
        // require the campaign has funds to be withdrawn
        require(campaign.balance > 0, "No funds to withdraw");

        uint256 amountToWithdraw = campaign.balance;

        // zero the campaign balance
        campaign.balance = 0;

        // transfer the balance to the initiator address;
        payable(campaign.initiator).transfer(amountToWithdraw);

        // emit an event to the blockchain
        emit WithdrawFunds(campaignId, campaign.initiator, amountToWithdraw);
    }

}

Enter fullscreen mode Exit fullscreen mode

Well done!!! 🎉🎉🎉
If you have been able to get here, I hope you have been able to learn more about creating smart contracts.
In the next part of this series, I'll be creating a UI for our smart contract using React or Next.js

Feel free to reach out to me on codementor If you have any suggestions or questions or if you just wanna say hi.

Top comments (3)

Collapse
 
beembuilds profile image
Beembuilds

Thanks for sharing. i want to use this on my ehsaas programme website

Collapse
 
joseph101 profile image
JOSEPH SMITH

Eagerly waiting for the next part of series. Can I hae a permission to use this for my nser survey online registration website.

Collapse
 
viratalex profile image
Viratalex

Can i use this for my ehsaas blog?