├── .gitignore
├── Ethereum
├── Contracts
│ └── Campaign.sol
├── campaign.js
├── compile.js
├── deploy.js
├── factory.js
└── web3.js
├── README.md
├── components
├── ContributeForm.js
├── Header.js
├── Layout.js
└── RequestRow.js
├── package.json
├── pages
├── campaigns
│ ├── new.js
│ ├── requests
│ │ ├── index.js
│ │ └── new.js
│ └── show.js
└── index.js
├── routes.js
├── server.js
└── test
└── Campaign.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /package-lock.json
3 | /Ethereum/build
4 | /Ethereum/.env
5 | /.next
6 |
--------------------------------------------------------------------------------
/Ethereum/Contracts/Campaign.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.17;
2 |
3 | contract CampaignFactory{
4 | // ==== Fields ====
5 | address[] public deployedCampaigns;
6 |
7 | // ==== Modifier ====
8 | // ==== create a new contract ====
9 | function createCampaign(uint minimum) public {
10 | address newCampaign = new Campaign(minimum,msg.sender);
11 | deployedCampaigns.push(newCampaign);
12 | }
13 |
14 | // ==== returning all the address of the deployed contract
15 | function getDeployedCampaigns() public view returns (address[]){
16 | return deployedCampaigns;
17 | }
18 |
19 | }
20 |
21 | contract Campaign{
22 | // collection of key value pairs
23 | struct Request{
24 | string description;
25 | uint value;
26 | address recipient;
27 | bool complete;
28 | uint approvalCount;
29 | mapping(address => bool) approvals;
30 | }
31 |
32 | // === Fields ===
33 | Request[] public requests;
34 | address public manager;
35 | uint public minimumContribution;
36 | mapping(address => bool) public approvers;
37 | uint public approversCount;
38 |
39 |
40 | // === Methods ===
41 |
42 | // == Modifier ==
43 | modifier authorization(){
44 | require(msg.sender == manager);
45 | _;
46 | }
47 |
48 | // == constructor ==
49 | //Setting the manager and minimum amount to contribute
50 | constructor(uint minimum, address creator) public {
51 | manager = creator;
52 | minimumContribution = minimum;
53 | }
54 |
55 | //donate money to compaign and became an approver
56 | function contribute() public payable{
57 | require(msg.value > minimumContribution);
58 |
59 | if(approvers[msg.sender]!= true){
60 | approvers[msg.sender] = true;
61 | approversCount++;
62 | }
63 | }
64 |
65 | //creating a new request by the manager
66 | function createRequest(string description, uint value, address recipient)
67 | public authorization{
68 | Request memory newReq = Request({
69 | description : description,
70 | value : value,
71 | recipient : recipient,
72 | complete : false,
73 | approvalCount : 0
74 | });
75 |
76 | requests.push(newReq);
77 | }
78 |
79 | //approving a particular request by the user
80 | function approveRequest(uint index) public {
81 | Request storage request = requests[index];
82 |
83 | require(approvers[msg.sender]);
84 | require(!request.approvals[msg.sender]);
85 | require(!request.complete);
86 |
87 | request.approvals[msg.sender] = true;
88 | request.approvalCount++;
89 | }
90 |
91 | //final approval of request by the manager and sending the amount
92 | function finalizeRequest(uint index) public authorization{
93 | Request storage request = requests[index];
94 |
95 | require(request.approvalCount > (approversCount/2));
96 | require(!request.complete);
97 |
98 | request.recipient.transfer(request.value);
99 | request.complete = true;
100 |
101 | }
102 |
103 | // function to retrieve Campaign balance, minimumContribution , no of requests , no of Contributors and manager address
104 | function getSummary() public view returns (
105 | uint, uint, uint, uint, address
106 | ) {
107 | return (
108 | minimumContribution,
109 | address(this).balance,
110 | requests.length,
111 | approversCount,
112 | manager
113 | );
114 | }
115 |
116 | // returing no of requests
117 | function getRequestsCount() public view returns (uint) {
118 | return requests.length;
119 | }
120 |
121 | }
--------------------------------------------------------------------------------
/Ethereum/campaign.js:
--------------------------------------------------------------------------------
1 | import web3 from './web3';
2 | import Campaign from './build/Campaign.json';
3 |
4 | export default (address) => {
5 | return new web3.eth.Contract(
6 | JSON.parse(Campaign.interface),
7 | address
8 | );
9 |
10 | }
--------------------------------------------------------------------------------
/Ethereum/compile.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const solc = require("solc");
3 | const fs = require("fs-extra");
4 |
5 | const buildPath = path.resolve(__dirname, "build");
6 | fs.removeSync(buildPath); // deleting the folder and all the content inside it
7 |
8 | const campaignPath = path.resolve(__dirname, "Contracts", "Campaign.sol");
9 | const source = fs.readFileSync(campaignPath, "utf8");
10 | const output = solc.compile(source, 1).contracts;
11 |
12 | fs.ensureDirSync(buildPath); // create a build folder if that folder doesn't exists
13 |
14 | for (let contract in output) {
15 | fs.outputJSONSync(
16 | path.resolve(buildPath, contract.replace(":", "").concat(".json")),
17 | output[contract]
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/Ethereum/deploy.js:
--------------------------------------------------------------------------------
1 | const HDWalletProvider = require('truffle-hdwallet-provider');
2 |
3 | const Web3 = require('web3');
4 | const compiledFactory = require('./build/CampaignFactory.json');
5 |
6 | require('dotenv').config();
7 |
8 | // const OPTIONS = {
9 | // defaultBlock: 'latest',
10 | // transactionConfirmationBlocks: 1,
11 | // transactionBlockTimeout: 8
12 | // }
13 | const provider = new HDWalletProvider(
14 | process.env.mnemonic,
15 | process.env.link
16 | );
17 |
18 | const web3 = new Web3(provider);
19 | const deploy = async () => {
20 | const accounts = await web3.eth.getAccounts();
21 | console.log('Attemping to deploy to accounts ', accounts[0]);
22 |
23 | const result = await new web3.eth.Contract(JSON.parse(compiledFactory.interface))
24 | .deploy({ data: '0x' + compiledFactory.bytecode })
25 | .send({ from: accounts[0] });
26 |
27 | console.log('Contract deploy to ', result.options.address);
28 | };
29 |
30 | deploy();
--------------------------------------------------------------------------------
/Ethereum/factory.js:
--------------------------------------------------------------------------------
1 | import web3 from './web3';
2 | import CampaignFactory from './build/CampaignFactory.json';
3 |
4 | const instance = new web3.eth.Contract(
5 | JSON.parse(CampaignFactory.interface),
6 | '0x6eb1BAb08268e4aBf4585875db85Ce224E68da1f'
7 | );
8 |
9 | export default instance;
--------------------------------------------------------------------------------
/Ethereum/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://rinkeby.infura.io/v3/01183f6b6e8d4aaf8aa97136aded1264'
13 | );
14 |
15 | web3 = new Web3(provider);
16 | }
17 |
18 | export default web3;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📜 Campaign Contract
2 | A new way of funding a project or venture by raising small amounts of money from a large number of people via ethereum blockchain technology without any middleman to function or to manage a user’s information.
3 |
4 | ## Screen-shot
5 | ### Home Page
6 | 
7 |
8 | ### Campaigns Dashboard
9 |
10 | 
11 |
12 | ### Campaigns Request
13 | 
14 |
15 |
16 | ### `CampaignFactory`
17 |
18 | **Variables**
19 |
20 | | variable |data types | desc |
21 | |--|--|--|
22 | | deployedCampaigns| address[] | addresses of all the deployed contract|
23 |
24 |
**Function**
25 |
26 | | name| desc |
27 | |--|--|
28 | | createCampaign| create a new campaign contract |
29 | |getDeployedCampaigns| return addresses of all the deployed contract|
30 |
31 | ### `Campaign`
32 |
33 | **Variables**
34 |
35 | |variable|data types|desc|
36 | |--|--|--|
37 | |manager |address |address of the person who is managing this compaign|
38 | |minimumContribution|unint|Minimum donation required to be considered a contributor or 'approver' |
39 | |approvers|address[]|List of addresses for every person who has donated money|
40 | |requests|Request[]|List of requests that the manager has created.|
41 |
42 |
**Functions**
43 |
44 | |name| desc |
45 | |--|--|
46 | |Campaign | constructor function that sets the minimumContribution and the owner |
47 | |contribute|called when someone wants to donate money to the compaign and become an 'approver'|
48 | |createRequest|called by the manager to create a new 'spending request'|
49 | |approveRequest|called by each contributor to approve a spending request|
50 | |finalizeRequest|After a request has gotten enough approvals, the manager can call this to get money sent to the vendor|
51 | |getSummary() |function to retrieving Campaign balance, minimumContribution , no of requests , no of Contributors and manager address |
52 | |getRequestsCount() | function returning no of requests |
53 |
54 |
**Request Struc**
55 |
56 | |Name |Type |Purpose|
57 | |--|--|--|
58 | | description |string |Describes why the request is being created|
59 | |value|uint|Amount of money that the manager wants to send to the vendor|
60 | |recipient|address|Address that the money will be sent to|
61 | |complete|bool|True if the request has already been processed (money sent)|
62 |
63 | ### `Prerequisite`
64 |
65 | 1. Install **Metamask** as Google Chrome Extension and Create an account.
66 | 2. Request Ether by sharing your ethereum address in social media
(`https://faucet.rinkeby.io/)`
67 | 3. Get 0.01 ether free by giving the ethereum address
`(http://rinkeby-faucet.com/)`
68 | 4. Create an account in [https://infura.io](https://infura.io/)
69 | 5. Create .env file in Ethereum directory and add these line to it.
70 |
71 |
72 | > mnemonic = 'Your mnemonic code'
73 | link = 'Your infura end point link '
74 |
75 | 6. Deploy Contract by going into Ethereum Directory and run.
76 | > node deploy.js
77 |
78 | Copy the contract deploy address and replace it in factory.js file.
79 |
80 | 7. Replace your "infura end point link" in web3.js file
81 |
82 | ### `Dependencies Used`
83 |
84 | | Name | Version | Description |
85 | |--|--|--|
86 | | solc | 0.4.26 | Programming language to write smart contracts |
87 | | ganache-cli | 6.5.1 | Local Ethereum Test Network |
88 | | mocha | 6.2.0 | JavaScript test framework |
89 | |truffle-hdwallet-provider | 1.0.16 | The **Truffle HDWallet provider** is a convenient and easy to configure network connection to ethereum through infura.io |
90 | | web3 | 1.0.0-beta.55 | Ethereum JavaScript API which connects to the Generic JSON RPC spec. |
91 | | dotenv| 8.0.0 | Loads environment variables from a `.env` file into `process.env`|
92 | | fs-extra| 8.1.0 | file system methods that aren't included in the native fs |
93 | | next | 9.0.3 | JavaScript framework to build server-side rendering and static web application using React |
94 | | react | 16.9.0 | JavaScript library for creating user interfaces |
95 | | react-dom | 16.9.0 | DOM specific methods that can be used on React application |
96 | | semantic-ui-react | 0.87.3 | react component kit provides pre define react component |
97 | | semantic-ui-css | 2.4.1 | react component kit provides theme to react component as CSS |
98 | | next-routes | 1.4.2 | Dynamic Routes for Next.js |
99 |
100 | ### `Steps`
101 | - **To install dependencies**
102 | > npm install
103 | - **To Compile the Contract**
104 | > node compile.js
105 | - **To test the Contract**
106 | > npm run test
107 | - **To deploy the Contract**
108 | > node deploy.js
109 | - **To run the application**
110 | > npm run dev
111 |
112 | ## `UI Routing`
113 | | Path | Description |
114 | |--|--|
115 | | / | List of Campaigns |
116 | | /campaigns/new | Form to make a campaign |
117 | | /campaigns/0x8147 | Campaign details for campaign at address 0x8147 |
118 | | /campaigns/0x8147/requests | Requests for campaign at address 0x8147 |
119 | | /campaigns/0x8147/requests/new | Form to create a request for campaign at address 0x8147 |
--------------------------------------------------------------------------------
/components/ContributeForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Form, Input, Message, Button } from 'semantic-ui-react';
3 | import Campaign from '../Ethereum/campaign';
4 | import web3 from '../Ethereum/web3';
5 | import { Router } from '../routes';
6 |
7 |
8 | export default class ContributeForm extends Component {
9 | state = {
10 | value: '',
11 | errorMessage: '',
12 | loading: false
13 | };
14 |
15 | onSubmit = async (event) => {
16 | event.preventDefault();
17 |
18 | const campaign = Campaign(this.props.address);
19 |
20 | this.setState({
21 | loading: true,
22 | errorMessage: ''
23 | });
24 |
25 | try {
26 | const accounts = await web3.eth.getAccounts();
27 | await campaign.methods.contribute().send({
28 | from: accounts[0],
29 | value: web3.utils.toWei(this.state.value, 'ether')
30 | });
31 |
32 | Router.replaceRoute(`/campaigns/${this.props.address}`);
33 | } catch (error) {
34 | this.setState({
35 | errorMessage: error.message
36 | });
37 | }
38 |
39 | this.setState({
40 | loading: false,
41 | value: ''
42 | });
43 | };
44 |
45 | render() {
46 | return (
47 |
49 |
50 | this.setState({
55 | value: event.target.value
56 | })}
57 | />
58 |
59 |
60 |
61 |
64 |
65 | );
66 | }
67 | }
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Menu } from 'semantic-ui-react';
3 | import { Link } from '../routes';
4 |
5 | export default () => {
6 | return (
7 |
27 | );
28 | };
--------------------------------------------------------------------------------
/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Header from './Header';
3 | import { Container } from 'semantic-ui-react';
4 | import Head from 'next/head';
5 |
6 | export default props => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | {props.children}
14 |
15 | );
16 | };
--------------------------------------------------------------------------------
/components/RequestRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Table, Button, Message, Icon, Popup } from 'semantic-ui-react';
3 | import web3 from '../Ethereum/web3';
4 | import Campaign from '../Ethereum/campaign';
5 |
6 | export default class RequestRow extends Component {
7 |
8 | state = {
9 | errorMessageApprove: '',
10 | loadingApprove: false,
11 | errorMessageFinalize: '',
12 | loadingFinalize: false,
13 | };
14 |
15 | onApprove = async () => {
16 |
17 | this.setState({
18 | loadingApprove: true,
19 | errorMessageApprove: ''
20 | });
21 |
22 | try {
23 | const campaign = Campaign(this.props.address);
24 |
25 | const accounts = await web3.eth.getAccounts();
26 | await campaign.methods.approveRequest(this.props.id).send({
27 | from: accounts[0]
28 | });
29 | }
30 | catch (error) {
31 | this.setState({
32 | errorMessageApprove: error.message
33 | });
34 | } finally {
35 | this.setState({
36 | loadingApprove: false,
37 | });
38 | }
39 | };
40 |
41 | onFinalize = async () => {
42 | this.setState({
43 | loadingFinalize: true,
44 | errorMessageFinalize: ''
45 | });
46 | try {
47 | const campaign = Campaign(this.props.address);
48 |
49 | const accounts = await web3.eth.getAccounts();
50 |
51 | await campaign.methods.finalizeRequest(this.props.id).send({
52 | from: accounts[0]
53 | });
54 | } catch (error) {
55 | this.setState({
56 | errorMessageFinalize: error.message
57 | });
58 | } finally {
59 | this.setState({
60 | loadingFinalize: false,
61 | });
62 | }
63 | };
64 |
65 | render() {
66 | const { Row, Cell } = Table;
67 | const { id, request, approversCount } = this.props;
68 | const readyToFinalize = request.approvalCount > approversCount / 2;
69 |
70 | return (
71 |
72 | {id} |
73 | {request.description} |
74 | {web3.utils.fromWei(request.value, 'ether')} (ether) |
75 | {request.recipient} |
76 | {request.approvalCount}/{approversCount} |
77 |
78 | }
83 | hoverable />
84 | {request.complete ? null : ()}
85 | |
86 |
87 | }
92 | hoverable />
93 | {request.complete ? null : ()}
94 | |
95 |
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "crowdfund",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha --timeout 10000",
8 | "dev": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "dotenv": "^8.0.0",
14 | "fs-extra": "^8.1.0",
15 | "ganache-cli": "^6.5.1",
16 | "mocha": "^6.2.0",
17 | "next": "^9.0.3",
18 | "next-routes": "^1.4.2",
19 | "react": "^16.9.0",
20 | "react-dom": "^16.9.0",
21 | "semantic-ui-css": "^2.4.1",
22 | "semantic-ui-react": "^0.87.3",
23 | "solc": "^0.4.26",
24 | "truffle-hdwallet-provider": "^1.0.16",
25 | "web3": "^1.0.0-beta.35"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/pages/campaigns/new.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Form, Button, Input, Message } from 'semantic-ui-react';
3 | import Layout from '../../components/Layout';
4 | import factory from '../../Ethereum/factory';
5 | import web3 from '../../Ethereum/web3';
6 | import { Router } from '../../routes';
7 |
8 | export default class CampaignNew extends Component {
9 | state = {
10 | minimumContribution: '',
11 | errorMessage: '',
12 | loading: false
13 | };
14 |
15 | onSubmit = async (event) => {
16 | event.preventDefault(); // keep the browser to automatically submit the form to the server
17 |
18 | this.setState({ loading: true, errorMessage: '' });
19 |
20 | try {
21 | const accounts = await web3.eth.getAccounts();
22 | await factory.methods.createCampaign(this.state.minimumContribution)
23 | .send({
24 | from: accounts[0]
25 | });
26 | Router.pushRoute('/');
27 | } catch (err) {
28 | this.setState({ errorMessage: err.message });
29 | } finally {
30 | this.setState({ loading: false });
31 | }
32 | };
33 |
34 | render() {
35 | return (
36 |
37 | Create a Campaign
38 |
39 |
41 |
42 | {
47 | this.setState({ minimumContribution: event.target.value })
48 | }}
49 | />
50 |
51 |
52 |
53 |
54 |
55 |
56 | );
57 | }
58 | }
--------------------------------------------------------------------------------
/pages/campaigns/requests/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Table, Grid, Icon, Label } from 'semantic-ui-react';
3 | import Layout from '../../../components/Layout';
4 | import { Link } from '../../../routes';
5 | import Campaings from '../../../Ethereum/campaign';
6 | import RequestRow from '../../../components/RequestRow';
7 | import web3 from '../../../Ethereum/web3';
8 |
9 | export default class RequestIndex extends Component {
10 |
11 | static async getInitialProps(props) {
12 | const { address } = props.query;
13 | const campaign = Campaings(address);
14 | const requestCount = await campaign.methods.getRequestsCount().call();
15 | const approversCount = await campaign.methods.approversCount().call();
16 | const summary = await campaign.methods.getSummary().call();
17 | const contractBalance = summary[1];
18 |
19 | const requests = await Promise.all(
20 | Array(parseInt(requestCount)).fill().map((element, index) => {
21 | return campaign.methods.requests(index).call()
22 | })
23 | );
24 |
25 | return { address, requests, requestCount, approversCount, contractBalance };
26 | }
27 |
28 | renderRows() {
29 | return this.props.requests.map((request, index) => {
30 | return ;
37 | });
38 | }
39 |
40 | render() {
41 |
42 | const { Header, Row, HeaderCell, Body } = Table;
43 | const campaignsBalance = this.props.contractBalance;
44 |
45 | return (
46 |
47 | Request
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | ID
77 | Description
78 | Amount
79 | Recipient
80 | Approval
81 | Approve
82 | Finalize
83 |
84 |
85 |
86 |
87 | {this.renderRows()}
88 |
89 |
90 |
91 | Found {this.props.requestCount} requests.
92 |
93 |
94 | )
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/pages/campaigns/requests/new.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Form, Button, Message, Input } from 'semantic-ui-react';
3 | import Campaigns from '../../../Ethereum/campaign';
4 | import web3 from '../../../Ethereum/web3';
5 | import { Link, Router } from '../../../routes';
6 | import Layout from '../../../components/Layout';
7 |
8 | export default class RequestNew extends Component {
9 |
10 | state = {
11 | value: '',
12 | description: '',
13 | recipient: '',
14 | errorMessage: '',
15 | loading: false
16 | };
17 |
18 | static async getInitialProps(props) {
19 | const { address } = props.query;
20 | return { address };
21 | }
22 |
23 | onSubmit = async event => {
24 | event.preventDefault();
25 | this.setState({ loading: true, errorMessage: '' });
26 |
27 | const campaign = Campaigns(this.props.address);
28 | const { description, value, recipient } = this.state;
29 |
30 | try {
31 | const accounts = await web3.eth.getAccounts();
32 |
33 | if (recipient == '') {
34 | throw new Error('address cannot be empty');
35 | }
36 | await campaign.methods.createRequest(
37 | description,
38 | web3.utils.toWei(value, 'ether'),
39 | recipient
40 | ).send({
41 | from: accounts[0]
42 | });
43 |
44 | Router.pushRoute(`/campaigns/${this.props.address}/requests/`)
45 | } catch (error) {
46 | this.setState({
47 | errorMessage: error.message
48 | });
49 | } finally {
50 | this.setState({
51 | loading: false
52 | });
53 | }
54 | };
55 |
56 | render() {
57 | return (
58 |
59 |
60 | Back
61 |
62 |
63 | Create a Request
64 |
66 |
67 |
70 | this.setState({ description: event.target.value })
71 | }
72 | />
73 |
74 |
75 |
76 |
77 |
80 | this.setState({ value: event.target.value })
81 | }
82 | />
83 |
84 |
85 |
86 |
87 |
90 | this.setState({ recipient: event.target.value })
91 | }
92 | />
93 |
94 |
95 |
96 |
97 |
98 |
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/pages/campaigns/show.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Layout from '../../components/Layout';
3 | import Campaign from '../../Ethereum/campaign';
4 | import { Card, Grid, Button } from 'semantic-ui-react'
5 | import web3 from '../../Ethereum/web3';
6 | import ContributeForm from '../../components/ContributeForm';
7 | import { Link } from '../../routes';
8 |
9 | export default class CampaignShow extends Component {
10 | static async getInitialProps(props) {
11 | const campaign = Campaign(props.query.address);
12 |
13 | const summary = await campaign.methods.getSummary().call();
14 | return {
15 | address: props.query.address,
16 | minimumContribution: summary[0],
17 | campaignBalance: summary[1],
18 | noOfReq: summary[2],
19 | noOfContributor: summary[3],
20 | manager: summary[4]
21 |
22 | };
23 | }
24 |
25 | renderCard() {
26 |
27 | // de structuring
28 | const {
29 | minimumContribution,
30 | campaignBalance,
31 | noOfReq,
32 | noOfContributor,
33 | manager,
34 | } = this.props;
35 |
36 | const items = [
37 | {
38 | header: manager,
39 | description: 'The Manager created this Campaign and can create requests to withdraw money.',
40 | meta: 'Address of Manager',
41 | style: { overflowWrap: 'break-word' }
42 | },
43 | {
44 | header: minimumContribution,
45 | description: 'You must contribute atleast this much wei to become a approver.',
46 | meta: 'Minimum Contribution (wei)',
47 | },
48 | {
49 | header: noOfReq,
50 | description: 'A request tries to withdraw money from the contract. A request must be approved by approvers.',
51 | meta: 'Number of Requests',
52 | },
53 | {
54 | header: noOfContributor,
55 | description: 'No of people who have already donated to the campaign.',
56 | meta: 'No of Approvers',
57 | },
58 | {
59 | header: web3.utils.fromWei(campaignBalance, 'ether'),
60 | description: 'The amount of money campaign has left to spend.',
61 | meta: 'Campaign Balance (ether)',
62 | }
63 | ];
64 |
65 | return ;
66 | }
67 |
68 | render() {
69 | return (
70 |
71 | Campaign Show
72 |
73 |
74 |
75 |
76 | {this.renderCard()}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | );
100 | }
101 | }
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import factory from '../Ethereum/factory';
3 | import { Card, Button } from 'semantic-ui-react';
4 | import Layout from '../components/Layout';
5 | import { Link } from '../routes';
6 |
7 | class CampaignIndex extends Component {
8 |
9 | static async getInitialProps() {
10 | // next js execute on the server side
11 | const campaigns = await factory.methods.getDeployedCampaigns().call();
12 | return { campaigns };
13 | }
14 |
15 | renderCampaigns() {
16 | const items = this.props.campaigns.map(address => {
17 | return {
18 | header: address,
19 | description: (
20 |
21 | View Campaign
22 |
23 | ),
24 | fluid: true,
25 | style: { overflowWrap: 'break-word' }
26 | };
27 | });
28 |
29 | return ;
30 | }
31 |
32 | render() {
33 | return (
34 |
35 |
36 |
Open Campaigns
37 |
38 |
39 |
40 |
46 |
47 |
48 | {this.renderCampaigns()}
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | export default CampaignIndex;
--------------------------------------------------------------------------------
/routes.js:
--------------------------------------------------------------------------------
1 | // Defines different routes
2 |
3 | const routes = require('next-routes')(); //return a function and () means execute it
4 |
5 | routes
6 | .add('/campaigns/new', '/campaigns/new')
7 | .add('/campaigns/:address', '/campaigns/show')
8 | .add('/campaigns/:address/requests', '/campaigns/requests/index')
9 | .add('/campaigns/:address/requests/new', '/campaigns/requests/new');
10 |
11 | module.exports = routes;
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // Boot up 'next' app, tell it to use routes.js
2 |
3 | const { createServer } = require('http');
4 | const next = require('next');
5 |
6 | const app = next({
7 | dev: process.env.NODE_ENV !== 'production'
8 | });
9 |
10 | const routes = require('./routes');
11 | const handler = routes.getRequestHandler(app);
12 |
13 | app.prepare().then(() => {
14 | createServer(handler).listen(3000, (err) => {
15 | if (err) throw err;
16 | console.log('Ready on localhost: 3000');
17 | });
18 | });
--------------------------------------------------------------------------------
/test/Campaign.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ganache = require('ganache-cli');
3 | const Web3 = require('web3');
4 | const web3 = new Web3(ganache.provider());
5 |
6 | const compileFactory = require('../Ethereum/build/CampaignFactory.json');
7 | const compileCampaign = require('../Ethereum/build/Campaign.json');
8 |
9 | let accounts;
10 | let factory;
11 | let campaignAddress;
12 | let campaign;
13 |
14 | beforeEach(async () => {
15 | accounts = await web3.eth.getAccounts();
16 |
17 | factory = await new web3.eth.Contract(JSON.parse(compileFactory.interface))
18 | .deploy({ data: compileFactory.bytecode })
19 | .send({ from: accounts[0], gas: '1000000' });
20 |
21 | await factory.methods.createCampaign('100').send({
22 | from: accounts[0],
23 | gas: '1000000'
24 | });
25 |
26 | [campaignAddress] = await factory.methods.getDeployedCampaigns().call(); // [campaignAddress] => campaignAddress = address[0]
27 |
28 | campaign = await new web3.eth.Contract(
29 | JSON.parse(compileCampaign.interface),
30 | campaignAddress
31 | );
32 | });
33 |
34 | describe('Campaigns', () => {
35 | it('deploys a factory and Campaigns', () => {
36 | assert.ok(factory.options.address);
37 | assert.ok(campaign.options.address);
38 | });
39 |
40 | it('marks caller as the campaign manager', async () => {
41 | const manager = await campaign.methods.manager().call();
42 | assert.equal(accounts[0], manager);
43 | });
44 |
45 | it('allows people to contribute money and marks them as approvers', async () => {
46 | await campaign.methods.contribute().send({
47 | value: 200,
48 | from: accounts[1]
49 | });
50 |
51 | assert(await campaign.methods.approvers(accounts[1]).call());
52 |
53 | });
54 |
55 | it('requires a minimum contribution', async () => {
56 | var check = true;
57 | try {
58 | await campaign.methods.contribute().send({
59 | from: accounts[0],
60 | value: 200
61 | });
62 | check = false;
63 | } catch (error) {
64 | check = true;
65 | }
66 | if (check) {
67 | assert(false);
68 | } else {
69 | assert(true);
70 | }
71 | });
72 |
73 | it('allows a manager to make a payment request', async () => {
74 | await campaign.methods.createRequest('Buy solar light', '100', accounts[1])
75 | .send({
76 | from: accounts[0],
77 | gas: 1000000
78 | });
79 |
80 | const request = await campaign.methods.requests(0).call();
81 |
82 | assert.equal('Buy solar light', request.description);
83 |
84 | });
85 |
86 | it('processes requests', async () => {
87 | await campaign.methods.contribute().send({
88 | from: accounts[0],
89 | value: web3.utils.toWei('10', 'ether')
90 | });
91 |
92 | var beforeBlnc = await web3.eth.getBalance(accounts[1]);
93 |
94 | await campaign.methods.createRequest('Buy solar light', web3.utils.toWei('5', 'ether'), accounts[1])
95 | .send({
96 | from: accounts[0],
97 | gas: 1000000
98 | });
99 |
100 | await campaign.methods.approveRequest(0).send({
101 | from: accounts[0],
102 | gas: '1000000'
103 | });
104 |
105 | await campaign.methods.finalizeRequest(0).send({
106 | from: accounts[0],
107 | gas: '1000000'
108 | });
109 |
110 | var afterBlnc = await web3.eth.getBalance(accounts[1]);
111 | beforeBlnc = parseFloat(web3.utils.fromWei(beforeBlnc, 'ether'));
112 | afterBlnc = parseFloat(web3.utils.fromWei(afterBlnc, 'ether'));
113 |
114 | assert(afterBlnc + 4 > beforeBlnc);
115 |
116 | });
117 |
118 | });
119 |
--------------------------------------------------------------------------------