Building a Decentralized P2P Nonprofit Donation Smart Contract on the Celo Blockchain: A Step-by-Step Tutorial

Building a Decentralized P2P Nonprofit Donation Smart Contract on the Celo Blockchain: A Step-by-Step Tutorial https://celo.academy/uploads/default/optimized/2X/f/f382d4465ff50b7999a264cf6090d7cbfafc56bb_2_1024x576.png
none 0.0 0

Introduction

The emergence of blockchain technology has opened up new possibilities for transparency, efficiency and direct peer-to-peer engagement in the realm of philanthropy and charitable giving.

The P2PNonprofitDonation smart contract is at the forefront of this transformative wave, offering a decentralized platform for connecting individuals and nonprofit organizations and facilitating seamless donations.

P2PNonprofitDonation, which is built on the Celo blockchain, takes an innovative approach to charitable giving by eliminating intermediaries and allowing donors and nonprofits to interact directly with one another. This smart contract paves the way for more transparent, efficient, and secure donations by eliminating the need for traditional third-party intermediaries.

We will go deeper into the inner workings of P2PNonprofitDonation in this advanced technical tutorial, exploring its key features, functionality, and the underlying logic that drives this game-changing solution. We will provide you with the knowledge and expertise to effectively leverage this smart contract, from understanding donation structuring to exploring reputation management and the various operations available to users.

This tutorial will serve as your comprehensive guide to unlocking the full potential of P2PNonprofitDonation, whether you are an aspiring blockchain developer, a nonprofit organization looking to leverage decentralized technologies, or simply an enthusiast curious about the potential of smart contracts.

Prerequisite

To fully understand and make the most of this P2PNonprofitDonation tutorial, it is recommended that you have a basic understanding of the following concepts:

  • Blockchain Technology: Understanding the fundamental principles of blockchain technology, such as decentralized networks, smart contracts, and transaction verification, will provide a solid foundation for understanding P2P Nonprofit Donations inner workings.

  • Solidity: A programming language used to create smart contracts that will help you understand the specific implementation details and syntax used in P2PNonprofitDonation.

  • Philanthropy and Charitable Giving: Understanding philanthropic practices and the traditional methods of charitable giving will help you appreciate the unique value proposition that P2PNonprofitDonation offers in revolutionizing this domain.

You will be better equipped to grasp the nuances of P2PNonprofitDonation and realize its full potential if you have a solid understanding of these concepts. Even if you are unfamiliar with these topics, this tutorial will walk you through the key aspects of the smart contract, ensuring that you have a thorough understanding of its functionality and implementation. Let us enter the thrilling world of P2PNonprofitDonation!

Requirements

Set Up

If you are using Celo Composer, Open up your terminal, type this command, and follow the process as seen in the image below

npx @celo/celo-composer@latest create

Now install the necessary dependencies you want using yarn or npm install. If you are using remix, just activate the Celo plugin. You can git clone the code repo and start your integration here

Smart Contract

The complete smart contract should look like this :+1:


// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0 <0.9.0;

