How to Build a Crowdfunding Web3 Dapp – Let's Buy Twitter!
We’ll consider how crowdfunding apps normally work, how they would work in the Web3 world, and how we could build our own crowdfunding Web3 decentralized app (“dapp”). We’ll even include some code samples to help you build your own decentralized crowdfunding platform.
Join the DZone community and get the full member experience.
Join For FreeJokingly I thought to myself, “What if we rallied together and bought Twitter instead?” I don’t have $44 billion, but maybe we could crowdfund it? Surely I could create a GoFundMe or Kickstarter project.
I’ve also recently been delving into the world of Web3, which is all about decentralization. So my next train of thought became, “What would it take to build a crowdfunding app using Web3 technology?”
This article will explore exactly that. We’ll consider how crowdfunding apps normally work, how they would work in the Web3 world, and how we could build our own crowdfunding Web3 decentralized app (“dapp”). We’ll even include some code samples to help you build your own decentralized crowdfunding platform.
Ready to take on Elon?
How Crowdfunding Apps Work
Crowdfunding apps like GoFundMe or Kickstarter allow users to create new fundraisers that anyone can contribute to. The fundraiser creator accepts the donations, usually under certain conditions, and then the crowdfunding platform takes a small percentage of the money as their share. Everybody wins.
For a platform like Kickstarter, the fundraising goal must be met by a deadline to release funds. If the goal is met in time, then the fundraiser creator receives the funds for their project, and all the contributors’ credit cards are charged for the amount they donated. If the deadline passes and the goal is not met, then everyone who contributed gets their money back (or rather, their credit cards are not charged).
This model works pretty well, and plenty of successful projects have been funded by platforms like Kickstarter. But what if we could cut out the middleman?
How a Web3 Crowdfunding Dapp Could Work
Web3 comes with its own transaction layer that allows users to transfer funds held in their crypto wallets. Popular wallets include Coinbase Wallet or MetaMask.
Web3 apps are commonly called “dapps,” due to the decentralized nature of the blockchain. Dapps are built with a frontend UI that interacts with a smart contract deployed to the blockchain, and this smart contract serves as the backend code and database that you’d see in a typical Web2 app.
For a web3 crowdfunding dapp, we could utilize a smart contract that allows people to pledge funds from their wallet toward a cause, just like a Kickstarter campaign. The smart contract could have logic built into it that only allows the crowdfunding project creator to withdraw the funds once the goal has been met. Until then, funds would be held in escrow on the smart contract. This means that donors would have the funds transferred from their wallets when they make their donations, but they could ask for a refund at any time as long as the goal has not yet been met.
Once the goal has been met and the funds have been withdrawn, the person who accepted the donations could do as they pleased with the money, so technically, they could take the money and run. If we wanted to take this idea one step further, we could explore decentralized autonomous organizations (DAOs) and how they handle not just crowdfunding but collective ownership and collective decision making. For now, however, we’ll stick with a simple smart contract only.
So, with that high-level architecture in mind, let’s check out an actual Web3 crowdfunding dapp we built! You can find all of the code for the demo app hosted on GitHub.
Our Web3 Crowdfunding Dapp
If a user does not have a crypto wallet browser extension, clicking the button will prompt Coinbase Wallet’s onboarding UI to pop up, enabling a new user to either connect an existing mobile wallet or create a new wallet in minutes.
Once their wallet is connected, the user can submit a donation by modifying the value in the input field and then clicking the “Donate” button. (We’ve set a minimum donation amount of 0.01 ether and a fund goal of 10 ether in the smart contract, but those values are arbitrary.) They can also click two other buttons to see the total amount contributed toward the goal or to request a refund of the money they previously pledged. There is a button at the bottom of the UI to reset the wallet connection to start over, if needed.
That’s really all there is to it, functionality-wise.
So, how did we build this? We used several technologies to create our dapp:
- React for the frontend UI
- Solidity for the smart contract
- Remix for compiling and deploying the smart contract
- Coinbase Wallet SDK for connecting to the user’s wallet
- Coinbase Wallet and MetaMask crypto wallets for sending and receiving funds
- Infura for a backup RPC endpoint
We’ve outlined all of the setup steps in the README, so we won’t go into step-by-step detail about how we built the app. If you’d like to follow along or build your own crowdfunding dapp, we’d highly recommend following the steps in the README file above!
Here we highlight two key files that supply the main functionality of the app: the Crowdfunding.sol
file for the smart contract, and the App.js
file for the React frontend UI.
The Crowdfunding.sol
file is reproduced below in full:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
/*********************************/
/* Learning Purposes ONLY */
/* DO NOT USE IN PRODUCTION */
/*********************************/
contract Crowdfunding {
uint256 fundGoal = 10 ether;
uint256 minContribution = 0.01 ether;
address payable destinationWallet = payable(0x733B9052fB62C40B344584B20280F6FCcA3D628e);
mapping(address => uint256) addressContributions;
function donate() public payable {
require(msg.value >= minContribution, "Donate Error: Did not meet minimum contribution");
addressContributions[msg.sender] = msg.value;
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
function withdraw() public {
require(address(this).balance >= fundGoal, "Withdraw Error: Did not meet contribution goal");
destinationWallet.transfer(address(this).balance);
}
function returnFunds() public {
require(address(this).balance < fundGoal, "ReturnFunds Error: Cannot refund, goal has been met");
require(addressContributions[msg.sender] != 0, "ReturnFunds Error: You have not contributed");
uint256 amount = addressContributions[msg.sender];
payable(msg.sender).transfer(amount);
}
// Need to have a fallback function for the contract to be able to receive funds
receive() external payable {}
}
Scanning through this file, you can see that we’ve defined methods for donate
, getBalance
, withdraw
, and returnFunds
. Each method does what its name implies.
- The
donate
method allows users to pledge donations. - The
getBalance
method shows the current total amount of donations contributed. - The
withdraw
method allows the funds to be withdrawn under the condition that the fundraiser goal has been met. - The
returnFunds
method allows users to request a refund of their pledged amount if they change their minds after contributing.
Now let’s look at the frontend code with our App.js
file, which is also reproduced in full below:
import React, { useEffect, useState } from 'react';
import Web3 from 'web3';
import Contract from 'web3-eth-contract';
import CoinbaseWalletSDK from '@coinbase/wallet-sdk';
import CrowdfundingContract from './contracts/Crowdfunding.json';
import elon from './elon.jpg';
import './App.css';
const APP_NAME = 'Coinbase Crowdfunding App';
const APP_LOGO_URL = './elon.jpg';
const RPC_URL = process.env.REACT_APP_INFURA_RPC_URL;
const CHAIN_ID = 3; // Ropsten Network ID
const CROWDFUNDING_CONTRACT_ADDRESS =
'0x6CE498a35a39Cb43c08B81e7A06f2bb09741359d';
const App = () => {
const [isWalletConnected, setIsWalletConnected] = useState(false);
const [account, setAccount] = useState();
const [walletSDKProvider, setWalletSDKProvider] = useState();
const [web3, setWeb3] = useState();
const [crowdfundingContractInstance, setCrowdfundingContractInstance] =
useState();
const [responseMessage, setResponseMessage] = useState();
useEffect(() => {
// Initialize Coinbase Wallet SDK
const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
});
// Initialize Web3 Provider
const walletSDKProvider = coinbaseWallet.makeWeb3Provider(
RPC_URL,
CHAIN_ID
);
setWalletSDKProvider(walletSDKProvider);
// Initialize Web3 object
const web3 = new Web3(walletSDKProvider);
setWeb3(web3);
// Initialize crowdfunding contract
const web3ForContract = new Web3(window.ethereum);
Contract.setProvider(web3ForContract);
const crowdfundingContractInstance = new Contract(
CrowdfundingContract,
CROWDFUNDING_CONTRACT_ADDRESS
);
setCrowdfundingContractInstance(crowdfundingContractInstance);
}, []);
const checkIfWalletIsConnected = () => {
if (!window.ethereum) {
console.log(
'No ethereum object found. Please install Coinbase Wallet extension or similar.'
);
// Enable the provider and cause the Coinbase Onboarding UI to pop up
web3.setProvider(walletSDKProvider.enable());
return;
}
console.log('Found the ethereum object:', window.ethereum);
connectWallet();
};
const connectWallet = async () => {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
});
if (!accounts.length) {
console.log('No authorized account found');
return;
}
if (accounts.length) {
const account = accounts[0];
console.log('Found an authorized account:', account);
setAccount(account);
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x3' }],
});
console.log('Successfully switched to Ropsten Network');
} catch (error) {
console.error(error);
}
}
setIsWalletConnected(true);
};
const donateETH = async () => {
if (!account || !window.ethereum) {
console.log('Wallet is not connected');
return;
}
const donationAmount = document.querySelector('#donationAmount').value;
const response = await crowdfundingContractInstance.methods.donate().send({
from: account,
value: donationAmount,
});
console.log(response);
setResponseMessage(
`Thank you for donating! Here's your receipt: ${response.transactionHash}`
);
};
const getDonationBalance = async () => {
const response = await crowdfundingContractInstance.methods
.getBalance()
.call();
setResponseMessage(
`Total contribution amount is ${web3.utils.fromWei(response)} ETH.`
);
};
const requestRefund = async () => {
await crowdfundingContractInstance.methods
.returnFunds()
.send({ from: account });
setResponseMessage('Your donation has been refunded.');
};
const resetCoinbaseWalletConnection = () => {
walletSDKProvider.close();
};
return (
<main className="app">
<header>
<img
src={elon}
className="headerImage"
alt="Elon holding the Twitter logo"
/>
<h1>Let's buy Twitter before Elon does!</h1>
</header>
{isWalletConnected ? (
<>
<p>Connected Account: {account}</p>
<div>
<input
type="number"
id="donationAmount"
defaultValue={10000000000000000}
/>
<label htmlFor="donationAmount">WEI</label>
<button onClick={donateETH} id="donate" type="button">
Donate
</button>
</div>
<div>
<button
id="getDonationBalance"
type="button"
onClick={getDonationBalance}
>
See Total Contribution Amount
</button>
</div>
<div>
<button id="requestRefund" type="button" onClick={requestRefund}>
Request Refund
</button>
</div>
<div>
<button
id="reset"
type="button"
onClick={resetCoinbaseWalletConnection}
>
Reset Connection
</button>
</div>
</>
) : (
<button onClick={checkIfWalletIsConnected} id="connect" type="button">
Connect Wallet
</button>
)}
<p>{responseMessage}</p>
</main>
);
};
export default App;
At a high level, that is the magic behind how all this works. For more detailed setup instructions, we would again refer you to the step-by-step guide found in the README on GitHub.
Conclusion
So, what have we learned today?
We’ve learned that Web3 technology allows for financial transactions without an intermediary institution. We’ve learned that besides transferring money from one individual to another, we can also use Web3 technology to support crowdfunding.
Finally, we’ve explored how a simple crowdfunding app might be built, the technologies behind it, and how using these technologies together can enable you to have an dapp up and running in a matter of hours.
Published at DZone with permission of Tyler Hawkins. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments