Crowdfund Dapp is a decentralized crowdfunding platform built on Ethereum. This site is intended to demonstrate a full featured dapp using the latest Ethereum and web development frameworks including Webpack, React, Redux, Semantic-ui, Solidity, Web3, and Truffle. Feel free to create projects and interact with it. All source code is available here and is based off truffle-box
77 |
82 |
83 |
88 | {this.getProjectsMessage(this.props.fundingHub)}
89 |
95 |
96 | );
97 | }
98 | }
99 |
100 | function mapStateToProps(state) {
101 | return {
102 | user: state.user,
103 | network: state.network,
104 | fundingHub: state.fundingHub,
105 | }
106 | }
107 |
108 | export default connect(mapStateToProps)(HomeContainer);
--------------------------------------------------------------------------------
/src/containers/projectContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {connect} from 'react-redux';
3 | import { Card, Grid } from 'semantic-ui-react';
4 | import ProjectCard from '../components/projectCard';
5 | import {
6 | fetchProject,
7 | contribute,
8 | fetchProjectBalance,
9 | fetchContributions
10 | } from '../actions/projectActions';
11 | import { fetchAccountsAndBalances } from '../actions/userActions';
12 | import ContributionList from '../components/contributionList';
13 | import ProjectDetails from '../components/projectDetails';
14 | import ContributeModal from '../components/contributeModal';
15 |
16 | var _this;
17 |
18 | class ProjectContainer extends Component {
19 |
20 | constructor(props) {
21 | super(props);
22 | _this = this;
23 |
24 | this.state = {
25 | showContributeModal: false
26 | }
27 |
28 | }
29 |
30 | componentDidMount() {
31 | const { dispatch, params } = _this.props;
32 | dispatch(fetchProject(params.address));
33 | dispatch(fetchContributions(params.address));
34 | dispatch(fetchProjectBalance(params.address));
35 | }
36 |
37 |
38 | toggleModalDisplayed() {
39 | _this.setState({
40 | showContributeModal: !_this.state.showContributeModal
41 | });
42 | }
43 |
44 | handleContributeClicked() {
45 | _this.toggleModalDisplayed();
46 | }
47 |
48 | handleContribute(amount) {
49 | const {dispatch, user, project} = _this.props;
50 |
51 | _this.toggleModalDisplayed();
52 |
53 | let selectedUserAddress = user.accounts[user.selectedAccount].address;
54 |
55 | if (!!selectedUserAddress) {
56 | dispatch(contribute(project.project.address, amount, selectedUserAddress))
57 | .then(() => {
58 | dispatch(fetchProject(project.project.address));
59 | dispatch(fetchContributions(project.project.address))
60 | dispatch(fetchAccountsAndBalances());
61 | dispatch(fetchProjectBalance(project.project.address));
62 | });
63 | }
64 | }
65 |
66 | render() {
67 | const {
68 | project,
69 | network: {
70 | network,
71 | currentBlock
72 | }
73 | } = this.props;
74 |
75 | let projectDetails = project.project;
76 | let contributions = project.contributions;
77 |
78 | return (
79 |
80 |
81 |
82 |
86 |
87 |
88 |
92 |
93 |
98 |
99 |
100 |
102 |
103 |
104 | )
105 | }
106 | }
107 |
108 | function mapStateToProps(state) {
109 | return {
110 | user: state.user,
111 | project: state.project,
112 | network: state.network,
113 | }
114 | }
115 |
116 | export default connect(mapStateToProps)(ProjectContainer);
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Decentralized Crowdfunding App
2 |
3 | This is a simple crowdfunding dapp intended to show off what I've learned from the B9 Lab Ethereum course. The contracts are written in Solidity and the app is utilizing the Truffle framework. The frontend of the app is built with React and Webpack.
4 |
5 | [https://github.com/tyndallm/crowdfund-dapp](https://github.com/tyndallm/crowdfund-dapp)
6 |
7 | ### Contracts
8 |
9 | **FundingHub.sol**
10 | The first contract is the Funding Hub. This contract is responsible for creating and maintaining a list of all Project contracts. FundingHub also offers a contribute method which can be used to contribute directly to a Project. To demonstrate a potential business model use-case Projects have been locked to only allow receiving of funds from their managing Funding Hub. You can imagine a scenario in which the FundingHub takes a small fee for managing each project.
11 |
12 | **Project.sol**
13 | This contract contains all of the logic around how a crowdfunding project should operate. Projects are "locked" to their Funding Hub and can only receive funds sent thru the associated FundingHub contract address.
14 |
15 | There are three main functions: (fund, payout, and refund)
16 |
17 | *Fund*
18 | This is the function called when the FundingHub receives a contribution. If the contribution was sent after the deadline of the project passed, or the full amount has been reached, the function must return the value to the originator of the transaction. If the full funding amount has been reached, the function must call payout.
19 | **NOTE**: This is slightly different than the original instructions. I wanted to enforce the withdrawal pattern in the refund method as opposed to a group send. The withdrawal pattern is generally considered safer and avoids some of the pitfalls of call depth and out-of-gas issues, [see more here](https://solidity.readthedocs.io/en/develop/common-patterns.html#withdrawal-from-contracts).
20 |
21 | *Payout*
22 | If funding goal has been met, transfer fund to project creator. This function protects against re-entrancy and is only payable to the project creator.
23 |
24 | *Refund*
25 | If the deadline is passed and the goal was not reached, allow contributors to withdraw their contributions.
26 | **NOTE** This is slightly different that the final project requirements, see above for details.
27 |
28 |
29 | ### App
30 | The frontend app for this project is built on React and forks off of the [truffle-webpack-demo project](https://github.com/ConsenSys/truffle-webpack-demo) by Consensys. The cool thing about this is that it combines the latest in regular frontend javscript development with Ethereum. In order to manage the state of the dapp, Redux was chosen.
31 |
32 | One of my goals of this project was to see if there was a way I could abstract the asynchronous web3 and contract calls into a simple API that I could then integrate into a standard React+Redux Action/Reducer flow. This was achieved with the web3Api.js file. This approach works well with the asynchronous nature of interacting with the blockchain as things like contract properties, and account balances can seemlessly notify the app when they have updated and the UI will reflect those changes instantly.
33 |
34 | Here is an example of how this flow works:
35 |
36 | 0. Upon initial load the app dispatchs ```fetchProjectsAndDetails()```
37 | 0. fetchProjectsAndDetails dispatchs the ```requestProjects``` Action and makes async request to web3Api's ```getProjects()``` function
38 | 0. When the ```getProjects()``` request resolves it returns the result to the ```fetchProjectAndDetails()``` function and dispatchs the ```receivedProjects``` Action which notifies the FundingHub Reducer that the state has changed.
39 | 0. When the app sees that the state for FundingHub has changed, the UI re-renders with the new project properties and the projects are displayed in a table on the page
40 |
41 |
42 | ### Screenshots
43 | 
44 | 
45 | 
46 |
47 |
48 | ### Running
49 |
50 | The Web3 RPC location will be picked up from the `truffle.js` file.
51 |
52 | 0. Clone this repo
53 | 0. `npm install`
54 | 0. Make sure `testrpc` is running on its default port. Then:
55 | - `npm run start` - Starts the development server
56 | - `npm run build` - Generates a build
57 | - `truffle test` - Run the rest suite
--------------------------------------------------------------------------------
/src/fonts/Oswald-300/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2016 The Oswald Project Authors (contact@sansoxygen.com)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/src/fonts/Oswald-regular/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2016 The Oswald Project Authors (contact@sansoxygen.com)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/contracts/Project.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.4;
2 |
3 | contract Project {
4 |
5 | struct Properties {
6 | uint goal;
7 | uint deadline;
8 | string title;
9 | address creator;
10 | }
11 |
12 | struct Contribution {
13 | uint amount;
14 | address contributor;
15 | }
16 |
17 | address public fundingHub;
18 |
19 | mapping (address => uint) public contributors;
20 | mapping (uint => Contribution) public contributions;
21 |
22 | uint public totalFunding;
23 | uint public contributionsCount;
24 | uint public contributorsCount;
25 |
26 | Properties public properties;
27 |
28 | event LogContributionReceived(address projectAddress, address contributor, uint amount);
29 | event LogPayoutInitiated(address projectAddress, address owner, uint totalPayout);
30 | event LogRefundIssued(address projectAddress, address contributor, uint refundAmount);
31 | event LogFundingGoalReached(address projectAddress, uint totalFunding, uint totalContributions);
32 | event LogFundingFailed(address projectAddress, uint totalFunding, uint totalContributions);
33 |
34 | event LogFailure(string message);
35 |
36 | modifier onlyFundingHub {
37 | if (fundingHub != msg.sender) throw;
38 | _;
39 | }
40 |
41 | modifier onlyFunded {
42 | if (totalFunding < properties.goal) {
43 | throw;
44 | }
45 | _;
46 | }
47 |
48 | function Project(uint _fundingGoal, uint _deadline, string _title, address _creator) {
49 |
50 | // Check to see the funding goal is greater than 0
51 | if (_fundingGoal <= 0) {
52 | LogFailure("Project funding goal must be greater than 0");
53 | throw;
54 | }
55 |
56 | // Check to see the deadline is in the future
57 | if (block.number >= _deadline) {
58 | LogFailure("Project deadline must be greater than the current block");
59 | throw;
60 | }
61 |
62 | // Check to see that a creator (payout) address is valid
63 | if (_creator == 0) {
64 | LogFailure("Project must include a valid creator address");
65 | throw;
66 | }
67 |
68 | fundingHub = msg.sender;
69 |
70 | // initialize properties struct
71 | properties = Properties({
72 | goal: _fundingGoal,
73 | deadline: _deadline,
74 | title: _title,
75 | creator: _creator
76 | });
77 |
78 | totalFunding = 0;
79 | contributionsCount = 0;
80 | contributorsCount = 0;
81 | }
82 |
83 | /**
84 | * Project values are indexed in return value:
85 | * [0] -> Project.properties.title
86 | * [1] -> Project.properties.goal
87 | * [2] -> Project.properties.deadline
88 | * [3] -> Project.properties.creator
89 | * [4] -> Project.totalFunding
90 | * [5] -> Project.contributionsCount
91 | * [6] -> Project.contributorsCount
92 | * [7] -> Project.fundingHub
93 | * [8] -> Project (address)
94 | */
95 | function getProject() returns (string, uint, uint, address, uint, uint, uint, address, address) {
96 | return (properties.title,
97 | properties.goal,
98 | properties.deadline,
99 | properties.creator,
100 | totalFunding,
101 | contributionsCount,
102 | contributorsCount,
103 | fundingHub,
104 | address(this));
105 | }
106 |
107 | /**
108 | * Retrieve indiviual contribution information
109 | * [0] -> Contribution.amount
110 | * [1] -> Contribution.contributor
111 | */
112 | function getContribution(uint _id) returns (uint, address) {
113 | Contribution c = contributions[_id];
114 | return (c.amount, c.contributor);
115 | }
116 |
117 | /**
118 | * This is the function called when the FundingHub receives a contribution.
119 | * If the contribution was sent after the deadline of the project passed,
120 | * or the full amount has been reached, the function must return the value
121 | * to the originator of the transaction.
122 | * If the full funding amount has been reached, the function must call payout.
123 | * [0] -> contribution was made
124 | */
125 | function fund(address _contributor) payable returns (bool successful) {
126 |
127 | // Check amount is greater than 0
128 | if (msg.value <= 0) {
129 | LogFailure("Funding contributions must be greater than 0 wei");
130 | throw;
131 | }
132 |
133 | // Check funding only comes thru fundingHub
134 | if (msg.sender != fundingHub) {
135 | LogFailure("Funding contributions can only be made through FundingHub contract");
136 | throw;
137 | }
138 |
139 | // 1. Check that the project dealine has not passed
140 | if (block.number > properties.deadline) {
141 | LogFundingFailed(address(this), totalFunding, contributionsCount);
142 | if (!_contributor.send(msg.value)) {
143 | LogFailure("Project deadline has passed, problem returning contribution");
144 | throw;
145 | }
146 | return false;
147 | }
148 |
149 | // 2. Check that funding goal has not already been met
150 | if (totalFunding >= properties.goal) {
151 | LogFundingGoalReached(address(this), totalFunding, contributionsCount);
152 | if (!_contributor.send(msg.value)) {
153 | LogFailure("Project deadline has passed, problem returning contribution");
154 | throw;
155 | }
156 | payout();
157 | return false;
158 | }
159 |
160 | // determine if this is a new contributor
161 | uint prevContributionBalance = contributors[_contributor];
162 |
163 | // Add contribution to contributions map
164 | Contribution c = contributions[contributionsCount];
165 | c.contributor = _contributor;
166 | c.amount = msg.value;
167 |
168 | // Update contributor's balance
169 | contributors[_contributor] += msg.value;
170 |
171 | totalFunding += msg.value;
172 | contributionsCount++;
173 |
174 | // Check if contributor is new and if so increase count
175 | if (prevContributionBalance == 0) {
176 | contributorsCount++;
177 | }
178 |
179 | LogContributionReceived(this, _contributor, msg.value);
180 |
181 | // Check again to see whether the last contribution met the fundingGoal
182 | if (totalFunding >= properties.goal) {
183 | LogFundingGoalReached(address(this), totalFunding, contributionsCount);
184 | payout();
185 | }
186 |
187 | return true;
188 | }
189 |
190 | /**
191 | * If funding goal has been met, transfer fund to project creator
192 | * [0] -> payout was successful
193 | */
194 | function payout() payable onlyFunded returns (bool successful) {
195 | uint amount = totalFunding;
196 |
197 | // prevent re-entrancy
198 | totalFunding = 0;
199 |
200 | if (properties.creator.send(amount)) {
201 | return true;
202 | } else {
203 | totalFunding = amount;
204 | return false;
205 | }
206 |
207 | return true;
208 | }
209 |
210 | /**
211 | * If the deadline is passed and the goal was not reached, allow contributors to withdraw their contributions.
212 | * This is slightly different that the final project requirements, see README for details
213 | * [0] -> refund was successful
214 | */
215 | function refund() payable returns (bool successful) {
216 |
217 | // Check that the project dealine has passed
218 | if (block.number < properties.deadline) {
219 | LogFailure("Refund is only possible if project is past deadline");
220 | throw;
221 | }
222 |
223 | // Check that funding goal has not already been met
224 | if (totalFunding >= properties.goal) {
225 | LogFailure("Refund is not possible if project has met goal");
226 | throw;
227 | }
228 |
229 | uint amount = contributors[msg.sender];
230 |
231 | //prevent re-entrancy attack
232 | contributors[msg.sender] = 0;
233 |
234 | if (msg.sender.send(amount)) {
235 | LogRefundIssued(address(this), msg.sender, amount);
236 | return true;
237 | } else {
238 | contributors[msg.sender] = amount;
239 | LogFailure("Refund did not send successfully");
240 | return false;
241 | }
242 | return true;
243 | }
244 |
245 | function kill() public onlyFundingHub {
246 | selfdestruct(fundingHub);
247 | }
248 |
249 | /**
250 | * Don't allow Ether to be sent blindly to this contract
251 | */
252 | function() {
253 | throw;
254 | }
255 | }
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | // Do this as the first thing so that any code reading it knows the right env.
2 | process.env.NODE_ENV = 'production';
3 |
4 | // Load environment variables from .env file. Suppress warnings using silent
5 | // if this file is missing. dotenv will never modify any environment variables
6 | // that have already been set.
7 | // https://github.com/motdotla/dotenv
8 | require('dotenv').config({silent: true});
9 |
10 | var chalk = require('chalk');
11 | var fs = require('fs-extra');
12 | var path = require('path');
13 | var pathExists = require('path-exists');
14 | var filesize = require('filesize');
15 | var gzipSize = require('gzip-size').sync;
16 | var webpack = require('webpack');
17 | var config = require('../config/webpack.config.prod');
18 | var paths = require('../config/paths');
19 | var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
20 | var recursive = require('recursive-readdir');
21 | var stripAnsi = require('strip-ansi');
22 |
23 | var useYarn = pathExists.sync(paths.yarnLockFile);
24 |
25 | // Warn and crash if required files are missing
26 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
27 | process.exit(1);
28 | }
29 |
30 | // Input: /User/dan/app/build/static/js/main.82be8.js
31 | // Output: /static/js/main.js
32 | function removeFileNameHash(fileName) {
33 | return fileName
34 | .replace(paths.appBuild, '')
35 | .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
36 | }
37 |
38 | // Input: 1024, 2048
39 | // Output: "(+1 KB)"
40 | function getDifferenceLabel(currentSize, previousSize) {
41 | var FIFTY_KILOBYTES = 1024 * 50;
42 | var difference = currentSize - previousSize;
43 | var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
44 | if (difference >= FIFTY_KILOBYTES) {
45 | return chalk.red('+' + fileSize);
46 | } else if (difference < FIFTY_KILOBYTES && difference > 0) {
47 | return chalk.yellow('+' + fileSize);
48 | } else if (difference < 0) {
49 | return chalk.green(fileSize);
50 | } else {
51 | return '';
52 | }
53 | }
54 |
55 | // First, read the current file sizes in build directory.
56 | // This lets us display how much they changed later.
57 | recursive(paths.appBuild, (err, fileNames) => {
58 | var previousSizeMap = (fileNames || [])
59 | .filter(fileName => /\.(js|css)$/.test(fileName))
60 | .reduce((memo, fileName) => {
61 | var contents = fs.readFileSync(fileName);
62 | var key = removeFileNameHash(fileName);
63 | memo[key] = gzipSize(contents);
64 | return memo;
65 | }, {});
66 |
67 | // Remove all content but keep the directory so that
68 | // if you're in it, you don't end up in Trash
69 | fs.emptyDirSync(paths.appBuild);
70 |
71 | // Start the webpack build
72 | build(previousSizeMap);
73 |
74 | // Merge with the public folder
75 | copyPublicFolder();
76 | });
77 |
78 | // Print a detailed summary of build files.
79 | function printFileSizes(stats, previousSizeMap) {
80 | var assets = stats.toJson().assets
81 | .filter(asset => /\.(js|css)$/.test(asset.name))
82 | .map(asset => {
83 | var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name);
84 | var size = gzipSize(fileContents);
85 | var previousSize = previousSizeMap[removeFileNameHash(asset.name)];
86 | var difference = getDifferenceLabel(size, previousSize);
87 | return {
88 | folder: path.join('build', path.dirname(asset.name)),
89 | name: path.basename(asset.name),
90 | size: size,
91 | sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '')
92 | };
93 | });
94 | assets.sort((a, b) => b.size - a.size);
95 | var longestSizeLabelLength = Math.max.apply(null,
96 | assets.map(a => stripAnsi(a.sizeLabel).length)
97 | );
98 | assets.forEach(asset => {
99 | var sizeLabel = asset.sizeLabel;
100 | var sizeLength = stripAnsi(sizeLabel).length;
101 | if (sizeLength < longestSizeLabelLength) {
102 | var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
103 | sizeLabel += rightPadding;
104 | }
105 | console.log(
106 | ' ' + sizeLabel +
107 | ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name)
108 | );
109 | });
110 | }
111 |
112 | // Print out errors
113 | function printErrors(summary, errors) {
114 | console.log(chalk.red(summary));
115 | console.log();
116 | errors.forEach(err => {
117 | console.log(err.message || err);
118 | console.log();
119 | });
120 | }
121 |
122 | // Create the production build and print the deployment instructions.
123 | function build(previousSizeMap) {
124 | console.log('Creating an optimized production build...');
125 | webpack(config).run((err, stats) => {
126 | if (err) {
127 | printErrors('Failed to compile.', [err]);
128 | process.exit(1);
129 | }
130 |
131 | if (stats.compilation.errors.length) {
132 | printErrors('Failed to compile.', stats.compilation.errors);
133 | process.exit(1);
134 | }
135 |
136 | if (process.env.CI && stats.compilation.warnings.length) {
137 | printErrors('Failed to compile.', stats.compilation.warnings);
138 | process.exit(1);
139 | }
140 |
141 | console.log(chalk.green('Compiled successfully.'));
142 | console.log();
143 |
144 | console.log('File sizes after gzip:');
145 | console.log();
146 | printFileSizes(stats, previousSizeMap);
147 | console.log();
148 |
149 | var openCommand = process.platform === 'win32' ? 'start' : 'open';
150 | var appPackage = require(paths.appPackageJson);
151 | var homepagePath = appPackage.homepage;
152 | var publicPath = config.output.publicPath;
153 | if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) {
154 | // "homepage": "http://user.github.io/project"
155 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
156 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
157 | console.log();
158 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
159 | console.log('To publish it at ' + chalk.green(homepagePath) + ', run:');
160 | // If script deploy has been added to package.json, skip the instructions
161 | if (typeof appPackage.scripts.deploy === 'undefined') {
162 | console.log();
163 | if (useYarn) {
164 | console.log(' ' + chalk.cyan('yarn') + ' add --dev gh-pages');
165 | } else {
166 | console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages');
167 | }
168 | console.log();
169 | console.log('Add the following script in your ' + chalk.cyan('package.json') + '.');
170 | console.log();
171 | console.log(' ' + chalk.dim('// ...'));
172 | console.log(' ' + chalk.yellow('"scripts"') + ': {');
173 | console.log(' ' + chalk.dim('// ...'));
174 | console.log(' ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"npm run build&&gh-pages -d build"'));
175 | console.log(' }');
176 | console.log();
177 | console.log('Then run:');
178 | }
179 | console.log();
180 | console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy');
181 | console.log();
182 | } else if (publicPath !== '/') {
183 | // "homepage": "http://mywebsite.com/project"
184 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.');
185 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
186 | console.log();
187 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
188 | console.log();
189 | } else {
190 | // no homepage or "homepage": "http://mywebsite.com"
191 | console.log('The project was built assuming it is hosted at the server root.');
192 | if (homepagePath) {
193 | // "homepage": "http://mywebsite.com"
194 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.');
195 | console.log();
196 | } else {
197 | // no homepage
198 | console.log('To override this, specify the ' + chalk.green('homepage') + ' in your ' + chalk.cyan('package.json') + '.');
199 | console.log('For example, add this to build it for GitHub Pages:')
200 | console.log();
201 | console.log(' ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(','));
202 | console.log();
203 | }
204 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.');
205 | console.log('You may also serve it locally with a static server:')
206 | console.log();
207 | if (useYarn) {
208 | console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server');
209 | } else {
210 | console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server');
211 | }
212 | console.log(' ' + chalk.cyan('pushstate-server') + ' build');
213 | console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000');
214 | console.log();
215 | }
216 | });
217 | }
218 |
219 | function copyPublicFolder() {
220 | fs.copySync(paths.appPublic, paths.appBuild, {
221 | dereference: true,
222 | filter: file => file !== paths.appHtml
223 | });
224 | }
225 |
--------------------------------------------------------------------------------
/src/api/web3Api.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3';
2 | import {getExtendedWeb3Provider} from '../utils/web3Utils';
3 | import FundingHubContract from '../../build/contracts/FundingHub.json';
4 | import ProjectContract from '../../build/contracts/Project.json';
5 |
6 | const contract = require('truffle-contract');
7 |
8 | let web3Provided;
9 |
10 | let provider;
11 | /* eslint-disable */
12 | if (typeof web3 !== 'undefined') {
13 | provider = new Web3(web3.currentProvider);
14 | } else {
15 | provider = new Web3.providers.HttpProvider('http://localhost:8545');
16 | }
17 | /* esling-enable */
18 |
19 | const web3 = new Web3(provider);
20 |
21 | const fundingHub = contract(FundingHubContract);
22 | fundingHub.setProvider(provider);
23 |
24 | const project = contract(ProjectContract);
25 | project.setProvider(provider);
26 |
27 | /**
28 | * Check for a local web3, otherwise fallback to an infura instance
29 | */
30 | function initializeWeb3() {
31 | /*eslint-disable */
32 | if (typeof web3 !== 'undefined') {
33 | web3Provided = new Web3(web3.currentProvider);
34 | } else {
35 | web3Provided = new Web3(new Web3.providers.HttpProvider(testrpcUrl));
36 | }
37 | /*eslint-enable */
38 |
39 | return getExtendedWeb3Provider(web3Provided);
40 | }
41 |
42 | function web3Client() {
43 | if (web3Provided) {
44 | return web3Provided;
45 | } else {
46 | return initializeWeb3();
47 | }
48 | }
49 |
50 | export function getAccounts() {
51 | return new Promise((resolve, reject) => {
52 | web3Client().eth.getAccounts(function (err, accts) {
53 | if (err != null) {
54 | console.log("Web3Api Error: ", err);
55 | reject();
56 | }
57 |
58 | if (accts.length === 0) {
59 | console.log("Web3Api Error: couldn't get any accounts");
60 | reject();
61 | }
62 |
63 | let accountsAndBalances = accts.map((address => {
64 | return getAccountBalance(address).then((balance) => {
65 | return { address, balance}
66 | });
67 | }));
68 |
69 | Promise.all(accountsAndBalances).then((accountsAndBalances) => {
70 | resolve(accountsAndBalances);
71 | });
72 |
73 | });
74 |
75 | });
76 | }
77 |
78 | export function getAccountBalance(account) {
79 | return new Promise((resolve, reject) => {
80 | web3Client().eth.getBalance(account, function(err, value) {
81 | resolve(value.valueOf());
82 | });
83 | });
84 | }
85 |
86 | export function getProjects() {
87 | return new Promise((resolve, reject) => {
88 | let fundingHubInstance;
89 | fundingHub.deployed().then(function(instance) {
90 | fundingHubInstance = instance;
91 | return fundingHubInstance.numOfProjects.call();
92 | }).then(function(result) {
93 | console.log("getProjects: ", result);
94 | let projectCount = result.valueOf();
95 |
96 | // create an array where length = projectCount
97 | let array = Array.apply(null, {length: projectCount}).map(Number.call, Number);
98 |
99 | // fill array with corresponding project contract addresses
100 | let projectPromises = array.map((id => {
101 | return getProjectAddress(id);
102 | }));
103 |
104 | // get projectDetails for each projectAddress promise
105 | Promise.all(projectPromises).then((projectAddresses) => {
106 | let projectDetailPromises = projectAddresses.map((address => {
107 | return getProjectDetails(address);
108 | }));
109 |
110 | Promise.all(projectDetailPromises).then((projects) => {
111 | resolve(projects);
112 | });
113 | });
114 | });
115 | });
116 | }
117 |
118 | function getProjectAddress(id) {
119 | return new Promise((resolve, reject) => {
120 | fundingHub.deployed().then(function(fundingHubInstance) {
121 | fundingHubInstance.projects.call(id).then(function(address) {
122 | resolve(address);
123 | });
124 | });
125 | });
126 | }
127 |
128 | export function getProjectDetails(address) {
129 | return new Promise((resolve, reject) => {
130 | let projectInstance;
131 | project.at(address).then(function(instance) {
132 | projectInstance = instance;
133 | projectInstance.getProject.call().then(function(projectDetails) {
134 | resolve({
135 | title: projectDetails[0],
136 | goal: fromWei(projectDetails[1].toNumber()),
137 | deadline: projectDetails[2].toNumber(),
138 | creator: projectDetails[3],
139 | totalFunding: fromWei(projectDetails[4].toNumber()),
140 | contributionsCount: projectDetails[5].toNumber(),
141 | contributorsCount: projectDetails[6].toNumber(),
142 | fundingHub: projectDetails[7],
143 | address: projectDetails[8]
144 | });
145 | });
146 | });
147 | });
148 | }
149 |
150 | export function createProject(params, creator) {
151 | return new Promise((resolve, reject) => {
152 | let fundingHubInstance;
153 | fundingHub.deployed().then(function(instance) {
154 | fundingHubInstance = instance;
155 | fundingHubInstance.createProject(
156 | toWei(params.projectGoalInEth),
157 | params.projectDeadline,
158 | params.projectName,
159 | {
160 | from: creator,
161 | gas: 1000000
162 | }
163 | ).then(function(tx) {
164 | console.log("web3Api.createProject() project tx: ", tx);
165 | resolve(tx);
166 | });
167 | });
168 | });
169 | }
170 |
171 | export function contribute(contractAddr, amount, contributorAddr) {
172 | console.log("contractAddr: ", contractAddr);
173 | console.log("amount: ", amount);
174 | console.log("contributorAddr: ", contributorAddr);
175 | // let amt = parseInt(amount); // possible bug here?
176 | let amountInWei = toWei(amount);
177 | console.log("amountInWei: ", amountInWei);
178 | return new Promise((resolve, reject) => {
179 | fundingHub.deployed().then(function(instance) {
180 | // web3Client().eth.sendTransaction({ to: "0XF9AEEE7969452E1934BCD2067E570D813BDA8D52", value: toWei(amount), from: contributorAddr, gas: 3000000}, function(result) {
181 | // console.log(result);
182 | // resolve(result);
183 | // });
184 | instance.contribute(contractAddr, { value: amountInWei, from: contributorAddr, gas: 3000000})
185 | .then(function(resultObject) {
186 | console.log("web3Api.contribute() transaction result object: ", resultObject);
187 | resolve(resultObject);
188 | });
189 | });
190 | });
191 | }
192 |
193 | export function getProjectContributions(address) {
194 | return new Promise((resolve, reject) => {
195 | project.at(address).then(function(instance) {
196 | instance.contributionsCount.call().then(function(num) {
197 | let contributionCount = num.valueOf();
198 |
199 | let array = Array.apply(null, {length: contributionCount}).map(Number.call, Number);
200 | let contributionPromises = array.map((id => {
201 | return getContribution(address, id);
202 | }));
203 |
204 | Promise.all(contributionPromises).then((contributions) => {
205 | resolve(contributions);
206 | });
207 | });
208 | });
209 | });
210 | }
211 |
212 | function getContribution(projectAddress, id) {
213 | return new Promise((resolve, reject) => {
214 | project.at(projectAddress).then(function(instance) {
215 | instance.getContribution.call(id).then(function(contribution) {
216 | resolve({
217 | amount: fromWei(contribution[0].toNumber()),
218 | contributor: contribution[1]
219 | });
220 | });
221 | });
222 | });
223 | }
224 |
225 | export function getAddressBalance(address) {
226 | return new Promise((resolve, reject) => {
227 | web3Client().eth.getBalance(address, function(err, value) {
228 | resolve(fromWei(value.valueOf()));
229 | });
230 | });
231 | }
232 |
233 | export function getCurrentBlockNumber() {
234 | return new Promise((resolve, reject) => {
235 | web3Client().eth.getBlockNumber(function (err, blockNum) {
236 | if (err) {
237 | reject();
238 | }
239 | resolve(blockNum);
240 | });
241 | });
242 | }
243 |
244 | export function getNetwork() {
245 | return new Promise((resolve, reject) => {
246 | web3Client().version.getNetwork(function (err, network) {
247 | if (err) {
248 | reject();
249 | }
250 | resolve(network);
251 | })
252 | })
253 | }
254 |
255 | export function toWei(ethValue) {
256 | return web3Client().toWei(ethValue, "ether");
257 | }
258 |
259 | export function fromWei(weiValue) {
260 | return web3Client().fromWei(weiValue, "ether");
261 | }
--------------------------------------------------------------------------------
/config/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var autoprefixer = require('autoprefixer');
2 | var webpack = require('webpack');
3 | var HtmlWebpackPlugin = require('html-webpack-plugin');
4 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
5 | var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
6 | var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
7 | var getClientEnvironment = require('./env');
8 | var paths = require('./paths');
9 |
10 |
11 |
12 | // Webpack uses `publicPath` to determine where the app is being served from.
13 | // In development, we always serve from the root. This makes config easier.
14 | var publicPath = '/';
15 | // `publicUrl` is just like `publicPath`, but we will provide it to our app
16 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
17 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
18 | var publicUrl = '';
19 | // Get environment variables to inject into our app.
20 | var env = getClientEnvironment(publicUrl);
21 |
22 | // This is the development configuration.
23 | // It is focused on developer experience and fast rebuilds.
24 | // The production configuration is different and lives in a separate file.
25 | module.exports = {
26 | // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
27 | // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
28 | devtool: 'cheap-module-source-map',
29 | // These are the "entry points" to our application.
30 | // This means they will be the "root" imports that are included in JS bundle.
31 | // The first two entry points enable "hot" CSS and auto-refreshes for JS.
32 | entry: [
33 | // Include an alternative client for WebpackDevServer. A client's job is to
34 | // connect to WebpackDevServer by a socket and get notified about changes.
35 | // When you save a file, the client will either apply hot updates (in case
36 | // of CSS changes), or refresh the page (in case of JS changes). When you
37 | // make a syntax error, this client will display a syntax error overlay.
38 | // Note: instead of the default WebpackDevServer client, we use a custom one
39 | // to bring better experience for Create React App users. You can replace
40 | // the line below with these two lines if you prefer the stock client:
41 | // require.resolve('webpack-dev-server/client') + '?/',
42 | // require.resolve('webpack/hot/dev-server'),
43 | require.resolve('react-dev-utils/webpackHotDevClient'),
44 | // We ship a few polyfills by default:
45 | require.resolve('./polyfills'),
46 | // Finally, this is your app's code:
47 | paths.appIndexJs
48 | // We include the app code last so that if there is a runtime error during
49 | // initialization, it doesn't blow up the WebpackDevServer client, and
50 | // changing JS code would still trigger a refresh.
51 | ],
52 | output: {
53 | // Next line is not used in dev but WebpackDevServer crashes without it:
54 | path: paths.appBuild,
55 | // Add /* filename */ comments to generated require()s in the output.
56 | pathinfo: true,
57 | // This does not produce a real file. It's just the virtual path that is
58 | // served by WebpackDevServer in development. This is the JS bundle
59 | // containing code from all our entry points, and the Webpack runtime.
60 | filename: 'static/js/bundle.js',
61 | // This is the URL that app is served from. We use "/" in development.
62 | publicPath: publicPath
63 | },
64 | resolve: {
65 | // This allows you to set a fallback for where Webpack should look for modules.
66 | // We read `NODE_PATH` environment variable in `paths.js` and pass paths here.
67 | // We use `fallback` instead of `root` because we want `node_modules` to "win"
68 | // if there any conflicts. This matches Node resolution mechanism.
69 | // https://github.com/facebookincubator/create-react-app/issues/253
70 | fallback: paths.nodePaths,
71 | // These are the reasonable defaults supported by the Node ecosystem.
72 | // We also include JSX as a common component filename extension to support
73 | // some tools, although we do not recommend using it, see:
74 | // https://github.com/facebookincubator/create-react-app/issues/290
75 | extensions: ['.js', '.json', '.jsx', ''],
76 | alias: {
77 | // Support React Native Web
78 | // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
79 | 'react-native': 'react-native-web'
80 | }
81 | },
82 |
83 | module: {
84 | // First, run the linter.
85 | // It's important to do this before Babel processes the JS.
86 | preLoaders: [
87 | {
88 | test: /\.(js|jsx)$/,
89 | loader: 'eslint',
90 | include: paths.appSrc,
91 | }
92 | ],
93 | loaders: [
94 | // Default loader: load all assets that are not handled
95 | // by other loaders with the url loader.
96 | // Note: This list needs to be updated with every change of extensions
97 | // the other loaders match.
98 | // E.g., when adding a loader for a new supported file extension,
99 | // we need to add the supported extension to this loader too.
100 | // Add one new line in `exclude` for each loader.
101 | //
102 | // "file" loader makes sure those assets get served by WebpackDevServer.
103 | // When you `import` an asset, you get its (virtual) filename.
104 | // In production, they would get copied to the `build` folder.
105 | // "url" loader works like "file" loader except that it embeds assets
106 | // smaller than specified limit in bytes as data URLs to avoid requests.
107 | // A missing `test` is equivalent to a match.
108 | {
109 | exclude: [
110 | /\.html$/,
111 | /\.(js|jsx)$/,
112 | /\.css$/,
113 | /\.json$/,
114 | /\.woff$/,
115 | /\.woff2$/,
116 | /\.(ttf|svg|eot)$/
117 | ],
118 | loader: 'url',
119 | query: {
120 | limit: 10000,
121 | name: 'static/media/[name].[hash:8].[ext]'
122 | }
123 | },
124 | // Process JS with Babel.
125 | {
126 | test: /\.(js|jsx)$/,
127 | include: [
128 | paths.appSrc,
129 | ],
130 | loader: 'babel',
131 | query: {
132 |
133 | // This is a feature of `babel-loader` for webpack (not Babel itself).
134 | // It enables caching results in ./node_modules/.cache/babel-loader/
135 | // directory for faster rebuilds.
136 | cacheDirectory: true
137 | }
138 | },
139 | // "postcss" loader applies autoprefixer to our CSS.
140 | // "css" loader resolves paths in CSS and adds assets as dependencies.
141 | // "style" loader turns CSS into JS modules that inject