contract P2PNonprofitDonation {
    // The minimum and maximum amount of ETH that can be donated
    uint public constant MIN_DONATION_AMOUNT = 0.1 ether;
    uint public constant MAX_DONATION_AMOUNT = 10 ether;
    // The minimum and maximum equity percentage that can be offered for a donation
    uint public constant MIN_EQUITY_PERCENTAGE = 1;
    uint public constant MAX_EQUITY_PERCENTAGE = 10;

    struct Donation {
        uint amount;
        uint equityPercentage;
        uint fundingDeadline;
        string nonprofitName;
        string description;
        uint valuation;
        address payable donor;
        address payable nonprofit;
        bool active;
        bool distributed;
    }

    struct Reputation {
        uint rating;
        string review;
    }
    mapping(address => Reputation) public donorReputations;
    mapping(address => Reputation) public nonprofitReputations;
    mapping(address => bool) public registeredUsers;
    mapping(address => bytes32) private userPasswords;
    mapping(uint => Donation) public donations;
    uint public donationCount;

    event DonationCreated(
        uint donationId,
        uint amount,
        uint equityPercentage,
        uint fundingDeadline,
        string nonprofitName,
        string description,
        uint valuation,
        address donor,
        address nonprofit
    );

    event DonationFunded(uint donationId, address funder, uint amount);
    event DonationDistributed(uint donationId, uint amount);
    event UserRegistered(address user);
    event UserLoggedIn(address user);

    modifier onlyRegisteredUser() {
        require(registeredUsers[msg.sender], "User is not registered");
        _;
    }

    modifier onlyActiveDonation(uint _donationId) {
        require(donations[_donationId].active, "Donation is not active");
        _;
    }

    modifier onlyDonor(uint _donationId) {
        require(
            msg.sender == donations[_donationId].donor,
            "Only the donor can perform this action"
        );
        _;
    }

    function registerUser(bytes32 _password) external {
        require(!registeredUsers[msg.sender], "User is already registered");
        require(_password != "", "Password cannot be empty");

        registeredUsers[msg.sender] = true;
        userPasswords[msg.sender] = _password;

        emit UserRegistered(msg.sender);
    }

    function login(bytes32 _password) external {
        require(registeredUsers[msg.sender], "User is not registered");
        require(userPasswords[msg.sender] == _password, "Invalid password");

        emit UserLoggedIn(msg.sender);
    }

    function rateDonor(
        address _donor,
        uint _rating,
        string memory _review
    ) external onlyRegisteredUser {
        require(_rating >= 1 && _rating <= 5, "Rating must be between 1 and 5");

        donorReputations[_donor].rating = _rating;
        donorReputations[_donor].review = _review;
    }

    function rateNonprofit(
        address _nonprofit,
        uint _rating,
        string memory _review
    ) external onlyRegisteredUser {
        require(_rating >= 1 && _rating <= 5, "Rating must be between 1 and 5");

        nonprofitReputations[_nonprofit].rating = _rating;
        nonprofitReputations[_nonprofit].review = _review;
    }

    function createDonation(
        uint _amount,
        uint _equityPercentage,
        string memory _nonprofitName,
        string memory _description,
        uint _valuation
    ) external payable {
        require(
            _amount >= MIN_DONATION_AMOUNT && _amount <= MAX_DONATION_AMOUNT,
            "Donation amount must be between MIN_DONATION_AMOUNT and MAX_DONATION_AMOUNT"
        );

        require(
            _equityPercentage >= MIN_EQUITY_PERCENTAGE &&
                _equityPercentage <= MAX_EQUITY_PERCENTAGE,
            "Equity percentage out of range"
        );

        require(
            bytes(_nonprofitName).length > 0,
            "Nonprofit name cannot be empty"
        );
        require(bytes(_description).length > 0, "Description cannot be empty");
        require(_valuation > 0, "Nonprofit valuation must be greater than 0");

        uint _fundingDeadline = block.number + (1 days);
        uint donationId = donationCount++;

        Donation storage donation = donations[donationId];
        donation.amount = _amount;
        donation.equityPercentage = _equityPercentage;
        donation.fundingDeadline = _fundingDeadline;
        donation.nonprofitName = _nonprofitName;
        donation.description = _description;
        donation.valuation = _valuation;
        donation.donor = payable(msg.sender);
        donation.nonprofit = payable(address(0));
        donation.active = true;
        donation.distributed = false;

        emit DonationCreated(
            donationId,
            _amount,
            _equityPercentage,
            _fundingDeadline,
            _nonprofitName,
            _description,
            _valuation,
            msg.sender,
            address(0)
        );
    }

    function fundDonation(
        uint _donationId
    ) external payable onlyActiveDonation(_donationId) {
        Donation storage donation = donations[_donationId];
        require(
            msg.sender != donation.donor,
            "Donor cannot fund their own donation"
        );
        require(donation.amount == msg.value, "Incorrect donation amount");
        require(
            block.number <= donation.fundingDeadline,
            "Donation funding deadline has passed"
        );
        payable(address(this)).transfer(msg.value);
        donation.nonprofit = payable(msg.sender);
        donation.active = false;

        emit DonationFunded(_donationId, msg.sender, msg.value);
    }

    function distributeDonation(
        uint _donationId
    ) external payable onlyActiveDonation(_donationId) onlyDonor(_donationId) {
        Donation storage donation = donations[_donationId];
        require(msg.value == donation.amount, "Incorrect distribution amount");
        donation.nonprofit.transfer(msg.value);
        donation.distributed = true;
        donation.active = false;

        emit DonationDistributed(_donationId, msg.value);
    }

    function extendFundingPeriod(
        uint _donationId,
        uint _extensionDays
    ) external onlyDonor(_donationId) onlyActiveDonation(_donationId) {
        Donation storage donation = donations[_donationId];
        require(_extensionDays > 0, "Extension days must be greater than 0");

        donation.fundingDeadline += (_extensionDays * 1 days);
    }

    function cancelDonation(
        uint _donationId
    ) external onlyDonor(_donationId) onlyActiveDonation(_donationId) {
        Donation storage donation = donations[_donationId];
        require(
            block.number < donation.fundingDeadline,
            "Donation funding deadline has passed"
        );

        donation.active = false;

        // Return the donation amount to the donor
        payable(donation.donor).transfer(donation.amount);
    }

    function getDonationInfo(
        uint _donationId
    )
        external
        view
        returns (
            uint amount,
            uint equityPercentage,
            uint fundingDeadline,
            string memory nonprofitName,
            string memory description,
            uint valuation,
            address donor,
            address nonprofit,
            bool active,
            bool distributed
        )
    {
        Donation storage donation = donations[_donationId];
        return (
            donation.amount,
            donation.equityPercentage,
            donation.fundingDeadline,
            donation.nonprofitName,
            donation.description,
            donation.valuation,
            donation.donor,
            donation.nonprofit,
            donation.active,
            donation.distributed
        );
    }

    function withdrawDonation(
        uint _donationId
    ) external onlyDonor(_donationId) {
        Donation storage donation = donations[_donationId];
        require(!donation.active, "Donation must be inactive to withdraw.");
        payable(msg.sender).transfer(donation.amount);
    }
}


