├── docs
├── HomePage.png
├── CampaignCreation
│ ├── 1_CampaignCreation.png
│ ├── 3_NewCampaignCreated.png
│ ├── 5_CampaignPageToPublic.png
│ ├── 2_ApproveWalletForCreation.png
│ └── 4_CampaignPageToFundRaiser.png
├── Contributing
│ ├── 2_ContributionSuccess.png
│ ├── 1_ContributingMinimumAmount.png
│ └── 3_ContributionUpdatedStatus.png
├── ViewingTransactionHistoryInEtherScan.png
├── WalletConnection
│ ├── 1_WalletConnection.png
│ ├── 3_WalletConnected.png
│ └── 2_WalletConnection_1.png
├── AbortingCampaign
│ ├── 1_Aborting_fundraiser.png
│ ├── 2_Aborting_clickAfterReason.png
│ ├── 4_Aborting_Backer'sWallet_After.png
│ ├── 3_Aborting_acceptingConfirmation.png
│ └── 0_Aborting_Backer'sWalletAfterContribution.png
├── ViewingTransactionHistoryInEtherScan_0.png
└── EndingCampaign
│ ├── 1_BeforeEndCampaignWalletBalance.png
│ ├── 3_WalletBalanceAfterSuccessfulEnd.png
│ └── 2_ApproveEndCampaignWalletConfirmation.png
├── artifacts
├── hardhat
│ └── console.sol
│ │ ├── console.dbg.json
│ │ └── console.json
└── contracts
│ ├── Campaign.sol
│ ├── Campaign.dbg.json
│ └── Campaign.json
│ └── CrowdHelp.sol
│ └── CrowdHelp.dbg.json
├── vite.config.js
├── utils
├── contract
│ ├── campaign.js
│ └── crowdHelp.js
├── web3.js
└── getCampaigns.js
├── src
├── pages
│ ├── campaigns
│ │ ├── SetMileStones.jsx
│ │ ├── ReviewCampaignDetails.jsx
│ │ ├── ActiveCampaigns.jsx
│ │ ├── CreateCampaignWrapper.jsx
│ │ ├── FillCampaignDetails.jsx
│ │ └── ViewCampaign.jsx
│ ├── HomePage.jsx
│ ├── ForgotPassword.jsx
│ ├── Profile.jsx
│ ├── SignIn.jsx
│ └── SignUp.jsx
├── main.jsx
├── components
│ ├── Copyright.jsx
│ ├── AuthProtectedRoute.jsx
│ ├── Footer.jsx
│ ├── CampaignCard.jsx
│ └── NavBar.jsx
├── config
│ └── firebase-config.js
├── App.css
├── index.css
├── App.jsx
├── contexts
│ └── AuthContext.jsx
└── assets
│ └── react.svg
├── .gitignore
├── index.html
├── LICENSE
├── contracts
├── CampaignSchemeEnums.sol
├── CrowdHelp.sol
└── Campaign.sol
├── public
└── vite.svg
├── package.json
├── hardhat.config.js
├── scripts
└── deploy.js
├── test
└── CrowdHelp.test.js
└── README.md
/docs/HomePage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/HomePage.png
--------------------------------------------------------------------------------
/artifacts/hardhat/console.sol/console.dbg.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-dbg-1",
3 | "buildInfo": "../../build-info/c8c1647ad1cca5e3b94146397d3b4f58.json"
4 | }
5 |
--------------------------------------------------------------------------------
/artifacts/contracts/Campaign.sol/Campaign.dbg.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-dbg-1",
3 | "buildInfo": "../../build-info/fef89d36fbf081e93b07e94bbd0b0c02.json"
4 | }
5 |
--------------------------------------------------------------------------------
/artifacts/contracts/CrowdHelp.sol/CrowdHelp.dbg.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-dbg-1",
3 | "buildInfo": "../../build-info/fef89d36fbf081e93b07e94bbd0b0c02.json"
4 | }
5 |
--------------------------------------------------------------------------------
/docs/CampaignCreation/1_CampaignCreation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/CampaignCreation/1_CampaignCreation.png
--------------------------------------------------------------------------------
/docs/Contributing/2_ContributionSuccess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/Contributing/2_ContributionSuccess.png
--------------------------------------------------------------------------------
/docs/ViewingTransactionHistoryInEtherScan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/ViewingTransactionHistoryInEtherScan.png
--------------------------------------------------------------------------------
/docs/WalletConnection/1_WalletConnection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/WalletConnection/1_WalletConnection.png
--------------------------------------------------------------------------------
/docs/WalletConnection/3_WalletConnected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/WalletConnection/3_WalletConnected.png
--------------------------------------------------------------------------------
/docs/AbortingCampaign/1_Aborting_fundraiser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/AbortingCampaign/1_Aborting_fundraiser.png
--------------------------------------------------------------------------------
/docs/CampaignCreation/3_NewCampaignCreated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/CampaignCreation/3_NewCampaignCreated.png
--------------------------------------------------------------------------------
/docs/ViewingTransactionHistoryInEtherScan_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/ViewingTransactionHistoryInEtherScan_0.png
--------------------------------------------------------------------------------
/docs/WalletConnection/2_WalletConnection_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/WalletConnection/2_WalletConnection_1.png
--------------------------------------------------------------------------------
/docs/CampaignCreation/5_CampaignPageToPublic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/CampaignCreation/5_CampaignPageToPublic.png
--------------------------------------------------------------------------------
/docs/Contributing/1_ContributingMinimumAmount.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/Contributing/1_ContributingMinimumAmount.png
--------------------------------------------------------------------------------
/docs/Contributing/3_ContributionUpdatedStatus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/Contributing/3_ContributionUpdatedStatus.png
--------------------------------------------------------------------------------
/docs/CampaignCreation/2_ApproveWalletForCreation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/CampaignCreation/2_ApproveWalletForCreation.png
--------------------------------------------------------------------------------
/docs/CampaignCreation/4_CampaignPageToFundRaiser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/CampaignCreation/4_CampaignPageToFundRaiser.png
--------------------------------------------------------------------------------
/docs/AbortingCampaign/2_Aborting_clickAfterReason.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/AbortingCampaign/2_Aborting_clickAfterReason.png
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/docs/AbortingCampaign/4_Aborting_Backer'sWallet_After.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/AbortingCampaign/4_Aborting_Backer'sWallet_After.png
--------------------------------------------------------------------------------
/docs/EndingCampaign/1_BeforeEndCampaignWalletBalance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/EndingCampaign/1_BeforeEndCampaignWalletBalance.png
--------------------------------------------------------------------------------
/docs/EndingCampaign/3_WalletBalanceAfterSuccessfulEnd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/EndingCampaign/3_WalletBalanceAfterSuccessfulEnd.png
--------------------------------------------------------------------------------
/docs/AbortingCampaign/3_Aborting_acceptingConfirmation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/AbortingCampaign/3_Aborting_acceptingConfirmation.png
--------------------------------------------------------------------------------
/docs/EndingCampaign/2_ApproveEndCampaignWalletConfirmation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/EndingCampaign/2_ApproveEndCampaignWalletConfirmation.png
--------------------------------------------------------------------------------
/docs/AbortingCampaign/0_Aborting_Backer'sWalletAfterContribution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Balaji-Ganesh/CrowdHelp-Blockchain-based-crowdfunding-platform/HEAD/docs/AbortingCampaign/0_Aborting_Backer'sWalletAfterContribution.png
--------------------------------------------------------------------------------
/utils/contract/campaign.js:
--------------------------------------------------------------------------------
1 | import web3 from "../web3";
2 | import Campaign from "../../artifacts/contracts/CrowdHelp.sol/Campaign.json";
3 |
4 | export default (address) => {
5 | return new web3.eth.Contract(Campaign.abi, address);
6 | };
7 |
--------------------------------------------------------------------------------
/src/pages/campaigns/SetMileStones.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function SetMileStones() {
4 | return (
5 | <>
6 |
SetMileStones - this feature will be added later
7 | >
8 | );
9 | }
10 |
11 | export default SetMileStones;
12 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | import "./index.css";
5 | import "./config/firebase-config"; // for firebase.
6 |
7 | ReactDOM.createRoot(document.getElementById("root")).render(
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/utils/contract/crowdHelp.js:
--------------------------------------------------------------------------------
1 | import web3 from "../web3";
2 | import CrowdHelp from "../../artifacts/contracts/CrowdHelp.sol/CrowdHelp.json";
3 |
4 | const crowdHelpContractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
5 | const crowdHelp = new web3.eth.Contract(
6 | CrowdHelp.abi,
7 | crowdHelpContractAddress
8 | );
9 |
10 | export default crowdHelp;
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | # hardhat
27 | cache
28 | deployments
29 |
30 | .env
--------------------------------------------------------------------------------
/src/components/Copyright.jsx:
--------------------------------------------------------------------------------
1 | import { Link, Typography } from "@mui/material";
2 | function Copyright() {
3 | return (
4 |
5 | {"Copyright © "}
6 |
7 | CrowdHelp
8 | {" "}
9 | {new Date().getFullYear()}
10 | {"."}
11 |
12 | );
13 | }
14 |
15 | export default Copyright;
16 |
--------------------------------------------------------------------------------
/utils/web3.js:
--------------------------------------------------------------------------------
1 | import Web3 from "web3";
2 |
3 | let web3;
4 |
5 | if (typeof window !== "undefined" && typeof window.web3 !== "undefined") {
6 | // we are in the browser and meta mask is installed
7 | web3 = new Web3(window.web3.currentProvider);
8 | } else {
9 | // we are on the server *OR* meta mask is not running
10 | // creating our own provider
11 | const provider = new Web3.providers.HttpProvider(
12 | "https://goerli.infura.io/v3/eaf842956c36444c8aaf54163a47e0d2"
13 | );
14 |
15 | web3 = new Web3(provider);
16 | }
17 |
18 | export default web3;
19 |
--------------------------------------------------------------------------------
/src/config/firebase-config.js:
--------------------------------------------------------------------------------
1 | import firebase from "firebase/compat/app";
2 |
3 | const firebaseConfig = {
4 | apiKey: "AIzaSyCqkXQ1RXsNc4h1o-crilJ4kyk-kP2sELY",
5 | authDomain: "fir-experiments-16493.firebaseapp.com",
6 | projectId: "fir-experiments-16493",
7 | storageBucket: "fir-experiments-16493.appspot.com",
8 | messagingSenderId: "646710968437",
9 | appId: "1:646710968437:web:a04fd85f922b759ca0783c",
10 | };
11 |
12 | // Initialize Firebase
13 | const app = firebase.initializeApp(firebaseConfig);
14 | export const auth = app.auth();
15 | // export 📦
16 | export default app;
17 |
--------------------------------------------------------------------------------
/src/components/AuthProtectedRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate } from "react-router-dom";
3 | import { useAuth } from "../contexts/AuthContext";
4 |
5 | // this going to work as a private router to actual router.
6 | // Why? to prevent from accessing certail pages.
7 | // i.e., without getting signed in, if trying to access profile page, what shall we show?
8 | const AuthProtectedRoute = ({ redirectPath = "/sign-in", children }) => {
9 | const { currentUserCredentials } = useAuth();
10 |
11 | if (!currentUserCredentials) {
12 | return ;
13 | }
14 |
15 | return children;
16 | };
17 |
18 | export default AuthProtectedRoute;
19 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CrowdFund - Get Help from Crowd
8 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/artifacts/hardhat/console.sol/console.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "console",
4 | "sourceName": "hardhat/console.sol",
5 | "abi": [],
6 | "bytecode": "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122029ce6e039ea322c9fb75e8353f8ef45c3f4ff6aedf8833d58f1691ccfa56accb64736f6c63430008000033",
7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122029ce6e039ea322c9fb75e8353f8ef45c3f4ff6aedf8833d58f1691ccfa56accb64736f6c63430008000033",
8 | "linkReferences": {},
9 | "deployedLinkReferences": {}
10 | }
11 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | /* #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | } */
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Balaji Ganesh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import CssBaseline from "@mui/material/CssBaseline";
3 | import Box from "@mui/material/Box";
4 | import Typography from "@mui/material/Typography";
5 | import Container from "@mui/material/Container";
6 | import Copyright from "./Copyright";
7 |
8 | export default function Footer() {
9 | return (
10 |
17 |
18 |
19 |
26 | theme.palette.mode === "light"
27 | ? theme.palette.grey[200]
28 | : theme.palette.grey[800],
29 | }}
30 | >
31 |
32 |
33 | CrowdHelp platform - [ONLY] Core functionalities done. 🚧.
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/contracts/CampaignSchemeEnums.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | uint constant SCHEMES_COUNT = 2; // Make sure to modify accordingly as per items in following enum.
5 | enum CampaignSchemeId {
6 | ALL_OR_NOTHING, // 0
7 | HALF_GOAL_WITHDRAW // 1
8 | // MILESTONES_BASED // upcoming..
9 | }
10 |
11 | function getSchemeTitle(uint idx) pure returns (string memory title) {
12 | require(idx < SCHEMES_COUNT, "Index out of range");
13 | if (idx == uint(CampaignSchemeId.ALL_OR_NOTHING)) return "All or Nothing";
14 | if (idx == uint(CampaignSchemeId.HALF_GOAL_WITHDRAW)) return "Half goal withdraw";
15 | return "Invalid scheme";
16 | }
17 |
18 | function getAllSchemeTitles() pure returns (string[] memory) {
19 | string[] memory allSchemeTitles = new string[](SCHEMES_COUNT);
20 | for (uint i = 0; i < SCHEMES_COUNT; i++) {
21 | allSchemeTitles[i] = getSchemeTitle(i);
22 | }
23 | return allSchemeTitles;
24 | }
25 |
26 | function getSchemeId(uint id) pure returns (CampaignSchemeId) {
27 | require(id < SCHEMES_COUNT , "Index out of range");
28 | if (id == uint(CampaignSchemeId.ALL_OR_NOTHING)) return CampaignSchemeId.ALL_OR_NOTHING;
29 | return CampaignSchemeId.HALF_GOAL_WITHDRAW;
30 | }
31 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | } */
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "crowdhelp",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "@emotion/react": "^11.10.5",
12 | "@emotion/styled": "^11.10.5",
13 | "@mui/icons-material": "^5.10.9",
14 | "@mui/lab": "^5.0.0-alpha.109",
15 | "@mui/material": "^5.10.11",
16 | "@nomiclabs/hardhat-ethers": "^2.2.1",
17 | "@truffle/hdwallet-provider": "^2.1.1",
18 | "axios": "^1.1.3",
19 | "dotenv": "^16.0.3",
20 | "firebase": "^9.13.0",
21 | "fs-extra": "^10.0.0",
22 | "moment": "^2.29.4",
23 | "react": "^18.2.0",
24 | "react-dom": "^18.2.0",
25 | "react-hook-form": "^7.39.5",
26 | "react-router-dom": "^6.4.3",
27 | "solc": "^0.8.0",
28 | "use-wallet": "0.10.0",
29 | "web3": "^1.3.5"
30 | },
31 | "devDependencies": {
32 | "@ethersproject/providers": "^5.4.7",
33 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
34 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
35 | "@nomicfoundation/hardhat-toolbox": "^2.0.0",
36 | "@nomiclabs/hardhat-etherscan": "^3.0.0",
37 | "@typechain/ethers-v5": "^10.1.0",
38 | "@typechain/hardhat": "^6.1.2",
39 | "@types/chai": "^4.2.0",
40 | "@types/mocha": "^9.1.0",
41 | "@types/react": "^18.0.22",
42 | "@types/react-dom": "^18.0.7",
43 | "@vitejs/plugin-react": "^2.2.0",
44 | "chai": "^4.2.0",
45 | "ethers": "^5.0.0",
46 | "hardhat": "^2.12.2",
47 | "hardhat-deploy": "^0.11.20",
48 | "hardhat-gas-reporter": "^1.0.8",
49 | "solidity-coverage": "^0.8.1",
50 | "ts-node": ">=8.0.0",
51 | "typechain": "^8.1.0",
52 | "typescript": ">=4.5.0",
53 | "vite": "^3.2.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/hardhat.config.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config({ path: __dirname + "/.env.local" }); // When deploying -- be careful about this path..
2 | require("@nomiclabs/hardhat-ethers");
3 | require("hardhat-deploy");
4 | require("@nomicfoundation/hardhat-toolbox");
5 |
6 | console.log(process.env.INFURA_API_KEY);
7 | console.log(process.env.PRIVATE_KEY);
8 |
9 | module.exports = {
10 | solidity: {
11 | version: "0.8.0",
12 | settings: {
13 | optimizer: {
14 | enabled: true,
15 | runs: 200,
16 | },
17 | // viaIR: true,
18 | },
19 | },
20 | mocha: {
21 | timeout: 90000,
22 | },
23 | networks: {
24 | localhost: {
25 | url: "http://127.0.0.1:8545",
26 | },
27 | hardhat: {
28 | initialBaseFeePerGas: 0,
29 | blockGasLimit: 18800000,
30 | chainId: 31337,
31 | },
32 | // goerli_testnet: {
33 | // // url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`, <<-- tried this way, its failing, so employed below way.
34 | // url: "https://goerli.infura.io/v3/eaf842956c36444c8aaf54163a47e0d2",
35 | // accounts: [process.env.PRIVATE_KEY],
36 | // gasPrice: 1000,
37 | // saveDeployments: true,
38 | // deploy: ["scripts/"],
39 | // },
40 | },
41 | };
42 |
43 | /**
44 | * guide: https://docs.palm.io/HowTo/Deploy-using-Hardhat/ - as redirected by Infura (to deploy via HardHat)
45 | * $ npx hardhat compile
46 | * $ npx hardhat --network goerli_testnet deploy
47 | Nothing to compile
48 | Contract deployed to address: 0xB2B305a50121d6acC8c0F8951a6cdb41d3bB0C6D
49 | deploying "CrowdHelp" (tx: 0xe7a0301eae21ef759c24c188d2554d538cbe5e11d3ea1b9c6c385862d38df927)...: deployed at 0x5a61c16165e797bb770887F339f9DCb6608dce02 with 2316759 gas
50 |
51 | new deploy..
52 | eaf842956c36444c8aaf54163a47e0d2
53 | 367d65fef68348fd92b6ba50e22b9bd63d45c7cf8c72072cedb6b8ae6ba7f8fc
54 | Nothing to compile
55 | Contract deployed to address: 0xB2B305a50121d6acC8c0F8951a6cdb41d3bB0C6D
56 | reusing "CrowdHelp" at 0x5a61c16165e797bb770887F339f9DCb6608dce02
57 | */
58 |
--------------------------------------------------------------------------------
/contracts/CrowdHelp.sol:
--------------------------------------------------------------------------------
1 | //SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import "./CampaignSchemeEnums.sol";
5 | import "./Campaign.sol";
6 | import "hardhat/console.sol";
7 |
8 | contract CrowdHelp {
9 | // events
10 | event CampaignStarted(
11 | address projectContractAddress,
12 | address creator,
13 | uint256 minContribution,
14 | uint256 projectDeadline,
15 | uint256 goalAmount,
16 | uint256 currentAmount,
17 | uint256 noOfContributors,
18 | string title,
19 | string desc,
20 | uint currentState
21 | );
22 |
23 | event ContributionReceived(
24 | address projectAddress,
25 | uint256 contributedAmount,
26 | address indexed contributor
27 | );
28 |
29 | event CampaignCreationFailed(string reason);
30 |
31 | Campaign[] private deployedCampaigns;
32 |
33 | // @dev Anyone can start a fund rising
34 | // @return null
35 |
36 | function createCampaign(
37 | string memory projectTitle,
38 | string memory projectDesc,
39 | uint256 minimumContribution,
40 | uint256 targetContribution,
41 | uint256 deadline,
42 | string memory bannerUrl,
43 | uint campaignSchemeId
44 | ) public {
45 | console.log("Creating campaign with scheme:", uint(campaignSchemeId));
46 | Campaign campaign = new Campaign(
47 | msg.sender,
48 | minimumContribution,
49 | deadline,
50 | targetContribution,
51 | projectTitle,
52 | projectDesc,
53 | bannerUrl,
54 | campaignSchemeId // passing directly like this..
55 | );
56 |
57 | console.log("Creating campaign with scheme:", uint(campaignSchemeId));
58 | deployedCampaigns.push(campaign);
59 | }
60 |
61 | // @dev Get deployedCampaigns list
62 | // @return array
63 |
64 | function returnDeployedCampaigns()
65 | external
66 | view
67 | returns (Campaign[] memory)
68 | {
69 | return deployedCampaigns;
70 | }
71 |
72 | function getDeployedCampaigns() public view returns (address[] memory) {
73 | address[] memory campaignAddresses = new address[](deployedCampaigns.length);
74 | for (uint i = 0; i < deployedCampaigns.length; i++) {
75 | campaignAddresses[i] = address(deployedCampaigns[i]);
76 | }
77 | return campaignAddresses;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | // library and component imports..
2 | import "./App.css";
3 |
4 | // custom imports
5 | // import Feed from "./components/Feed";
6 | import { Box } from "@mui/material";
7 | import SignIn from "./pages/SignIn";
8 | import SignUp from "./pages/SignUp";
9 | import CreateCampaignWrapper from "./pages/campaigns/CreateCampaignWrapper";
10 | import FillCampaignDetails from "./pages/campaigns/FillCampaignDetails";
11 | import ReviewCampaignDetails from "./pages/campaigns/ReviewCampaignDetails";
12 | import HomePage from "./pages/HomePage";
13 | import ActiveCampaigns from "./pages/campaigns/ActiveCampaigns";
14 | import AuthProvider from "./contexts/AuthContext";
15 | import Profile from "./pages/Profile";
16 | import ViewCampaign from "./pages/campaigns/ViewCampaign";
17 |
18 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
19 | import AuthProtectedRoute from "./components/AuthProtectedRoute";
20 | import ForgotPassword from "./pages/ForgotPassword";
21 |
22 | // For wallet connection & usage..
23 | import { UseWalletProvider } from "use-wallet";
24 |
25 | function App() {
26 | return (
27 |
36 |
37 |
38 |
39 | } />
40 | } />
41 | } />
42 | } />
43 |
47 |
48 |
49 | }
50 | />
51 |
55 |
56 | // {/* */}
57 | }
58 | />
59 | } />
60 | } />
61 |
62 |
63 |
64 |
65 | );
66 | }
67 |
68 | export default App;
69 |
--------------------------------------------------------------------------------
/src/pages/campaigns/ReviewCampaignDetails.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Container,
4 | TextField,
5 | Typography,
6 | styled,
7 | Avatar,
8 | Link,
9 | List,
10 | ListItem,
11 | Box,
12 | Grid,
13 | ListItemText,
14 | } from "@mui/material";
15 | import React from "react";
16 |
17 | const addresses = [
18 | "Keshav Memorial Institute of Technology",
19 | "Keshav Memorial Lane",
20 | "Narayanaguda",
21 | "Hyderabad",
22 | "TS",
23 | ];
24 | const milestones = [
25 | { name: "Milestone 1", detail: "1st Jan" },
26 | { name: "Milestone 2", detail: "1st Feb" },
27 | { name: "Milestone 3", detail: "1st March" },
28 | ];
29 |
30 | const campaignDetails = [
31 | { name: "Campaign Title", detail: "Covid-19 help" },
32 | {
33 | name: "Campaign Description",
34 | detail: "To help the people needy in covid-19 vaccine.",
35 | },
36 | { name: "State", detail: "Telangana" },
37 | { name: "City", detail: "Hyderabad" },
38 | { name: "Category", detail: "Medical" },
39 | { name: "Amount to be raised", detail: "50 ETH" },
40 | { name: "Timeline", detail: "3rd March" },
41 | ];
42 |
43 | const StyledTypographyTitle = styled(Typography)(({ theme }) => ({
44 | marginTop: theme.spacing(2),
45 | }));
46 |
47 | function ReviewCampaignDetails() {
48 | return (
49 | <>
50 |
51 | Campaign Details
52 |
53 |
54 | {campaignDetails.map((campaignDetail) => (
55 |
56 |
57 | {campaignDetail.name}
58 |
59 |
60 | {campaignDetail.detail}
61 |
62 |
63 | ))}
64 |
65 |
66 |
67 |
68 | Shipping
69 |
70 | Fund Raiser Name
71 | {addresses.join(", ")}
72 |
73 |
74 |
75 | Milestones summary
76 |
77 |
78 | {milestones.map((milestone) => (
79 |
80 |
81 | {milestone.name}
82 |
83 |
84 | {milestone.detail}
85 |
86 |
87 | ))}
88 |
89 |
90 |
91 | >
92 | );
93 | }
94 |
95 | export default ReviewCampaignDetails;
96 |
--------------------------------------------------------------------------------
/scripts/deploy.js:
--------------------------------------------------------------------------------
1 | const hre = require("hardhat");
2 |
3 | async function main() {
4 | const CrowdHelp = await hre.ethers.getContractFactory("CrowdHelp");
5 | const crowdHelp = await CrowdHelp.deploy();
6 | await crowdHelp.createCampaign(
7 | "Project X",
8 | "Desc",
9 | 100,
10 | 1000,
11 | 1212124,
12 | "bannerUrl",
13 | 0
14 | ); // Assuming scheme = 0
15 |
16 | // const [deployer] = await hre.ethers.getSigners();
17 |
18 | // const Campaign = await hre.ethers.getContractFactory("Campaign");
19 |
20 | // const campaign = await Campaign.deploy(
21 | // deployer.address,
22 | // 100, // minimum
23 | // 1713728020, // deadline timestamp
24 | // 1000, // target
25 | // "Test Project", // title
26 | // "This is a test", // desc
27 | // "http://banner.url", // banner
28 | // 0 // schemeId (assuming enum Basic)
29 | // );
30 |
31 | await crowdHelp.deployed();
32 |
33 | console.log("CrowdHelp deployed to:", crowdHelp.address);
34 | }
35 |
36 | main()
37 | .then(() => process.exit(0))
38 | .catch((error) => {
39 | console.error("Deployment failed:", error);
40 | process.exit(1);
41 | });
42 |
43 | // const hre = require("hardhat");
44 |
45 | // async function main() {
46 | // // Deploy the factory contract
47 | // const CrowdHelp = await hre.ethers.getContractFactory("CrowdHelp");
48 | // const crowdHelp = await CrowdHelp.deploy();
49 | // await crowdHelp.deployed();
50 |
51 | // console.log("CrowdHelp deployed to:", crowdHelp.address);
52 |
53 | // // Now create a campaign with VALID parameters
54 | // const minContribution = hre.ethers.utils.parseEther("0.01");
55 | // const goal = hre.ethers.utils.parseEther("1");
56 | // const deadline = Math.floor(Date.now() / 1000) + 86400; // 24 hours from now
57 |
58 | // const tx = await crowdHelp.createCampaign(
59 | // "Save the Forests", // projectTitle
60 | // "Planting 10,000 trees to fight climate change", // projectDesc
61 | // minContribution,
62 | // goal,
63 | // deadline,
64 | // "https://images.unsplash.com/photo-1503785640985-f62e3aeee448", // bannerUrl
65 | // 0 // campaignSchemeId - ✅ must be 0 or 1 only
66 | // );
67 |
68 | // await tx.wait();
69 | // console.log("Campaign created successfully!");
70 |
71 | // // fetch the created campaign
72 | // const campaigns = await crowdHelp.returnDeployedCampaigns();
73 | // console.log("Deployed campaigns:", campaigns);
74 |
75 | // console.log("Deploying campaign with:");
76 | // console.log({
77 | // projectTitle: "Some Title",
78 | // projectDesc: "Some Description",
79 | // minimumContribution: ethers.utils.parseEther("0.01").toString(),
80 | // targetContribution: ethers.utils.parseEther("1").toString(),
81 | // deadline: Math.floor(Date.now() / 1000) + 86400,
82 | // bannerUrl: "https://someurl.com/banner.jpg",
83 | // campaignSchemeId: 0, // or 1 ONLY
84 | // });
85 | // }
86 |
87 | // // Standard error handler
88 | // main()
89 | // .then(() => process.exit(0))
90 | // .catch((error) => {
91 | // console.error("Deployment failed:", error);
92 | // process.exit(1);
93 | // });
94 |
--------------------------------------------------------------------------------
/src/pages/campaigns/ActiveCampaigns.jsx:
--------------------------------------------------------------------------------
1 | // library and component imports..
2 | import React, { useEffect } from "react";
3 | import Button from "@mui/material/Button";
4 |
5 | import Grid from "@mui/material/Grid";
6 | import Stack from "@mui/material/Stack";
7 | import Box from "@mui/material/Box";
8 | import Typography from "@mui/material/Typography";
9 | import Container from "@mui/material/Container";
10 | import CircularProgress from "@mui/material/CircularProgress";
11 |
12 | // local imports
13 | import CampaignCard from "../../components/CampaignCard";
14 | import Footer from "../../components/Footer";
15 | import NavBar from "../../components/NavBar";
16 |
17 | // service imports..
18 | import axios from "axios";
19 |
20 | const api_url = "http://localhost:4000/api/";
21 |
22 | function ActiveCampaigns() {
23 | // hooks..
24 | const [activeCampaigns, setActiveCampaigns] = React.useState([]);
25 |
26 | useEffect(() => {
27 | console.log("useEffect called");
28 | let ignore = false;
29 | // fetch the campaigns..
30 | const fetchData = async () => {
31 | const response = await axios.get(api_url + "active-campaigns/10");
32 | console.info(response.data);
33 | if (!ignore && response.status == 200) setActiveCampaigns(response.data);
34 | };
35 |
36 | fetchData(); // call the function to fetch the data
37 |
38 | return () => {
39 | ignore = true; // to avoid rendering multiple times..
40 | };
41 | }, []);
42 |
43 | return (
44 | <>
45 |
46 |
47 | {/* Hero unit */}
48 |
55 |
56 |
63 | Active Campaigns
64 |
65 |
71 | A list of active campaigns that are running currently and in need
72 | of contributors like you. Join hands with fund raisers to fulfill
73 | the noble cause.
74 |
75 |
76 |
77 |
78 | {/* End hero unit */}
79 |
80 |
81 | {activeCampaigns.map((activeCampaign, idx) => (
82 |
83 |
84 |
85 | ))}
86 |
87 | {/* load as long as data is not fetched. */}
88 | {activeCampaigns.length == 0 && }
89 |
90 |
91 |
92 |
93 | >
94 | );
95 | }
96 |
97 | export default ActiveCampaigns;
98 |
--------------------------------------------------------------------------------
/utils/getCampaigns.js:
--------------------------------------------------------------------------------
1 | // [block-chain] smart-contract related imports..
2 |
3 | import crowdHelp from "./contract/crowdHelp";
4 | import web3 from "./web3";
5 | import Campaign from "./contract/campaign";
6 |
7 | // fetch the deployed campaigns addresses.
8 | export const getDeployedCampaigns = async () => {
9 | // get the addresses of the deployed campaigns..
10 | console.log("get deployed campaigns called");
11 | const campaignsList = await crowdHelp.methods
12 | .returnDeployedCampaigns()
13 | .call();
14 |
15 | console.log("deployed: " + campaignsList);
16 | return campaignsList;
17 | };
18 |
19 | export const getCampaignsSummary = async (campaigns) => {
20 | console.log("Called with..");
21 | console.log(campaigns);
22 | try {
23 | // get details of all the campaigns
24 | const campaignsSummary = await Promise.all(
25 | campaigns.map((campaign, idx) =>
26 | Campaign(campaigns[idx]).methods.getCampaignSummary().call()
27 | )
28 | );
29 | // log it to check..
30 | console.log(campaignsSummary);
31 |
32 | // will be getting as array .. cvt to object.. i.e., in an understandable format
33 | var formattedSummaries = [];
34 | campaignsSummary.forEach((summary, idx) => {
35 | formattedSummaries.push(formatSummary(summary, campaigns[idx]));
36 | });
37 |
38 | // return the work did..
39 | return formattedSummaries;
40 | } catch (err) {
41 | console.error("[ERROR] occured in getting campaigns summary");
42 | console.error(err);
43 | }
44 | };
45 |
46 | export const getCampaignDetails = async (campaignId) => {
47 | try {
48 | // get details of all the campaigns
49 | const summary = await Campaign(campaignId)
50 | .methods.getCampaignSummary()
51 | .call();
52 |
53 | // log it to check..
54 | console.log(summary);
55 |
56 | return formatSummary(summary, campaignId);
57 | // will be getting as array .. cvt to object.. i.e., in an understandable format
58 | } catch (err) {
59 | console.error("[ERROR] occured in getting a campaign summary");
60 | console.error(err);
61 | }
62 | };
63 |
64 | // helpers..
65 | function formatSummary(summary, campaignId) {
66 | const formattedSummary = {
67 | id: campaignId,
68 | title: summary["title"],
69 | description: summary["desc"],
70 | ethRaised: web3.utils.fromWei(summary["goalAmount"], "ether"),
71 | ethFunded: web3.utils.fromWei(summary["currentAmount"], "ether"),
72 | minContribAmount: web3.utils.fromWei(summary["minContribution"], "ether"),
73 | createdBy: summary["projectStarter"],
74 | bannerUrl: summary["imageUrl"],
75 | deadline: parseInt(summary["projectDeadline"]),
76 | campaignStatus: cvtIntStatusToEnum(summary["currentState"]),
77 | // requestsCount: summary[2],
78 | backersCount: summary["numBackers"],
79 | };
80 |
81 | // return the work did..
82 | return formattedSummary;
83 | }
84 |
85 | function cvtIntStatusToEnum(status) {
86 | var str = "";
87 | switch (status) {
88 | case "0":
89 | str = "ACTIVE";
90 | break;
91 | case "1":
92 | str = "SUCCESS";
93 | break;
94 | case "2":
95 | str = "EXPIRED";
96 | break;
97 | case "3":
98 | str = "ABORTED";
99 | break;
100 | default:
101 | str = "UNKNOWN";
102 | }
103 | return str;
104 | }
105 |
106 | export const getAvailableFundingSchemes = async() =>{
107 | const fundingSchemesList = await crowdHelp.methods.getAllSchemeTitles().call();
108 | console.debug ("obtained funding schemes list: ", fundingSchemesList);
109 | return fundingSchemesList;
110 | }
--------------------------------------------------------------------------------
/src/contexts/AuthContext.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from "react";
2 | import { auth } from "../config/firebase-config";
3 | import firebase from "firebase/compat/app";
4 |
5 | // service imports..
6 | import { useNavigate } from "react-router-dom";
7 |
8 | // to maintain the status of the authentication across the application
9 | const AuthContext = React.createContext();
10 |
11 | // hook access the context outside this..
12 | export function useAuth() {
13 | return useContext(AuthContext);
14 | }
15 |
16 | export function AuthProvider({ children }) {
17 | // hooks..
18 | const [currentUserCredentials, setCurrentUserCredentials] = useState();
19 | const [isLoading, setIsLoading] = useState(true); // to store the status .. whether processing or stopped.
20 |
21 | const [authState, setAuthState] = React.useState(
22 | false || window.localStorage.getItem("authStatus") === "true"
23 | );
24 | const [token, setToken] = React.useState("");
25 |
26 | const navigate = useNavigate();
27 |
28 | // hooks..handle whenever refreshed..
29 | useEffect(() => {
30 | // set the credentials, whenver sign-up/sign-in happens.
31 | const unsubscriber = auth.onAuthStateChanged((userCredentials) => {
32 | setIsLoading(false); // as soon as done. stop
33 | if (userCredentials) {
34 | setCurrentUserCredentials(userCredentials);
35 | // if yes, keep logged in.
36 | setAuthState(true);
37 | window.localStorage.setItem("authStatus", "true"); // save in local storage.. can cut-off the delay
38 | // get token..
39 | userCredentials.getIdToken().then((token) => {
40 | setToken(token);
41 | navigate("/"); // navigate to home page
42 | // console.log(token);
43 | });
44 | }
45 | });
46 | return unsubscriber; // for unsubscribe from onAuthStateChanged listener [later when needed]
47 | }, []);
48 |
49 | // helpers
50 | function signUpWithEmailAndPassword(email, password) {
51 | // console.log(`Signup called with ${email} & ${password}`);
52 | return auth.createUserWithEmailAndPassword(email, password); // this returns the promise, later based on its value Signup_Success or Failure will be taken care.
53 | }
54 |
55 | const signInWithGooglePopup = () => {
56 | return auth.signInWithPopup(new firebase.auth.GoogleAuthProvider());
57 | // .then((userCredentials) => {
58 | // console.log(userCredentials);
59 | // });
60 | };
61 |
62 | const signInWithEmailAndPassword = (email, password) => {
63 | return auth.signInWithEmailAndPassword(email, password);
64 | };
65 |
66 | const signout = (email, password) => {
67 | return auth.signOut();
68 | };
69 |
70 | const resetPassword = (email) => {
71 | const promise = auth.sendPasswordResetEmail(email);
72 | console.log("Password reset email sent");
73 | return promise;
74 | };
75 |
76 | const updateEmail = (email) => {
77 | return currentUserCredentials.updateEmail(email);
78 | };
79 |
80 | const updatePassword = (password) => {
81 | return currentUserCredentials.updatePassword(password);
82 | };
83 |
84 | // context value thats going to be used in other pages..
85 | const value = {
86 | currentUserCredentials,
87 | signUpWithEmailAndPassword,
88 | signInWithGooglePopup,
89 | signInWithEmailAndPassword,
90 | signout,
91 | resetPassword,
92 | updateEmail,
93 | updatePassword,
94 | };
95 |
96 | return (
97 |
98 | {!isLoading && children}
99 | {/* ☝🏽 Don't render until current user loads..*/}
100 |
101 | );
102 | }
103 |
104 | export default AuthProvider;
105 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | // UI imports
4 | import { Stack } from "@mui/system";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | import Box from "@mui/material/Box";
7 | import Typography from "@mui/material/Typography";
8 | import Container from "@mui/material/Container";
9 | import Link from "@mui/material/Link";
10 | import Button from "@mui/material/Button";
11 | import { useNavigate } from "react-router-dom";
12 | import Grid from "@mui/material/Grid";
13 | import CircularProgress from "@mui/material/CircularProgress";
14 |
15 | // [block-chain] smart-contract related imports..
16 | import {
17 | getDeployedCampaigns,
18 | getCampaignsSummary,
19 | } from "../../utils/getCampaigns";
20 |
21 | // local imports..
22 | import NavBar from "../components/NavBar";
23 | import Footer from "../components/Footer";
24 | import CampaignCard from "../components/CampaignCard";
25 |
26 | // service imports..
27 | import axios from "axios";
28 |
29 | const api_url = "http://localhost:4000/api/";
30 |
31 | function HomePage() {
32 | // for navigation..
33 | const navigate = useNavigate();
34 |
35 | // hooks..
36 | const [campaignsList, setCampaignsList] = React.useState([]);
37 |
38 | useEffect(() => {
39 | // console.log("useEffect called");
40 | let ignore = false;
41 | // fetch the campaigns..
42 | const fetchData = async () => {
43 | const deployedCampaignsList = await getDeployedCampaigns(); // call the function to fetch the data
44 | // console.log(deployedCampaignsList);
45 | setCampaignsList(await getCampaignsSummary(deployedCampaignsList));
46 | console.log("fetched campaigns");
47 | console.log(campaignsList);
48 | };
49 |
50 | // fetch the data..
51 | fetchData();
52 | return () => {
53 | ignore = true; // to avoid rendering multiple times..
54 | };
55 | }, []);
56 |
57 | return (
58 |
59 |
60 | {/* */}
61 |
62 |
68 |
69 |
70 | 🧑🤝🧑 Crowd Help :💰
71 |
72 |
73 | {"Get Help from Crowd..!!"}
74 | {" Raise a campaign to help the needy."}
75 |
76 | Welcome 👋 to the community.
77 | [ONLY] Core functionalities done 🚧. More features on the way..!! 🏃
78 |
79 |
80 |
81 |
87 |
88 |
89 | Take part in active campaigns..
90 |
91 |
92 | Top {campaignsList.length} recent campaigns..
93 |
94 |
95 | {/* */}
98 |
99 |
100 | {/* End hero unit */}
101 | {/* load as long as data is not fetched. */}
102 | {campaignsList.length == 0 && (
103 |
104 | )}
105 |
106 | {campaignsList.map((activeCampaign, idx) => (
107 |
108 |
109 |
110 | ))}
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | );
119 | }
120 |
121 | export default HomePage;
122 |
--------------------------------------------------------------------------------
/src/pages/campaigns/CreateCampaignWrapper.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Container,
4 | TextField,
5 | Typography,
6 | styled,
7 | Avatar,
8 | Link,
9 | Box,
10 | Grid,
11 | Stepper,
12 | Step,
13 | StepLabel,
14 | } from "@mui/material";
15 | import React from "react";
16 | import FillCampaignDetails from "./FillCampaignDetails";
17 | import SetMileStones from "./SetMileStones";
18 | import ReviewCampaignDetails from "./ReviewCampaignDetails";
19 | function Copyright() {
20 | return (
21 |
22 | {"Copyright © "}
23 |
24 | CrowdHelp
25 | {" "}
26 | {new Date().getFullYear()}
27 | {"."}
28 |
29 | );
30 | }
31 |
32 | const steps = [
33 | "Campaign Details",
34 | // "Set Milestones", -- next iteration feature
35 | // "Review campaign details", --- this feature needs milestones to be set, else meaning-less.
36 | ];
37 |
38 | function getStepContent(step) {
39 | switch (step) {
40 | case 0:
41 | return ;
42 | // case 1:
43 | // return ; -- next iteration feature
44 | case 1:
45 | return ;
46 | default:
47 | throw new Error("Unknown step");
48 | }
49 | }
50 |
51 | const StyledDivLayout = styled("div")(({ theme }) => ({
52 | width: "auto",
53 | marginLeft: theme.spacing(2),
54 | marginRight: theme.spacing(2),
55 | [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
56 | width: 600,
57 | marginLeft: "auto",
58 | marginRight: "auto",
59 | },
60 | }));
61 |
62 | const StyledDivPaper = styled("div")(({ theme }) => ({
63 | marginTop: theme.spacing(3),
64 | marginBottom: theme.spacing(3),
65 | padding: theme.spacing(2),
66 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
67 | marginTop: theme.spacing(6),
68 | marginBottom: theme.spacing(6),
69 | padding: theme.spacing(3),
70 | },
71 | }));
72 |
73 | const StyledStepper = styled(Stepper)(({ theme }) => ({
74 | padding: theme.spacing(3, 0, 5),
75 | }));
76 |
77 | const StyledContainer = styled(Container)(({ theme }) => ({
78 | [theme.breakpoints.up("sm")]: {
79 | width: "80%",
80 | },
81 | [theme.breakpoints.down("sm")]: {
82 | width: "40%",
83 | },
84 | }));
85 | function CreateCampaignWrapper() {
86 | const [activeStep, setActiveStep] = React.useState(0);
87 |
88 | const handleNext = () => {
89 | setActiveStep(activeStep + 1);
90 | };
91 |
92 | const handleBack = () => {
93 | setActiveStep(activeStep - 1);
94 | };
95 | return (
96 |
101 |
102 |
103 |
104 |
105 | Create Campaign
106 |
107 | <>
108 | {activeStep == steps.length ? (
109 | <>
110 |
111 | Welcome to the community..!!
112 |
113 |
114 | Thanks for joining in our team to make this world a better
115 | place to live.
116 |
117 |
118 | Campaign has created successfully. An acknowledgement will
119 | be sent to your registered maill-id.
120 |
121 | >
122 | ) : (
123 | <>
124 | {getStepContent(activeStep)}
125 |
126 | {activeStep !== 0 && (
127 |
128 | )}
129 |
138 |
139 | >
140 | )}
141 | >
142 |
143 |
144 |
145 |
146 | );
147 | }
148 |
149 | export default CreateCampaignWrapper;
150 |
--------------------------------------------------------------------------------
/src/components/CampaignCard.jsx:
--------------------------------------------------------------------------------
1 | // library and compoent imports//
2 | import React from "react";
3 | import Card from "@mui/material/Card";
4 | import CardActions from "@mui/material/CardActions";
5 | import CardContent from "@mui/material/CardContent";
6 | import CardMedia from "@mui/material/CardMedia";
7 | import Typography from "@mui/material/Typography";
8 | import Button from "@mui/material/Button";
9 | import LinearProgress from "@mui/material/LinearProgress";
10 | import Box from "@mui/material/Box";
11 | import { Stack } from "@mui/system";
12 | import IconButton from "@mui/material/IconButton";
13 | import CardActionArea from "@mui/material/CardActionArea";
14 | import FavoriteRoundedIcon from "@mui/icons-material/FavoriteRounded";
15 | import FavoriteBorderRoundedIcon from "@mui/icons-material/FavoriteBorderRounded";
16 | import AccessTimeRoundedIcon from "@mui/icons-material/AccessTimeRounded";
17 | import ShareIcon from "@mui/icons-material/Share";
18 |
19 | // service imports..
20 | import { useNavigate } from "react-router-dom";
21 |
22 | function CampaignCard(props) {
23 | const navigate = useNavigate();
24 |
25 | // extract the details..
26 | const {
27 | bannerUrl,
28 | campaignStatus,
29 | isFavoriteCampaign,
30 | title,
31 | description,
32 | ethRaised,
33 | ethFunded,
34 | deadline,
35 | id,
36 | } = props.details;
37 |
38 | // Find the no. of days left..
39 | const today = Date.now();
40 | console.log(deadline);
41 | console.log(new Date(deadline));
42 | const diffTime = Math.abs(today - new Date(deadline)); // get difference in milli-seconds
43 | const daysLeft = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); // find diff in days.
44 | console.log(daysLeft);
45 |
46 | // helpers ..
47 | function LinearProgressWithLabel(props) {
48 | return (
49 |
50 |
51 |
52 |
53 |
54 | {`${Math.round(
55 | props.value
56 | )}%`}
57 |
58 |
59 | );
60 | }
61 |
62 | return (
63 | <>
64 |
72 |
82 |
83 | navigate(`/../campaign/${id}`)}
86 | >
87 |
92 |
101 | {campaignStatus}
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | {title}
110 |
111 |
121 | {description}
122 |
123 |
128 |
129 |
130 | {`Ξ ${ethFunded} `}
131 |
132 |
133 | ETH funded
134 |
135 |
136 |
137 |
138 | {`Ξ ${ethRaised} `}
139 |
140 |
141 | ETH raised
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | {daysLeft} days left
150 |
151 |
152 |
153 |
154 |
155 | >
156 | );
157 | }
158 |
159 | export default CampaignCard;
160 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | // UI imports..
3 | import Avatar from "@mui/material/Avatar";
4 | import Button from "@mui/material/Button";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | import TextField from "@mui/material/TextField";
7 | import Link from "@mui/material/Link";
8 | import Grid from "@mui/material/Grid";
9 | import Box from "@mui/material/Box";
10 | import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
11 | import Typography from "@mui/material/Typography";
12 | import Container from "@mui/material/Container";
13 | import { createTheme, ThemeProvider } from "@mui/material/styles";
14 | import Snackbar from "@mui/material/Snackbar";
15 | import Alert from "@mui/material/Alert";
16 |
17 | // service imports..
18 | import firebase from "firebase/compat/app";
19 | import "firebase/compat/auth";
20 | import { useEffect } from "react";
21 | import { useAuth } from "../contexts/AuthContext";
22 | import { useNavigate } from "react-router-dom";
23 |
24 | function Copyright(props) {
25 | return (
26 |
32 | {"Copyright © "}
33 |
34 | CrowdHelp
35 | {" "}
36 | {new Date().getFullYear()}
37 | {"."}
38 |
39 | );
40 | }
41 |
42 | const theme = createTheme();
43 |
44 | export default function SignIn() {
45 | // context's..
46 | const { resetPassword } = useAuth();
47 | // hooks..
48 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
49 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
50 | const [responseSeverity, setResponseSeverity] = React.useState("error");
51 | const [isLoading, setIsLoading] = React.useState(false); // to prevent multiple submits while processing..
52 |
53 | const navigate = useNavigate(); // for auto-navigation to home page.
54 |
55 | useEffect(() => {}, [showResponse]); // render on response to be shown or hidden
56 |
57 | // helpers..
58 | const handleSubmit = async (event) => {
59 | event.preventDefault();
60 | const data = new FormData(event.currentTarget);
61 |
62 | // do password reset..
63 | // perform client-side validations..
64 | if (data.get("email") == "") {
65 | setShowResponse(true);
66 | setResponseSeverity("error");
67 | return setResponseMsg("Please enter your e-mail"); // exit from function..--- 👁️🗨️ on this...
68 | }
69 | try {
70 | // set the messages to default..
71 | setShowResponse(false);
72 | setResponseMsg("");
73 | setIsLoading(true);
74 | await resetPassword(data.get("email"));
75 | console.log("Password reset message");
76 | setShowResponse(true);
77 | setResponseMsg("Please check your e-mail for further instructions..");
78 | setResponseSeverity("success");
79 | navigate("/sign-in"); // auto-navigate to sign-in (After successful reset-password)
80 | } catch (error) {
81 | setShowResponse(true);
82 | setResponseSeverity("error");
83 | setResponseMsg(error.message);
84 | console.log(error);
85 | } // after done with password-reset.
86 | setIsLoading(false);
87 | };
88 |
89 | const handleClose = (event, reason) => {
90 | if (reason === "clickaway") {
91 | return;
92 | }
93 | setShowResponse(false);
94 | };
95 |
96 | return (
97 |
98 |
99 |
100 |
108 |
109 |
110 |
111 |
112 | Forgot Password
113 |
114 |
115 | Please enter your email to get password reset link to your mail.
116 |
117 |
123 |
133 |
134 |
142 |
143 |
144 |
145 |
146 | Back to sign-in
147 |
148 |
149 |
150 |
151 | {"Don't have an account? Sign Up"}
152 |
153 |
154 |
155 |
156 |
157 |
158 |
164 |
165 | {responseMsg}
166 |
167 |
168 |
169 |
170 | );
171 | }
172 |
--------------------------------------------------------------------------------
/src/components/NavBar.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | // UI imports..
3 | import {
4 | AppBar,
5 | Toolbar,
6 | Typography,
7 | Badge,
8 | styled,
9 | Avatar,
10 | Button,
11 | Box,
12 | InputBase,
13 | Menu,
14 | MenuItem,
15 | } from "@mui/material";
16 | import StorefrontIcon from "@mui/icons-material/Storefront";
17 | import EmailIcon from "@mui/icons-material/Email";
18 | import BadgeUnstyled from "@mui/base/BadgeUnstyled";
19 | import NotificationsIcon from "@mui/icons-material/Notifications";
20 | import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
21 | import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
22 | import ListItemIcon from "@mui/material/ListItemIcon";
23 | import PersonIcon from "@mui/icons-material/Person";
24 | import CreateIcon from "@mui/icons-material/Create";
25 | import { LoadingButton } from "@mui/lab";
26 |
27 | // service imports..
28 | import { useAuth } from "../contexts/AuthContext";
29 | import { useNavigate } from "react-router-dom";
30 | import { useState } from "react";
31 |
32 | // Wallet connection..
33 | import { useWallet } from "use-wallet";
34 |
35 | // Custom styling to components
36 | const StyledToolbar = styled(Toolbar)({
37 | display: "flex",
38 | justifyContent: "space-between",
39 | });
40 |
41 | const SearchBar = styled("div")(({ theme }) => ({
42 | backgroundColor: "white",
43 | padding: "0 10px",
44 | borderRadius: theme.shape.borderRadius,
45 | width: "40%",
46 | }));
47 |
48 | const UserActions = styled("div")(({ theme }) => ({
49 | display: "none",
50 | justifyContent: "space-between",
51 | alignItems: "center",
52 | gap: "20px",
53 | padding: "0 10px",
54 | borderRadius: theme.shape.borderRadius,
55 | [theme.breakpoints.up("sm")]: {
56 | display: "flex",
57 | },
58 | }));
59 |
60 | const UserProfile = styled("div")(({ theme }) => ({
61 | display: "flex",
62 | justifyContent: "space-between",
63 | alignItems: "center",
64 | gap: "10px",
65 | borderRadius: theme.shape.borderRadius,
66 | [theme.breakpoints.up("sm")]: {
67 | display: "none",
68 | },
69 | }));
70 |
71 | function NavBar() {
72 | // hooks ..
73 | const [profileMenuDisplayStatus, setProfileMenuDisplayStatus] =
74 | useState(false);
75 | // hooks..
76 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
77 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
78 | const [responseSeverity, setResponseSeverity] = React.useState("error");
79 | const navigate = useNavigate();
80 |
81 | const wallet = useWallet();
82 |
83 | const { currentUserCredentials, signout } = useAuth();
84 |
85 | const handleSignout = async () => {
86 | // set the response activations to default.
87 | setShowResponse(false);
88 | setResponseMsg("");
89 | setResponseSeverity("error"); // doesn't allowing to have empty, so kept this. Anyway, as showing is false, no worries.
90 |
91 | // do signout.
92 | try {
93 | await signout();
94 | navigate("/sign-in"); // navigate to sign-in page, after successful logout.
95 | } catch (error) {
96 | setShowResponse(true);
97 | setResponseMsg(error.message);
98 | setResponseSeverity("error");
99 | }
100 | };
101 |
102 | return (
103 |
104 |
105 |
115 | CrowdHelp
116 |
117 |
125 | {/*
126 |
127 | */}
128 | {/* {isLoggedIn && ( */}
129 |
130 |
131 |
132 | }
138 | onClick={() => navigate("/create-campaign")}
139 | >
140 | Create Campaign
141 |
142 |
143 | {wallet.status === "connected" ? (
144 | <>
145 | }
148 | onClick={() => setProfileMenuDisplayStatus(true)}
149 | color="primary"
150 | >
151 | {wallet.account.substr(0, 10) + "..."}
152 |
153 | >
154 | ) : (
155 | <>
156 | }
162 | onClick={() => wallet.connect()}
163 | >
164 | Connect Wallet
165 |
166 | >
167 | )}
168 |
169 | {/* */}
170 |
171 |
199 |
200 | );
201 | }
202 |
203 | export default NavBar;
204 |
--------------------------------------------------------------------------------
/src/pages/Profile.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | // UI imports..
3 | import Avatar from "@mui/material/Avatar";
4 | import Button from "@mui/material/Button";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | import TextField from "@mui/material/TextField";
7 | import Link from "@mui/material/Link";
8 | import Grid from "@mui/material/Grid";
9 | import Box from "@mui/material/Box";
10 | import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
11 | import Typography from "@mui/material/Typography";
12 | import Container from "@mui/material/Container";
13 | import { createTheme, ThemeProvider } from "@mui/material/styles";
14 | import Snackbar from "@mui/material/Snackbar";
15 | import Alert from "@mui/material/Alert";
16 |
17 | import NavBar from "../components/NavBar";
18 |
19 | // service imports..
20 | import firebase from "firebase/compat/app";
21 | import "firebase/compat/auth";
22 | import { useEffect } from "react";
23 | import { useAuth } from "../contexts/AuthContext";
24 | import { useNavigate } from "react-router-dom";
25 |
26 | function Copyright(props) {
27 | return (
28 |
34 | {"Copyright © "}
35 |
36 | CrowdHelp
37 | {" "}
38 | {new Date().getFullYear()}
39 | {"."}
40 |
41 | );
42 | }
43 |
44 | const theme = createTheme();
45 |
46 | export default function Profile() {
47 | // context's..
48 | const { currentUserCredentials, updateEmail, updatePassword } = useAuth();
49 | // hooks..
50 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
51 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
52 | const [responseSeverity, setResponseSeverity] = React.useState("error");
53 | const [isLoading, setIsLoading] = React.useState(false); // to prevent multiple submits while processing..
54 |
55 | const [isUpdatingProfile, setIsUpdatingProfile] = React.useState(false);
56 |
57 | const navigate = useNavigate(); // for auto-navigation to home page.
58 |
59 | // helpers..
60 | const handleSubmit = (event) => {
61 | event.preventDefault();
62 | // Check: Is updating password..?
63 | if (isUpdatingProfile == false) {
64 | // if not .. enable updation..
65 | setIsUpdatingProfile(true);
66 | return;
67 | }
68 |
69 | // Start Perform updation.. -- ONLY when enabled..!!
70 | const data = new FormData(event.currentTarget);
71 | // perform client-side validations..
72 | if (data.get("password") != data.get("confirm-password")) {
73 | setShowResponse(true);
74 | setResponseSeverity("error");
75 | return setResponseMsg("Passwords do not match");
76 | }
77 | if (
78 | data.get("password") == "" &&
79 | (data.get("email") == currentUserCredentials.email ||
80 | data.get("email") == "")
81 | ) {
82 | setShowResponse(true);
83 | setResponseSeverity("error");
84 | return setResponseMsg("No fileds updated. Update cancelled.");
85 | }
86 |
87 | // after passing validations.. go for updation.
88 | const promises = [];
89 | // Take only what to update..
90 | if (data.get("email") != currentUserCredentials.email) {
91 | // if e-mail has changed..
92 | promises.push(updateEmail(data.get("email")));
93 | }
94 | if (data.get("password")) {
95 | // if password has changed..
96 | promises.push(updatePassword(data.get("password")));
97 | }
98 |
99 | // Run the promises..
100 | setIsLoading(true);
101 | Promise.all(promises)
102 | .then(() => {
103 | setShowResponse(true);
104 | setResponseSeverity("success");
105 | setResponseMsg("Profile updated successfully..!");
106 | navigate("/profile"); // navigate to profile page.
107 | })
108 | .catch((error) => {
109 | setShowResponse(true);
110 | setResponseSeverity("error");
111 | setResponseMsg(error.message);
112 | })
113 | .finally(() => {
114 | setIsLoading(false);
115 | /// looks like theres is 🐛 bug here. (Not perfectly sure). look in next iteration,
116 | // Showing incorrect response..
117 | setShowResponse(true);
118 | setResponseSeverity("success");
119 | setResponseMsg("Profile updated successfullye");
120 | });
121 |
122 | setIsLoading(false);
123 | };
124 |
125 | const handleClose = (event, reason) => {
126 | if (reason === "clickaway") {
127 | return;
128 | }
129 | setShowResponse(false);
130 | };
131 |
132 | return (
133 |
134 |
135 |
136 |
137 |
145 |
146 |
147 |
148 | {isUpdatingProfile ? (
149 | <>
150 |
151 | Update Profile
152 |
153 |
154 | Enter only required fields to update. Leave blank to keep keep
155 | as it is.
156 |
157 | >
158 | ) : (
159 |
160 | Profile
161 |
162 | )}
163 |
164 |
170 | {isUpdatingProfile ? (
171 |
172 |
173 |
181 |
182 |
183 |
192 |
193 |
194 |
203 |
204 |
205 | ) : (
206 |
212 | )}
213 |
214 |
222 | {isUpdatingProfile ? (
223 |
224 | {"Cancel"}
225 |
226 | ) : (
227 |
228 | {"Back to Home"}
229 |
230 | )}
231 |
232 |
233 |
234 |
240 |
241 | {responseMsg}
242 |
243 |
244 |
245 |
246 | );
247 | }
248 |
--------------------------------------------------------------------------------
/contracts/Campaign.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import "./CampaignSchemeEnums.sol";
5 | import "hardhat/console.sol"; // for local testing only. Have to be deleted if to be deploying in remote testnets.
6 |
7 | contract Campaign {
8 | // Campaign state
9 | enum State {
10 | ACTIVE, // Campaign can receive funds / contributions
11 | SUCCESS, // Campaign has reached its goal before the deadling
12 | EXPIRED, // Campaign has withdrawn amount of successfully ended campaign
13 | ABORTED // Campaign has terminted in-between, all the raised amount has refunded back to backers.
14 | }
15 |
16 | struct Contribution {
17 | address payable contributor;
18 | uint256 amount;
19 | }
20 |
21 | // Variables
22 | address payable public creator;
23 | uint256 public minimumContribution;
24 | uint256 public deadline;
25 | uint256 public targetContribution; // required to reach at least this much amount
26 | uint public reachedTargetAt; // what's the usage of this..?? -- stores when it has ended (by the fundraiser)
27 | uint256 public raisedAmount; // Total raised amount till now
28 | uint256 public noOfContributors;
29 | string public projectTitle;
30 | string public projectDes;
31 | string public bannerUrl;
32 | State public state = State.ACTIVE;
33 | CampaignSchemeId public campaignSchemeId;
34 |
35 | // mapping (uint => Contribution) public contributors; // use indices as address with amountContributed as its values.
36 | Contribution[] contributions;
37 | // after work... replace this with contributions.. -- as of type array
38 | // Modifiers
39 | modifier isCreator() {
40 | require(
41 | msg.sender == creator,
42 | "You dont have access to perform this operation !"
43 | );
44 | _;
45 | }
46 |
47 | modifier canContribute() {
48 | require(
49 | state == State.ACTIVE || state == State.SUCCESS,
50 | "Invalid state"
51 | );
52 | require(
53 | block.timestamp < deadline,
54 | "Sorry backer, deadline has passed! No contributions can be accepted now."
55 | );
56 | _;
57 | }
58 |
59 | // Events
60 | // Event that will be emitted whenever funding will be received
61 | event FundingReceived(address contributor, uint amount, uint currentTotal);
62 | // Event gets emitted when amount gets credited to fund raiser - when campaign has ended
63 | event AmountCredited(address contributor, uint amountTotal);
64 | // Event gets emitted when campaign gets aborted [before deadline]
65 | event AmountRefunded(uint noOfContributors, uint amountTotal);
66 |
67 | event CampaignCreated(uint deadline, uint blockTime);
68 |
69 | constructor(
70 | address _creator,
71 | uint256 _minimumContribution,
72 | uint256 _deadline,
73 | uint256 _targetContribution,
74 | string memory _projectTitle,
75 | string memory _projectDes,
76 | string memory _bannerUrl,
77 | uint _campaignSchemeId
78 | ) {
79 | // console.log("Constructor reached");
80 | // Pre-requisites to move further..
81 | emit CampaignCreated(_deadline, block.timestamp);
82 | require(_campaignSchemeId < SCHEMES_COUNT, "Invalid scheme id");
83 | console.log("Block timestamp:", block.timestamp); // needs `import "hardhat/console.sol";`
84 | console.log("Given deadline:", _deadline);
85 | require(_deadline > block.timestamp, "Deadline must be in the future");
86 | require(_minimumContribution > 0, "Minimum contribution must be greater than 0");
87 | require(_targetContribution > 0, "Target must be greater than 0");
88 |
89 | // emit FundingReceived(payable(msg.sender), 0, 0); // debug
90 | creator = payable(_creator);
91 | minimumContribution = _minimumContribution;
92 | deadline = _deadline;
93 | targetContribution = _targetContribution;
94 | projectTitle = _projectTitle;
95 | projectDes = _projectDes;
96 | raisedAmount = 0;
97 | bannerUrl = _bannerUrl;
98 | campaignSchemeId = CampaignSchemeId(getSchemeId(_campaignSchemeId));
99 | }
100 |
101 | function getContributorContribution(
102 | address _contributor
103 | ) internal view returns (int) {
104 | // Takes the contributor's address and returns their contribution amount (if found) else 0.
105 | int contribIndex = -1;
106 | for (int idx = 0; idx < int(noOfContributors); idx++) {
107 | if (_contributor == contributions[uint(idx)].contributor) {
108 | contribIndex = idx;
109 | break;
110 | }
111 | }
112 | return contribIndex; // when not found -- send "-1" to indicate as not found. NOTE: Only this is THE negative value used.
113 | }
114 |
115 | // @dev Anyone can contribute
116 | function contribute() public payable canContribute {
117 | // validation
118 | require(
119 | msg.value >= minimumContribution,
120 | "Contribution amount is too low !"
121 | );
122 | address payable contributor = payable(msg.sender);
123 | int contributionIdx = getContributorContribution(contributor);
124 | if (contributionIdx == -1) {
125 | // if contributing for the first time..
126 | noOfContributors++;
127 | contributions.push(Contribution(contributor, msg.value)); // store the amount of funds funded
128 | }
129 | // if contributed already..
130 | else {
131 | contributions[uint(contributionIdx)].amount += msg.value; // update the contribution
132 | }
133 | // contributors[contributor] += msg.value; // store the amount of funds funded -- older version
134 | // update the global value
135 | raisedAmount += msg.value;
136 | emit FundingReceived(contributor, msg.value, raisedAmount);
137 | isFundingReachedTarget();
138 | }
139 |
140 | function isFundingReachedTarget() internal {
141 | if (raisedAmount >= targetContribution && block.timestamp < deadline) {
142 | state = State.SUCCESS;
143 | reachedTargetAt = block.timestamp;
144 | }
145 | }
146 |
147 | function endCampaignAndCredit() public payable isCreator {
148 | // perform validation..
149 | require(
150 | state == State.SUCCESS,
151 | "Goal not reached, amount cannot be withdrawn. Please abort to refund to backers"
152 | );
153 | require(
154 | block.timestamp < deadline,
155 | "Campaign cannot be ended, deadline not reached."
156 | );
157 | // transfer the WHOLE amount raised to fund raiser
158 | creator.transfer(raisedAmount);
159 |
160 | // Update the campaign state
161 | state = State.EXPIRED;
162 | // emit the event..
163 | emit AmountCredited(creator, raisedAmount);
164 | }
165 |
166 | function abortCampaignAndRefund() public payable isCreator {
167 | // perform validation..
168 | require(
169 | block.timestamp < deadline,
170 | "Campaign cannot be aborted, deadline passed."
171 | );
172 | require(
173 | state == State.ACTIVE || state == State.SUCCESS,
174 | "Invalid state. Cannot abort campaign."
175 | );
176 | // refund money to backers
177 | for (uint idx = 0; idx < noOfContributors; idx++) {
178 | // iterate through all addresses of contrdibutors
179 | contributions[idx].contributor.transfer(contributions[idx].amount);
180 | delete contributions[idx];
181 | }
182 | // update the state..
183 | state = State.ABORTED;
184 | // emit the event..
185 | emit AmountRefunded(noOfContributors, raisedAmount);
186 | }
187 |
188 | function getContractBalance() public view returns (uint256) {
189 | return address(this).balance;
190 | }
191 |
192 | function getCampaignSummary()
193 | public
194 | view
195 | returns (
196 | address payable projectStarter,
197 | uint256 minContribution,
198 | uint256 projectDeadline,
199 | uint256 goalAmount,
200 | uint completedTime,
201 | uint256 currentAmount,
202 | string memory title,
203 | string memory desc,
204 | State currentState,
205 | uint256 balance,
206 | string memory imageUrl,
207 | uint256 numBackers,
208 | uint schemeId
209 | )
210 | {
211 | projectStarter = creator;
212 | minContribution = minimumContribution;
213 | projectDeadline = deadline;
214 | goalAmount = targetContribution;
215 | completedTime = reachedTargetAt;
216 | currentAmount = raisedAmount;
217 | title = projectTitle;
218 | desc = projectDes;
219 | currentState = state;
220 | balance = address(this).balance;
221 | imageUrl = bannerUrl;
222 | numBackers = noOfContributors;
223 | schemeId = uint(campaignSchemeId);
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/test/CrowdHelp.test.js:
--------------------------------------------------------------------------------
1 | const { expect } = require("chai");
2 | const { ethers } = require("hardhat");
3 | const { time } = require("@nomicfoundation/hardhat-network-helpers");
4 |
5 | describe("CrowdHelp Contract", () => {
6 | let crowdHelp;
7 | let owner, addr1, addr2;
8 | const schemeId = 0;
9 |
10 | beforeEach(async () => {
11 | [owner, addr1, addr2] = await ethers.getSigners();
12 | const CrowdHelp = await ethers.getContractFactory("CrowdHelp");
13 | crowdHelp = await CrowdHelp.deploy();
14 | await crowdHelp.deployed();
15 | });
16 |
17 | it("should deploy and allow creating a campaign", async () => {
18 | const blockNumBefore = await ethers.provider.getBlockNumber();
19 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
20 | const currentTimestamp = blockBefore.timestamp;
21 |
22 | const deadline = currentTimestamp + 3600; // deadline 1 hour in future
23 | console.log("Deadline being set:", deadline);
24 |
25 | await crowdHelp.createCampaign(
26 | "Clean Water Project",
27 | "Providing clean water to remote areas",
28 | ethers.utils.parseEther("0.01"),
29 | ethers.utils.parseEther("1"),
30 | deadline,
31 | "https://example.com/banner.jpg",
32 | schemeId
33 | );
34 |
35 | const deployed = await crowdHelp.getDeployedCampaigns();
36 | expect(deployed.length).to.equal(1);
37 | });
38 |
39 | it("should fail if deadline is in the past", async () => {
40 | const pastDeadline = Math.floor(Date.now() / 1000) - 1000;
41 |
42 | await expect(
43 | crowdHelp.createCampaign(
44 | "Expired",
45 | "Should fail",
46 | ethers.utils.parseEther("0.01"),
47 | ethers.utils.parseEther("1"),
48 | pastDeadline,
49 | "https://example.com/banner.jpg",
50 | schemeId
51 | )
52 | ).to.be.revertedWith("Deadline must be in the future");
53 | });
54 |
55 | it("should track multiple campaigns", async () => {
56 | const titles = ["A", "B"];
57 | for (const title of titles) {
58 | const blockNumBefore = await ethers.provider.getBlockNumber();
59 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
60 | const currentTimestamp = blockBefore.timestamp;
61 |
62 | const deadline = currentTimestamp + 3600; // deadline 1 hour in future
63 | console.log("Deadline being set:", deadline);
64 |
65 | await crowdHelp.createCampaign(
66 | title,
67 | "desc",
68 | ethers.utils.parseEther("0.01"),
69 | ethers.utils.parseEther("1"),
70 | deadline,
71 | "url",
72 | schemeId
73 | );
74 | }
75 |
76 | const deployed = await crowdHelp.getDeployedCampaigns();
77 | expect(deployed.length).to.equal(2);
78 | });
79 |
80 | it("should allow contributing to a campaign and update state", async () => {
81 | const blockNumBefore = await ethers.provider.getBlockNumber();
82 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
83 | const currentTimestamp = blockBefore.timestamp;
84 |
85 | const deadline = currentTimestamp + 3600; // deadline 1 hour in future
86 |
87 | await crowdHelp.createCampaign(
88 | "Health Fund",
89 | "Medical aid",
90 | ethers.utils.parseEther("0.01"),
91 | ethers.utils.parseEther("1"),
92 | deadline,
93 | "url",
94 | schemeId
95 | );
96 |
97 | const [campaignAddr] = await crowdHelp.getDeployedCampaigns();
98 | const Campaign = await ethers.getContractFactory("Campaign");
99 | const campaign = await Campaign.attach(campaignAddr);
100 |
101 | await campaign.connect(addr1).contribute({
102 | value: ethers.utils.parseEther("0.05"),
103 | });
104 |
105 | const balance = await ethers.provider.getBalance(campaign.address);
106 | expect(balance).to.equal(ethers.utils.parseEther("0.05"));
107 | });
108 |
109 | it("should reject contribution below minimum", async () => {
110 | const blockNumBefore = await ethers.provider.getBlockNumber();
111 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
112 | const currentTimestamp = blockBefore.timestamp;
113 |
114 | const deadline = currentTimestamp + 3600; // deadline 1 hour in future
115 | await crowdHelp.createCampaign(
116 | "Low Reject",
117 | "Testing",
118 | ethers.utils.parseEther("0.1"),
119 | ethers.utils.parseEther("1"),
120 | deadline,
121 | "url",
122 | schemeId
123 | );
124 |
125 | const [campaignAddr] = await crowdHelp.getDeployedCampaigns();
126 | const Campaign = await ethers.getContractFactory("Campaign");
127 | const campaign = await Campaign.attach(campaignAddr);
128 |
129 | await expect(
130 | campaign.connect(addr2).contribute({
131 | value: ethers.utils.parseEther("0.01"),
132 | })
133 | ).to.be.revertedWith("Contribution amount is too low !");
134 | });
135 |
136 | it("should store correct details for each campaign", async () => {
137 | const blockNumBefore = await ethers.provider.getBlockNumber();
138 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
139 | const currentTimestamp = blockBefore.timestamp;
140 |
141 | const deadline = currentTimestamp + 3600; // deadline 1 hour in futurene();
142 | await crowdHelp.createCampaign(
143 | "Education",
144 | "Helping students",
145 | ethers.utils.parseEther("0.05"),
146 | ethers.utils.parseEther("2"),
147 | deadline,
148 | "url",
149 | schemeId
150 | );
151 |
152 | const [campaignAddr] = await crowdHelp.getDeployedCampaigns();
153 | const Campaign = await ethers.getContractFactory("Campaign");
154 | const campaign = await Campaign.attach(campaignAddr);
155 |
156 | expect(await campaign.projectTitle()).to.equal("Education");
157 | expect(await campaign.projectDes()).to.equal("Helping students");
158 | });
159 |
160 | it("should refund all contributors when campaign is aborted", async function () {
161 | // Setup campaign
162 | const blockNumBefore = await ethers.provider.getBlockNumber();
163 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
164 | const currentTimestamp = blockBefore.timestamp;
165 | const deadline = currentTimestamp + 3600;
166 |
167 | await crowdHelp.createCampaign(
168 | "Abortable Campaign",
169 | "Testing refund on abort",
170 | ethers.utils.parseEther("0.1"),
171 | ethers.utils.parseEther("1"),
172 | deadline,
173 | "url",
174 | schemeId
175 | );
176 |
177 | const [campaignAddr] = await crowdHelp.getDeployedCampaigns();
178 | const Campaign = await ethers.getContractFactory("Campaign");
179 | const campaign = await Campaign.attach(campaignAddr);
180 |
181 | const contribution1 = ethers.utils.parseEther("0.3");
182 | const contribution2 = ethers.utils.parseEther("0.4");
183 |
184 | await campaign.connect(addr1).contribute({ value: contribution1 });
185 | await campaign.connect(addr2).contribute({ value: contribution2 });
186 |
187 | const balance1Before = await ethers.provider.getBalance(addr1.address);
188 | const balance2Before = await ethers.provider.getBalance(addr2.address);
189 |
190 | const tx = await campaign.connect(owner).abortCampaignAndRefund();
191 | await tx.wait();
192 |
193 | const summary = await campaign.getCampaignSummary();
194 | expect(summary.currentState).to.equal(3); // ABORTED
195 |
196 | const balance1After = await ethers.provider.getBalance(addr1.address);
197 | const balance2After = await ethers.provider.getBalance(addr2.address);
198 |
199 | expect(balance1After).to.be.above(
200 | balance1Before.sub(contribution1.div(100))
201 | );
202 | expect(balance2After).to.be.above(
203 | balance2Before.sub(contribution2.div(100))
204 | );
205 | });
206 |
207 | it("should allow creator to end campaign and receive funds when target is met", async function () {
208 | // Setup campaign
209 | const blockNumBefore = await ethers.provider.getBlockNumber();
210 | const blockBefore = await ethers.provider.getBlock(blockNumBefore);
211 | const currentTimestamp = blockBefore.timestamp;
212 | const deadline = currentTimestamp + 3600;
213 |
214 | await crowdHelp.createCampaign(
215 | "Success Campaign",
216 | "Testing payout on success",
217 | ethers.utils.parseEther("0.1"),
218 | ethers.utils.parseEther("1"),
219 | deadline,
220 | "url",
221 | schemeId
222 | );
223 |
224 | const [campaignAddr] = await crowdHelp.getDeployedCampaigns();
225 | const Campaign = await ethers.getContractFactory("Campaign");
226 | const campaign = await Campaign.attach(campaignAddr);
227 |
228 | const contribution1 = ethers.utils.parseEther("0.6");
229 | const contribution2 = ethers.utils.parseEther("0.5");
230 |
231 | await campaign.connect(addr1).contribute({ value: contribution1 });
232 | await campaign.connect(addr2).contribute({ value: contribution2 });
233 |
234 | const summaryBefore = await campaign.getCampaignSummary();
235 | expect(summaryBefore.currentState).to.equal(1); // SUCCESS
236 |
237 | const creatorBalanceBefore = await ethers.provider.getBalance(
238 | owner.address
239 | );
240 |
241 | const tx = await campaign.connect(owner).endCampaignAndCredit();
242 | const receipt = await tx.wait();
243 |
244 | const gasUsed = receipt.gasUsed.mul(receipt.effectiveGasPrice);
245 |
246 | const creatorBalanceAfter = await ethers.provider.getBalance(owner.address);
247 | const expectedPayout = contribution1.add(contribution2);
248 |
249 | expect(creatorBalanceAfter).to.be.closeTo(
250 | creatorBalanceBefore.add(expectedPayout).sub(gasUsed),
251 | ethers.utils.parseEther("0.01")
252 | );
253 |
254 | const summaryAfter = await campaign.getCampaignSummary();
255 | expect(summaryAfter.currentState).to.equal(2); // EXPIRED
256 | });
257 | });
258 |
--------------------------------------------------------------------------------
/src/pages/SignIn.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | // UI imports..
3 | import Avatar from "@mui/material/Avatar";
4 | import Button from "@mui/material/Button";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | // import TextField from "@mui/material/TextField";
7 | import Link from "@mui/material/Link";
8 | import Grid from "@mui/material/Grid";
9 | import Box from "@mui/material/Box";
10 | import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
11 | import Typography from "@mui/material/Typography";
12 | import Container from "@mui/material/Container";
13 | import { createTheme, ThemeProvider } from "@mui/material/styles";
14 | import Snackbar from "@mui/material/Snackbar";
15 | import Alert from "@mui/material/Alert";
16 | import InputAdornment from "@mui/material/InputAdornment";
17 | import Visibility from "@mui/icons-material/Visibility";
18 | import VisibilityOff from "@mui/icons-material/VisibilityOff";
19 | import IconButton from "@mui/material/IconButton";
20 | import FormHelperText from "@mui/material/FormHelperText";
21 |
22 | // service imports..
23 | import firebase from "firebase/compat/app";
24 | import "firebase/compat/auth";
25 | import { useEffect } from "react";
26 | import { useAuth } from "../contexts/AuthContext";
27 | import { useNavigate } from "react-router-dom";
28 | import { FormControl, Input, InputLabel } from "@mui/material";
29 |
30 | function Copyright(props) {
31 | return (
32 |
38 | {"Copyright © "}
39 |
40 | CrowdHelp
41 | {" "}
42 | {new Date().getFullYear()}
43 | {"."}
44 |
45 | );
46 | }
47 |
48 | const theme = createTheme();
49 |
50 | export default function SignIn() {
51 | // context's..
52 | const { signInWithEmailAndPassword, signInWithGooglePopup } = useAuth();
53 | /*............................hooks............................*/
54 | // for validations..
55 | const [showPassword, setShowPassword] = React.useState(false);
56 | const [formValues, setFormValues] = React.useState({
57 | email: "",
58 | password: "",
59 | });
60 | const [formErrors, setFormErrors] = React.useState({});
61 |
62 | // for showing response...
63 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
64 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
65 | const [responseSeverity, setResponseSeverity] = React.useState("error");
66 | const [isLoading, setIsLoading] = React.useState(false); // to prevent multiple submits while processing..
67 |
68 | const navigate = useNavigate(); // for auto-navigation to home page.
69 |
70 | /// helpers..
71 | useEffect(() => {
72 | console.log(formErrors);
73 | if (Object.keys(formErrors).length === 0) {
74 | console.log(formValues);
75 | }
76 | }, [formErrors]);
77 | // validations..
78 | const handleClickShowPassword = () => {
79 | setShowPassword(!showPassword);
80 | };
81 |
82 | const handleMouseDownPassword = (event) => {
83 | event.preventDefault();
84 | };
85 |
86 | const handleChange = (e) => {
87 | const { name, value } = e.target;
88 | setFormValues({ ...formValues, [name]: value });
89 | };
90 |
91 | const validate = (values) => {
92 | const errors = {};
93 | console.info("Values received");
94 | console.log(values.email);
95 | const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
96 | if (values.email == undefined || values.email == "") {
97 | errors.email = "Email is required!";
98 | } else if (!regex.test(values.email)) {
99 | errors.email = "Invalid email format!";
100 | }
101 | if (!values.password) {
102 | errors.password = "Password is required";
103 | } else if (values.password.length < 6) {
104 | errors.password = "Password must be more than 6 characters";
105 | } else if (values.password.length > 10) {
106 | errors.password = "Password cannot exceed more than 10 characters";
107 | }
108 | console.error(errors);
109 | return errors;
110 | };
111 |
112 | const handleSubmit = async (event) => {
113 | event.preventDefault();
114 | const data = new FormData(event.currentTarget);
115 |
116 | console.log({
117 | email: data.get("email"),
118 | password: data.get("password"),
119 | });
120 |
121 | // do signin..
122 | setFormErrors(validate(formValues)); // perform validation..
123 | console.info("Errors received");
124 | console.log(formErrors);
125 | // Proceed next, only if passed.
126 | if (
127 | Object.keys(formErrors).length == 0 &&
128 | formValues.email != "" &&
129 | formValues.password !== ""
130 | ) {
131 | console.info("Validation passed");
132 | try {
133 | // set the messages to default..
134 | setShowResponse(false);
135 | setResponseMsg("");
136 | setIsLoading(true);
137 | await signInWithEmailAndPassword(
138 | data.get("email"),
139 | data.get("password")
140 | );
141 | // console.log(data);
142 | setShowResponse(true);
143 | setResponseMsg("Sign in success.");
144 | setResponseSeverity("success");
145 | // console.log("Sign up success " + showResponse);
146 | navigate("/"); // auto-navigate to homepage (After successful sign-in)
147 | } catch (error) {
148 | setShowResponse(true);
149 | setResponseSeverity("error");
150 | setResponseMsg(error.message);
151 | console.log(error);
152 | } finally {
153 | // after done with sign-in.
154 | setIsLoading(false);
155 | }
156 | }
157 | };
158 |
159 | const handleClose = (event, reason) => {
160 | if (reason === "clickaway") {
161 | return;
162 | }
163 | setShowResponse(false);
164 | };
165 |
166 | return (
167 |
168 |
169 |
170 |
178 |
179 |
180 |
181 |
182 | Sign in
183 |
184 |
185 | We know, you'll be back. Please enter your credentials to recognize
186 | you.
187 |
188 |
194 |
195 | Email Address
196 |
203 | {formErrors && (
204 | {formErrors.email}
205 | )}
206 |
207 |
208 |
209 | Password
210 |
219 |
224 | {showPassword ? : }
225 |
226 |
227 | }
228 | />
229 | {formErrors && (
230 | {formErrors.password}
231 | )}
232 |
233 |
234 |
243 |
252 |
253 |
254 |
255 | Forgot password?
256 |
257 |
258 |
259 |
260 | {"Don't have an account? Sign Up"}
261 |
262 |
263 |
264 |
265 |
266 |
267 |
273 |
274 | {responseMsg}
275 |
276 |
277 |
278 |
279 | );
280 | }
281 |
--------------------------------------------------------------------------------
/src/pages/SignUp.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | // UI imports..
3 | import Avatar from "@mui/material/Avatar";
4 | import Button from "@mui/material/Button";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | import TextField from "@mui/material/TextField";
7 | import FormControlLabel from "@mui/material/FormControlLabel";
8 | import Checkbox from "@mui/material/Checkbox";
9 | import Link from "@mui/material/Link";
10 | import Grid from "@mui/material/Grid";
11 | import Box from "@mui/material/Box";
12 | import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
13 | import Typography from "@mui/material/Typography";
14 | import Container from "@mui/material/Container";
15 | import { createTheme, ThemeProvider } from "@mui/material/styles";
16 | import Snackbar from "@mui/material/Snackbar";
17 | import Alert from "@mui/material/Alert";
18 |
19 | // service imports..
20 | import firebase from "firebase/compat/app";
21 | import "firebase/compat/auth";
22 | import { useEffect } from "react";
23 | import axios from "axios";
24 | import { useAuth } from "../contexts/AuthContext";
25 | import { useState } from "react";
26 | import { useNavigate } from "react-router-dom";
27 |
28 | function Copyright(props) {
29 | return (
30 |
36 | {"Copyright © "}
37 |
38 | CrowdHelp
39 | {" "}
40 | {new Date().getFullYear()}
41 | {"."}
42 |
43 | );
44 | }
45 |
46 | const theme = createTheme();
47 |
48 | export default function SignUp() {
49 | const { signUpWithEmailAndPassword } = useAuth();
50 | useEffect(() => {
51 | console.log("Sign up..");
52 | }, []); // update when response is to be displayed.
53 | /*............................hooks............................*/
54 | // for validations..
55 | const [showPassword, setShowPassword] = React.useState(false);
56 | const [formValues, setFormValues] = React.useState({
57 | email: "",
58 | password: "",
59 | });
60 | const [formErrors, setFormErrors] = React.useState({});
61 |
62 | // for showing response...
63 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
64 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
65 | const [responseSeverity, setResponseSeverity] = React.useState("error");
66 | const [isLoading, setIsLoading] = React.useState(false); // to prevent multiple submits while processing..
67 |
68 | const navigate = useNavigate(); // for auto-navigation to home page.
69 |
70 | /// helpers..
71 | useEffect(() => {
72 | console.log(formErrors);
73 | if (Object.keys(formErrors).length === 0) {
74 | console.log(formValues);
75 | }
76 | }, [formErrors]);
77 | // validations..
78 | const handleClickShowPassword = () => {
79 | setShowPassword(!showPassword);
80 | };
81 |
82 | const handleMouseDownPassword = (event) => {
83 | event.preventDefault();
84 | };
85 |
86 | const handleChange = (e) => {
87 | const { name, value } = e.target;
88 | setFormValues({ ...formValues, [name]: value });
89 | };
90 |
91 | const validate = (values) => {
92 | const errors = {};
93 | console.info("Values received");
94 | console.log(values.email);
95 | const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
96 | if (values.email == undefined || values.email == "") {
97 | errors.email = "Email is required!";
98 | } else if (!regex.test(values.email)) {
99 | errors.email = "Invalid email format!";
100 | }
101 | if (!values.password) {
102 | errors.password = "Password is required";
103 | } else if (values.password.length < 6) {
104 | errors.password = "Password must be more than 6 characters";
105 | } else if (values.password.length > 10) {
106 | errors.password = "Password cannot exceed more than 10 characters";
107 | }
108 | if (!values.confirm_password) {
109 | errors.confirmPassword = "Password is required";
110 | } else if (values.password != values.confirm_password) {
111 | errors.confirmPassword = "Passwords do not match.";
112 | }
113 | // console.error(errors);
114 | return errors;
115 | };
116 |
117 | // helpers..
118 | const handleSignUp = async (event) => {
119 | event.preventDefault(); // prevent from refreshing
120 | const data = new FormData(event.currentTarget);
121 | // just for debugging..🐛 🐛
122 | console.log({
123 | email: data.get("email"),
124 | password: data.get("password"),
125 | confirm_password: data.get("confirm-password"),
126 | });
127 |
128 | // do signup..
129 | // perform client-side validations..
130 | setFormErrors(validate(formValues)); // perform validation..
131 | console.info("Errors received");
132 | console.log(formErrors);
133 | if (Object.keys(formErrors).length == 0) {
134 | console.info("Validation passed");
135 |
136 | try {
137 | // set the messages to default..
138 | setShowResponse(false);
139 | setResponseMsg("");
140 | setIsLoading(true);
141 | await signUpWithEmailAndPassword(
142 | data.get("email"),
143 | data.get("password")
144 | );
145 | // console.log(data);
146 | setShowResponse(true);
147 | setResponseMsg("Account created successfully.");
148 | setResponseSeverity("success");
149 | navigate("/"); // auto-navigate to homepage (After successful sign-in)
150 | // console.log("Sign up success " + showResponse);
151 | } catch (error) {
152 | setShowResponse(true);
153 | setResponseSeverity("error");
154 | setResponseMsg(error.message);
155 | console.log(showResponse);
156 | } finally {
157 | // after done with sign-up.
158 | setIsLoading(false);
159 | // setShowResponse(false);
160 | }
161 | }
162 | };
163 | const handleClose = (event, reason) => {
164 | if (reason === "clickaway") {
165 | return;
166 | }
167 | setShowResponse(false);
168 | };
169 |
170 | return (
171 |
172 |
173 |
174 |
175 |
183 |
184 |
185 |
186 |
187 | Sign up
188 |
189 |
190 | Welcome to the community, dear friend.
191 |
192 |
193 | Please introduce yourself.
194 |
195 |
201 |
202 |
203 |
215 |
216 |
217 |
230 |
231 |
232 |
245 |
246 |
247 |
250 | }
251 | label="I agree to the terms and conditions."
252 | />
253 |
254 |
255 |
264 |
265 |
266 |
267 | Already have an account? Sign in
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
281 |
282 | {responseMsg}
283 |
284 |
285 |
286 |
287 | );
288 | }
289 |
--------------------------------------------------------------------------------
/src/pages/campaigns/FillCampaignDetails.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | ErrorRounded,
3 | LocalConvenienceStoreOutlined,
4 | } from "@mui/icons-material";
5 | import {
6 | Button,
7 | Container,
8 | TextField,
9 | Typography,
10 | styled,
11 | Avatar,
12 | Link,
13 | Box,
14 | Grid,
15 | FormControlLabel,
16 | Checkbox,
17 | } from "@mui/material";
18 | import React, { useEffect } from "react";
19 | import Snackbar from "@mui/material/Snackbar";
20 | import Alert from "@mui/material/Alert";
21 | import { LoadingButton } from "@mui/lab";
22 | import Select from "@mui/material/Select";
23 | import MenuItem from "@mui/material/MenuItem";
24 | // local imports...
25 | import NavBar from "../../components/NavBar";
26 |
27 | // service imports..
28 | import axios from "axios";
29 | import { useNavigate } from "react-router-dom";
30 | import { useForm } from "react-hook-form";
31 | import moment from "moment";
32 |
33 | // Wallet connection..
34 | import { useWallet } from "use-wallet";
35 |
36 | // smart-contract interaction -- for campaign creation..
37 | import crowdHelp from "../../../utils/contract/crowdHelp";
38 | import web3 from "../../../utils/web3";
39 |
40 | // [block-chain] smart-contract related imports..
41 | import { getAllSchemeTitles } from "../../../utils/getCampaigns";
42 |
43 | const api_url = "http://localhost:4000/api/";
44 |
45 | function FillCampaignDetails() {
46 | const wallet = useWallet();
47 | const navigate = useNavigate();
48 |
49 | const [schemeTitles, setSchemeTitles] = React.useState([]);
50 |
51 | useEffect(() => {
52 | // fetch the funding schemes...
53 | const fetchData = async () => {
54 | setSchemeTitles(await getAllSchemeTitles());
55 | console.debug("Obtained Schemes", campaignsList);
56 | };
57 |
58 | // fetch the data..
59 | fetchData();
60 | return () => {
61 | ignore = true; // to avoid rendering multiple times..
62 | };
63 | }, []);
64 |
65 | // hooks for getting form data..
66 | const {
67 | handleSubmit,
68 | register,
69 | formState: { isSubmitting, errors },
70 | } = useForm({
71 | mode: "onChange",
72 | });
73 | const [data, setData] = React.useState("");
74 | const [error, setError] = React.useState("");
75 |
76 | // hooks to handle acknowledgements..
77 | // hooks..
78 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
79 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
80 | const [responseSeverity, setResponseSeverity] = React.useState("error");
81 |
82 | // helpers..
83 | async function handleFilledCampaignDetails(data) {
84 | console.log("ABout to print data");
85 | console.log(data);
86 | console.log("deadline: " + data.deadlineDate + " " + data.deadlineTime);
87 | const timestamp = moment(
88 | data.deadlineDate + " " + data.deadlineTime,
89 | "YYYY-MM-DD HH:mm"
90 | ).valueOf();
91 | console.log(timestamp);
92 | console.log("timestamp printed");
93 |
94 | try {
95 | const accounts = await web3.eth.getAccounts();
96 | // Create campaign by taking all the details..
97 | await crowdHelp.methods
98 | .createCampaign(
99 | data.title,
100 | data.description,
101 | web3.utils.toWei(data.minContribAmount, "ether"),
102 | web3.utils.toWei(data.ethRaised, "ether"),
103 | timestamp,
104 | data.bannerUrl
105 | )
106 | .send({
107 | from: accounts[0],
108 | });
109 |
110 | // After successful creation..
111 | ///// REQUIRED: Find way to get the created campaign address, so that, can navigate to that page.
112 | navigate("/"); // navigate to home page
113 | } catch (err) {
114 | // upon error.. be on the same page and show the error.
115 | setError(err.message);
116 | console.log(err);
117 | } finally {
118 | console.log("job done");
119 | }
120 | }
121 |
122 | const StyledDivLayout = styled("div")(({ theme }) => ({
123 | width: "auto",
124 | marginLeft: theme.spacing(2),
125 | marginRight: theme.spacing(2),
126 | [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
127 | width: 600,
128 | marginLeft: "auto",
129 | marginRight: "auto",
130 | },
131 | }));
132 |
133 | const StyledDivPaper = styled("div")(({ theme }) => ({
134 | marginTop: theme.spacing(3),
135 | marginBottom: theme.spacing(3),
136 | padding: theme.spacing(2),
137 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
138 | marginTop: theme.spacing(6),
139 | marginBottom: theme.spacing(6),
140 | padding: theme.spacing(3),
141 | },
142 | }));
143 |
144 | const StyledContainer = styled(Container)(({ theme }) => ({
145 | [theme.breakpoints.up("sm")]: {
146 | width: "80%",
147 | },
148 | [theme.breakpoints.down("sm")]: {
149 | width: "40%",
150 | },
151 | }));
152 |
153 | const handleClose = (event, reason) => {
154 | if (reason === "clickaway") {
155 | return;
156 | }
157 | setShowResponse(false);
158 | };
159 |
160 | return (
161 | <>
162 |
163 |
168 |
169 |
170 |
176 | Campaign Details
177 |
178 | {/* Handle wallet connection here.. */}
179 | {wallet.status !== "connected" ? (
180 | wallet.connect()}
188 | >
189 | Connect
190 |
191 | }
192 | >
193 | Please connect your wallet to proceed.
194 |
195 | ) : (
196 | wallet.status === "error" && (
197 | wallet.connect()}
205 | >
206 | Try again
207 |
208 | }
209 | >
210 | Error connecting to wallet.
211 |
212 | )
213 | )}
214 |
215 | {/* For displaying errors.. */}
216 | {error && (
217 |
218 | {error}
219 |
220 | )}
221 | {errors.title ||
222 | errors.description ||
223 | errors.bannerUrl ||
224 | errors.minContribAmount ||
225 | errors.ethRaised ||
226 | errors.walletAddress ||
227 | errors.deadlineDate ||
228 | errors.deadlineTime ? (
229 |
230 | All fields are required
231 |
232 | ) : null}
233 |
234 |
418 |
419 |
420 |
421 |
427 |
428 | {responseMsg}
429 |
430 |
431 | >
432 | );
433 | }
434 |
435 | export default FillCampaignDetails;
436 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Index - _What's in this?_
2 |
3 | - [Current status of Project](#current-status-of-the-project-)
4 | - [**Application Features**](#-application-features-)
5 | - [Currently implemented](#currently-implemented-)
6 | - [Planned in next versions](#kept-for-next-version-)
7 | - [**How the application works?**](#understanding-application-flow)
8 | - [Terminology used](#understanding-the-terminology-used-)
9 | - [How the application works?](#understanding-the-flow-of-application-with-screenshots-)
10 | flow of ...
11 | 1. Wallet connection
12 | 2. Campaign Creation & Display
13 | 3. Contributing funds
14 | 4. Ending Campaign
15 | 5. Aborting Campaign
16 | 6. Viewing campaign's transaction on etherscan.io
17 | - [**Ingredients of recipe**](#ingredients-of-recipe) _(here, recipe refers to application)_
18 | - [Key ingredients](#packages-used-)
19 | - [Tools to prepare](#tools-used-%EF%B8%8F)
20 | - [**Running on your own**](#how-to-run-locally--remotely-%EF%B8%8F)
21 | - [Pre-requisites to run](#pre-requisites-%EF%B8%8F)
22 | - [Running locally](#running-locally-with-hardhat-%EF%B8%8F)
23 | - [Running remotely](#running-remotely-%EF%B8%8F)
24 | - [**Crucial components of implementation - References**](#references-taken-)
25 | - [Getting the gist](#for-understanding-of-crowdfunding-idea-with-blockchain--developing-smart-contract)
26 | - [Making the recipe](#for-implementation-)
27 | - [Resources you can use](#docs--reports-)
28 | - [Acknowledgements](#acknowledgements)
29 |
30 | ## Current Status of the project 📃
31 |
32 | - Application is deployed on Netlify _(for frontend)_ and Infura _(Goerli test network)_ with essential features _(listed below)_.
33 | - Please checkout at [here](https://crowdhelp.netlify.app). Found any bugs? or felt like need some features -- please feel free to raise a new issue or submit your pull request.
34 | - **Thanks** for your time in looking into this project.
35 |
36 | # ✨ Application features ✨
37 |
38 | ## Currently implemented 💡
39 |
40 | | **Feature** | **User** | **Explanation** |
41 | | -----------------------: | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42 | | **Wallet connection** | Fund raiser / backer | Whether campaign creation / contributing funds or any action related (except viewing) needs wallet connection & authorization at each crucial step for providing strong security. |
43 | | **Campaign Creation** | Fund Raiser | A campaign to raise funds for a cause. Creator has to fill few mandatory details like `title`, `description`, `image-URL` (for displaying as a banner), `minimum contribution amount`, `target amount`, `deadline` (date & time). |
44 | | **Displaying campaigns** | Any user | Displays list of campaigns in the home page, where each campaign will be displayed as a card & current status is displayed as a progress bar. Upon clicking campaign-specific page will be displayed. |
45 | | **Fund raising** | Backers | A backer can support to a campaign by raising atleast minimum amount _(as set at the time of campaign creation)_. Contributed amount will be stored in the smart contract until the project end. |
46 | | **Abort campaign** | Fund raiser | Fund raiser can abort campaign (before deadline) - with whatsoever may be the reason. In this case, all the raised funds (if any) will be payed back to backers. |
47 | | **Ending & withdrawing** | Fund raiser | Fund raiser can end the campaign [ONLY] after the deadline & campaign has reached its goal. Else they need to abort to pay back to backers. |
48 |
49 | ## Kept for next version 💼
50 |
51 | | **Feature** | **User** | **Purpose** |
52 | | -----------------------------------: | :-------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
53 | | **Setting Milestones** | Fund raiser | Has to set at time of campaign creation.
**Why?** To protect the funds of backer. The money will be distributed in chunks by showing the progress of funds usage. |
54 | | **Updating campaign page** | Fund raiser | To elaborate the cause. |
55 | | **Raising Withdraw requests** | Fund raiser | A request with amount needed and reason.
**Why?** the funds raised of backers. The request will be approved only if > 50% of contributors accept it. |
56 | | **Approving withdraw requests** | Backer | Depending on the progress achieved by campaign & amount needed, he can approve request.
NOTE: Any backer who has contributed >= Minimum amount will be considered as approver. |
57 | | **Contributions page** | Backer | A separate page, where backer can only view the campaigns they funded with the amount of contributions.
**Why?** to facilitate how many campaigns they are supporting & to know **SPECIFIC** campaigns status to monitor how their contributed funds are in usage. |
58 | | **Funds usage** | Backer / public | A page which shows the flow of funds (_Inflow:_ from backers, _Outflow:_ by fundraiser with a reason) with all the transaction IDs which can be checked publicly via [etherscan.io](https://etherscan.io)
**Why?** to provide transparency. |
59 | | **Searching campaigns with filters** | Public | (Mainly) to facilitate backers & public, to search for campaigns & know the status of it, usage of funds ... etc., |
60 | | **Backers withdrawing funds** | Backer | This is still in question. |
61 |
62 | # Understanding application flow
63 |
64 | ## Understanding the terminology used ..
65 |
66 | **Users of application**
67 |
68 | | **User** | **Explanation** |
69 | | --------------: | ------------------------------------------------------------------------ |
70 | | **Public** | - Can be any one. This includes fundraisers, backers and general public. |
71 | | **Fund Raiser** | - The one who creates the campaign to raise funds for a cause. |
72 | | **Backer** | - The one who can contribute to the campaign. |
73 |
74 | **of Campaign**
75 |
76 | | **Term** | **Meaning** |
77 | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
78 | | `ACTIVE` | - Campaign has raised & running.
- Hasn't reached the goal set.
- Open for contributions. |
79 | | `SUCCESS` | - Campaign has reached the goal set before the deadline.
- Still, open to contributions. |
80 | | `EXPIRED` | - Campaign has achieved `SUCCESS` stage and its deadline has expired.
- No contributions are accepted now. |
81 | | `ABORTED` | - Campaign has raised, and its aborted in between, whether it might be in `SUCCESS` or `ACTIVE`.
- In this case, all the raised funds will be returned back to backers.
- No contributions are accepted now. |
82 |
83 | Now, good to go.
84 | Ready with understanding and basic setup.
85 |
86 | ## Understanding the flow of application (with screenshots) 📸
87 |
88 | **Home page**
89 | 
90 |
91 | ### **1. Wallet connection**
92 |
93 | 1. Click on **Connect Wallet** at top-right in navbar.
94 | 2. Select the accounts you would like to connect with the site.
95 | 
96 | 3. Click on **Connect**
97 | 
98 |
99 | - Notice at the top-right of navbar, that showing connnected wallet's address instead of **Connect wallet** button. The user can click on it, to get option to disconnect the wallet.
100 | 
101 |
102 | ### **2. Campaign Creation & displaying**
103 |
104 | 1. Click on **Create campaign** button at top-right in navbar.
105 | 2. Fill the details.
106 | 
107 | 3. Authenticate with wallet.
108 | 
109 | 4. After successful creation, navigates to the homepage that shows the newly created campaign.
110 | 
111 | 5. [Page Appearance] When fundraiser clicks on the campaign which they had created.
Notice that, both are of same addresses, the connected wallet address (at top-right) and in the _Wallet address of Fundraiser_.
112 | 
113 | 6. [Page Appearance] When public/backer clicks on the campaign which others had created.
Notice that, both are of different addresses, the connected wallet address (at top-right) and in the _Wallet address of Fundraiser_.
114 | 
115 |
116 | ### **3. Contributing to campaigns**
117 |
118 | 1. Select the campaign you would like to fund/contribute.
119 | 2. Enter the amount >= minimum amount and click on **Contribute funds**. Click on **Confirm** to finish authentication with the wallet.
120 | 
121 | 3. Status of contribution
122 |
123 | - showing Success message after funding.
124 | 
125 | - Showing updated status (after closing the message, page gets reloaded).
126 | 
127 |
128 | ### **4. Ending Campaign**
129 |
130 | 0. FundRaiser's wallet balance before ending the campaign.
131 | 
132 |
133 | 1. After fundraiser clicks on **End Campaign & withdraw**, asking for wallet authentication. Noice that, the wallet balance has increased from `0.2065 ETH` to `0.2173 ETH`.
134 | 
135 | 2. Fundraiser's wallet after successful end.
136 | 
137 |
138 | ### **5. Aborting Campaign**
139 |
140 | 0. Backer's wallet balance before the fundraiser aborting the campaign.
141 | 
142 | 1. Fund raiser clicking on Aborting campaign.
143 | 
144 | 2. Fund raiser filling the reason for aborting and accepting the condition of refund to backers.
145 | 
146 | 3. Fund raiser authenticating the transaction with the wallet.
147 | 
148 | 4. Backer's wallet balance after fund raiser aborting the campaign. Notice that, the balance of backer has increased from `0.4396 ETH` to `0.5396 ETH`.
149 | After aborting, each backer will get their **whole** amount, even if they contribute partially multiple times.
150 | 
151 |
152 | ### **6. Viewing [any] campaign's transactions**
153 |
154 | 1. Any user can view the campaign's transactions in [etherscan.io](https://goerli.etherscan.io) -- By clicking on **View on Goerli therscan** link on any campaign page.
155 | 
156 | 2. This opens up a new page of `goerli.etherscan.io`, showing the transactions.
157 | 
158 |
159 | - This provides transparency and trust that,
160 | - When backer has funded, with what amount..
161 | - When did the campaign has ended, aborted...
162 |
163 | # Ingredients of recipe
164 |
165 | ## Packages used 📦
166 |
167 | | **Packages used** | **Purpose** |
168 | | ------------------------------------------------------------------------------: | ------------------------------------------------------------------------------------------------- |
169 | | [Vitejs](https://vitejs.dev/) | Fronted tool for fast bundling |
170 | | [MUI](https://mui.com/) | UI tool based on Google's [Material design](https://m3.material.io/) standards. |
171 | | [Solidity v0.8.4](https://docs.soliditylang.org/en/v0.8.4/) | For writing the smart-contracts that can interact with the ethereum & ethereum-based blockchains. |
172 | | [hardhat](https://hardhat.org/docs) | Ethereum development environment |
173 | | [ether.js docs](https://docs.ethers.io/v5/) | A compact library for interacting with the Ethereum Blockchain and its ecosystem. |
174 | | [web3.js](https://www.npmjs.com/package/web3), [docs](https://docs.web3js.org/) | The ultimate JavaScript library for Ethereum |
175 |
176 | ## Tools used ⚒️
177 |
178 | | **Tool** | **Used for** |
179 | | --------------------------------------------------------: | ---------------------------------------------------------------------------------------------------- |
180 | | [github](https://github.com/) | A Version control system, for managing different versions & issue tracking. |
181 | | [VS code](https://code.visualstudio.com/) | An IDE, for writing code. |
182 | | [Prettier](https://prettier.io/) | A VSCode extension, for auto-formatting of code when saved. |
183 | | [gitmoji](https://gitmoji.dev/) | A git commit tool, to use emojis in commits. |
184 | | [metamask](https://metamask.io/) | A digital wallet as a browser extension, to manage different test accounts & authorize transactions. |
185 | | [**Firefox**](https://www.mozilla.org/en-US/firefox/new/) | Browser, for inspection of code with console. |
186 | | [Netlify](https://www.netlify.com/) | For deploying frontend site. |
187 | | [Infura](https://www.infura.io/) | To deploy smart-contract to remotely in `Goerli` test network. |
188 | | [Yarn](https://yarnpkg.com/) | A package manager, to add & remove packages for the project as per need. |
189 |
190 | # How to run _(locally & remotely)_? 🏃♂️
191 |
192 | ## Pre-requisites 🛠️
193 |
194 | - Metamask wallet with some `GoerliETH`, fine even if had < 0.5 ETH _(for testing purposes)_.
195 | - Lacking GoerliETH ? -- get it free of 0.2 ETH/day at [Alchemy's Goerli faucet](https://goerlifaucet.com/) -- note that, this needs sign-up.
196 | - Clone this repo.
197 |
198 | _(ONLY for running remotely)_
199 |
200 | - Create an account on [Infura](https://infura.io), can also be on [Alchemy](https://www.alchemy.com/). \_(This project used Infura with `goerli-testnet`)
201 | - Create a new project & get an end-point of it.
202 | - Create a new file with name `.env.local` in project's root directory.
203 | - Store the API key as ..
204 |
205 | ```.env
206 | INFURA_API_KEY=
207 | PRIVATE_KEY=
208 | ```
209 |
210 | **How to get Private key?** _(In metamask wallet)_
211 |
212 | 1. Open your metamask wallet and choose the account of which you need the private key.
213 | 2. Goto **Account Options** _(3 dots at top-right)_ → **Account Details**.
214 | 3. Now click on **Export private Key** and enter your metamask password.
215 | 4. Get this and paste in `.env.local` file as `PRIVATE_KEY`.
216 |
217 | ## Running locally _(with **hardhat**)_ 🏃♂️
218 |
219 | _(Run these commands by being in project root directory)_
220 |
221 | - Install the project dependencies
222 | ```sh
223 | yarn dev
224 | ```
225 | - Run local hardhat network
226 | ```sh
227 | npx hardhat node
228 | ```
229 | **NOTE**
230 | - This command alone should be run in a separate terminal and as long as this keeps running, local blockchain network stays. Once gets quit, everything gets lost. Had to repeat all the transactions made.
231 | - Thus, following commands to be run in separate terminal.
232 |
233 | _Make changes to the smart-contract if needed._
234 | - Compile the smart contract via
235 | ```sh
236 | npx hardhat clean # If previously compiled and would like to discard
237 | npx hardhat compile
238 | ```
239 | - Deploy the smart contract
240 | ```sh
241 | npx hardhat run scripts/deploy.js --network localhost
242 | ```
243 | - This outputs the message as..
244 | ```
245 | Contract deployed to address:
246 | ```
247 | - Paste this address at `utils/contract/crowdHelp.js` as value for `crowdHelpContractAddress` variable.
248 |
249 | - Run the frontend
250 | ```sh
251 | yarn dev # or use "npm run dev"
252 | ```
253 | - This will run server on `http://127.0.0.1:5173`
254 |
255 | ### Making transactions with metamask and hardhat
256 | - Please refer [ChainStack: Using MetaMask with a Hardhat node](https://support.chainstack.com/hc/en-us/articles/4408642503449-Using-MetaMask-with-a-Hardhat-node); [metamask docs](https://docs.metamask.io/wallet/how-to/run-devnet/) to connect hardhat to metamask wallet.
257 | - Once connected, import the private key of 20 accounts provided by hardhat in metamask.
258 | - At least importing of 2 accounts is must, as the campaign creation and funding cannot happen from same account (considering the actual usecase, fund-raiser himself won't be funding his own campaign).
259 | - Now you are ready to test the features - Campaign creation, Funding etc.
260 |
261 | ## Running remotely 🏃♂️
262 |
263 | - Follow the above steps. Now just change the network as `goerli_testnet` while deploying.
264 | ```
265 | npx hardhat run scripts/deploy.js --network goerli_testnet
266 | ```
267 | - To run on different test-net..
268 | - Use different URL in `hardhat.config.js`.
269 |
270 | # References taken ⚓
271 |
272 | ## for understanding of crowdfunding idea with blockchain & developing smart-contract
273 |
274 | **Research papers** 📝
275 |
276 | **Web references** 🕸️
277 |
278 | ## for implementation 🎬
279 |
280 | _(Majorly...)_
281 |
282 | 1. [Betterfund - @harsh242](https://github.com/harsh242/betterfund-crowdfunding-in-blockchain)
283 |
284 | - for UI designs, smart-contract & connection.
285 |
286 | 2. [Crowdfunding-DAPP - @spandan114](https://github.com/spandan114/Crowdfunding-DAPP)
287 |
288 | - for upgrading the smart contract with latest version - v0.8.4.
289 | - running via `hardhat`, deploying locally & remotely.
290 |
291 | 3. [MUI](https://mui.com/)
292 |
293 | - their templates, for ready use & little customization.
294 | - for implementing various design components
295 |
296 | # Docs & reports 📘
297 |
298 | - Detailed Documentation PDF version - [_Earlier version_](https://drive.google.com/file/d/1k-kUZ3tw3iFYRNsqDxP8UjEcs0CnIu1K/view?usp=sharing), [_Latest with new changes_](https://drive.google.com/file/d/1AAaWbmMpCXoIaLle3ne3xcyUUrJ9TbNi/view?usp=sharing)
299 | - PPTs - [Review_1 - Introduction, SRS & Literature Survey](https://drive.google.com/file/d/1pClPlQjDtnnx3ibsLzgpA3nKgT4GuNIh/view?usp=sharing), [Review_2- System Design, Use cases & Sequence diagrams](https://drive.google.com/file/d/1L1RBqO5CgtdZDzJoeR5TE6tjPZieNk8L/view?usp=sharing), Review\*3 - Implementation screenshots, conclusion & future upgrades _(Not prepared, but all these are available in this README.md)_ - [Final Viva - concised version](https://drive.google.com/file/d/1hGgSI76YmqfYDj6Ptt-tszw0HTcuZstd/view?usp=sharing) _(Almost similar to Review-3)_
300 | - Design files - [done in app.diagrams.net _(formerly draw.io)_](https://drive.google.com/file/d/1Jc0BBMfM1klZfjZ2x4Wah0PLK0q1V8qP/view?usp=sharing)
301 |
302 | # Acknowledgements
303 |
304 | _(Specially)_
305 |
306 | - Finally, to Omniscient, without whom, this project doesn't even reach this stage. All is **HIS** grace, nothing is mine.
307 |
--------------------------------------------------------------------------------
/src/pages/campaigns/ViewCampaign.jsx:
--------------------------------------------------------------------------------
1 | // UI imports..
2 | import React from "react";
3 | import Stack from "@mui/material/Stack";
4 | import Divider from "@mui/material/Divider";
5 | import { Box, Button, fabClasses, styled } from "@mui/material";
6 | import { Container, Link, TextField, Typography } from "@mui/material";
7 | import LinearProgress from "@mui/material/LinearProgress";
8 | import Modal from "@mui/material/Modal";
9 | import FormGroup from "@mui/material/FormGroup";
10 | import FormControlLabel from "@mui/material/FormControlLabel";
11 | import Checkbox from "@mui/material/Checkbox";
12 | import Snackbar from "@mui/material/Snackbar";
13 | import Alert from "@mui/material/Alert";
14 | import AlertTitle from "@mui/material/AlertTitle";
15 | import Chip from "@mui/material/Chip";
16 | import { LoadingButton } from "@mui/lab";
17 |
18 | // local imports..
19 | import NavBar from "../../components/NavBar";
20 | // service imports..
21 | import axios from "axios";
22 | import { useEffect } from "react";
23 | // Wallet connection..
24 | import { useWallet } from "use-wallet";
25 | // form handling
26 | import { useForm } from "react-hook-form";
27 | // [block-chain] smart-contract related imports..
28 | import { getCampaignDetails } from "../../../utils/getCampaigns";
29 |
30 | // smart-contract interaction -- for contribution of funds, withdrawing money & ending campaign.
31 | import Campaign from "../../../utils/contract/campaign";
32 | import web3 from "../../../utils/web3";
33 |
34 | // stylings..
35 | const StyledModal = styled(Modal)({
36 | display: "flex",
37 | alignItems: "center",
38 | justifyContent: "center",
39 | });
40 |
41 | const api_url = "http://localhost:4000/api/";
42 |
43 | function ViewCampaign() {
44 | const campaignId = window.location.pathname.substring(10); // will get as '/campaign/xx`, trimmed to get ONLY the id.
45 |
46 | // hooks..
47 | const [responseMsg, setResponseMsg] = React.useState(""); // to display error messages.
48 | const [showResponse, setShowResponse] = React.useState(false); // To know whether error occured. ⁉ why not use length of error message
49 | const [responseSeverity, setResponseSeverity] = React.useState("error");
50 |
51 | const [abortCampaignMsg, setAbortCampaignMsg] = React.useState("");
52 |
53 | const [fundingAmount, setFundingAmount] = React.useState(0); // set this via ref's and onchange.
54 | const enteredAmountRef = React.useRef(0);
55 |
56 | const [campaignData, setCampaignData] = React.useState({});
57 | const wallet = useWallet();
58 |
59 | // for dealing with form values -- at contribution..
60 | const {
61 | handleSubmit: contributionHandleSubmit,
62 | register: contributionRegister,
63 | formState: contributionFormState,
64 | reset: contributionReset,
65 | } = useForm({
66 | mode: "onChange",
67 | });
68 |
69 | // for dealing with form values -- at abort campaign..
70 | const {
71 | handleSubmit: abortHandleSubmit,
72 | register: abortRegister,
73 | formState: abortFormState,
74 | reset: abortReset,
75 | } = useForm({
76 | mode: "onChange",
77 | });
78 |
79 | // for dealing with form values -- at withdraw raised funds..
80 | const {
81 | handleSubmit: withdrawHandleSubmit,
82 | register: withdrawRegister,
83 | formState: withdrawFormState,
84 | reset: withdrawReset,
85 | } = useForm({
86 | mode: "onChange",
87 | });
88 |
89 | const [isContributionSuccess, setIsContributionSuccess] =
90 | React.useState(false);
91 | const [contributionError, setContributionError] = React.useState("");
92 | const [abortingError, setAbortingError] = React.useState("");
93 | const [endAndWithdrawError, setEndAndWithdrawError] = React.useState("");
94 |
95 | // for testing purpose..
96 | const etherScanAddress = "0x4d496ccc28058b1d74b7a19541663e21154f9c84"; // some dummy address.
97 | // these are for the -- campaignData[] -- where usage of '.' operator isn't working.
98 | const minAmountKey = "minContribAmount";
99 | const raisedMoneyKey = "ethRaised";
100 |
101 | // hooks..
102 | const [showEndCampaignConfirmation, setShowEndCampaignConfirmation] =
103 | React.useState(false);
104 | const [acceptanceStatus, setAcceptanceStatus] = React.useState(false);
105 |
106 | useEffect(() => {
107 | console.log("fetching a campaign..");
108 | let ignore = false;
109 | // fetch the campaigns..
110 | const fetchData = async () => {
111 | await getCampaignDetails(campaignId).then((data) => {
112 | console.info(data);
113 | if (!ignore && data != undefined) setCampaignData(data);
114 | });
115 | };
116 |
117 | fetchData(); // call the function to fetch the data
118 |
119 | return () => {
120 | ignore = true; // to avoid rendering multiple times..
121 | };
122 | }, []);
123 |
124 | // helpers ..
125 | function LinearProgressWithLabel(props) {
126 | return (
127 |
128 |
129 |
130 |
131 |
132 | {`${Math.round(
133 | props.value
134 | )}%`}
135 |
136 |
137 | );
138 | }
139 |
140 | // async function abortCampaign() {
141 | const handleAbortCampaign = async (data) => {
142 | console.log("abort campaign called");
143 | console.log(data);
144 | if (data.acceptCondition === false && data.campaignAbortReason.length == 0)
145 | return;
146 |
147 | // proceed further, only when valid.
148 | console.log("about to perform aborting..");
149 | try {
150 | const campaign = Campaign(campaignData.id); // get the campaign
151 | const accounts = await web3.eth.getAccounts(); // backer account..
152 | await campaign.methods.abortCampaignAndRefund().send({
153 | from: accounts[0],
154 | });
155 |
156 | console.log("abort success");
157 | window.location.reload();
158 | // after successful abort..
159 | abortReset("", { keepValues: false }); // clear the values entered.
160 | } catch (err) {
161 | console.log(err);
162 | setAbortingError(err);
163 | }
164 | };
165 |
166 | const handleClose = (event, reason) => {
167 | if (reason === "clickaway") {
168 | return;
169 | }
170 | setShowResponse(false);
171 | };
172 |
173 | const handleAmountChange = (e) => {
174 | const balanceAmount = campaignData.ethRaised - campaignData.ethFunded;
175 | const value = e.target.value;
176 | const minValue = campaignData.minContribAmount;
177 | if (value < minValue) {
178 | setFundingAmount(minValue);
179 | enteredAmountRef.current.value = minValue;
180 | } else if (value > balanceAmount) {
181 | setFundingAmount(balanceAmount);
182 | enteredAmountRef.current.value = balanceAmount;
183 | } else setFundingAmount(value);
184 | };
185 |
186 | const handleContributedFunds = async (data) => {
187 | console.info("handle contribute funds called");
188 | console.log(data);
189 |
190 | try {
191 | if (data.contribAmount < campaignData.minContribAmount)
192 | throw {
193 | name: "Invalid contribution amount. Transaction aborted",
194 | message: `Contribution cannot be < ${campaignData.minContribAmount}.`,
195 | };
196 |
197 | const campaign = Campaign(campaignData.id); // get the campaign
198 | const accounts = await web3.eth.getAccounts(); // backer account..
199 | await campaign.methods.contribute().send({
200 | // register contribution..
201 | from: accounts[0],
202 | value: web3.utils.toWei(data.contribAmount, "ether"),
203 | });
204 |
205 | // after successful contribution..
206 | contributionReset("", { keepValues: false }); // clear the values entered.
207 | setIsContributionSuccess(true);
208 | } catch (err) {
209 | console.log(err);
210 | setContributionError(err);
211 | }
212 | };
213 |
214 | const handleEndAndWithdraw = async (data) => {
215 | console.log("Withdraw called");
216 | try {
217 | const campaign = Campaign(campaignData.id); // get the campaign
218 | const accounts = await web3.eth.getAccounts(); // backer account..
219 | await campaign.methods.endCampaignAndCredit().send({
220 | from: accounts[0],
221 | });
222 |
223 | // after successful end & credit..
224 | console.log("Funds credited successfully to fundraiser's wallet.");
225 | window.location.reload();
226 | } catch (err) {
227 | console.log(err);
228 | setEndAndWithdrawError(err);
229 | }
230 | };
231 |
232 | // components..
233 | // other modules..
234 | function ShowCampaignDetails() {
235 | return (
236 | <>
237 |
238 |
239 | About Campaign
240 |
241 |
242 |
247 |
248 | {campaignData.title}
249 |
250 |
260 |
261 |
262 | {campaignData.description}
263 |
264 |
265 |
269 | View on Goerli Etherscan
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | >
278 | );
279 | }
280 |
281 | function ShowContributionDetails() {
282 | return (
283 | <>
284 | Contribution Details
285 |
286 | Minimum Contribution amount
287 | {campaignData.minContribAmount} ETH
288 |
289 |
290 | Goal
291 | {`${campaignData.ethRaised}`} ETH
292 |
293 |
294 |
295 | Wallet Address of FundRaiser
296 |
297 | {`${campaignData.createdBy}`}
298 |
299 |
300 |
301 | Contributions are accepted till (Deadline)
302 |
303 | {`${new Date(campaignData.deadline)}`}
304 | {(campaignData.campaignStatus == "EXPIRED" ||
305 | campaignData.campaignStatus == "ABORTED") && (
306 |
307 | No contributions can be accepted now.
308 |
309 | )}
310 |
311 | >
312 | );
313 | }
314 |
315 | function ShowCampaignBalance() {
316 | return (
317 | <>
318 |
322 | Campaign balance
323 |
324 | Amount stored in smart contract.
325 |
326 |
329 |
330 | {`${campaignData.ethFunded}`} ETH funded by{" "}
331 | {`${campaignData.backersCount}`} backers.
332 |
333 |
334 | >
335 | );
336 | }
337 |
338 | function BecomeBacker() {
339 | return (
340 |
345 |
346 | Be a backer
347 |
348 |
349 | How much would you like to fund?
350 |
351 |
356 | ≥ {campaignData.minContribAmount} ETH & ≤{" "}
357 | {campaignData.ethRaised - campaignData.ethFunded} ETH
358 |
359 |
360 | {isContributionSuccess == true ? (
361 | {
365 | setIsContributionSuccess(false);
366 | window.location.reload();
367 | }}
368 | >
369 | Funded successfully
370 | Thanks for your valuable contributions.
371 |
372 | ) : (
373 |
429 | )}
430 |
431 |
432 | Scheme - All or Nothing.
433 |
434 |
435 | The money you fund, will be stored in smart contract that you can
436 | trust. Your money gets refunded in-case if the project doesn't reach
437 | goal or cancelled in-between, and transferred if reached goal.
438 |
439 |
440 |
441 | );
442 | }
443 |
444 | function WithDrawFunds() {
445 | return (
446 |
451 |
452 | Withdraw Raised Funds
453 |
454 | {/* -------- Here.. based on the deadline of the campaign, validate can withdraw or not. */}
455 |
456 | To withdraw raised funds, campaign has to be ended
457 |
458 | {endAndWithdrawError && (
459 | {
463 | setEndAndWithdrawError(""); // erase the error msg.
464 | window.location.reload(); // re-load the page to get the updated status
465 | }}
466 | >
467 | {endAndWithdrawError.name}
468 | {endAndWithdrawError.message}
469 |
470 | )}
471 |
498 |
499 | );
500 | }
501 |
502 | function EndCampaign() {
503 | return (
504 | <>
505 | {wallet.account === campaignData.createdBy &&
506 | (campaignData.campaignStatus === "ACTIVE" ||
507 | campaignData.campaignStatus === "SUCCESS") && (
508 | <>
509 | Danger Zone
510 |
517 |
518 |
519 |
520 | Quit & Refund
521 |
522 |
523 | Once you end a campaign, there is no going back. Please be
524 | certain.
525 |
526 |
527 | setShowEndCampaignConfirmation(true)}
533 | >
534 | Abort campaign
535 |
536 |
537 |
538 | >
539 | )}
540 | >
541 | );
542 | }
543 |
544 | function EndCampaignDialog() {
545 | return (
546 | <>
547 | setShowEndCampaignConfirmation(false)}
550 | >
551 |
565 |
571 | Abort Campaign
572 |
573 | {abortingError && (
574 | {
578 | setAbortingError(""); // erase the error msg.
579 | window.location.reload(); // re-load the page to get the updated status
580 | }}
581 | >
582 | {abortingError.name}
583 | {abortingError.message}
584 |
585 | )}
586 |
634 |
635 |
636 | >
637 | );
638 | }
639 |
640 | return (
641 | <>
642 |
643 | }
646 | spacing={2}
647 | justifyContent="space-around"
648 | alignItems="baseline"
649 | >
650 |
651 |
652 |
653 |
654 | Current Status of campaign
655 |
656 |
657 | {campaignData.campaignStatus === "ACTIVE" ||
658 | campaignData.campaignStatus == "SUCCESS" ? (
659 | <>
660 | {wallet.account !== campaignData.createdBy ? (
661 | <>
662 |
663 | Contribute
664 |
665 |
666 | >
667 | ) : (
668 | <>
669 |
670 | Withdraw
671 |
672 |
673 | >
674 | )}
675 | >
676 | ) : (
677 | <>
678 |
679 | End status
680 |
681 |
682 |
683 | {campaignData.campaignStatus == "EXPIRED"
684 | ? "Campaign has ended successfully..!!"
685 | : "Campign has aborted in between. "}
686 |
687 |
688 | >
689 | )}
690 |
691 |
692 |
693 |
694 |
700 |
701 | {responseMsg}
702 |
703 |
704 | >
705 | );
706 | }
707 |
708 | export default ViewCampaign;
709 |
--------------------------------------------------------------------------------
/artifacts/contracts/Campaign.sol/Campaign.json:
--------------------------------------------------------------------------------
1 | {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "Campaign",
4 | "sourceName": "contracts/Campaign.sol",
5 | "abi": [
6 | {
7 | "inputs": [
8 | {
9 | "internalType": "address",
10 | "name": "_creator",
11 | "type": "address"
12 | },
13 | {
14 | "internalType": "uint256",
15 | "name": "_minimumContribution",
16 | "type": "uint256"
17 | },
18 | {
19 | "internalType": "uint256",
20 | "name": "_deadline",
21 | "type": "uint256"
22 | },
23 | {
24 | "internalType": "uint256",
25 | "name": "_targetContribution",
26 | "type": "uint256"
27 | },
28 | {
29 | "internalType": "string",
30 | "name": "_projectTitle",
31 | "type": "string"
32 | },
33 | {
34 | "internalType": "string",
35 | "name": "_projectDes",
36 | "type": "string"
37 | },
38 | {
39 | "internalType": "string",
40 | "name": "_bannerUrl",
41 | "type": "string"
42 | },
43 | {
44 | "internalType": "uint256",
45 | "name": "_campaignSchemeId",
46 | "type": "uint256"
47 | }
48 | ],
49 | "stateMutability": "nonpayable",
50 | "type": "constructor"
51 | },
52 | {
53 | "anonymous": false,
54 | "inputs": [
55 | {
56 | "indexed": false,
57 | "internalType": "address",
58 | "name": "contributor",
59 | "type": "address"
60 | },
61 | {
62 | "indexed": false,
63 | "internalType": "uint256",
64 | "name": "amountTotal",
65 | "type": "uint256"
66 | }
67 | ],
68 | "name": "AmountCredited",
69 | "type": "event"
70 | },
71 | {
72 | "anonymous": false,
73 | "inputs": [
74 | {
75 | "indexed": false,
76 | "internalType": "uint256",
77 | "name": "noOfContributors",
78 | "type": "uint256"
79 | },
80 | {
81 | "indexed": false,
82 | "internalType": "uint256",
83 | "name": "amountTotal",
84 | "type": "uint256"
85 | }
86 | ],
87 | "name": "AmountRefunded",
88 | "type": "event"
89 | },
90 | {
91 | "anonymous": false,
92 | "inputs": [
93 | {
94 | "indexed": false,
95 | "internalType": "uint256",
96 | "name": "deadline",
97 | "type": "uint256"
98 | },
99 | {
100 | "indexed": false,
101 | "internalType": "uint256",
102 | "name": "blockTime",
103 | "type": "uint256"
104 | }
105 | ],
106 | "name": "CampaignCreated",
107 | "type": "event"
108 | },
109 | {
110 | "anonymous": false,
111 | "inputs": [
112 | {
113 | "indexed": false,
114 | "internalType": "address",
115 | "name": "contributor",
116 | "type": "address"
117 | },
118 | {
119 | "indexed": false,
120 | "internalType": "uint256",
121 | "name": "amount",
122 | "type": "uint256"
123 | },
124 | {
125 | "indexed": false,
126 | "internalType": "uint256",
127 | "name": "currentTotal",
128 | "type": "uint256"
129 | }
130 | ],
131 | "name": "FundingReceived",
132 | "type": "event"
133 | },
134 | {
135 | "inputs": [],
136 | "name": "abortCampaignAndRefund",
137 | "outputs": [],
138 | "stateMutability": "payable",
139 | "type": "function"
140 | },
141 | {
142 | "inputs": [],
143 | "name": "bannerUrl",
144 | "outputs": [
145 | {
146 | "internalType": "string",
147 | "name": "",
148 | "type": "string"
149 | }
150 | ],
151 | "stateMutability": "view",
152 | "type": "function"
153 | },
154 | {
155 | "inputs": [],
156 | "name": "campaignSchemeId",
157 | "outputs": [
158 | {
159 | "internalType": "enum CampaignSchemeId",
160 | "name": "",
161 | "type": "uint8"
162 | }
163 | ],
164 | "stateMutability": "view",
165 | "type": "function"
166 | },
167 | {
168 | "inputs": [],
169 | "name": "contribute",
170 | "outputs": [],
171 | "stateMutability": "payable",
172 | "type": "function"
173 | },
174 | {
175 | "inputs": [],
176 | "name": "creator",
177 | "outputs": [
178 | {
179 | "internalType": "address payable",
180 | "name": "",
181 | "type": "address"
182 | }
183 | ],
184 | "stateMutability": "view",
185 | "type": "function"
186 | },
187 | {
188 | "inputs": [],
189 | "name": "deadline",
190 | "outputs": [
191 | {
192 | "internalType": "uint256",
193 | "name": "",
194 | "type": "uint256"
195 | }
196 | ],
197 | "stateMutability": "view",
198 | "type": "function"
199 | },
200 | {
201 | "inputs": [],
202 | "name": "endCampaignAndCredit",
203 | "outputs": [],
204 | "stateMutability": "payable",
205 | "type": "function"
206 | },
207 | {
208 | "inputs": [],
209 | "name": "getCampaignSummary",
210 | "outputs": [
211 | {
212 | "internalType": "address payable",
213 | "name": "projectStarter",
214 | "type": "address"
215 | },
216 | {
217 | "internalType": "uint256",
218 | "name": "minContribution",
219 | "type": "uint256"
220 | },
221 | {
222 | "internalType": "uint256",
223 | "name": "projectDeadline",
224 | "type": "uint256"
225 | },
226 | {
227 | "internalType": "uint256",
228 | "name": "goalAmount",
229 | "type": "uint256"
230 | },
231 | {
232 | "internalType": "uint256",
233 | "name": "completedTime",
234 | "type": "uint256"
235 | },
236 | {
237 | "internalType": "uint256",
238 | "name": "currentAmount",
239 | "type": "uint256"
240 | },
241 | {
242 | "internalType": "string",
243 | "name": "title",
244 | "type": "string"
245 | },
246 | {
247 | "internalType": "string",
248 | "name": "desc",
249 | "type": "string"
250 | },
251 | {
252 | "internalType": "enum Campaign.State",
253 | "name": "currentState",
254 | "type": "uint8"
255 | },
256 | {
257 | "internalType": "uint256",
258 | "name": "balance",
259 | "type": "uint256"
260 | },
261 | {
262 | "internalType": "string",
263 | "name": "imageUrl",
264 | "type": "string"
265 | },
266 | {
267 | "internalType": "uint256",
268 | "name": "numBackers",
269 | "type": "uint256"
270 | },
271 | {
272 | "internalType": "uint256",
273 | "name": "schemeId",
274 | "type": "uint256"
275 | }
276 | ],
277 | "stateMutability": "view",
278 | "type": "function"
279 | },
280 | {
281 | "inputs": [],
282 | "name": "getContractBalance",
283 | "outputs": [
284 | {
285 | "internalType": "uint256",
286 | "name": "",
287 | "type": "uint256"
288 | }
289 | ],
290 | "stateMutability": "view",
291 | "type": "function"
292 | },
293 | {
294 | "inputs": [],
295 | "name": "minimumContribution",
296 | "outputs": [
297 | {
298 | "internalType": "uint256",
299 | "name": "",
300 | "type": "uint256"
301 | }
302 | ],
303 | "stateMutability": "view",
304 | "type": "function"
305 | },
306 | {
307 | "inputs": [],
308 | "name": "noOfContributors",
309 | "outputs": [
310 | {
311 | "internalType": "uint256",
312 | "name": "",
313 | "type": "uint256"
314 | }
315 | ],
316 | "stateMutability": "view",
317 | "type": "function"
318 | },
319 | {
320 | "inputs": [],
321 | "name": "projectDes",
322 | "outputs": [
323 | {
324 | "internalType": "string",
325 | "name": "",
326 | "type": "string"
327 | }
328 | ],
329 | "stateMutability": "view",
330 | "type": "function"
331 | },
332 | {
333 | "inputs": [],
334 | "name": "projectTitle",
335 | "outputs": [
336 | {
337 | "internalType": "string",
338 | "name": "",
339 | "type": "string"
340 | }
341 | ],
342 | "stateMutability": "view",
343 | "type": "function"
344 | },
345 | {
346 | "inputs": [],
347 | "name": "raisedAmount",
348 | "outputs": [
349 | {
350 | "internalType": "uint256",
351 | "name": "",
352 | "type": "uint256"
353 | }
354 | ],
355 | "stateMutability": "view",
356 | "type": "function"
357 | },
358 | {
359 | "inputs": [],
360 | "name": "reachedTargetAt",
361 | "outputs": [
362 | {
363 | "internalType": "uint256",
364 | "name": "",
365 | "type": "uint256"
366 | }
367 | ],
368 | "stateMutability": "view",
369 | "type": "function"
370 | },
371 | {
372 | "inputs": [],
373 | "name": "state",
374 | "outputs": [
375 | {
376 | "internalType": "enum Campaign.State",
377 | "name": "",
378 | "type": "uint8"
379 | }
380 | ],
381 | "stateMutability": "view",
382 | "type": "function"
383 | },
384 | {
385 | "inputs": [],
386 | "name": "targetContribution",
387 | "outputs": [
388 | {
389 | "internalType": "uint256",
390 | "name": "",
391 | "type": "uint256"
392 | }
393 | ],
394 | "stateMutability": "view",
395 | "type": "function"
396 | }
397 | ],
398 | "bytecode": "0x6080604052600a805460ff191690553480156200001b57600080fd5b5060405162001845380380620018458339810160408190526200003e916200043c565b7fdf1ccadd90c8987aa73568b11e0c57744db13dccf98de0a82f2b9bc6edb3d10486426040516200007192919062000652565b60405180910390a160028110620000a55760405162461bcd60e51b81526004016200009c9062000627565b60405180910390fd5b620000e46040518060400160405280601081526020016f213637b1b5903a34b6b2b9ba30b6b81d60811b815250426200024660201b62000b261760201c565b620001226040518060400160405280600f81526020016e23b4bb32b7103232b0b23634b7329d60891b815250876200024660201b62000b261760201c565b428611620001445760405162461bcd60e51b81526004016200009c90620005f0565b60008711620001675760405162461bcd60e51b81526004016200009c9062000542565b600085116200018a5760405162461bcd60e51b81526004016200009c906200058d565b600080546001600160a01b0319166001600160a01b038a161790556001879055600286905560038590558351620001c9906007906020870190620002f5565b508251620001df906008906020860190620002f5565b5060006005558151620001fa906009906020850190620002f5565b50620002068162000297565b600a805461ff0019166101008360018111156200023357634e487b7160e01b600052602160045260246000fd5b02179055505050505050505050620006e6565b6200029382826040516024016200025f92919062000505565b60408051601f198184030181529190526020810180516001600160e01b03908116632d839cb360e21b17909152620002d416565b5050565b600060028210620002bc5760405162461bcd60e51b81526004016200009c90620005c4565b81620002cb57506000620002cf565b5060015b919050565b80516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b828054620003039062000693565b90600052602060002090601f01602090048101928262000327576000855562000372565b82601f106200034257805160ff191683800117855562000372565b8280016001018555821562000372579182015b828111156200037257825182559160200191906001019062000355565b506200038092915062000384565b5090565b5b8082111562000380576000815560010162000385565b80516001600160a01b0381168114620002cf57600080fd5b600082601f830112620003c4578081fd5b81516001600160401b0380821115620003e157620003e1620006d0565b604051601f8301601f191681016020018281118282101715620004085762000408620006d0565b60405282815284830160200186101562000420578384fd5b6200043383602083016020880162000660565b95945050505050565b600080600080600080600080610100898b03121562000459578384fd5b62000464896200039b565b60208a015160408b015160608c015160808d0151939b50919950975095506001600160401b038082111562000497578586fd5b620004a58c838d01620003b3565b955060a08b0151915080821115620004bb578485fd5b620004c98c838d01620003b3565b945060c08b0151915080821115620004df578384fd5b50620004ee8b828c01620003b3565b92505060e089015190509295985092959890939650565b60006040825283518060408401526200052681606085016020880162000660565b602083019390935250601f91909101601f191601606001919050565b6020808252602b908201527f4d696e696d756d20636f6e747269627574696f6e206d7573742062652067726560408201526a061746572207468616e20360ac1b606082015260800190565b6020808252601d908201527f546172676574206d7573742062652067726561746572207468616e2030000000604082015260600190565b602080825260129082015271496e646578206f7574206f662072616e676560701b604082015260600190565b6020808252601e908201527f446561646c696e65206d75737420626520696e20746865206675747572650000604082015260600190565b602080825260119082015270125b9d985b1a59081cd8da195b59481a59607a1b604082015260600190565b918252602082015260400190565b60005b838110156200067d57818101518382015260200162000663565b838111156200068d576000848401525b50505050565b600281046001821680620006a857607f821691505b60208210811415620006ca57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b61114f80620006f66000396000f3fe6080604052600436106100fe5760003560e01c80638d27289e11610095578063b82c86d611610064578063b82c86d61461023d578063c19d93fb14610252578063c3e20c9f14610274578063c59ee1dc14610289578063d7bb99ba1461029e576100fe565b80638d27289e146101dd578063937e09b1146101e55780639d43b9b5146101fa578063a00c96d71461020f576100fe565b80634e260f6f116100d15780634e260f6f1461017c5780636eef7d95146101915780636f9fb98a146101b35780637a9a8055146101c8576100fe565b806302d05d3f1461010357806303c29d581461012e57806329dcb0cf146101505780633d0300bd14610172575b600080fd5b34801561010f57600080fd5b506101186102a6565b6040516101259190610c90565b60405180910390f35b34801561013a57600080fd5b506101436102b5565b6040516101259190610da8565b34801561015c57600080fd5b50610165610343565b6040516101259190611053565b61017a610349565b005b34801561018857600080fd5b50610165610474565b34801561019d57600080fd5b506101a661047a565b6040516101259190610d7a565b3480156101bf57600080fd5b50610165610488565b3480156101d457600080fd5b5061014361048c565b61017a610499565b3480156101f157600080fd5b5061016561069f565b34801561020657600080fd5b506101656106a5565b34801561021b57600080fd5b506102246106ab565b6040516101259d9c9b9a99989796959493929190610cde565b34801561024957600080fd5b506101656108e0565b34801561025e57600080fd5b506102676108e6565b6040516101259190610d94565b34801561028057600080fd5b506101436108ef565b34801561029557600080fd5b506101656108fc565b61017a610902565b6000546001600160a01b031681565b600880546102c290611082565b80601f01602080910402602001604051908101604052809291908181526020018280546102ee90611082565b801561033b5780601f106103105761010080835404028352916020019161033b565b820191906000526020600020905b81548152906001019060200180831161031e57829003601f168201915b505050505081565b60025481565b6000546001600160a01b0316331461037c5760405162461bcd60e51b815260040161037390610e78565b60405180910390fd5b6001600a5460ff1660038111156103a357634e487b7160e01b600052602160045260246000fd5b146103c05760405162461bcd60e51b815260040161037390610ec8565b60025442106103e15760405162461bcd60e51b815260040161037390610de4565b600080546005546040516001600160a01b039092169281156108fc029290818181858888f1935050505015801561041c573d6000803e3d6000fd5b50600a805460ff191660021790556000546005546040517fc364254574c58e1bdd9077b0e35e609ee391ca636748a275674d5fb5bdc1d3f69261046a926001600160a01b0390911691610ca4565b60405180910390a1565b60065481565b600a54610100900460ff1681565b4790565b600980546102c290611082565b6000546001600160a01b031633146104c35760405162461bcd60e51b815260040161037390610e78565b60025442106104e45760405162461bcd60e51b815260040161037390611007565b6000600a5460ff16600381111561050b57634e487b7160e01b600052602160045260246000fd5b148061053b57506001600a5460ff16600381111561053957634e487b7160e01b600052602160045260246000fd5b145b6105575760405162461bcd60e51b815260040161037390610e33565b60005b60065481101561065b57600b818154811061058557634e487b7160e01b600052603260045260246000fd5b6000918252602090912060029091020154600b80546001600160a01b03909216916108fc9190849081106105c957634e487b7160e01b600052603260045260246000fd5b9060005260206000209060020201600101549081150290604051600060405180830381858888f19350505050158015610606573d6000803e3d6000fd5b50600b818154811061062857634e487b7160e01b600052603260045260246000fd5b60009182526020822060029091020180546001600160a01b03191681556001015580610653816110dd565b91505061055a565b50600a805460ff191660031790556006546005546040517fd69eae259b3e1e42f22f60a8a54dbf4d1983d006a10d5f36c068ccc87eb493919261046a92909161105c565b60015481565b60045481565b60008054600154600254600354600454600554600780546001600160a01b0390971697959694959394929391926060928392909182918491839182916106f090611082565b80601f016020809104026020016040519081016040528092919081815260200182805461071c90611082565b80156107695780601f1061073e57610100808354040283529160200191610769565b820191906000526020600020905b81548152906001019060200180831161074c57829003601f168201915b505050505096506008805461077d90611082565b80601f01602080910402602001604051908101604052809291908181526020018280546107a990611082565b80156107f65780601f106107cb576101008083540402835291602001916107f6565b820191906000526020600020905b8154815290600101906020018083116107d957829003601f168201915b5050600a5460098054959b5060ff90911699504798509361081b935091506110829050565b80601f016020809104026020016040519081016040528092919081815260200182805461084790611082565b80156108945780601f1061086957610100808354040283529160200191610894565b820191906000526020600020905b81548152906001019060200180831161087757829003601f168201915b505050505092506006549150600a60019054906101000a900460ff1660018111156108cf57634e487b7160e01b600052602160045260246000fd5b9050909192939495969798999a9b9c565b60035481565b600a5460ff1681565b600780546102c290611082565b60055481565b6000600a5460ff16600381111561092957634e487b7160e01b600052602160045260246000fd5b148061095957506001600a5460ff16600381111561095757634e487b7160e01b600052602160045260246000fd5b145b6109755760405162461bcd60e51b815260040161037390610f3d565b60025442106109965760405162461bcd60e51b815260040161037390610f99565b6001543410156109b85760405162461bcd60e51b815260040161037390610f64565b3360006109c482610b6b565b9050806000191415610a7e57600680549060006109e0836110dd565b9091555050604080518082019091526001600160a01b0383811682523460208301908152600b805460018101825560009190915292517f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600290940293840180546001600160a01b0319169190931617909155517f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba90910155610ac6565b34600b8281548110610aa057634e487b7160e01b600052603260045260246000fd5b90600052602060002090600202016001016000828254610ac0919061106a565b90915550505b3460056000828254610ad8919061106a565b90915550506005546040517f5cdc4ab8d637538ead6cc1904d4160343656c8c82bd266635cfd31ac008fffe091610b129185913491610cbd565b60405180910390a1610b22610be2565b5050565b610b228282604051602401610b3c929190610dc2565b60408051601f198184030181529190526020810180516001600160e01b0316632d839cb360e21b179052610c10565b6000600019815b600654811215610bdb57600b8181548110610b9d57634e487b7160e01b600052603260045260246000fd5b60009182526020909120600290910201546001600160a01b0385811691161415610bc957809150610bdb565b80610bd3816110bd565b915050610b72565b5092915050565b60035460055410158015610bf7575060025442105b15610c0e57600a805460ff19166001179055426004555b565b80516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60048110610c4157610c41611103565b9052565b60008151808452815b81811015610c6a57602081850181015186830182015201610c4e565b81811115610c7b5782602083870101525b50601f01601f19169290920160200192915050565b6001600160a01b0391909116815260200190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b039390931683526020830191909152604082015260600190565b600060018060a01b038f1682528d60208301528c60408301528b60608301528a60808301528960a08301526101a060c0830152610d1f6101a083018a610c45565b82810360e0840152610d31818a610c45565b9050610d41610100840189610c31565b86610120840152828103610140840152610d5b8187610c45565b610160840195909552505061018001529b9a5050505050505050505050565b6020810160028310610d8e57610d8e611103565b91905290565b60208101610da28284610c31565b92915050565b600060208252610dbb6020830184610c45565b9392505050565b600060408252610dd56040830185610c45565b90508260208301529392505050565b6020808252602f908201527f43616d706169676e2063616e6e6f7420626520656e6465642c20646561646c6960408201526e3732903737ba103932b0b1b432b21760891b606082015260800190565b60208082526025908201527f496e76616c69642073746174652e2043616e6e6f742061626f72742063616d7060408201526430b4b3b71760d91b606082015260800190565b60208082526030908201527f596f7520646f6e7420686176652061636365737320746f20706572666f726d2060408201526f74686973206f7065726174696f6e202160801b606082015260800190565b6020808252604f908201527f476f616c206e6f7420726561636865642c20616d6f756e742063616e6e6f742060408201527f62652077697468647261776e2e20506c656173652061626f727420746f20726560608201526e66756e6420746f206261636b65727360881b608082015260a00190565b6020808252600d908201526c496e76616c696420737461746560981b604082015260600190565b6020808252818101527f436f6e747269627574696f6e20616d6f756e7420697320746f6f206c6f772021604082015260600190565b60208082526048908201527f536f727279206261636b65722c20646561646c696e652068617320706173736560408201527f6421204e6f20636f6e747269627574696f6e732063616e2062652061636365706060820152673a32b2103737bb9760c11b608082015260a00190565b6020808252602c908201527f43616d706169676e2063616e6e6f742062652061626f727465642c206465616460408201526b3634b732903830b9b9b2b21760a11b606082015260800190565b90815260200190565b918252602082015260400190565b6000821982111561107d5761107d6110ed565b500190565b60028104600182168061109657607f821691505b602082108114156110b757634e487b7160e01b600052602260045260246000fd5b50919050565b60006001600160ff1b038214156110d6576110d66110ed565b5060010190565b60006000198214156110d6576110d65b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fdfea264697066735822122045950afbcb708d931ee1510b40790c32bc61cb04db09c55ae3e5e3e680b1925a64736f6c63430008000033",
399 | "deployedBytecode": "0x6080604052600436106100fe5760003560e01c80638d27289e11610095578063b82c86d611610064578063b82c86d61461023d578063c19d93fb14610252578063c3e20c9f14610274578063c59ee1dc14610289578063d7bb99ba1461029e576100fe565b80638d27289e146101dd578063937e09b1146101e55780639d43b9b5146101fa578063a00c96d71461020f576100fe565b80634e260f6f116100d15780634e260f6f1461017c5780636eef7d95146101915780636f9fb98a146101b35780637a9a8055146101c8576100fe565b806302d05d3f1461010357806303c29d581461012e57806329dcb0cf146101505780633d0300bd14610172575b600080fd5b34801561010f57600080fd5b506101186102a6565b6040516101259190610c90565b60405180910390f35b34801561013a57600080fd5b506101436102b5565b6040516101259190610da8565b34801561015c57600080fd5b50610165610343565b6040516101259190611053565b61017a610349565b005b34801561018857600080fd5b50610165610474565b34801561019d57600080fd5b506101a661047a565b6040516101259190610d7a565b3480156101bf57600080fd5b50610165610488565b3480156101d457600080fd5b5061014361048c565b61017a610499565b3480156101f157600080fd5b5061016561069f565b34801561020657600080fd5b506101656106a5565b34801561021b57600080fd5b506102246106ab565b6040516101259d9c9b9a99989796959493929190610cde565b34801561024957600080fd5b506101656108e0565b34801561025e57600080fd5b506102676108e6565b6040516101259190610d94565b34801561028057600080fd5b506101436108ef565b34801561029557600080fd5b506101656108fc565b61017a610902565b6000546001600160a01b031681565b600880546102c290611082565b80601f01602080910402602001604051908101604052809291908181526020018280546102ee90611082565b801561033b5780601f106103105761010080835404028352916020019161033b565b820191906000526020600020905b81548152906001019060200180831161031e57829003601f168201915b505050505081565b60025481565b6000546001600160a01b0316331461037c5760405162461bcd60e51b815260040161037390610e78565b60405180910390fd5b6001600a5460ff1660038111156103a357634e487b7160e01b600052602160045260246000fd5b146103c05760405162461bcd60e51b815260040161037390610ec8565b60025442106103e15760405162461bcd60e51b815260040161037390610de4565b600080546005546040516001600160a01b039092169281156108fc029290818181858888f1935050505015801561041c573d6000803e3d6000fd5b50600a805460ff191660021790556000546005546040517fc364254574c58e1bdd9077b0e35e609ee391ca636748a275674d5fb5bdc1d3f69261046a926001600160a01b0390911691610ca4565b60405180910390a1565b60065481565b600a54610100900460ff1681565b4790565b600980546102c290611082565b6000546001600160a01b031633146104c35760405162461bcd60e51b815260040161037390610e78565b60025442106104e45760405162461bcd60e51b815260040161037390611007565b6000600a5460ff16600381111561050b57634e487b7160e01b600052602160045260246000fd5b148061053b57506001600a5460ff16600381111561053957634e487b7160e01b600052602160045260246000fd5b145b6105575760405162461bcd60e51b815260040161037390610e33565b60005b60065481101561065b57600b818154811061058557634e487b7160e01b600052603260045260246000fd5b6000918252602090912060029091020154600b80546001600160a01b03909216916108fc9190849081106105c957634e487b7160e01b600052603260045260246000fd5b9060005260206000209060020201600101549081150290604051600060405180830381858888f19350505050158015610606573d6000803e3d6000fd5b50600b818154811061062857634e487b7160e01b600052603260045260246000fd5b60009182526020822060029091020180546001600160a01b03191681556001015580610653816110dd565b91505061055a565b50600a805460ff191660031790556006546005546040517fd69eae259b3e1e42f22f60a8a54dbf4d1983d006a10d5f36c068ccc87eb493919261046a92909161105c565b60015481565b60045481565b60008054600154600254600354600454600554600780546001600160a01b0390971697959694959394929391926060928392909182918491839182916106f090611082565b80601f016020809104026020016040519081016040528092919081815260200182805461071c90611082565b80156107695780601f1061073e57610100808354040283529160200191610769565b820191906000526020600020905b81548152906001019060200180831161074c57829003601f168201915b505050505096506008805461077d90611082565b80601f01602080910402602001604051908101604052809291908181526020018280546107a990611082565b80156107f65780601f106107cb576101008083540402835291602001916107f6565b820191906000526020600020905b8154815290600101906020018083116107d957829003601f168201915b5050600a5460098054959b5060ff90911699504798509361081b935091506110829050565b80601f016020809104026020016040519081016040528092919081815260200182805461084790611082565b80156108945780601f1061086957610100808354040283529160200191610894565b820191906000526020600020905b81548152906001019060200180831161087757829003601f168201915b505050505092506006549150600a60019054906101000a900460ff1660018111156108cf57634e487b7160e01b600052602160045260246000fd5b9050909192939495969798999a9b9c565b60035481565b600a5460ff1681565b600780546102c290611082565b60055481565b6000600a5460ff16600381111561092957634e487b7160e01b600052602160045260246000fd5b148061095957506001600a5460ff16600381111561095757634e487b7160e01b600052602160045260246000fd5b145b6109755760405162461bcd60e51b815260040161037390610f3d565b60025442106109965760405162461bcd60e51b815260040161037390610f99565b6001543410156109b85760405162461bcd60e51b815260040161037390610f64565b3360006109c482610b6b565b9050806000191415610a7e57600680549060006109e0836110dd565b9091555050604080518082019091526001600160a01b0383811682523460208301908152600b805460018101825560009190915292517f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600290940293840180546001600160a01b0319169190931617909155517f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba90910155610ac6565b34600b8281548110610aa057634e487b7160e01b600052603260045260246000fd5b90600052602060002090600202016001016000828254610ac0919061106a565b90915550505b3460056000828254610ad8919061106a565b90915550506005546040517f5cdc4ab8d637538ead6cc1904d4160343656c8c82bd266635cfd31ac008fffe091610b129185913491610cbd565b60405180910390a1610b22610be2565b5050565b610b228282604051602401610b3c929190610dc2565b60408051601f198184030181529190526020810180516001600160e01b0316632d839cb360e21b179052610c10565b6000600019815b600654811215610bdb57600b8181548110610b9d57634e487b7160e01b600052603260045260246000fd5b60009182526020909120600290910201546001600160a01b0385811691161415610bc957809150610bdb565b80610bd3816110bd565b915050610b72565b5092915050565b60035460055410158015610bf7575060025442105b15610c0e57600a805460ff19166001179055426004555b565b80516a636f6e736f6c652e6c6f67602083016000808483855afa5050505050565b60048110610c4157610c41611103565b9052565b60008151808452815b81811015610c6a57602081850181015186830182015201610c4e565b81811115610c7b5782602083870101525b50601f01601f19169290920160200192915050565b6001600160a01b0391909116815260200190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b039390931683526020830191909152604082015260600190565b600060018060a01b038f1682528d60208301528c60408301528b60608301528a60808301528960a08301526101a060c0830152610d1f6101a083018a610c45565b82810360e0840152610d31818a610c45565b9050610d41610100840189610c31565b86610120840152828103610140840152610d5b8187610c45565b610160840195909552505061018001529b9a5050505050505050505050565b6020810160028310610d8e57610d8e611103565b91905290565b60208101610da28284610c31565b92915050565b600060208252610dbb6020830184610c45565b9392505050565b600060408252610dd56040830185610c45565b90508260208301529392505050565b6020808252602f908201527f43616d706169676e2063616e6e6f7420626520656e6465642c20646561646c6960408201526e3732903737ba103932b0b1b432b21760891b606082015260800190565b60208082526025908201527f496e76616c69642073746174652e2043616e6e6f742061626f72742063616d7060408201526430b4b3b71760d91b606082015260800190565b60208082526030908201527f596f7520646f6e7420686176652061636365737320746f20706572666f726d2060408201526f74686973206f7065726174696f6e202160801b606082015260800190565b6020808252604f908201527f476f616c206e6f7420726561636865642c20616d6f756e742063616e6e6f742060408201527f62652077697468647261776e2e20506c656173652061626f727420746f20726560608201526e66756e6420746f206261636b65727360881b608082015260a00190565b6020808252600d908201526c496e76616c696420737461746560981b604082015260600190565b6020808252818101527f436f6e747269627574696f6e20616d6f756e7420697320746f6f206c6f772021604082015260600190565b60208082526048908201527f536f727279206261636b65722c20646561646c696e652068617320706173736560408201527f6421204e6f20636f6e747269627574696f6e732063616e2062652061636365706060820152673a32b2103737bb9760c11b608082015260a00190565b6020808252602c908201527f43616d706169676e2063616e6e6f742062652061626f727465642c206465616460408201526b3634b732903830b9b9b2b21760a11b606082015260800190565b90815260200190565b918252602082015260400190565b6000821982111561107d5761107d6110ed565b500190565b60028104600182168061109657607f821691505b602082108114156110b757634e487b7160e01b600052602260045260246000fd5b50919050565b60006001600160ff1b038214156110d6576110d66110ed565b5060010190565b60006000198214156110d6576110d65b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052602160045260246000fdfea264697066735822122045950afbcb708d931ee1510b40790c32bc61cb04db09c55ae3e5e3e680b1925a64736f6c63430008000033",
400 | "linkReferences": {},
401 | "deployedLinkReferences": {}
402 | }
403 |
--------------------------------------------------------------------------------