Introduction
In a world increasingly driven by digital innovation, crowdfunding has become a popular method for raising funds for various causes, ideas, and projects. But, there’s an even more exciting innovation in the crowdfunding scene—decentralized crowdfunding, powered by blockchain technology. Celo, with its fast transaction times, low fees, and user-friendly approach, makes an excellent blockchain for building a crowdfunding platform.
In this tutorial, we’re going to construct a Celo-based crowdfunding platform using smart contracts and web3.js. By the end of this guide, you’ll have built a secure and transparent platform to raise funds, cutting out intermediaries and bringing power back to the people.
Prerequisites
Before diving into this tutorial, there are a few things you need to take care of. Firstly, you should have a solid understanding of JavaScript and Solidity, as we’ll be using these languages extensively. Familiarity with blockchain concepts and the Ethereum ecosystem will also be beneficial, given that Celo is a fork of Ethereum and shares many similarities.
Requirements
For this tutorial, you would need to have the following tools installed on your computer:
- Node.js and NPM: To install, follow the instructions at Node.js.
- Truffle: This is a popular development framework for building Ethereum-based applications. It allows you to test and deploy on celo. You can install it using npm by running
npm install -g truffle
. - A text editor of your choice (e.g., Visual Studio Code).
- A Celo wallet with some testnet CELO and cUSD tokens. You can follow the
Setting Up a Celo Wallet
section here.
Setting up the Development Environment
Before we start coding, let’s set up our development environment. We’ll be using Truffle, a development environment, testing framework, and asset pipeline for Ethereum.
-
Install Truffle:Open your terminal and install Truffle globally on your system using the following command:
npm install -g truffle
-
Initialize a new Truffle project: Navigate to the directory where you want to create your project and initialize a new Truffle project using the following command:
mkdir celo-crowdfunding cd celo-crowdfunding truffle init
This will create a new directory called
celo-crowdfunding
and initialize an empty Truffle project inside it. -
Next, let’s install truffle/hdwallet-provider. This allows you to sign transactions for addresses derived from a mnemonic. You’ll use this to connect to Celo in your truffle configuration file.
Run the command below in your terminal.npm install @truffle/hdwallet-provider --save
Ensure you run the command above in the root directory of your project.
Creating the Smart Contract
Our next step is to create a Solidity smart contract for our crowdfunding platform.
- Create a new file for the smart contract: Inside the
contracts
directory of your Truffle project, create a new file namedCrowdfunding.sol
.cd contracts touch Crowdfunding.sol
- Add the contract code: Open
Crowdfunding.sol
in your text editor and add the following Solidity code:
This is just the skeleton of our contract. We will fill in the details later on.//SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Crowdfunding { // Contract code goes here }
Developing the Smart Contract
-
Defining the Project Structure:In our Crowdfunding contract, we’ll have a
Project
struct that will represent each crowdfunding project. This struct will contain details like the creator of the project, the target amount of funds to be raised, the deadline, and the amount of funds currently raised.struct Project { address payable creator; uint targetAmount; uint deadline; uint numFunders; uint amountRaised; }
-
Defining the Funder Structure:Similarly, we’ll have a
Funder
struct representing each person funding the projects.struct Funder { address payable addr; uint amount; }
Add the above definitions inside your
Crowdfunding
contract. The full contract should look something like this://SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Crowdfunding { struct Project { address payable creator; uint targetAmount; uint deadline; uint numFunders; uint amountRaised; } struct Funder { address payable addr; uint amount; } }
We’ll continue developing the contract in the next part, where we’ll add the functions for creating and funding projects.
Adding Functions to the Smart Contract
-
Creating a New Project:We’ll add a function named
createProject
that will allow users to create new projects. Each project will have a target amount and a deadline.function createProject(uint targetAmount, uint deadline) public { Project newProject = Project(payable(msg.sender), targetAmount, deadline, 0, 0); projects.push(newProject); }
This function takes the target amount and deadline as arguments, creates a new
Project
struct with the sender of the transaction as the creator, and adds the new project to an array of all projects. -
Funding a Project:Next, we’ll add a function named
fundProject
that will allow users to send funds to a project.function fundProject(uint projectId) public payable { Project storage project = projects[projectId]; require(block.timestamp < project.deadline, "Project funding period has ended"); require(msg.value <= project.targetAmount - project.amountRaised, "Project has reached its funding goal"); project.amountRaised += msg.value; project.numFunders++; }
This function takes the ID of the project as an argument. It then retrieves the project from the array and checks if the project’s deadline has not passed and if the project has not already reached its funding goal. If both checks pass, the function adds the sent amount (
msg.value
) to the project’samountRaised
and incrementsnumFunders
.Your
Crowdfunding
contract should now look something like this://SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Crowdfunding { struct Project { address payable creator; uint targetAmount; uint deadline; uint numFunders; uint amountRaised; } struct Funder { address payable addr; uint amount; } Project[] public projects; function createProject(uint targetAmount, uint deadline) public { Project memory newProject = Project(payable(msg.sender), targetAmount, deadline, 0, 0); projects.push(newProject); } function fundProject(uint projectId) public payable { Project storage project = projects[projectId]; require(block.timestamp < project.deadline, "Project funding period has ended"); require(msg.value <= project.targetAmount - project.amountRaised, "Project has reached its funding goal"); project.amountRaised += msg.value; project.numFunders++; } }
Create a file named
2_deploy_contracts.js
in the migrations folder and populate it with the code below.var Voting = artifacts.require("Crowdfunding"); module.exports = function (deployer) { deployer.deploy(Crowdfunding); };
The code defines a deployment script for the Crowdfunding contract, using the Truffle framework’s deployer function. The script specifies that the Crowdfunding contract will be deployed, and then calls the deploy function with Crowdfunding as its argument to actually deploy the contract.
In the next part, we will compile and deploy our contract to the Celo Alfajores Testnet.
Deploying the Smart Contract
To deploy the smart contract, we need to compile it first. In your terminal, run the following command in the root directory of the project:
truffle compile
This will compile the smart contract and generate the artifacts in the /build/contracts directory.
Next, let’s configure Truffle to deploy the smart contract to the Celo network. In the truffle-config.js file, add the following code:
const HDWalletProvider = require("@truffle/hdwallet-provider");
const mnemonic = "client great south good cement bucket rank free legend green"; // replace with your MNEMONIC
module.exports = {
networks: {
local: {
host: "127.0.0.1",
port: 7545,
network_id: "*",
},
alfajores: {
provider: function () {
return new HDWalletProvider(
mnemonic,
"https://alfajores-forno.celo-testnet.org"
);
},
network_id: 44787,
gas: 20000000,
deployTimeout: 300000,
networkCheckTimeout: 300000,
},
mainnet: {
provider: function () {
return new HDWalletProvider(mnemonic, "https://forno.celo.org");
},
network_id: 42220,
gas: 4000000,
deployTimeout: 300000,
networkCheckTimeout: 300000,
},
},
// Configure your compilers
compilers: {
solc: {
version: "0.5.16", // Fetch exact version from solc-bin (default: truffle's version)
},
},
};
Replace the mnemonic in the code above with your own Celo account’s mnemonic. You can find your mnemonic in your Celo account’s settings.
Finally, run the following command in your terminal to deploy the smart contract to the Alfajores celo network:
truffle deploy --network alfajores
This will deploy your smart contract to the Celo network and output the contract address once the deployment is complete. The terminal output should be similar to this:
Interacting with the Smart Contract
Now, let’s interact with the smart contract. We will create a simple command-line interface (CLI) that will allow users to create and fund a project.
First, let’s install the web3
and readline-sync
packages:
npm install web3
npm install readline-sync
Next, we will create a new file called crowdfunding-cli.js
in the project directory and add the following code:
const Web3 = require("web3");
const Crowdfunding = require("./build/contracts/Crowdfunding.json");
const readlineSync = require("readline-sync");
const web3 = new Web3("https://alfajores-forno.celo-testnet.org");
async function createProject(contract) {
const targetAmount = readlineSync.question("Enter the target amount: ");
const deadline = readlineSync.question(
"Enter the deadline (UNIX timestamp): "
);
const privateKey = "YOUR_PRIVATE_KEY"; // replace with the private key of your celo account
const celoAccount = web3.eth.accounts.privateKeyToAccount(privateKey);
web3.eth.accounts.wallet.add(celoAccount);
await contract.methods
.createProject(targetAmount, deadline)
.send({
from: celoAccount.address,
gas: 3000000,
gasPrice: "10000000000",
})
.then((result) => {
console.log(
`Project created with ID ${result.events.ProjectCreated.returnValues.projectId}`
);
})
.catch((error) => {
console.error(error);
});
}
async function fundProject(contract) {
const projectId = readlineSync.question("Enter the project ID: ");
const amount = readlineSync.question("Enter the amount to fund: ");
const privateKey = "YOUR_PRIVATE_KEY"; // replace with the private key of your celo account
const celoAccount = web3.eth.accounts.privateKeyToAccount(privateKey);
web3.eth.accounts.wallet.add(celoAccount);
await contract.methods
.fundProject(projectId)
.send({
from: celoAccount.address,
value: web3.utils.toWei(amount, "ether"),
gas: 3000000,
gasPrice: "10000000000",
})
.then((result) => {
console.log(`Funded project ${projectId} with amount ${amount}`);
})
.catch((error) => {
console.error(error);
});
}
async function main() {
const contractAddress = "YOUR_CONTRACT_ADDRESS"; // Change to you your contract address
// Create an instance of the contract
const contract = new web3.eth.Contract(Crowdfunding.abi, contractAddress);
const action = readlineSync.question(
"Enter action (createProject/fundProject): "
);
switch (action) {
case "createProject":
await createProject(contract);
break;
case "fundProject":
await fundProject(contract);
break;
default:
console.log("Invalid action");
}
}
main();
In this code, we imported the web3
and readline-sync
libraries. We also create a new instance of web3
and point it to the Celo network node hosted at https://alfajores-forno.celo-testnet.org.
The code above exposes three main functions:
-
createProject(contract)
: This function interacts with the smart contract to create a new project. It first asks the user for the necessary input, such as the target amount and deadline. Then, it creates a new Celo account using the provided private key. It adds this account to web3’s local wallet (this is a way to manage accounts locally, it doesn’t involve any external wallet like Metamask). Finally, it sends a transaction to the smart contract, calling thecreateProject
function with the input provided by the user. Once the transaction is mined and the function is executed, it will log the ID of the project created. -
fundProject(contract)
: This function allows a user to fund a specific project. It first asks the user for the account address, project ID, and the amount to fund. Similar to thecreateProject
function, it creates a new Celo account using the provided private key and adds it to web3’s local wallet. Then, it sends a transaction to the smart contract, calling thefundProject
function with the provided project ID and sends the provided amount as value. Once the transaction is mined and the function is executed, it will log a success message. -
main()
: This is the main function that gets executed when the program starts. It first creates an instance of the Crowdfunding contract using its ABI and deployed address. Then, it asks the user what action they want to perform, either “createProject” or “fundProject”. Depending on the user’s input, it will call either thecreateProject
function or thefundProject
function.
Please replace “YOUR_PRIVATE_KEY” with your private key and “YOUR_CONTRACT_ADDRESS” with the address of your deployed Crowdfunding contract.
You can run the application using the command:
node crowdfunding-cli.js
This will prompt the user for an action (createProject or fundProject) and then execute the corresponding function.
Common Errors
- Invalid account address or private key: Make sure the account address and private key you’re using are correct.
- Insufficient funds: The account you’re using to create or fund a project must have enough funds to cover the gas fees and the amount you’re funding.
- Project funding period has ended: Make sure you’re not trying to fund a project after its deadline. You can check the project’s deadline by calling the
projects
function on the contract with the project ID as the argument. - Project has reached its funding goal: If a project has already reached its funding goal, you won’t be able to fund it anymore. You can check the current amount raised by a project by calling the
projects
function on the contract with the project ID as the argument.
Conclusion
Congratulations! You’ve just built a decentralized crowdfunding platform on the Celo blockchain using smart contracts and web3.js. Through this tutorial, you’ve learned how to create and interact with a smart contract on Celo, how to create a simple command-line interface for your DApp, and how to handle common errors.
Next Steps
Now that you’ve built a basic crowdfunding platform, you can take it to the next level by adding more features. For example, you could implement a reward system where funders receive tokens in return for their contribution, or add a voting mechanism for project approval. You could also build a frontend for your DApp to make it more user-friendly.
About the Author
Abdur-Rahman Olawale is a blockchain developer and a technical writer. Specializing in creating decentralized applications and insightful technical content, Abdur-Rahman Olawale aims to foster understanding and drive innovation within the blockchain community.