Breakdown

  • First, we declared the solidity version.

The following constants are included in the P2PNonprofitDonation contract:

  • MIN_DONATION_AMOUNT: The minimum amount of Ether (ETH) that can be donated is represented by this constant. It is set to 0.1 ether, indicating that any donation less than this amount is invalid.

  • MAX_DONATION_AMOUNT: The maximum amount of Ether (ETH) that can be donated is represented by this constant. It is set to 10 ether, indicating that any donation in excess of this amount will be rejected.

  • MIN_EQUITY_PERCENTAGE: This constant represents the smallest percentage of equity that can be offered in exchange for a donation. It is set to one, indicating that donors must provide at least 1% equity in exchange for their contribution.

  • MAX_EQUITY_PERCENTAGE: This constant represents the maximum percentage of equity that can be offered in exchange for a donation. It is set to 10, indicating that donors are not permitted to offer more than 10% equity in exchange for their contribution.

These constants define predefined limits and constraints to ensure that donations made via the P2PNonprofitDonation contract fall within a certain range of amounts and equity percentages. The contract aims to maintain fairness and prevent extreme values from disrupting the donation process by establishing these boundaries.

The P2PNonprofitDonation contract includes a struct called “Donation,” which represents a single donation made through the contract. Here are the fields of the Donation struct:

  • amount: A uint variable that represents the donation amount in Ether (ETH).

  • equityPercentage: A uint variable that represents the percentage of equity offered by the donor in exchange for the donation.

  • fundingDeadline: A uint variable that represents the timestamp indicating the deadline for funding the donation.

  • nonprofitName: A string variable containing the name of the nonprofit organization associated with the donation.

  • description: A string variable containing a description or additional information about the donation.

  • valuation: A uint variable that represents the nonprofit organization’s valuation or estimated value associated with the donation.

  • donor: A variable that stores the Ethereum address of the donor who made the donation.

  • nonprofit: A variable in the address payable that stores the Ethereum address of the nonprofit organization receiving the donation.

  • active: A boolean variable indicating whether or not the donation is currently active.

  • distributed: A boolean variable that indicates whether or not the donated funds were distributed.

The Donation struct is a data structure that stores all relevant information about a specific donation made via the contract. These fields allow the contract to effectively track and manage donations, ensuring transparency and accurate record-keeping throughout the donation process.

The P2PNonprofitDonation contract includes a struct called “Reputation,” which represents a user’s (donor’s or nonprofit’s) reputation or feedback within the system. The fields of the Reputation struct are as follows:

  • rating: A uint variable that represents the numerical rating given by other users to evaluate the user’s reputation. The rating is typically assigned on a scale of 1 to 5, with 1 being the lowest and 5 being the highest.

  • review: A textual review or feedback provided by other users to describe their experience or opinion about the user’s performance, trustworthiness, or any other relevant aspect.

The Reputation struct enables the P2PNonprofitDonation contract to keep a reputation system in place where users can rate and review one another based on their interactions and experiences. This feature improves the platform’s transparency, trust, and accountability, allowing users to make informed decisions when interacting with donors or nonprofits.

Several mapping variables and an uint variable are included in the P2PNonprofitDonation contract. Each is described briefly below:

  • donorReputations: This mapping connects an Ethereum address to a Reputation struct, allowing the contract to store and access donor reputation data.

  • nonprofitReputations: Like donorReputations, this mapping associates an Ethereum address with a Reputation struct, allowing the contract to store and access nonprofit reputation data.

  • registeredUsers: This mapping pairs an Ethereum address with a boolean value that indicates whether or not the address is a registered user. It aids the contract in keeping track of system users who have registered.

  • userPasswords: This mapping associates an Ethereum address with a bytes32 value representing the user’s hashed password. It enables the contract to securely store and verify user passwords.

  • Donations: A uint value (donationId) is associated with a Donation struct by this mapping. It enables the contract to store and retrieve data on individual donations made through the system.

  • donationCount: This uint variable keeps track of the total number of donations received during the course of the contract. It provides a donation count or index, allowing for efficient retrieval and iteration over the donation mapping.

Within the P2PNonprofitDonation contract, these mappings and the donationCount variable provide the necessary data structures for managing and storing information related to reputations, user registration, passwords, and donations. They enable efficient data retrieval and updating, ensuring that the contract’s functionalities run smoothly.

When a new donation is created, the P2PNonprofitDonation contract emits an event called “DonationCreated,” which is emitted when a new donation is created. The following are the event parameters:

  • donationId: A uint parameter representing the newly created donation’s unique identifier. It aids in the identification and tracking of donations within the contract.

  • amount: A uint parameter indicating the monetary value of the donation in Ether (ETH).

  • equityPercentage: An unsigned integer indicating the percentage of equity offered by the donor in exchange for the donation.

  • fundingDeadline: A uint parameter that represents the timestamp for funding the donation.

  • nonprofitName: A string parameter that contains the name of the nonprofit organization with which the donation is associated.

  • description: A string parameter that provides a description of the donation or additional information.

  • valuation: A uint parameter representing the nonprofit organization’s valuation or estimated value associated with the donation.

  • donor: An address parameter representing the donor’s Ethereum address who made the donation.

  • nonprofit: An address parameter that represents the Ethereum address of the nonprofit organization that will receive the donation.

The DonationCreated event is emitted to notify listeners or external systems that a new donation has been created. It includes relevant donation information such as the identifier, amount, equity percentage, funding deadline, nonprofit details, and donor information. This event helps track the donation lifecycle and allows external systems to respond to new donations as needed.

The P2PNonprofitDonation contract contains a number of events that are used to communicate specific occurrences within the contract. Each event is described below:

  1. DonationFunded:

Parameters:

  • donationId: A uint parameter that represents the identifier of the funded donation.

  • funder: An address parameter representing the user’s Ethereum address who funded the donation.

  • amount: A uint parameter indicating the amount of Ether (ETH) allocated for the donation.

This event is triggered when a donation is successfully funded. It includes information about the donation, the user who made it possible, and the amount raised. This event can be used by external systems or listeners to track funding activity within the contract.

  1. DonationDistributed:

Parameters:

  • donationId: A uint parameter representing the identifier of the distributed donation.

  • amount: An unsigned integer indicating the amount of Ether (ETH) distributed for the donation.

This event is triggered when donated funds are successfully distributed to a nonprofit organization. It denotes that the donation has been fulfilled and the funds have been delivered to the intended recipient. This event can be used by external systems or listeners to keep track of the distribution process.

  1. UserRegistered:

Parameters:

  • user: An address parameter representing the registered user’s Ethereum address.

This event is triggered when a user successfully registers with the system. It denotes the completion of the registration process and provides the registered user’s Ethereum address. This event can be captured by external systems or listeners in order to keep an updated list of registered users or to trigger additional actions upon user registration.

  1. UserLoggedIn:

Parameters:

  • user: An address parameter representing the Ethereum address of the logged-in user.

When a user successfully logs into the system, this event is emitted. It confirms the user’s authentication and returns the logged-in user’s Ethereum address. This event can be used by external systems or listeners to track user login activities or trigger specific actions upon user login.

These events function as contract notifications, allowing external systems or listeners to respond to critical events such as donation funding, distribution, user registration, and login activities.

The P2PNonprofitDonation contract contains three modifiers that require specific conditions to be met before performing certain functions. Each modifier is described below:

  1. onlyRegisteredUser:

This modifier restricts access to a function to registered users only. It verifies the presence of the Ethereum address calling the function in the registeredUsers mapping to see if it is registered within the contract.

The caller’s address must be present in the registeredUsers mapping for the modifier to work.

The error message “User is not registered” is returned if the condition is not met.

  1. onlyActiveDonation:

Parameters:

  • _donationId: A uint parameter that represents the donation’s identifier.

This modifier restricts access to a function to only those who have made the specified donation. To determine whether a donation is active or not, it checks the active field of the corresponding Donation struct in the donations mapping.

The specified donation must be active for the modifier to work.

If the condition is not met, the error message “Donation is not active” is displayed.

  1. onlyDonor:

Parameters:

  • _donationId: A uint parameter that represents the donation’s identifier.

This modifier restricts access to a function to the donor of the specified donation only. It checks whether the Ethereum address used to call the function matches the donor field of the corresponding Donation struct in the donations mapping.

The condition is that the caller’s address matches the donor’s address for the specified donation.

  • Message of Error: The error message “Only the donor can perform this action” is returned if the condition is not met.

These contract modifiers add extra security and permission control by ensuring that only registered users can access certain functions, that only active donations can be interacted with, and that only the designated donor can perform specific actions on a donation.

By providing a password, the registerUser function allows a user to register within the P2PNonprofitDonation contract. The function is broken down as follows:

Parameters:

  • _password: A bytes32 parameter that represents the password that the user entered during registration.

This function registers a user within the contract and associates an Ethereum address with a password. The user must meet the following requirements:

The user has not previously registered. The registeredUsers mapping is checked to ensure that the address (msg.sender) of the caller is not already registered. If the caller has already been registered, the function will return with the message “User is already registered.”

The provided password is not empty. The function ensures that the _password parameter is not empty. If an empty password is entered, the function will fail with the message “Password cannot be empty.”

  • Procedure for Registration:

If the user meets the registration requirements, the following actions are taken:

  • The registeredUsers mapping is updated so that the caller’s address is associated with the value true, indicating that the user is registered.

  • The userPasswords mapping is updated to match the caller’s address with the _password provided.

  • The UserRegistered event is emitted when the user is successfully registered. The address of the caller is included in the event (msg.sender).

Users can register within the P2PNonprofitDonation contract by calling this function and fulfilling the registration conditions, allowing them to access other functionalities and interact with the contract as registered users.

A registered user can use the login function to log in to the P2PNonprofitDonation contract by entering their password. Here’s a rundown of the function:

Parameters:

  • _password: A bytes32 parameter representing the user’s password entered during login.

  • Function Description: This function validates a registered user’s login credentials. The user must fufill the following requirements:

The user has signed up. The function determines whether the caller’s address (msg.sender) is in the registeredUsers mapping. If the caller is not registered, the function returns with the message “User is not registered.”

The provided password matches the user’s stored password. The _password parameter is compared to the value stored in the userPasswords mapping for the caller’s address.

If the passwords do not match, the function returns with the message “Invalid password.”

  • Login Procedure:

If the login credentials are correct, the following action is taken:

The UserLoggedIn event is fired after the user has successfully logged in. The address of the caller is included in the event (msg.sender).

Registered users can log in to the P2PNonprofitDonation contract and access the functionalities and features available to registered users by calling this function with the correct password.

A registered user can use the rateDonor function to rate and review a specific donor within the P2PNonprofitDonation contract. The function is broken down as follows:

Parameters:

  • _donor: An address parameter representing the Ethereum address of the donor, which the user wishes to rate and review.

  • _rating: A uint parameter representing the user’s rating, which ranges from 1 to 5.

  • _review: A string parameter representing the user’s review or feedback.

Modifier:

  • onlyRegisteredUser: This modifier restricts access to and execution of this function to registered users only.

This function allows registered users to rate and review donors based on their experiences. The user must fufill the following requirements:

The provided rating falls between 1 and 5. The function determines whether the _rating parameter is greater than or less than 1 and less than or equal to 5. If the rating falls outside of this range, the function will fail with the message “Rating must be between 1 and 5.”

  • Rating Procedure:

If the user meets the rating criteria, the following actions are carried out:

The donor’s rating field: The specified donor’s reputation mapping is updated with the provided _rating.

The donor’s review field: The mapping of reputations for the specified donor _donor is updated with the provided _review.

Registered users can rate and review specific donors by calling this function and providing a valid rating and review, contributing to the reputation system within the P2PNonprofitDonation contract.

Registered users can use the rateNonprofit function to rate and review a specific nonprofit organization within the P2PNonprofitDonation contract. Let’s take a closer look at the function:

Parameters:

  • _nonprofit: A string that represents the Ethereum address of the nonprofit organization that the user wishes to rate and review.

  • _rating: A uint parameter representing the user’s rating, which ranges from 1 to 5.

  • _review: A string parameter containing the user’s review or feedback.

Modifier:

  • onlyRegisteredUser: This modifier restricts access to and execution of this function to registered users only.

This function allows registered users to rate and review a nonprofit organization based on their own experiences. The user has to meet the following requirements:

The provided rating falls between 1 and 5. The function determines whether the _rating parameter is greater than or less than 1 and less than or equal to 5. If the rating is not between 1 and 5, the function returns with the error message “Rating must be between 1 and 5.”

  • Rating Procedure:

If the user meets the rating criteria, the following actions are taken:

  • The provided _rating is added to the rating field of the nonprofitReputations mapping for the specified nonprofit _nonprofit.

  • The nonprofitReputations mapping’s review field for the specified nonprofit _nonprofit is updated with the provided _review.

Registered users can rate and review specific nonprofit organizations by invoking this function and providing a valid rating and review, contributing to the reputation system within the P2PNonprofitDonation contract.

Users can use the createDonation function to make a new donation within the P2PNonprofitDonation contract. Let’s dig into the function’s specifics:

Parameters:

  • _amount: An unsigned integer indicating the amount of Ether to be donated.

  • _equityPercentage: An unsigned integer indicating the equity percentage offered in exchange for the donation.

  • _nonprofitName: A string parameter that contains the name of the nonprofit organization with which the donation is associated.

  • _description: A string parameter that provides a description or additional information about the donation.

  • _valuation: A uint parameter representing the nonprofit organization’s valuation.

  • There are no modifiers.

This function generates a new donation by carrying out the following steps:

  • Validation of Donations:

  • It determines whether the provided _amount is within the acceptable range defined by MIN_DONATION_AMOUNT and MAX_DONATION_AMOUNT. If the donation amount falls outside of this range, the function returns with the message “Donation amount must be between MIN_DONATION_AMOUNT and MAX_DONATION_AMOUNT.”

  • It checks to see if the _equityPercentage offered for the donation falls within the range defined by MIN_EQUITY_PERCENTAGE and MAX_EQUITY_PERCENTAGE. If the equity percentage falls outside of this range, the function returns with the error message “Equity percentage falls outside of range.”

  • It ensures that the _nonprofitName parameter is not empty. If the name is left blank, the function returns with the message “Nonprofit name cannot be empty.”

  • It determines whether or not the _description field is empty. If the description is left blank, the function returns with the message “Description cannot be empty.”

  • It verifies that the nonprofit organization’s _value is greater than zero. If the valuation is 0 or a negative number, the function returns with the error message “Nonprofit valuation must be greater than 0.”

  • Creating Donations:

  • The _fundingDeadline is set to the current block number plus one day.

  • It gives the donation a unique donationId by incrementing the donationCount variable.

  • It creates a new Donation struct with the parameters provided and adds it to the donation mapping with the donationId as the key.

  • Donation information includes the donation amount, equity percentage, funding deadline, nonprofit name, description, valuation, donor address, nonprofit address, and status flags.

  • The function fires the DonationCreated event, which contains information about the newly created donation.

Users can create donations within the P2PNonprofitDonation contract by invoking this function and providing valid parameters, allowing them to contribute funds to nonprofit organizations for specific causes or initiatives.

The code you provided updates the Donation struct with the provided values and emits the DonationCreated event after the validation checks and donation creation in the createDonation function. Let’s go over the code:

  • Storage of Donations:

The line Donation storage donation = donations[donationId] creates a storage reference to the Donation struct in the donations mapping associated with the donationId. This facilitates access to and manipulation of the struct.

  • Updating Donation Information:

The following lines update the donation struct’s attributes with the provided values:

  • donation.amount = _amount; assigns the _amount parameter’s amount attribute.

  • donation.equityPercentage = _equityPercentage; the _equityPercentage parameter is assigned to the equityPercentage attribute.

  • donation.funding= _Funding Deadline sets the funding Deadline attribute to the calculated _funding.Deadline.

  • donation.nonprofitName = _nonprofitName; assigns the nonprofitName attribute to the _nonprofitName parameter.

  • donation.description = _description; this function assigns the description attribute to the _description parameter.

  • donation.valuation = _valuation; the _valuation parameter is assigned to the valuation attribute.

  • donation.donor = payable(msg.sender); sets the donor attribute to the function caller’s (msg.sender) address, which has been converted to a payable address.

  • donation.nonprofit = payable(address(0)); sets the nonprofit attribute to 0 to indicate that it is currently unset.

  • donation.active = true; indicates that the donation is active by setting the active attribute to true.

  • donation.distributed = false; indicates that the donation has not yet been distributed by setting the distributed attribute to false.

  • Making a DonationEvent Created:

Finally, the DonationCreated event is fired, which contains the donationId, _amount, _equityPercentage, _fundingDeadline, _nonprofitName, _description, _valuation, the caller’s address (msg.sender), and the address 0 representing the nonprofit (which is unset at this point).

When this code is executed, the Donation struct is updated with the provided values, indicating the creation of a new donation. The DonationCreated event is emitted to notify external entities that the donation has been successfully created, allowing them to track and react to the newly created donation.

Users can fund a specific donation by passing the donationId as a parameter to the fundDonation function. Let’s decipher the code:

  • Storage of Donations:

The line Donation storage donation = donations[_donationId] creates a storage reference to the Donation struct in the donations mapping associated with the provided donationId. This facilitates access and manipulation of the struct.

  • Validation Procedures:

  • The onlyActiveDonation(_donationId) modifier ensures that the donation with the given donationId is still active, which means it can still receive funds.

  • The line require(msg.sender!= donation.donor, "Donor cannot fund their own donation") ensures that the function caller is not the original donor. Donors are unable to fund their own contributions.

  • The line require(donation.amount == msg.value, "Incorrect donation amount") ensures that the value sent with the function call corresponds to the amount specified in the original donation. This ensures that the correct amount is funded.

  • require(block.number = donation.funding)Deadline, "Donation funding deadline has passed") checks that the current block number is less than or equal to the donation’s funding deadline. The function call will revert if the deadline has passed.

  • Transferring Money:

The payable(address(this)) line.transfer(msg.value) moves the funds received (msg.value) to the contract itself (address(this)). The contract allows the funds to be held until they are distributed to the nonprofit.

  • Updating Donation Information:

  • The line donation.nonprofit = payable(msg.sender) makes the function caller’s address (msg.sender) the nonprofit for the donation. This indicates that the donation has been funded and links it to the nonprofit that provided the funding.

  • The line donation.active = false changes the donation’s active attribute to false, indicating that the donation is no longer active.

  • Putting on a Donation-Funded Event:

Finally, the DonationFunded event is fired with the donationId, msg.sender (the address of the funding nonprofit), and msg.value (the amount funded) as parameters. This event allows you to track and record the successful funding of a donation.

A user (nonprofit) can fund a specific donation by providing the correct donationId and sending the exact amount specified in the original donation by executing this function. The function performs several checks to ensure the funding is valid and updates the donation’s relevant attributes.

The donor of a donation uses the distributeDonation function to distribute the funds to the associated nonprofit. Let’s decipher the code:

  • Storage of Donations:

The line Donation storage donation = donations[_donationId] creates a storage reference to the Donation struct in the donations mapping associated with the provided donationId. This facilitates access and manipulation of the struct.

  • Validation Procedures:

OnlyActiveDonation(_donationId) ensures that the donation with the specified donationId is active and ready for distribution.

The onlyDonor(_donationId) modifier validates that the function caller is the original donor of the donation. The distribution can only be started by the donor.

  • Validation and Fund Transfer:

  • The line require(msg.value == donation.amount, “Incorrect distribution amount”) ensures that the value sent with the function call corresponds to the donation’s original amount. This ensures that the proper amount is distributed.

  • The line donation.nonprofit.transfer(msg.value) transfers the contract’s received funds (msg.value) to the associated nonprofit’s address (donation.nonprofit). This completes the donation distribution.

  • Updating Donation Information:

  • The line donation.distributed = true; sets the donation’s distributed attribute to true, indicating that the funds were successfully distributed.

  • The line donation.active = false; changes the donation’s active attribute to false, indicating that the donation is no longer active.

  • Distributed Event: Emitting Donation

Finally, the DonationDistributed event is fired with the parameters donationId and msg.value (the amount distributed). This event allows you to track and document the successful distribution of a donation.

The original donor of a donation can distribute the funds to the associated nonprofit by providing the correct donationId and sending the exact amount specified in the original donation by executing this function. The function checks the distribution for validity, transfers the funds, and updates the donation’s relevant attributes.

The extendFundingPeriod function allows a donation’s donor to extend the funding period by a number of days. The code is broken down as follows:

  • Storage of Donations:

The line Donation storage donation = donations[_donationId] creates a storage reference to the Donation struct in the donations mapping associated with the provided donationId. This facilitates access and manipulation of the struct.

  • Validation Procedures:

  • The onlyDonor(_donationId) modifier validates that the function caller is the original donor of the donation. Only the donor has the authority to extend the funding period.

  • OnlyActiveDonation(_donationId) ensures that the donation with the given donationId is active and extendable.

  • Validation of Extension Days:

The statement require(_extensionDays > 0, “Extension days must be greater than 0”) ensures that the _extensionDays parameter is positive. It guards against invalid or unnecessary extensions.

  • Funding Deadline Extending:

The donation line.fundingDeadline += (_extensionDays * 1 days) extends the donation’s funding deadline by the number of days specified in _extensionDays.Days multiplied by one day (the number of seconds in a day) This extends the existing funding deadline by the desired amount of time.

  • Validation of Extension Days:

The statement require(_extensionDays > 0, “Extension days must be greater than 0”) ensures that the _extensionDays parameter is positive. It guards against invalid or unnecessary extensions.

  • Funding Deadline Extending:

  • The donation line.funding Deadline += (_extensionDays * 1 days) extends the donation’s funding deadline by the number of days specified in _extensionDays. Days multiplied by one day (the number of seconds in a day) This extends the existing funding deadline by the desired amount of time.

  • The donor can extend the funding period of a donation by calling this function and providing the correct donationId and the number of days to extend. The function validates the extension and updates the donation’s fundingDeadline attribute accordingly.

If the funding deadline has not passed, the cancelDonation function allows the donor to cancel the donation and retrieve the donated amount. The code is broken down as follows:

  • Storage of Donations:

The line Donation storage donation = donations[_donationId] creates a storage reference to the Donation struct in the donations mapping associated with the provided donationId. This facilitates access and manipulation of the struct.

  • Validation Procedures:

Only the modifier is used.Donor(_donationId) confirms that the function caller is the original donor. Only the donor has the ability to cancel the donation.

OnlyActiveDonation(_donationId) ensures that the donation with the given donationId is active and cancellable.

  • Check the funding deadline:

The statement requires(block.number donation.funding)Deadline, “Donation funding deadline has passed,” confirms that the current block number is less than the donation’s funding deadline. This ensures that the deadline is still not past.

  • Changing the Donation Status:

The donation line.active = false; sets the donation’s active flag to false, indicating that the donation has been canceled.

  • Amount of Donation Returned:

The payable line (donation.donor).transfer(donation.amount); returns the amount donated to the donor. The funds were effectively returned to the original donor.

The donor can cancel their donation by calling this function and providing the correct donationId. The function validates the cancellation based on the donor, the donation’s status, and the funding deadline. If the cancellation is successful, the donor receives the amount donated back.

The getDonationInfo function retrieves donation information based on the donation ID. Here’s a rundown of the function:

  1. Storage of Donations:

The line Donation storage donation = donations[_donationId] creates a storage reference to the Donation struct in the donations mapping associated with the provided donationId. This gives you access to the donation’s information.

  1. Statement of Return:

The view modifier indicates that the function only retrieves data and does not modify the contract’s state.

The function returns a tuple containing the following donation information:

  • amount: The amount donated.

  • equityPercentage: The percentage of equity offered in exchange for the donation.

  • Funding: The deadline for completing the donation.

  • nonprofitName: The name of the charitable organization associated with the donation.

  • description: The donation’s description.

  • valuation: The nonprofit’s valuation.

  • donor: The donor’s address.

  • The address of the nonprofit that will receive the donation.

  • active: A boolean that indicates whether or not the donation is active.

  • A boolean indicating whether or not the donation has been distributed.

  1. Values of Return:
  • The function uses the tuple format to return the values of the donation struct’s respective fields (value1, value2,…).

  • These values are obtained from the donation of storage references.

You can obtain detailed information about a specific donation stored in the contract by calling this function with a valid donationId. The values returned give an overview of the donation’s properties and status.

If a donation is inactive, the withdrawDonation function allows the donor to withdraw their donated amount. The function is broken down as follows:

Parameters to enter:

The function requires an uint _donationId parameter, which is the ID of the donation from which the withdrawal will be made.

  • Storage of Donations:

The line Donation storage donation = donations[_donationId]; creates a storage reference to the Donation struct in the donations mapping associated with the provided donationId. This gives you access to the donation’s information.

  • Check for Requirements:

The line require(!donation.active, “Donation must be inactive to withdraw.”); ensures that the donation is no longer active, allowing the withdrawal to be made only when the donation is no longer active.

  • Transfer Money:

  • The payable line (msg.sender).transfer(donation.amount) returns the donated amount to the donor’s address (msg.sender), allowing them to effectively withdraw their donation.

  • This function is only available to the donor of the donation (as specified by the onlyDonor modifier). If the donation has become inactive, it allows the donor to retrieve their funds.

Deployment

In your deploy.js file, delete everything and add this code:

Then you add your private key to the .env file and deploy by running the following command: yarn or npm run deploy

If you are using remix for deployment:

  • To deploy our smart contract successfully, we need the Celo extension wallet, which can be downloaded from here

  • Next, we need to fund our newly created wallet, which can be done using the Celo Alfajores faucet here

  • You can now fund your wallet and deploy your contract using the Celo plugin in Remix.

Conclusion

Finally, this tutorial has provided a comprehensive guide to the P2PNonprofitDonation smart contract, unlocking a world of decentralized philanthropy possibilities. This contract enables seamless peer-to-peer donations with transparent and immutable records by leveraging the power of blockchain technology.

Throughout the tutorial, we’ve looked at a variety of important concepts and features, such as user registration, donation creation, funding, distribution, reputation rating, and more. You are now equipped to build your own P2P donation platform or enhance existing ones with smart contract capabilities by understanding the code and its underlying principles.

Donors can choose the amount, equity percentage, and funding deadline for their contributions with P2PNonprofitDonation. Meanwhile, nonprofits can accept donations, build trust through reputation ratings, and ultimately benefit society.

You have become a part of a revolution that brings transparency, efficiency, and accountability to the world of nonprofit donations by immersing yourself in this tutorial. Consider a future in which communities collaborate directly, without the use of intermediaries, to effect meaningful change.

So, what are you holding out for? Accept the power of decentralized philanthropy, create new opportunities, and inspire others with your creative solutions. Let us work together to reshape the landscape of giving and create a brighter future for all. Have fun coding!

Next Steps

8 Likes

i look forward to seeing how different thus is from other funding projects.

4 Likes

Looking forward to this
You have my vote :100:

2 Likes

Patiently anticipating🙏

1 Like

Looking forward to this and how it comes out

3 Likes

Certainly this is a p2p non-profit donation smart contract :slightly_smiling_face:

2 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

3 Likes

I will be reviewing this in a day or two @maxwellonyeka2487

4 Likes