├── .gitignore ├── chapter-10+11 ├── LICENSE ├── client │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── FundraiserCard.js │ │ ├── Home.js │ │ ├── NewFundraiser.js │ │ ├── Receipts.js │ │ ├── contracts │ │ │ ├── Fundraiser.json │ │ │ ├── FundraiserFactory.json │ │ │ ├── Migrations.json │ │ │ ├── Ownable.json │ │ │ └── SafeMath.json │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ └── utils │ │ │ └── getWeb3.js │ └── yarn.lock ├── contracts │ ├── Fundraiser.sol │ ├── FundraiserFactory.sol │ └── Migrations.sol ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_fundraiser_factory.js ├── package-lock.json ├── test │ ├── TestSimpleStorage.sol │ └── simplestorage.js └── truffle-config.js ├── chapter-4 └── greeter │ ├── contracts │ ├── Greeter.sol │ └── Migrations.sol │ ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_greeter.js │ ├── package-lock.json │ ├── test │ └── greeter_test.js │ └── truffle-config.js ├── chapter-5 └── greeter │ ├── build │ └── contracts │ │ ├── Greeter.json │ │ ├── Migrations.json │ │ └── Ownable.json │ ├── client │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── contracts │ │ ├── Context.json │ │ ├── Greeter.json │ │ ├── Migrations.json │ │ └── Ownable.json │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ └── utils │ │ └── getWeb3.js │ ├── contracts │ ├── Greeter.sol │ └── Migrations.sol │ ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_greeter.js │ ├── package-lock.json │ ├── package.json │ ├── test │ └── greeter_test.js │ └── truffle-config.js ├── chapter-6 └── fundraiser │ ├── LICENSE │ ├── client │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ └── utils │ │ │ └── getWeb3.js │ └── yarn.lock │ ├── contracts │ ├── Fundraiser.sol │ └── Migrations.sol │ ├── migrations │ └── 1_initial_migration.js │ ├── package-lock.json │ ├── test │ └── fundraiser_test.js │ └── truffle-config.js ├── chapter-7 └── fundraiser │ ├── LICENSE │ ├── client │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ └── utils │ │ │ └── getWeb3.js │ └── yarn.lock │ ├── contracts │ ├── Fundraiser.sol │ ├── FundraiserFactory.sol │ └── Migrations.sol │ ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_fundraiser_factory.js │ ├── package-lock.json │ ├── test │ ├── fundraiser_factory_test.js │ └── fundraiser_test.js │ └── truffle-config.js └── chapter-9 ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── FundraiserCard.js ├── Home.js ├── NewFundraiser.js ├── Receipts.js ├── contracts │ ├── Factory.json │ ├── Fundraiser.json │ ├── Migrations.json │ └── SimpleStorage.json ├── index.css ├── index.js ├── logo.svg ├── serviceWorker.js └── utils │ └── getWeb3.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | build/ 4 | -------------------------------------------------------------------------------- /chapter-10+11/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Truffle 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 | 23 | -------------------------------------------------------------------------------- /chapter-10+11/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /chapter-10+11/client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /chapter-10+11/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.3.1", 7 | "@openzeppelin/contracts": "^2.3.0", 8 | "bootstrap": "^4.3.1", 9 | "cryptocompare": "^1.0.0", 10 | "get-eth-price": "^1.0.0", 11 | "normalize.css": "^8.0.1", 12 | "react": "^16.8.0", 13 | "react-bootstrap": "^1.0.0-beta.10", 14 | "react-dom": "^16.9.0", 15 | "react-redux": "^7.1.1", 16 | "react-router-dom": "^5.0.1", 17 | "react-scripts": "2.1.1", 18 | "redux": "^4.0.4", 19 | "web3": "^1.2.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": "react-app" 29 | }, 30 | "browserslist": [ 31 | ">0.2%", 32 | "not dead", 33 | "not ie <= 11", 34 | "not op_mini all" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /chapter-10+11/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedSquirrelTech/hoscdev/244df40900531db9e3ac6417ca6b3ab91ea3fcde/chapter-10+11/client/public/favicon.ico -------------------------------------------------------------------------------- /chapter-10+11/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter-10+11/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 !important; 3 | } 4 | 5 | .App { 6 | text-align: center; 7 | } 8 | 9 | .donation-input-container { 10 | display: flex; 11 | } 12 | 13 | .withdrawal-button { 14 | margin-left: 10px; 15 | } 16 | 17 | .receipt-header { 18 | border-bottom: 1px solid gray; 19 | } 20 | 21 | .receipt-container { 22 | text-align: center; 23 | } 24 | 25 | .receipt-info { 26 | display: flex; 27 | padding: 50px; 28 | justify-content: space-between; 29 | } 30 | 31 | .donation-receipt-link { 32 | color: inherit; 33 | text-decoration: none; 34 | } 35 | 36 | .main-container { 37 | margin: 20px; 38 | } 39 | 40 | .donation-list { 41 | margin-top: 10px; 42 | margin-bottom: 10px; 43 | display: flex; 44 | justify-content: space-between; 45 | } 46 | 47 | .fundraiser-card-container { 48 | display: inline-flex; 49 | width: 250px; 50 | height: 250px; 51 | margin: 20px; 52 | } 53 | 54 | .MuiTextField-root { 55 | display: block !important; 56 | } 57 | 58 | .MuiInputBase-root { 59 | width: 300px !important; 60 | margin-left: 3px; 61 | } 62 | 63 | .App-logo { 64 | animation: App-logo-spin infinite 20s linear; 65 | height: 40vmin; 66 | } 67 | 68 | .App-header { 69 | background-color: #282c34; 70 | min-height: 100vh; 71 | display: flex; 72 | flex-direction: column; 73 | align-items: center; 74 | justify-content: center; 75 | font-size: calc(10px + 2vmin); 76 | color: white; 77 | } 78 | 79 | .App-link { 80 | color: #61dafb; 81 | } 82 | 83 | @keyframes App-logo-spin { 84 | from { 85 | transform: rotate(0deg); 86 | } 87 | to { 88 | transform: rotate(360deg); 89 | } 90 | } 91 | 92 | .owner-actions-container { 93 | button { 94 | margin-left: 20px; 95 | } 96 | } 97 | 98 | .nav-link { 99 | color: inherit; 100 | text-decoration: none; 101 | margin-right: 15px; 102 | } 103 | 104 | .nav-link:hover, 105 | .nav-link:active, 106 | .nav-link:visited { 107 | color: black; 108 | text-decoration: none; 109 | } 110 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/App.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import FactoryContract from "./contracts/FundraiserFactory.json"; 4 | import getWeb3 from "./utils/getWeb3"; 5 | import { makeStyles } from '@material-ui/core/styles'; 6 | import AppBar from '@material-ui/core/AppBar'; 7 | import Toolbar from '@material-ui/core/Toolbar'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import { BrowserRouter as Router, Route, NavLink } from "react-router-dom"; 10 | 11 | import NewFundraiser from './NewFundraiser' 12 | import Home from './Home' 13 | import Receipts from './Receipts' 14 | 15 | import "./App.css"; 16 | 17 | const App = () => { 18 | const [state, setState] = useState({web3: null, accounts: null, contract: null}); 19 | 20 | useEffect(() => { 21 | const init = async() => { 22 | try { 23 | // Get network provider and web3 instance. 24 | const web3 = await getWeb3(); 25 | 26 | // Use web3 to get the user's accounts. 27 | const accounts = await web3.eth.getAccounts(); 28 | 29 | // Get the contract instance. 30 | const networkId = await web3.eth.net.getId(); 31 | const deployedNetwork = FactoryContract.networks[networkId]; 32 | const instance = new web3.eth.Contract( 33 | FactoryContract.abi, 34 | deployedNetwork && deployedNetwork.address, 35 | ); 36 | 37 | // Set web3, accounts, and contract to the state, and then proceed with an 38 | setState({web3, accounts, contract: instance}); 39 | 40 | } catch(error) { 41 | // Catch any errors for any of the above operations. 42 | alert( 43 | `Failed to load web3, accounts, or contract. Check console for details.`, 44 | ); 45 | console.error(error); 46 | } 47 | } 48 | init(); 49 | }, []); 50 | 51 | const useStyles = makeStyles({ 52 | root: { 53 | flexGrow: 1, 54 | }, 55 | }); 56 | 57 | const runExample = async () => { 58 | const { accounts, contract } = state; 59 | }; 60 | 61 | return ( 62 |
63 | 64 | 65 | 66 | 67 | Home 68 | 69 | New 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 | ) 79 | } 80 | 81 | 82 | export default App; 83 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/FundraiserCard.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActionArea from '@material-ui/core/CardActionArea'; 5 | import CardActions from '@material-ui/core/CardActions'; 6 | import CardContent from '@material-ui/core/CardContent'; 7 | import CardMedia from '@material-ui/core/CardMedia'; 8 | import Button from '@material-ui/core/Button'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import Dialog from '@material-ui/core/Dialog'; 11 | import DialogActions from '@material-ui/core/DialogActions'; 12 | import DialogContent from '@material-ui/core/DialogContent'; 13 | import DialogContentText from '@material-ui/core/DialogContentText'; 14 | import DialogTitle from '@material-ui/core/DialogTitle'; 15 | 16 | import FilledInput from '@material-ui/core/FilledInput'; 17 | import FormControl from '@material-ui/core/FormControl'; 18 | import FormHelperText from '@material-ui/core/FormHelperText'; 19 | import Input from '@material-ui/core/Input'; 20 | import InputLabel from '@material-ui/core/InputLabel'; 21 | import OutlinedInput from '@material-ui/core/OutlinedInput'; 22 | 23 | import getWeb3 from "./utils/getWeb3"; 24 | import FundraiserContract from "./contracts/Fundraiser.json"; 25 | import Web3 from 'web3' 26 | 27 | import { Link } from 'react-router-dom' 28 | 29 | const cc = require('cryptocompare') 30 | 31 | const getModalStyle =() => { 32 | const top = 50; 33 | const left = 50; 34 | 35 | return { 36 | top, 37 | left, 38 | }; 39 | } 40 | 41 | const useStyles = makeStyles(theme => ({ 42 | container: { 43 | display: 'flex', 44 | flexWrap: 'wrap', 45 | }, 46 | formControl: { 47 | margin: theme.spacing(1), 48 | display: 'table-cell' 49 | }, 50 | card: { 51 | maxWidth: 450, 52 | height: 400 53 | }, 54 | media: { 55 | height: 140, 56 | }, 57 | paper: { 58 | position: 'absolute', 59 | width: 500, 60 | backgroundColor: theme.palette.background.paper, 61 | border: 'none', 62 | boxShadow: 'none', 63 | padding: 4, 64 | }, 65 | })); 66 | 67 | const FundraiserCard = (props) => { 68 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 69 | 70 | const [ contract, setContract] = useState(null) 71 | const [ accounts, setAccounts ] = useState(null) 72 | const [ fund, setFundraiser ] = useState(null) 73 | const [ fundName, setFundname ] = useState(null) 74 | const [ description, setDescription ] = useState(null) 75 | const [ totalDonations, setTotalDonations ] = useState(null) 76 | const [ imageURL, setImageURL ] = useState(null) 77 | const [ url, setURL ] = useState(null) 78 | const [ open, setOpen] = React.useState(false); 79 | const [ donationAmount, setDonationAmount] = useState(null) 80 | const [ exchangeRate, setExchangeRate ] = useState(null) 81 | const [ userDonations, setUserDonations ] = useState(null) 82 | const [ isOwner, setIsOwner ] = useState(false) 83 | const [ beneficiary, setNewBeneficiary ] = useState('') 84 | 85 | const ethAmount = (donationAmount / exchangeRate || 0).toFixed(4) 86 | 87 | const { fundraiser } = props 88 | 89 | const classes = useStyles(); 90 | 91 | useEffect(() => { 92 | if (fundraiser) { 93 | init(fundraiser) 94 | } 95 | }, [fundraiser]); 96 | 97 | const init = async (fundraiser) => { 98 | try { 99 | const fund = fundraiser 100 | const networkId = await web3.eth.net.getId(); 101 | const deployedNetwork = FundraiserContract.networks[networkId]; 102 | const accounts = await web3.eth.getAccounts(); 103 | const instance = new web3.eth.Contract( 104 | FundraiserContract.abi, 105 | fund 106 | ); 107 | setContract(instance) 108 | setAccounts(accounts) 109 | 110 | const name = await instance.methods.name().call() 111 | const description = await instance.methods.description().call() 112 | const totalDonations = await instance.methods.totalDonations().call() 113 | const imageURL = await instance.methods.imageURL().call() 114 | const url = await instance.methods.url().call() 115 | 116 | const exchangeRate = await cc.price('ETH', ['USD']) 117 | setExchangeRate(exchangeRate.USD) 118 | const eth = web3.utils.fromWei(totalDonations, 'ether') 119 | const dollarDonationAmount = exchangeRate.USD * eth 120 | 121 | setTotalDonations(dollarDonationAmount.toFixed(2)) 122 | setFundname(name) 123 | setDescription(description) 124 | setImageURL(imageURL) 125 | setURL(url) 126 | 127 | const userDonations = await instance.methods.myDonations().call({ from: accounts[0]}) 128 | console.log(userDonations) 129 | setUserDonations(userDonations) 130 | 131 | const isUser = accounts[0] 132 | const isOwner = await instance.methods.owner().call() 133 | 134 | if (isOwner === accounts[0]) { 135 | setIsOwner(true) 136 | } 137 | } 138 | catch(error) { 139 | alert( 140 | `Failed to load web3, accounts, or contract. Check console for details.`, 141 | ); 142 | console.error(error); 143 | } 144 | } 145 | 146 | window.ethereum.on('accountsChanged', function (accounts) { 147 | window.location.reload() 148 | }) 149 | 150 | const handleOpen = () => { 151 | setOpen(true); 152 | }; 153 | 154 | const handleClose = () => { 155 | setOpen(false); 156 | }; 157 | 158 | const submitFunds = async () => { 159 | const fundraisercontract = contract 160 | const ethRate = exchangeRate 161 | const ethTotal = donationAmount / ethRate 162 | const donation = web3.utils.toWei(ethTotal.toString()) 163 | 164 | await contract.methods.donate().send({ 165 | from: accounts[0], 166 | value: donation, 167 | gas: 650000 168 | }) 169 | setOpen(false); 170 | } 171 | 172 | const renderDonationsList = () => { 173 | var donations = userDonations 174 | if (donations === null) {return null} 175 | 176 | const totalDonations = donations.values.length 177 | let donationList = [] 178 | var i 179 | for (i = 0; i < totalDonations; i++) { 180 | const ethAmount = web3.utils.fromWei(donations.values[i]) 181 | const userDonation = exchangeRate * ethAmount 182 | const donationDate = donations.dates[i] 183 | donationList.push({ donationAmount: userDonation.toFixed(2), date: donationDate}) 184 | } 185 | 186 | return donationList.map((donation) => { 187 | return ( 188 |
189 |

${donation.donationAmount}

190 | 191 | 196 |
197 | ) 198 | }) 199 | } 200 | 201 | const withdrawalFunds = async () => { 202 | await contract.methods.withdraw().send({ 203 | from: accounts[0], 204 | }) 205 | 206 | alert('Funds Withdrawn!') 207 | } 208 | 209 | const setBeneficiary = async () => { 210 | await contract.methods.setBeneficiary(beneficiary).send({ 211 | from: accounts[0], 212 | }) 213 | 214 | alert(`Fundraiser Beneficiary Changed`) 215 | } 216 | 217 | return ( 218 |
219 | 220 | 221 | Donate to {fundName} 222 | 223 | 224 | 225 | 226 |

{description}

227 | 228 |
229 | 230 | $ 231 | setDonationAmount(e.target.value)} 235 | placeholder="0.00" 236 | /> 237 | 238 | 239 |

Eth: {ethAmount}

240 |
241 | 242 | 245 | 246 |
247 |

My donations

248 | {renderDonationsList()} 249 |
250 | 251 | 252 | {isOwner && 253 |
254 | 255 | Beneficiary: 256 | setNewBeneficiary(e.target.value)} 259 | placeholder="Set Beneficiary" 260 | /> 261 | 262 | 263 | 266 |
267 | } 268 |
269 |
270 | 271 | 274 | {isOwner && 275 | 282 | } 283 | 284 |
285 | 286 | 287 | 288 | 293 | 294 | 295 | {fundName} 296 | 297 | 298 |

{description}

299 |

Total Donations: ${totalDonations}

300 |
301 |
302 |
303 | 304 | 310 | 311 |
312 |
313 | ) 314 | } 315 | 316 | export default FundraiserCard; 317 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/Home.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import FundraiserCard from './FundraiserCard' 5 | import getWeb3 from "./utils/getWeb3"; 6 | import FactoryContract from "./contracts/FundraiserFactory.json"; 7 | import Web3 from 'web3' 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | button: { 11 | margin: theme.spacing(1), 12 | }, 13 | input: { 14 | display: 'none', 15 | }, 16 | })); 17 | 18 | 19 | const Home = () => { 20 | const [ contract, setContract] = useState(null) 21 | const [ accounts, setAccounts ] = useState(null) 22 | const [ funds, setFunds ] = useState([]) 23 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 24 | 25 | useEffect(() => { 26 | init() 27 | }, []); 28 | 29 | const init = async () => { 30 | try { 31 | const networkId = await web3.eth.net.getId(); 32 | const deployedNetwork = FactoryContract.networks[networkId]; 33 | const accounts = await web3.eth.getAccounts(); 34 | const instance = new web3.eth.Contract( 35 | FactoryContract.abi, 36 | deployedNetwork && deployedNetwork.address, 37 | ); 38 | setContract(instance) 39 | setAccounts(accounts) 40 | 41 | const funds = await instance.methods.fundraisers(10, 0).call() 42 | 43 | setFunds(funds) 44 | } 45 | catch(error) { 46 | alert( 47 | `Failed to load web3, accounts, or contract. Check console for details.`, 48 | ); 49 | console.error(error); 50 | } 51 | } 52 | 53 | const displayFundraisers = () => { 54 | return funds.map((fundraiser) => { 55 | return ( 56 | 60 | ) 61 | }) 62 | } 63 | 64 | return ( 65 |
66 | {displayFundraisers()} 67 |
68 | ) 69 | } 70 | 71 | export default Home; 72 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/NewFundraiser.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import FormControl from '@material-ui/core/FormControl'; 4 | import { makeStyles } from '@material-ui/core/styles'; 5 | import Button from '@material-ui/core/Button'; 6 | import MenuItem from '@material-ui/core/MenuItem'; 7 | import TextField from '@material-ui/core/TextField'; 8 | import getWeb3 from "./utils/getWeb3"; 9 | import FactoryContract from "./contracts/FundraiserFactory.json"; 10 | import Web3 from 'web3' 11 | 12 | const useStyles = makeStyles(theme => ({ 13 | container: { 14 | display: 'flex', 15 | flexWrap: 'wrap', 16 | }, 17 | textField: { 18 | marginLeft: theme.spacing(1), 19 | marginRight: theme.spacing(1), 20 | }, 21 | dense: { 22 | marginTop: theme.spacing(2), 23 | }, 24 | menu: { 25 | width: 200, 26 | }, 27 | })); 28 | 29 | const NewFundraiser = () => { 30 | const [labelWidth, setLabelWidth] = React.useState(0); 31 | const labelRef = React.useRef(null); 32 | const classes = useStyles(); 33 | const [ web3, setWeb3 ] = useState(null) 34 | 35 | useEffect(() => { 36 | const init = async() => { 37 | try { 38 | const web3 = await getWeb3(); 39 | const networkId = await web3.eth.net.getId(); 40 | const deployedNetwork = FactoryContract.networks[networkId]; 41 | const accounts = await web3.eth.getAccounts(); 42 | const instance = new web3.eth.Contract( 43 | FactoryContract.abi, 44 | deployedNetwork && deployedNetwork.address, 45 | ); 46 | 47 | setWeb3(web3) 48 | setContract(instance) 49 | setAccounts(accounts) 50 | 51 | } catch(error) { 52 | alert( 53 | `Failed to load web3, accounts, or contract. Check console for details.`, 54 | ); 55 | console.error(error); 56 | } 57 | } 58 | init(); 59 | }, []); 60 | 61 | const [ name, setFundraiserName ] = useState(null) 62 | const [ website, setFundraiserWebsite ] = useState(null) 63 | const [ description, setFundraiserDescription ] = useState(null) 64 | const [ image, setImage ] = useState(null) 65 | const [ address, setAddress ] = useState(null) 66 | const [ contract, setContract] = useState(null) 67 | const [ accounts, setAccounts ] = useState(null) 68 | 69 | const handleSubmit = async () => { 70 | const imageURL = image 71 | const url = website 72 | const beneficiary = address 73 | const currentUser = await web3.currentProvider.selectedAddress 74 | 75 | const transaction = await contract.methods.createFundraiser( 76 | name, 77 | url, 78 | imageURL, 79 | description, 80 | beneficiary 81 | ).send({ from: accounts[0] }) 82 | 83 | alert('Successfully created fundraiser') 84 | } 85 | 86 | return ( 87 |
88 |

Create A New Fundraiser

89 | 90 | 91 | setFundraiserName(e.target.value)} 97 | variant="outlined" 98 | inputProps={{ 'aria-label': 'bare' }} 99 | /> 100 | 101 | 102 | setFundraiserWebsite(e.target.value)} 108 | variant="outlined" 109 | inputProps={{ 'aria-label': 'bare' }} 110 | /> 111 | 112 | 113 | setFundraiserDescription(e.target.value)} 119 | variant="outlined" 120 | inputProps={{ 'aria-label': 'bare' }} 121 | /> 122 | 123 | 124 | setImage(e.target.value)} 130 | variant="outlined" 131 | inputProps={{ 'aria-label': 'bare' }} 132 | /> 133 | 134 | 135 | setAddress(e.target.value)} 141 | variant="outlined" 142 | inputProps={{ 'aria-label': 'bare' }} 143 | /> 144 | 145 | 151 |
152 | ) 153 | } 154 | 155 | 156 | export default NewFundraiser; 157 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/Receipts.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | 3 | const Receipts = (props) => { 4 | const [ donation, setDonation ] = useState(null) 5 | const [ fundName, setFundName ] = useState(null) 6 | const [ date, setDate ] = useState(null) 7 | 8 | useEffect(() => { 9 | const { donation, date, fund } = props.location.state 10 | 11 | const formattedDate = new Date(parseInt(date)) 12 | 13 | setDonation(donation) 14 | setDate(formattedDate.toString()) 15 | setFundName(fund) 16 | }, []); 17 | 18 | return ( 19 |
20 |
21 |

Thank you for your donation to {fundName}

22 |
23 | 24 |
25 |
Date of Donation: {date}
26 |
Donation Value: ${donation}
27 |
28 |
29 | ) 30 | } 31 | 32 | export default Receipts; 33 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom' 4 | import App from './App'; 5 | 6 | ReactDOM.render(( 7 | // 8 | 9 | 10 | 11 | // 12 | ), document.getElementById('root')) 13 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /chapter-10+11/client/src/utils/getWeb3.js: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | const getWeb3 = () => 4 | new Promise((resolve, reject) => { 5 | // Wait for loading completion to avoid race conditions with web3 injection timing. 6 | window.addEventListener("load", async () => { 7 | // Modern dapp browsers... 8 | if (window.ethereum) { 9 | const web3 = new Web3(window.ethereum); 10 | try { 11 | // Request account access if needed 12 | await window.ethereum.enable(); 13 | // Acccounts now exposed 14 | resolve(web3); 15 | } catch (error) { 16 | reject(error); 17 | } 18 | } 19 | // Legacy dapp browsers... 20 | else if (window.web3) { 21 | // Use Mist/MetaMask's provider. 22 | const web3 = window.web3; 23 | console.log("Injected web3 detected."); 24 | resolve(web3); 25 | } 26 | // Fallback to localhost; use dev console port by default... 27 | else { 28 | const provider = new Web3.providers.HttpProvider( 29 | "http://127.0.0.1:8545" 30 | ); 31 | const web3 = new Web3(provider); 32 | console.log("No web3 instance injected, using Local web3."); 33 | resolve(web3); 34 | } 35 | }); 36 | }); 37 | 38 | export default getWeb3; 39 | -------------------------------------------------------------------------------- /chapter-10+11/contracts/Fundraiser.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.23 <0.7.0; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | contract Fundraiser is Ownable { 7 | using SafeMath for uint256; 8 | 9 | struct Donation { 10 | uint256 value; 11 | uint256 date; 12 | } 13 | mapping(address => Donation[]) private _donations; 14 | 15 | event DonationReceived(address indexed donor, uint256 value); 16 | event Withdraw(uint256 amount); 17 | 18 | string public name; 19 | string public url; 20 | string public imageURL; 21 | string public description; 22 | 23 | address payable public beneficiary; 24 | 25 | uint256 public totalDonations; 26 | uint256 public donationsCount; 27 | 28 | constructor( 29 | string memory _name, 30 | string memory _url, 31 | string memory _imageURL, 32 | string memory _description, 33 | address payable _beneficiary, 34 | address _custodian 35 | ) 36 | public 37 | { 38 | name = _name; 39 | url = _url; 40 | imageURL = _imageURL; 41 | description = _description; 42 | beneficiary = _beneficiary; 43 | _transferOwnership(_custodian); 44 | } 45 | 46 | function setBeneficiary(address payable _beneficiary) public onlyOwner { 47 | beneficiary = _beneficiary; 48 | } 49 | 50 | function myDonationsCount() public view returns(uint256) { 51 | return _donations[msg.sender].length; 52 | } 53 | 54 | function donate() public payable { 55 | Donation memory donation = Donation({ 56 | value: msg.value, 57 | date: block.timestamp 58 | }); 59 | _donations[msg.sender].push(donation); 60 | totalDonations = totalDonations.add(msg.value); 61 | donationsCount++; 62 | 63 | emit DonationReceived(msg.sender, msg.value); 64 | } 65 | 66 | function myDonations() public view returns( 67 | uint256[] memory values, 68 | uint256[] memory dates 69 | ) 70 | { 71 | uint256 count = myDonationsCount(); 72 | values = new uint256[](count); 73 | dates = new uint256[](count); 74 | 75 | for (uint256 i = 0; i < count; i++) { 76 | Donation storage donation = _donations[msg.sender][i]; 77 | values[i] = donation.value; 78 | dates[i] = donation.date; 79 | } 80 | 81 | return (values, dates); 82 | } 83 | 84 | function withdraw() public onlyOwner { 85 | uint256 balance = address(this).balance; 86 | beneficiary.transfer(balance); 87 | emit Withdraw(balance); 88 | } 89 | 90 | function () external payable { 91 | totalDonations = totalDonations.add(msg.value); 92 | donationsCount++; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /chapter-10+11/contracts/FundraiserFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.23 <0.7.0; 2 | 3 | import "./Fundraiser.sol"; 4 | 5 | contract FundraiserFactory { 6 | uint256 constant maxLimit = 20; 7 | Fundraiser[] private _fundraisers; 8 | 9 | event FundraiserCreated(Fundraiser indexed fundraiser, address indexed owner); 10 | 11 | function createFundraiser( 12 | string memory name, 13 | string memory url, 14 | string memory imageURL, 15 | string memory description, 16 | address payable beneficiary 17 | ) 18 | public 19 | { 20 | Fundraiser fundraiser = new Fundraiser( 21 | name, 22 | url, 23 | imageURL, 24 | description, 25 | beneficiary, 26 | msg.sender 27 | ); 28 | _fundraisers.push(fundraiser); 29 | emit FundraiserCreated(fundraiser, msg.sender); 30 | } 31 | 32 | function fundraisersCount() public view returns(uint256) { 33 | return _fundraisers.length; 34 | } 35 | 36 | function fundraisers(uint256 limit, uint256 offset) 37 | public 38 | view 39 | returns(Fundraiser[] memory coll) 40 | { 41 | require(offset <= fundraisersCount(), "offset out of bounds"); 42 | 43 | uint256 size = fundraisersCount() - offset; 44 | size = size < limit ? size : limit; 45 | size = size < maxLimit ? size : maxLimit; 46 | coll = new Fundraiser[](size); 47 | 48 | for(uint256 i = 0; i < size; i++) { 49 | coll[i] = _fundraisers[offset + i]; 50 | } 51 | 52 | return coll; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chapter-10+11/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-10+11/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /chapter-10+11/migrations/2_deploy_fundraiser_factory.js: -------------------------------------------------------------------------------- 1 | const FundraiserFactoryContract = artifacts.require("FundraiserFactory"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(FundraiserFactoryContract); 5 | } 6 | -------------------------------------------------------------------------------- /chapter-10+11/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "openzeppelin-solidity": { 6 | "version": "2.3.0", 7 | "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", 8 | "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-10+11/test/TestSimpleStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "truffle/Assert.sol"; 4 | import "truffle/DeployedAddresses.sol"; 5 | import "../contracts/SimpleStorage.sol"; 6 | 7 | contract TestSimpleStorage { 8 | 9 | function testItStoresAValue() public { 10 | SimpleStorage simpleStorage = SimpleStorage(DeployedAddresses.SimpleStorage()); 11 | 12 | simpleStorage.set(89); 13 | 14 | uint expected = 89; 15 | 16 | Assert.equal(simpleStorage.get(), expected, "It should store the value 89."); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /chapter-10+11/test/simplestorage.js: -------------------------------------------------------------------------------- 1 | const SimpleStorage = artifacts.require("./SimpleStorage.sol"); 2 | 3 | contract("SimpleStorage", accounts => { 4 | it("...should store the value 89.", async () => { 5 | const simpleStorageInstance = await SimpleStorage.deployed(); 6 | 7 | // Set value of 89 8 | await simpleStorageInstance.set(89, { from: accounts[0] }); 9 | 10 | // Get stored value 11 | const storedData = await simpleStorageInstance.get.call(); 12 | 13 | assert.equal(storedData, 89, "The value 89 was not stored."); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /chapter-10+11/truffle-config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | // See 5 | // to customize your Truffle configuration! 6 | contracts_build_directory: path.join(__dirname, "client/src/contracts"), 7 | networks: { 8 | develop: { 9 | port: 8545 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /chapter-4/greeter/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.4.0 < 0.7.0; 2 | 3 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 4 | 5 | contract Greeter is Ownable { 6 | string private _greeting = "Hello, World!"; 7 | 8 | function greet() external view returns(string memory) { 9 | return _greeting; 10 | } 11 | 12 | function setGreeting(string calldata greeting) external onlyOwner { 13 | _greeting = greeting; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-4/greeter/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-4/greeter/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /chapter-4/greeter/migrations/2_deploy_greeter.js: -------------------------------------------------------------------------------- 1 | const GreeterContract = artifacts.require("Greeter"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(GreeterContract); 5 | } 6 | -------------------------------------------------------------------------------- /chapter-4/greeter/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "openzeppelin-solidity": { 6 | "version": "2.3.0", 7 | "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", 8 | "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-4/greeter/test/greeter_test.js: -------------------------------------------------------------------------------- 1 | const GreeterContract = artifacts.require("Greeter"); 2 | 3 | contract("Greeter", (accounts) => { 4 | describe("deployment", () => { 5 | it("has been deployed successfully", async () => { 6 | const greeter = await GreeterContract.deployed(); 7 | assert(greeter, "contract failed to deploy"); 8 | }); 9 | }); 10 | 11 | describe("greet()", () => { 12 | it("returns 'Hello, World!'", async () => { 13 | const greeter = await GreeterContract.deployed(); 14 | const expected = "Hello, World!"; 15 | const actual = await greeter.greet(); 16 | 17 | assert.equal(actual, expected, "greeted with 'Hello, World!'"); 18 | }) 19 | }); 20 | 21 | describe("owner()", () => { 22 | it("returns the address of the owner", async () => { 23 | const greeter = await GreeterContract.deployed(); 24 | const owner = await greeter.owner(); 25 | 26 | assert(owner, "the current owner"); 27 | }); 28 | 29 | it("matches the address that originally deployed the contract", async () => { 30 | const greeter = await GreeterContract.deployed(); 31 | const owner = await greeter.owner(); 32 | const expected = accounts[0]; 33 | 34 | assert.equal(owner, expected, "matches address used to deploy contract"); 35 | }); 36 | }); 37 | }); 38 | 39 | contract("Greeter: update greeting", (accounts) => { 40 | describe("setGreeting(string)", () => { 41 | describe("when message is sent by the owner", () => { 42 | it("sets greeting to passed in string", async () => { 43 | const greeter = await GreeterContract.deployed() 44 | const expected = "The owner changed the message"; 45 | 46 | await greeter.setGreeting(expected); 47 | const actual = await greeter.greet(); 48 | 49 | assert.equal(actual, expected, "greeting was not updated"); 50 | }); 51 | }); 52 | 53 | describe("when message is sent by another account", () => { 54 | it("does not set the greeting", async () => { 55 | const greeter = await GreeterContract.deployed() 56 | const expected = await greeter.greet(); 57 | 58 | try { 59 | await greeter.setGreeting("Not the owner", { from: accounts[1] }); 60 | } catch(err) { 61 | const errorMessage = "Ownable: caller is not the owner" 62 | assert.equal(err.reason, errorMessage, "greeting should not update"); 63 | return; 64 | } 65 | assert(false, "greeting should not update"); 66 | }); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /chapter-4/greeter/truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | // development: { 46 | // host: "127.0.0.1", // Localhost (default: none) 47 | // port: 8545, // Standard Ethereum port (default: none) 48 | // network_id: "*", // Any network (default: none) 49 | // }, 50 | 51 | // Another network with more advanced options... 52 | // advanced: { 53 | // port: 8777, // Custom port 54 | // network_id: 1342, // Custom network 55 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 56 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 57 | // from:
, // Account to send txs from (default: accounts[0]) 58 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 59 | // }, 60 | 61 | // Useful for deploying to a public network. 62 | // NB: It's important to wrap the provider as a function. 63 | // ropsten: { 64 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 65 | // network_id: 3, // Ropsten's id 66 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 67 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 68 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 69 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 70 | // }, 71 | 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | // timeout: 100000 83 | }, 84 | 85 | // Configure your compilers 86 | compilers: { 87 | solc: { 88 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 89 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 90 | // settings: { // See the solidity docs for advice about optimization and evmVersion 91 | // optimizer: { 92 | // enabled: false, 93 | // runs: 200 94 | // }, 95 | // evmVersion: "byzantium" 96 | // } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.6.3", 7 | "react-dom": "^16.6.3", 8 | "react-scripts": "2.1.1", 9 | "web3": "^1.3.4" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "eslintConfig": { 18 | "extends": "react-app" 19 | }, 20 | "browserslist": [ 21 | ">0.2%", 22 | "not dead", 23 | "not ie <= 11", 24 | "not op_mini all" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedSquirrelTech/hoscdev/244df40900531db9e3ac6417ca6b3ab91ea3fcde/chapter-5/greeter/client/public/favicon.ico -------------------------------------------------------------------------------- /chapter-5/greeter/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import GreeterContract from "./contracts/Greeter.json"; 3 | import getWeb3 from "./utils/getWeb3"; 4 | 5 | import "./App.css"; 6 | 7 | class App extends Component { 8 | state = { greeting: '', web3: null, accounts: null, contract: null }; 9 | 10 | componentDidMount = async () => { 11 | try { 12 | // Get network provider and web3 instance. 13 | const web3 = await getWeb3(); 14 | 15 | // Use web3 to get the user's accounts. 16 | const accounts = await web3.eth.getAccounts(); 17 | 18 | // Get the contract instance. 19 | const networkId = await web3.eth.net.getId(); 20 | const deployedNetwork = GreeterContract.networks[networkId]; 21 | const instance = new web3.eth.Contract( 22 | GreeterContract.abi, 23 | deployedNetwork && deployedNetwork.address, 24 | ); 25 | 26 | // Set web3, accounts, and contract to the state, and then proceed with an 27 | // example of interacting with the contract's methods. 28 | this.setState({ web3, accounts, contract: instance }, this.runExample); 29 | } catch (error) { 30 | // Catch any errors for any of the above operations. 31 | alert( 32 | `Failed to load web3, accounts, or contract. Check console for details.`, 33 | ); 34 | console.error(error); 35 | } 36 | }; 37 | 38 | runExample = async () => { 39 | const { accounts, contract } = this.state; 40 | const response = await contract.methods.greet().call() 41 | 42 | this.setState({ greeting: response }); 43 | }; 44 | 45 | handleGreetingChange = (e) => { 46 | const inputVal = e.target.value 47 | this.setState({ greeting: inputVal }) 48 | } 49 | 50 | formSubmitHandler = async () => { 51 | const { accounts, contract, greeting } = this.state; 52 | const updatedGreeting = await contract.methods.setGreeting(greeting).send({from: accounts[0]}); 53 | } 54 | 55 | render() { 56 | if (!this.state.web3) { 57 | return
Loading Web3, accounts, and contract...
; 58 | } 59 | return ( 60 |
61 |

Greeter

62 | 63 | {this.state.greeting} 64 | 65 |
66 | 70 |
71 | 72 | 73 | 74 |
75 | ); 76 | } 77 | } 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /chapter-5/greeter/client/src/utils/getWeb3.js: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | const getWeb3 = () => 4 | new Promise((resolve, reject) => { 5 | // Wait for loading completion to avoid race conditions with web3 injection timing. 6 | window.addEventListener("load", async () => { 7 | // Modern dapp browsers... 8 | if (window.ethereum) { 9 | const web3 = new Web3(window.ethereum); 10 | try { 11 | // Request account access if needed 12 | await window.ethereum.enable(); 13 | // Acccounts now exposed 14 | resolve(web3); 15 | } catch (error) { 16 | reject(error); 17 | } 18 | } 19 | // Legacy dapp browsers... 20 | else if (window.web3) { 21 | // Use Mist/MetaMask's provider. 22 | const web3 = window.web3; 23 | console.log("Injected web3 detected."); 24 | resolve(web3); 25 | } 26 | // Fallback to localhost; use dev console port by default... 27 | else { 28 | const provider = new Web3.providers.HttpProvider( 29 | "http://127.0.0.1:9545" 30 | ); 31 | const web3 = new Web3(provider); 32 | console.log("No web3 instance injected, using Local web3."); 33 | resolve(web3); 34 | } 35 | }); 36 | }); 37 | 38 | export default getWeb3; 39 | -------------------------------------------------------------------------------- /chapter-5/greeter/contracts/Greeter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.4.0 < 0.7.0; 2 | 3 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 4 | 5 | contract Greeter is Ownable { 6 | string private _greeting = "Hello, World!"; 7 | 8 | function greet() external view returns(string memory) { 9 | return _greeting; 10 | } 11 | 12 | function setGreeting(string calldata greeting) external onlyOwner { 13 | _greeting = greeting; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-5/greeter/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.7.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-5/greeter/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /chapter-5/greeter/migrations/2_deploy_greeter.js: -------------------------------------------------------------------------------- 1 | const GreeterContract = artifacts.require("Greeter"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(GreeterContract); 5 | } 6 | -------------------------------------------------------------------------------- /chapter-5/greeter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "openzeppelin-solidity": "^3.4.0" 4 | }, 5 | "devDependencies": { 6 | "truffle-hdwallet-provider": "^1.0.17" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-5/greeter/test/greeter_test.js: -------------------------------------------------------------------------------- 1 | const GreeterContract = artifacts.require("Greeter"); 2 | 3 | contract("Greeter", (accounts) => { 4 | it("has been deployed successfully", async () => { 5 | const greeter = await GreeterContract.deployed(); 6 | assert(greeter, "contract has been deployed"); 7 | }); 8 | 9 | describe("greet()", () => { 10 | it("returns 'Hello, World!'", async () => { 11 | const greeter = await GreeterContract.deployed(); 12 | const expected = "Hello, World!"; 13 | const actual = await greeter.greet(); 14 | 15 | assert.equal(actual, expected, "greeted with 'Hello, World!'"); 16 | }) 17 | }); 18 | 19 | describe("owner()", () => { 20 | it("returns the address of the owner", async () => { 21 | const greeter = await GreeterContract.deployed(); 22 | const owner = await greeter.owner(); 23 | 24 | assert(owner, "the current owner"); 25 | }); 26 | 27 | it("matches the address that originally deployed the contract", async () => { 28 | const greeter = await GreeterContract.deployed(); 29 | const owner = await greeter.owner(); 30 | const expected = accounts[0]; 31 | 32 | assert.equal(owner, expected, "matches address used to deploy contract"); 33 | }); 34 | }); 35 | }); 36 | 37 | contract("Greeter: update greeting", (accounts) => { 38 | describe("setGreeting(string)", () => { 39 | describe("when message is sent by the owner", () => { 40 | it("sets greeting to passed in string", async () => { 41 | const greeter = await GreeterContract.deployed() 42 | const expected = "The owner changed the message"; 43 | 44 | await greeter.setGreeting(expected); 45 | const actual = await greeter.greet(); 46 | 47 | assert.equal(actual, expected, "greeting updated"); 48 | }); 49 | }); 50 | 51 | describe("when message is sent by another account", () => { 52 | it("does not set the greeting", async () => { 53 | const greeter = await GreeterContract.deployed() 54 | const expected = await greeter.greet(); 55 | 56 | try { 57 | await greeter.setGreeting("Not the owner", { from: accounts[1] }); 58 | } catch(err) { 59 | const errorMessage = "Ownable: caller is not the owner" 60 | assert.equal(err.reason, errorMessage, "greeting should not update"); 61 | return; 62 | } 63 | assert(false, "greeting should not update"); 64 | }); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /chapter-5/greeter/truffle-config.js: -------------------------------------------------------------------------------- 1 | const HDWallterProvider = require("truffle-hdwallet-provider"); 2 | /** 3 | * Use this file to configure your truffle project. It's seeded with some 4 | * common settings for different networks and features like migrations, 5 | * compilation and testing. Uncomment the ones you need or modify 6 | * them to suit your project as necessary. 7 | * 8 | * More information about configuration can be found at: 9 | * 10 | * truffleframework.com/docs/advanced/configuration 11 | * 12 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 13 | * to sign your transactions before they're sent to a remote public node. Infura accounts 14 | * are available for free at: infura.io/register. 15 | * 16 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 17 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 18 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 19 | * 20 | */ 21 | 22 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 23 | // const infuraKey = "fj4jll3k....."; 24 | // 25 | // const fs = require('fs'); 26 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 27 | 28 | module.exports = { 29 | contracts_build_directory: "./client/src/contracts", 30 | 31 | /** 32 | * Networks define how you connect to your ethereum client and let you set the 33 | * defaults web3 uses to send transactions. If you don't specify one truffle 34 | * will spin up a development blockchain for you on port 9545 when you 35 | * run `develop` or `test`. You can ask a truffle command to use a specific 36 | * network from the command line, e.g 37 | * 38 | * $ truffle test --network 39 | */ 40 | 41 | networks: { 42 | // Useful for testing. The `development` name is special - truffle uses it by default 43 | // if it's defined here and no other network is specified at the command line. 44 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 45 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 46 | // options below to some value. 47 | // 48 | development: { 49 | host: "127.0.0.1", // Localhost (default: none) 50 | port: 7545, // Standard Ethereum port (default: none) 51 | network_id: "*", // Any network (default: none) 52 | }, 53 | 54 | goerli: { 55 | provider: () => { 56 | const mnemonic = process.env["MNEMONIC"] 57 | return new HDWallterProvider(mnemonic, "http://127.0.0.1:8545"); 58 | }, 59 | network_id: "*", 60 | }, 61 | 62 | rinkeby: { 63 | provider: () => { 64 | const mnemonic = process.env["MNEMONIC"] 65 | const project_id = process.env["INFURA_PROJECT_ID"] 66 | return new HDWallterProvider( 67 | mnemonic, 68 | `https://rinkeby.infura.io/v3/${project_id}` 69 | ); 70 | }, 71 | network_id: "*" 72 | } 73 | 74 | // Another network with more advanced options... 75 | // advanced: { 76 | // port: 8777, // Custom port 77 | // network_id: 1342, // Custom network 78 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 79 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 80 | // from:
, // Account to send txs from (default: accounts[0]) 81 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 82 | // }, 83 | 84 | // Useful for deploying to a public network. 85 | // NB: It's important to wrap the provider as a function. 86 | // ropsten: { 87 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 88 | // network_id: 3, // Ropsten's id 89 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 90 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 91 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 92 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 93 | // }, 94 | 95 | // Useful for private networks 96 | // private: { 97 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 98 | // network_id: 2111, // This network is yours, in the cloud. 99 | // production: true // Treats this network as if it was a public net. (default: false) 100 | // } 101 | }, 102 | 103 | // Set default mocha options here, use special reporters etc. 104 | mocha: { 105 | // timeout: 100000 106 | }, 107 | 108 | // Configure your compilers 109 | compilers: { 110 | solc: { 111 | version: "0.6", // Fetch exact version from solc-bin (default: truffle's version) 112 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 113 | // settings: { // See the solidity docs for advice about optimization and evmVersion 114 | // optimizer: { 115 | // enabled: false, 116 | // runs: 200 117 | // }, 118 | // evmVersion: "byzantium" 119 | // } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Truffle 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 | 23 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.6.3", 7 | "react-dom": "^16.6.3", 8 | "react-scripts": "2.1.1", 9 | "web3": "^1.0.0-beta.37" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "eslintConfig": { 18 | "extends": "react-app" 19 | }, 20 | "browserslist": [ 21 | ">0.2%", 22 | "not dead", 23 | "not ie <= 11", 24 | "not op_mini all" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedSquirrelTech/hoscdev/244df40900531db9e3ac6417ca6b3ab91ea3fcde/chapter-6/fundraiser/client/public/favicon.ico -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import SimpleStorageContract from "./contracts/SimpleStorage.json"; 3 | import getWeb3 from "./utils/getWeb3"; 4 | 5 | import "./App.css"; 6 | 7 | class App extends Component { 8 | state = { storageValue: 0, web3: null, accounts: null, contract: null }; 9 | 10 | componentDidMount = async () => { 11 | try { 12 | // Get network provider and web3 instance. 13 | const web3 = await getWeb3(); 14 | 15 | // Use web3 to get the user's accounts. 16 | const accounts = await web3.eth.getAccounts(); 17 | 18 | // Get the contract instance. 19 | const networkId = await web3.eth.net.getId(); 20 | const deployedNetwork = SimpleStorageContract.networks[networkId]; 21 | const instance = new web3.eth.Contract( 22 | SimpleStorageContract.abi, 23 | deployedNetwork && deployedNetwork.address, 24 | ); 25 | 26 | // Set web3, accounts, and contract to the state, and then proceed with an 27 | // example of interacting with the contract's methods. 28 | this.setState({ web3, accounts, contract: instance }, this.runExample); 29 | } catch (error) { 30 | // Catch any errors for any of the above operations. 31 | alert( 32 | `Failed to load web3, accounts, or contract. Check console for details.`, 33 | ); 34 | console.error(error); 35 | } 36 | }; 37 | 38 | runExample = async () => { 39 | const { accounts, contract } = this.state; 40 | 41 | // Stores a given value, 5 by default. 42 | await contract.methods.set(5).send({ from: accounts[0] }); 43 | 44 | // Get the value from the contract to prove it worked. 45 | const response = await contract.methods.get().call(); 46 | 47 | // Update state with the result. 48 | this.setState({ storageValue: response }); 49 | }; 50 | 51 | render() { 52 | if (!this.state.web3) { 53 | return
Loading Web3, accounts, and contract...
; 54 | } 55 | return ( 56 |
57 |

Good to Go!

58 |

Your Truffle Box is installed and ready.

59 |

Smart Contract Example

60 |

61 | If your contracts compiled and migrated successfully, below will show 62 | a stored value of 5 (by default). 63 |

64 |

65 | Try changing the value stored on line 40 of App.js. 66 |

67 |
The stored value is: {this.state.storageValue}
68 |
69 | ); 70 | } 71 | } 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/client/src/utils/getWeb3.js: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | const getWeb3 = () => 4 | new Promise((resolve, reject) => { 5 | // Wait for loading completion to avoid race conditions with web3 injection timing. 6 | window.addEventListener("load", async () => { 7 | // Modern dapp browsers... 8 | if (window.ethereum) { 9 | const web3 = new Web3(window.ethereum); 10 | try { 11 | // Request account access if needed 12 | await window.ethereum.enable(); 13 | // Acccounts now exposed 14 | resolve(web3); 15 | } catch (error) { 16 | reject(error); 17 | } 18 | } 19 | // Legacy dapp browsers... 20 | else if (window.web3) { 21 | // Use Mist/MetaMask's provider. 22 | const web3 = window.web3; 23 | console.log("Injected web3 detected."); 24 | resolve(web3); 25 | } 26 | // Fallback to localhost; use dev console port by default... 27 | else { 28 | const provider = new Web3.providers.HttpProvider( 29 | "http://127.0.0.1:8545" 30 | ); 31 | const web3 = new Web3(provider); 32 | console.log("No web3 instance injected, using Local web3."); 33 | resolve(web3); 34 | } 35 | }); 36 | }); 37 | 38 | export default getWeb3; 39 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/contracts/Fundraiser.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.23 <0.7.0; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | contract Fundraiser is Ownable { 7 | using SafeMath for uint256; 8 | 9 | struct Donation { 10 | uint256 value; 11 | uint256 date; 12 | } 13 | mapping(address => Donation[]) private _donations; 14 | 15 | event DonationReceived(address indexed donor, uint256 value); 16 | event Withdraw(uint256 amount); 17 | 18 | string public name; 19 | string public url; 20 | string public imageURL; 21 | string public description; 22 | 23 | address payable public beneficiary; 24 | address private _owner; 25 | 26 | uint256 public totalDonations; 27 | uint256 public donationsCount; 28 | 29 | constructor( 30 | string memory _name, 31 | string memory _url, 32 | string memory _imageURL, 33 | string memory _description, 34 | address payable _beneficiary, 35 | address _custodian 36 | ) 37 | public 38 | { 39 | name = _name; 40 | url = _url; 41 | imageURL = _imageURL; 42 | description = _description; 43 | beneficiary = _beneficiary; 44 | _owner = _custodian; 45 | } 46 | 47 | function setBeneficiary(address payable _beneficiary) public onlyOwner { 48 | beneficiary = _beneficiary; 49 | } 50 | 51 | function myDonationsCount() public view returns(uint256) { 52 | return _donations[msg.sender].length; 53 | } 54 | 55 | function donate() public payable { 56 | Donation memory donation = Donation({ 57 | value: msg.value, 58 | date: block.timestamp 59 | }); 60 | _donations[msg.sender].push(donation); 61 | totalDonations = totalDonations.add(msg.value); 62 | donationsCount++; 63 | 64 | emit DonationReceived(msg.sender, msg.value); 65 | } 66 | 67 | function myDonations() public view returns( 68 | uint256[] memory values, 69 | uint256[] memory dates 70 | ) 71 | { 72 | uint256 count = myDonationsCount(); 73 | values = new uint256[](count); 74 | dates = new uint256[](count); 75 | 76 | for (uint256 i = 0; i < count; i++) { 77 | Donation storage donation = _donations[msg.sender][i]; 78 | values[i] = donation.value; 79 | dates[i] = donation.date; 80 | } 81 | 82 | return (values, dates); 83 | } 84 | 85 | function withdraw() public onlyOwner { 86 | uint256 balance = address(this).balance; 87 | beneficiary.transfer(balance); 88 | emit Withdraw(balance); 89 | } 90 | 91 | function () external payable { 92 | totalDonations = totalDonations.add(msg.value); 93 | donationsCount++; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "openzeppelin-solidity": { 6 | "version": "2.3.0", 7 | "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", 8 | "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/test/fundraiser_test.js: -------------------------------------------------------------------------------- 1 | const FundraiserContract = artifacts.require("Fundraiser"); 2 | 3 | contract("Fundraiser", accounts => { 4 | let fundraiser; 5 | const name = "Beneficiary Name"; 6 | const url = "beneficiaryname.org"; 7 | const imageURL = "https://placekitten.com/600/350"; 8 | const description = "Beneficiary description"; 9 | const beneficiary = accounts[1]; 10 | const owner = accounts[0]; 11 | 12 | beforeEach(async () => { 13 | fundraiser = await FundraiserContract.new( 14 | name, 15 | url, 16 | imageURL, 17 | description, 18 | beneficiary, 19 | owner 20 | ) 21 | }); 22 | 23 | describe("initialization", () => { 24 | it("gets the beneficiary name", async () => { 25 | const actual = await fundraiser.name(); 26 | assert.equal(actual, name, "names should match"); 27 | }); 28 | 29 | it("gets the beneficiary url", async () => { 30 | const actual = await fundraiser.url(); 31 | assert.equal(actual, url, "url should match"); 32 | }); 33 | 34 | it("gets the beneficiary image url", async () => { 35 | const actual = await fundraiser.imageURL(); 36 | assert.equal(actual, imageURL, "imageURL should match"); 37 | }); 38 | 39 | it("gets the beneficiary description", async () => { 40 | const actual = await fundraiser.description(); 41 | assert.equal(actual, description, "description should match"); 42 | }); 43 | 44 | it("gets the beneficiary", async () => { 45 | const actual = await fundraiser.beneficiary(); 46 | assert.equal(actual, beneficiary, "beneficiary addresses should match"); 47 | }); 48 | 49 | it("gets the owner", async () => { 50 | const actual = await fundraiser.owner(); 51 | assert.equal(actual, owner, "bios should match"); 52 | }); 53 | }); 54 | 55 | describe("setBeneficiary", () => { 56 | const newBeneficiary = accounts[2]; 57 | 58 | it("updated beneficiary when called by owner account", async () => { 59 | await fundraiser.setBeneficiary(newBeneficiary, {from: owner}); 60 | const actualBeneficiary = await fundraiser.beneficiary(); 61 | assert.equal(actualBeneficiary, newBeneficiary, "beneficiaries should match"); 62 | }); 63 | 64 | it("throws and error when called from a non-owner account", async () => { 65 | try { 66 | await fundraiser.setBeneficiary(newBeneficiary, {from: accounts[3]}); 67 | assert.fail("withdraw was not restricted to owners") 68 | } catch(err) { 69 | const expectedError = "Ownable: caller is not the owner" 70 | const actualError = err.reason; 71 | assert.equal(actualError, expectedError, "should not be permitted") 72 | } 73 | }); 74 | }); 75 | 76 | describe("making donations", () => { 77 | const value = web3.utils.toWei('0.0289'); 78 | const donor = accounts[2]; 79 | 80 | it("increases myDonationsCount", async () => { 81 | const currentDonationsCount = await fundraiser.myDonationsCount( 82 | {from: donor} 83 | ); 84 | await fundraiser.donate({from: donor, value}); 85 | const newDonationsCount = await fundraiser.myDonationsCount({from: donor}); 86 | 87 | assert.equal( 88 | 1, 89 | newDonationsCount - currentDonationsCount, 90 | "myDonationsCount should increment by 1"); 91 | }); 92 | 93 | it("includes donation in myDonations", async () => { 94 | await fundraiser.donate({from: donor, value}); 95 | const {values, dates} = await fundraiser.myDonations( 96 | {from: donor} 97 | ); 98 | 99 | assert.equal( 100 | value, 101 | values[0], 102 | "values should match" 103 | ); 104 | assert(dates[0], "date should be present"); 105 | }); 106 | 107 | it("increases the totalDonations amount", async () => { 108 | const currentTotalDonations = await fundraiser.totalDonations(); 109 | await fundraiser.donate({from: donor, value}); 110 | const newTotalDonations = await fundraiser.totalDonations(); 111 | 112 | const diff = newTotalDonations - currentTotalDonations; 113 | 114 | assert.equal( 115 | diff, 116 | value, 117 | "difference should match the donation value" 118 | ) 119 | }); 120 | 121 | it("increases donationsCount", async () => { 122 | const currentDonationsCount = await fundraiser.donationsCount(); 123 | await fundraiser.donate({from: donor, value}); 124 | const newDonationsCount = await fundraiser.donationsCount(); 125 | 126 | assert.equal( 127 | 1, 128 | newDonationsCount - currentDonationsCount, 129 | "donationsCount should increment by 1"); 130 | }); 131 | 132 | it("emits the DonationReceived event", async () => { 133 | const tx = await fundraiser.donate({from: donor, value}); 134 | const expectedEvent = "DonationReceived"; 135 | const actualEvent = tx.logs[0].event; 136 | 137 | assert.equal(actualEvent, expectedEvent, "events should match"); 138 | }); 139 | }); 140 | 141 | describe("withdrawing funds", () => { 142 | beforeEach(async () => { 143 | await fundraiser.donate( 144 | {from: accounts[2], value: web3.utils.toWei('0.1')} 145 | ); 146 | }); 147 | 148 | describe("access controls", () => { 149 | it("throws and error when called from a non-owner account", async () => { 150 | try { 151 | await fundraiser.withdraw({from: accounts[3]}); 152 | assert.fail("withdraw was not restricted to owners") 153 | } catch(err) { 154 | const expectedError = "Ownable: caller is not the owner" 155 | const actualError = err.reason; 156 | assert.equal(actualError, expectedError, "should not be permitted") 157 | } 158 | }); 159 | 160 | it("permits the owner to call the function", async () => { 161 | try { 162 | await fundraiser.withdraw({from: owner}); 163 | assert(true, "no errors were thrown"); 164 | } catch(err) { 165 | assert.fail("should not have thrown an error"); 166 | } 167 | }); 168 | }); 169 | 170 | it("transfers balance to beneficiary", async () => { 171 | const currentContractBalance = await web3.eth.getBalance(fundraiser.address); 172 | const currentBeneficiaryBalance = await web3.eth.getBalance(beneficiary); 173 | 174 | await fundraiser.withdraw({from: owner}); 175 | 176 | const newContractBalance = await web3.eth.getBalance(fundraiser.address); 177 | const newBeneficiaryBalance = await web3.eth.getBalance(beneficiary); 178 | const beneficiaryDifference = newBeneficiaryBalance - currentBeneficiaryBalance; 179 | 180 | assert.equal( 181 | newContractBalance, 182 | 0, 183 | "contract should have a 0 balance" 184 | ); 185 | assert.equal( 186 | beneficiaryDifference, 187 | currentContractBalance, 188 | "beneficiary should receive all the funds" 189 | ); 190 | }); 191 | 192 | it("emits Withdraw event", async () => { 193 | const tx = await fundraiser.withdraw({from: owner}); 194 | const expectedEvent = "Withdraw"; 195 | const actualEvent = tx.logs[0].event; 196 | 197 | assert.equal( 198 | actualEvent, 199 | expectedEvent, 200 | "events should match" 201 | ); 202 | }); 203 | }); 204 | 205 | describe("fallback function", () => { 206 | const value = web3.utils.toWei('0.0289'); 207 | 208 | it("increases the totalDonations amount", async () => { 209 | const currentTotalDonations = await fundraiser.totalDonations(); 210 | await web3.eth.sendTransaction( 211 | {to: fundraiser.address, from: accounts[9], value} 212 | ); 213 | const newTotalDonations = await fundraiser.totalDonations(); 214 | 215 | const diff = newTotalDonations - currentTotalDonations; 216 | 217 | assert.equal( 218 | diff, 219 | value, 220 | "difference should match the donation value" 221 | ) 222 | }); 223 | 224 | it("increases donationsCount", async () => { 225 | const currentDonationsCount = await fundraiser.donationsCount(); 226 | await web3.eth.sendTransaction( 227 | {to: fundraiser.address, from: accounts[9], value} 228 | ); 229 | const newDonationsCount = await fundraiser.donationsCount(); 230 | 231 | assert.equal( 232 | 1, 233 | newDonationsCount - currentDonationsCount, 234 | "donationsCount should increment by 1"); 235 | }); 236 | }); 237 | });; 238 | -------------------------------------------------------------------------------- /chapter-6/fundraiser/truffle-config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | // See 5 | // to customize your Truffle configuration! 6 | contracts_build_directory: path.join(__dirname, "client/src/contracts"), 7 | networks: { 8 | develop: { 9 | port: 8545 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Truffle 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 | 23 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.6.3", 7 | "react-dom": "^16.6.3", 8 | "react-scripts": "2.1.1", 9 | "web3": "^1.0.0-beta.37" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "eslintConfig": { 18 | "extends": "react-app" 19 | }, 20 | "browserslist": [ 21 | ">0.2%", 22 | "not dead", 23 | "not ie <= 11", 24 | "not op_mini all" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedSquirrelTech/hoscdev/244df40900531db9e3ac6417ca6b3ab91ea3fcde/chapter-7/fundraiser/client/public/favicon.ico -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import SimpleStorageContract from "./contracts/SimpleStorage.json"; 3 | import getWeb3 from "./utils/getWeb3"; 4 | 5 | import "./App.css"; 6 | 7 | class App extends Component { 8 | state = { storageValue: 0, web3: null, accounts: null, contract: null }; 9 | 10 | componentDidMount = async () => { 11 | try { 12 | // Get network provider and web3 instance. 13 | const web3 = await getWeb3(); 14 | 15 | // Use web3 to get the user's accounts. 16 | const accounts = await web3.eth.getAccounts(); 17 | 18 | // Get the contract instance. 19 | const networkId = await web3.eth.net.getId(); 20 | const deployedNetwork = SimpleStorageContract.networks[networkId]; 21 | const instance = new web3.eth.Contract( 22 | SimpleStorageContract.abi, 23 | deployedNetwork && deployedNetwork.address, 24 | ); 25 | 26 | // Set web3, accounts, and contract to the state, and then proceed with an 27 | // example of interacting with the contract's methods. 28 | this.setState({ web3, accounts, contract: instance }, this.runExample); 29 | } catch (error) { 30 | // Catch any errors for any of the above operations. 31 | alert( 32 | `Failed to load web3, accounts, or contract. Check console for details.`, 33 | ); 34 | console.error(error); 35 | } 36 | }; 37 | 38 | runExample = async () => { 39 | const { accounts, contract } = this.state; 40 | 41 | // Stores a given value, 5 by default. 42 | await contract.methods.set(5).send({ from: accounts[0] }); 43 | 44 | // Get the value from the contract to prove it worked. 45 | const response = await contract.methods.get().call(); 46 | 47 | // Update state with the result. 48 | this.setState({ storageValue: response }); 49 | }; 50 | 51 | render() { 52 | if (!this.state.web3) { 53 | return
Loading Web3, accounts, and contract...
; 54 | } 55 | return ( 56 |
57 |

Good to Go!

58 |

Your Truffle Box is installed and ready.

59 |

Smart Contract Example

60 |

61 | If your contracts compiled and migrated successfully, below will show 62 | a stored value of 5 (by default). 63 |

64 |

65 | Try changing the value stored on line 40 of App.js. 66 |

67 |
The stored value is: {this.state.storageValue}
68 |
69 | ); 70 | } 71 | } 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/client/src/utils/getWeb3.js: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | const getWeb3 = () => 4 | new Promise((resolve, reject) => { 5 | // Wait for loading completion to avoid race conditions with web3 injection timing. 6 | window.addEventListener("load", async () => { 7 | // Modern dapp browsers... 8 | if (window.ethereum) { 9 | const web3 = new Web3(window.ethereum); 10 | try { 11 | // Request account access if needed 12 | await window.ethereum.enable(); 13 | // Acccounts now exposed 14 | resolve(web3); 15 | } catch (error) { 16 | reject(error); 17 | } 18 | } 19 | // Legacy dapp browsers... 20 | else if (window.web3) { 21 | // Use Mist/MetaMask's provider. 22 | const web3 = window.web3; 23 | console.log("Injected web3 detected."); 24 | resolve(web3); 25 | } 26 | // Fallback to localhost; use dev console port by default... 27 | else { 28 | const provider = new Web3.providers.HttpProvider( 29 | "http://127.0.0.1:8545" 30 | ); 31 | const web3 = new Web3(provider); 32 | console.log("No web3 instance injected, using Local web3."); 33 | resolve(web3); 34 | } 35 | }); 36 | }); 37 | 38 | export default getWeb3; 39 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/contracts/Fundraiser.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.23 <0.7.0; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | 6 | contract Fundraiser is Ownable { 7 | using SafeMath for uint256; 8 | 9 | struct Donation { 10 | uint256 value; 11 | uint256 date; 12 | } 13 | mapping(address => Donation[]) private _donations; 14 | 15 | event DonationReceived(address indexed donor, uint256 value); 16 | event Withdraw(uint256 amount); 17 | 18 | string public name; 19 | string public url; 20 | string public imageURL; 21 | string public description; 22 | 23 | address payable public beneficiary; 24 | 25 | uint256 public totalDonations; 26 | uint256 public donationsCount; 27 | 28 | constructor( 29 | string memory _name, 30 | string memory _url, 31 | string memory _imageURL, 32 | string memory _description, 33 | address payable _beneficiary, 34 | address _custodian 35 | ) 36 | public 37 | { 38 | name = _name; 39 | url = _url; 40 | imageURL = _imageURL; 41 | description = _description; 42 | beneficiary = _beneficiary; 43 | _transferOwnership(_custodian); 44 | } 45 | 46 | function setBeneficiary(address payable _beneficiary) public onlyOwner { 47 | beneficiary = _beneficiary; 48 | } 49 | 50 | function myDonationsCount() public view returns(uint256) { 51 | return _donations[msg.sender].length; 52 | } 53 | 54 | function donate() public payable { 55 | Donation memory donation = Donation({ 56 | value: msg.value, 57 | date: block.timestamp 58 | }); 59 | _donations[msg.sender].push(donation); 60 | totalDonations = totalDonations.add(msg.value); 61 | donationsCount++; 62 | 63 | emit DonationReceived(msg.sender, msg.value); 64 | } 65 | 66 | function myDonations() public view returns( 67 | uint256[] memory values, 68 | uint256[] memory dates 69 | ) 70 | { 71 | uint256 count = myDonationsCount(); 72 | values = new uint256[](count); 73 | dates = new uint256[](count); 74 | 75 | for (uint256 i = 0; i < count; i++) { 76 | Donation storage donation = _donations[msg.sender][i]; 77 | values[i] = donation.value; 78 | dates[i] = donation.date; 79 | } 80 | 81 | return (values, dates); 82 | } 83 | 84 | function withdraw() public onlyOwner { 85 | uint256 balance = address(this).balance; 86 | beneficiary.transfer(balance); 87 | emit Withdraw(balance); 88 | } 89 | 90 | function () external payable { 91 | totalDonations = totalDonations.add(msg.value); 92 | donationsCount++; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/contracts/FundraiserFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.23 <0.7.0; 2 | 3 | import "./Fundraiser.sol"; 4 | 5 | contract FundraiserFactory { 6 | uint256 constant maxLimit = 20; 7 | Fundraiser[] private _fundraisers; 8 | 9 | event FundraiserCreated(Fundraiser indexed fundraiser, address indexed owner); 10 | 11 | function createFundraiser( 12 | string memory name, 13 | string memory url, 14 | string memory imageURL, 15 | string memory description, 16 | address payable beneficiary 17 | ) 18 | public 19 | { 20 | Fundraiser fundraiser = new Fundraiser( 21 | name, 22 | url, 23 | imageURL, 24 | description, 25 | beneficiary, 26 | msg.sender 27 | ); 28 | _fundraisers.push(fundraiser); 29 | emit FundraiserCreated(fundraiser, msg.sender); 30 | } 31 | 32 | function fundraisersCount() public view returns(uint256) { 33 | return _fundraisers.length; 34 | } 35 | 36 | function fundraisers(uint256 limit, uint256 offset) 37 | public 38 | view 39 | returns(Fundraiser[] memory coll) 40 | { 41 | require(offset <= fundraisersCount(), "offset out of bounds"); 42 | 43 | uint256 size = fundraisersCount() - offset; 44 | size = size < limit ? size : limit; 45 | size = size < maxLimit ? size : maxLimit; 46 | coll = new Fundraiser[](size); 47 | 48 | for(uint256 i = 0; i < size; i++) { 49 | coll[i] = _fundraisers[offset + i]; 50 | } 51 | 52 | return coll; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/migrations/2_deploy_fundraiser_factory.js: -------------------------------------------------------------------------------- 1 | const FundraiserFactoryContract = artifacts.require("FundraiserFactory"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(FundraiserFactoryContract); 5 | } 6 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "openzeppelin-solidity": { 6 | "version": "2.3.0", 7 | "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", 8 | "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/test/fundraiser_factory_test.js: -------------------------------------------------------------------------------- 1 | const FundraiserFactoryContract = artifacts.require("FundraiserFactory"); 2 | const FundraiserContract = artifacts.require("Fundraiser"); 3 | 4 | contract("FundraiserFactory: deployment", () => { 5 | it("has been deployed", async () => { 6 | const fundraiserFactory = FundraiserFactoryContract.deployed(); 7 | assert(fundraiserFactory, "fundraiser factory was not deployed"); 8 | }); 9 | }); 10 | 11 | contract("FundraiserFactory: createFundraiser", (accounts) => { 12 | let fundraiserFactory; 13 | // fundraiser args 14 | const name = "Beneficiary Name"; 15 | const url = "beneficiaryname.org"; 16 | const imageURL = "https://placekitten.com/600/350" 17 | const description = "Beneficiary Description" 18 | const beneficiary = accounts[1]; 19 | 20 | it("increments the fundraisersCount", async () => { 21 | fundraiserFactory = await FundraiserFactoryContract.deployed(); 22 | const currentFundraisersCount = await fundraiserFactory.fundraisersCount(); 23 | await fundraiserFactory.createFundraiser( 24 | name, 25 | url, 26 | imageURL, 27 | description, 28 | beneficiary 29 | ); 30 | const newFundraisersCount = await fundraiserFactory.fundraisersCount(); 31 | 32 | assert.equal( 33 | newFundraisersCount - currentFundraisersCount, 34 | 1, 35 | "should increment by 1" 36 | ) 37 | }); 38 | 39 | it("emits the FundraiserCreated event", async () => { 40 | fundraiserFactory = await FundraiserFactoryContract.deployed(); 41 | const tx = await fundraiserFactory.createFundraiser( 42 | name, 43 | url, 44 | imageURL, 45 | description, 46 | beneficiary 47 | ); 48 | const expectedEvent = "FundraiserCreated"; 49 | const actualEvent = tx.logs[0].event; 50 | 51 | assert.equal( 52 | actualEvent, 53 | expectedEvent, 54 | "events should match" 55 | ); 56 | }); 57 | }); 58 | 59 | contract("FundraiserFactory: fundraisers", (accounts) => { 60 | async function createFundraiserFactory(fundraiserCount, accounts) { 61 | const factory = await FundraiserFactoryContract.new(); 62 | await addFundraisers(factory, fundraiserCount, accounts); 63 | return factory; 64 | } 65 | 66 | async function addFundraisers(factory, count, accounts) { 67 | const name = "Beneficiary"; 68 | const lowerCaseName = name.toLowerCase(); 69 | const beneficiary = accounts[1]; 70 | 71 | for (let i=0; i < count; i++) { 72 | await factory.createFundraiser( 73 | `${name} ${i}`, 74 | `${lowerCaseName}${i}.com`, 75 | `${lowerCaseName}${i}.png`, 76 | `Description for ${name} ${i}`, 77 | beneficiary 78 | ); 79 | } 80 | } 81 | 82 | describe("when fundraisers collection is empty", () => { 83 | it("returns an empty collection", async () => { 84 | const factory = await createFundraiserFactory(0, accounts); 85 | const fundraisers = await factory.fundraisers(10, 0); 86 | assert.equal( 87 | fundraisers.length, 88 | 0, 89 | "collection should be empty" 90 | ); 91 | }); 92 | }); 93 | 94 | describe("varying limits", async () => { 95 | let factory; 96 | beforeEach(async () => { 97 | factory = await createFundraiserFactory(30, accounts); 98 | }) 99 | 100 | it("returns 10 results when limit requested is 10", async ()=>{ 101 | const fundraisers = await factory.fundraisers(10, 0); 102 | assert.equal( 103 | fundraisers.length, 104 | 10, 105 | "results size should be 10" 106 | ); 107 | }); 108 | 109 | it("returns 20 results when limit requested is 20", async ()=>{ 110 | const fundraisers = await factory.fundraisers(20, 0); 111 | assert.equal( 112 | fundraisers.length, 113 | 20, 114 | "results size should be 20" 115 | ); 116 | }); 117 | 118 | it("returns 20 results when limit requested is 30", async ()=>{ 119 | const fundraisers = await factory.fundraisers(30, 0); 120 | assert.equal( 121 | fundraisers.length, 122 | 20, 123 | "results size should be 20" 124 | ); 125 | }); 126 | }) 127 | 128 | describe("varying offset", () => { 129 | let factory; 130 | beforeEach(async () => { 131 | factory = await createFundraiserFactory(10, accounts); 132 | }); 133 | 134 | it("contains the fundraiser with the appropriate offset", async ()=>{ 135 | const fundraisers = await factory.fundraisers(1, 0); 136 | const fundraiser = await FundraiserContract.at(fundraisers[0]); 137 | const name = await fundraiser.name(); 138 | assert.ok(await name.includes(0), `${name} did not include the offset`); 139 | }); 140 | 141 | it("contains the fundraiser with the appropriate offset", async ()=>{ 142 | const fundraisers = await factory.fundraisers(1, 7); 143 | const fundraiser = await FundraiserContract.at(fundraisers[0]); 144 | const name = await fundraiser.name(); 145 | assert.ok(await name.includes(7), `${name} did not include the offset`); 146 | }); 147 | }); 148 | 149 | describe("boundry conditions", () => { 150 | let factory; 151 | beforeEach(async () => { 152 | factory = await createFundraiserFactory(10, accounts); 153 | }); 154 | 155 | it("raises out of bounds error", async () => { 156 | try { 157 | await factory.fundraisers(1, 11); 158 | assert.fail("error was not raised") 159 | } catch(err) { 160 | const expected = "offset out of bounds"; 161 | assert.ok(err.message.includes(expected), `${err.message}`); 162 | } 163 | }); 164 | 165 | it("adjusts return size to prevent out of bounds error", async () => { 166 | try { 167 | const fundraisers = await factory.fundraisers(10, 5); 168 | assert.equal( 169 | fundraisers.length, 170 | 5, 171 | "collection adjusted" 172 | ); 173 | } catch(err) { 174 | assert.fail("limit and offset exceeded bounds"); 175 | } 176 | }); 177 | }); 178 | }); 179 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/test/fundraiser_test.js: -------------------------------------------------------------------------------- 1 | const FundraiserContract = artifacts.require("Fundraiser"); 2 | 3 | contract("Fundraiser", accounts => { 4 | let fundraiser; 5 | const name = "Beneficiary Name"; 6 | const url = "beneficiaryname.org"; 7 | const imageURL = "https://placekitten.com/600/350"; 8 | const description = "Beneficiary description"; 9 | const beneficiary = accounts[1]; 10 | const owner = accounts[0]; 11 | 12 | beforeEach(async () => { 13 | fundraiser = await FundraiserContract.new( 14 | name, 15 | url, 16 | imageURL, 17 | description, 18 | beneficiary, 19 | owner 20 | ) 21 | }); 22 | 23 | describe("initialization", () => { 24 | it("gets the beneficiary name", async () => { 25 | const actual = await fundraiser.name(); 26 | assert.equal(actual, name, "names should match"); 27 | }); 28 | 29 | it("gets the beneficiary url", async () => { 30 | const actual = await fundraiser.url(); 31 | assert.equal(actual, url, "url should match"); 32 | }); 33 | 34 | it("gets the beneficiary image url", async () => { 35 | const actual = await fundraiser.imageURL(); 36 | assert.equal(actual, imageURL, "imageURL should match"); 37 | }); 38 | 39 | it("gets the beneficiary description", async () => { 40 | const actual = await fundraiser.description(); 41 | assert.equal(actual, description, "description should match"); 42 | }); 43 | 44 | it("gets the beneficiary", async () => { 45 | const actual = await fundraiser.beneficiary(); 46 | assert.equal(actual, beneficiary, "beneficiary addresses should match"); 47 | }); 48 | 49 | it("gets the owner", async () => { 50 | const actual = await fundraiser.owner(); 51 | assert.equal(actual, owner, "bios should match"); 52 | }); 53 | }); 54 | 55 | describe("setBeneficiary", () => { 56 | const newBeneficiary = accounts[2]; 57 | 58 | it("updated beneficiary when called by owner account", async () => { 59 | await fundraiser.setBeneficiary(newBeneficiary, {from: owner}); 60 | const actualBeneficiary = await fundraiser.beneficiary(); 61 | assert.equal(actualBeneficiary, newBeneficiary, "beneficiaries should match"); 62 | }); 63 | 64 | it("throws and error when called from a non-owner account", async () => { 65 | try { 66 | await fundraiser.setBeneficiary(newBeneficiary, {from: accounts[3]}); 67 | assert.fail("withdraw was not restricted to owners") 68 | } catch(err) { 69 | const expectedError = "Ownable: caller is not the owner" 70 | const actualError = err.reason; 71 | assert.equal(actualError, expectedError, "should not be permitted") 72 | } 73 | }); 74 | }); 75 | 76 | describe("making donations", () => { 77 | const value = web3.utils.toWei('0.0289'); 78 | const donor = accounts[2]; 79 | 80 | it("increases myDonationsCount", async () => { 81 | const currentDonationsCount = await fundraiser.myDonationsCount( 82 | {from: donor} 83 | ); 84 | await fundraiser.donate({from: donor, value}); 85 | const newDonationsCount = await fundraiser.myDonationsCount({from: donor}); 86 | 87 | assert.equal( 88 | 1, 89 | newDonationsCount - currentDonationsCount, 90 | "myDonationsCount should increment by 1"); 91 | }); 92 | 93 | it("includes donation in myDonations", async () => { 94 | await fundraiser.donate({from: donor, value}); 95 | const {values, dates} = await fundraiser.myDonations( 96 | {from: donor} 97 | ); 98 | 99 | assert.equal( 100 | value, 101 | values[0], 102 | "values should match" 103 | ); 104 | assert(dates[0], "date should be present"); 105 | }); 106 | 107 | it("increases the totalDonations amount", async () => { 108 | const currentTotalDonations = await fundraiser.totalDonations(); 109 | await fundraiser.donate({from: donor, value}); 110 | const newTotalDonations = await fundraiser.totalDonations(); 111 | 112 | const diff = newTotalDonations - currentTotalDonations; 113 | 114 | assert.equal( 115 | diff, 116 | value, 117 | "difference should match the donation value" 118 | ) 119 | }); 120 | 121 | it("increases donationsCount", async () => { 122 | const currentDonationsCount = await fundraiser.donationsCount(); 123 | await fundraiser.donate({from: donor, value}); 124 | const newDonationsCount = await fundraiser.donationsCount(); 125 | 126 | assert.equal( 127 | 1, 128 | newDonationsCount - currentDonationsCount, 129 | "donationsCount should increment by 1"); 130 | }); 131 | 132 | it("emits the DonationReceived event", async () => { 133 | const tx = await fundraiser.donate({from: donor, value}); 134 | const expectedEvent = "DonationReceived"; 135 | const actualEvent = tx.logs[0].event; 136 | 137 | assert.equal(actualEvent, expectedEvent, "events should match"); 138 | }); 139 | }); 140 | 141 | describe("withdrawing funds", () => { 142 | beforeEach(async () => { 143 | await fundraiser.donate( 144 | {from: accounts[2], value: web3.utils.toWei('0.1')} 145 | ); 146 | }); 147 | 148 | describe("access controls", () => { 149 | it("throws and error when called from a non-owner account", async () => { 150 | try { 151 | await fundraiser.withdraw({from: accounts[3]}); 152 | assert.fail("withdraw was not restricted to owners") 153 | } catch(err) { 154 | const expectedError = "Ownable: caller is not the owner" 155 | const actualError = err.reason; 156 | assert.equal(actualError, expectedError, "should not be permitted") 157 | } 158 | }); 159 | 160 | it("permits the owner to call the function", async () => { 161 | try { 162 | await fundraiser.withdraw({from: owner}); 163 | assert(true, "no errors were thrown"); 164 | } catch(err) { 165 | assert.fail("should not have thrown an error"); 166 | } 167 | }); 168 | }); 169 | 170 | it("transfers balance to beneficiary", async () => { 171 | const currentContractBalance = await web3.eth.getBalance(fundraiser.address); 172 | const currentBeneficiaryBalance = await web3.eth.getBalance(beneficiary); 173 | 174 | await fundraiser.withdraw({from: owner}); 175 | 176 | const newContractBalance = await web3.eth.getBalance(fundraiser.address); 177 | const newBeneficiaryBalance = await web3.eth.getBalance(beneficiary); 178 | const beneficiaryDifference = newBeneficiaryBalance - currentBeneficiaryBalance; 179 | 180 | assert.equal( 181 | newContractBalance, 182 | 0, 183 | "contract should have a 0 balance" 184 | ); 185 | assert.equal( 186 | beneficiaryDifference, 187 | currentContractBalance, 188 | "beneficiary should receive all the funds" 189 | ); 190 | }); 191 | 192 | it("emits Withdraw event", async () => { 193 | const tx = await fundraiser.withdraw({from: owner}); 194 | const expectedEvent = "Withdraw"; 195 | const actualEvent = tx.logs[0].event; 196 | 197 | assert.equal( 198 | actualEvent, 199 | expectedEvent, 200 | "events should match" 201 | ); 202 | }); 203 | }); 204 | 205 | describe("fallback function", () => { 206 | const value = web3.utils.toWei('0.0289'); 207 | 208 | it("increases the totalDonations amount", async () => { 209 | const currentTotalDonations = await fundraiser.totalDonations(); 210 | await web3.eth.sendTransaction( 211 | {to: fundraiser.address, from: accounts[9], value} 212 | ); 213 | const newTotalDonations = await fundraiser.totalDonations(); 214 | 215 | const diff = newTotalDonations - currentTotalDonations; 216 | 217 | assert.equal( 218 | diff, 219 | value, 220 | "difference should match the donation value" 221 | ) 222 | }); 223 | 224 | it("increases donationsCount", async () => { 225 | const currentDonationsCount = await fundraiser.donationsCount(); 226 | await web3.eth.sendTransaction( 227 | {to: fundraiser.address, from: accounts[9], value} 228 | ); 229 | const newDonationsCount = await fundraiser.donationsCount(); 230 | 231 | assert.equal( 232 | 1, 233 | newDonationsCount - currentDonationsCount, 234 | "donationsCount should increment by 1"); 235 | }); 236 | }); 237 | });; 238 | -------------------------------------------------------------------------------- /chapter-7/fundraiser/truffle-config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | // See 5 | // to customize your Truffle configuration! 6 | contracts_build_directory: path.join(__dirname, "client/src/contracts"), 7 | networks: { 8 | develop: { 9 | port: 8545 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /chapter-9/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /chapter-9/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.3.1", 7 | "@openzeppelin/contracts": "^2.3.0", 8 | "bootstrap": "^4.3.1", 9 | "cryptocompare": "^1.0.0", 10 | "get-eth-price": "^1.0.0", 11 | "normalize.css": "^8.0.1", 12 | "react": "^16.8.0", 13 | "react-bootstrap": "^1.0.0-beta.10", 14 | "react-dom": "^16.9.0", 15 | "react-redux": "^7.1.1", 16 | "react-router-dom": "^5.0.1", 17 | "react-scripts": "2.1.1", 18 | "redux": "^4.0.4", 19 | "web3": "^1.2.1" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": "react-app" 29 | }, 30 | "browserslist": [ 31 | ">0.2%", 32 | "not dead", 33 | "not ie <= 11", 34 | "not op_mini all" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /chapter-9/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedSquirrelTech/hoscdev/244df40900531db9e3ac6417ca6b3ab91ea3fcde/chapter-9/public/favicon.ico -------------------------------------------------------------------------------- /chapter-9/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chapter-9/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /chapter-9/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 !important; 3 | } 4 | 5 | .App { 6 | text-align: center; 7 | } 8 | 9 | .donation-input-container { 10 | display: flex; 11 | } 12 | 13 | .withdrawal-button { 14 | margin-left: 10px; 15 | } 16 | 17 | .receipt-header { 18 | border-bottom: 1px solid gray; 19 | } 20 | 21 | .receipt-container { 22 | text-align: center; 23 | } 24 | 25 | .receipt-info { 26 | display: flex; 27 | padding: 50px; 28 | justify-content: space-between; 29 | } 30 | 31 | .donation-receipt-link { 32 | color: inherit; 33 | text-decoration: none; 34 | } 35 | 36 | .main-container { 37 | margin: 20px; 38 | } 39 | 40 | .donation-list { 41 | margin-top: 10px; 42 | margin-bottom: 10px; 43 | display: flex; 44 | justify-content: space-between; 45 | } 46 | 47 | .fundraiser-card-container { 48 | display: inline-flex; 49 | width: 250px; 50 | height: 250px; 51 | margin: 20px; 52 | } 53 | 54 | .MuiTextField-root { 55 | display: block !important; 56 | } 57 | 58 | .MuiInputBase-root { 59 | width: 300px !important; 60 | margin-left: 3px; 61 | } 62 | 63 | .App-logo { 64 | animation: App-logo-spin infinite 20s linear; 65 | height: 40vmin; 66 | } 67 | 68 | .App-header { 69 | background-color: #282c34; 70 | min-height: 100vh; 71 | display: flex; 72 | flex-direction: column; 73 | align-items: center; 74 | justify-content: center; 75 | font-size: calc(10px + 2vmin); 76 | color: white; 77 | } 78 | 79 | .App-link { 80 | color: #61dafb; 81 | } 82 | 83 | @keyframes App-logo-spin { 84 | from { 85 | transform: rotate(0deg); 86 | } 87 | to { 88 | transform: rotate(360deg); 89 | } 90 | } 91 | 92 | 93 | 94 | .nav-link { 95 | color: inherit; 96 | text-decoration: none; 97 | margin-right: 15px; 98 | } 99 | 100 | .nav-link:hover, 101 | .nav-link:active, 102 | .nav-link:visited { 103 | color: black; 104 | text-decoration: none; 105 | } 106 | -------------------------------------------------------------------------------- /chapter-9/src/App.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import FactoryContract from "./contracts/Factory.json"; 4 | import getWeb3 from "./utils/getWeb3"; 5 | import { makeStyles } from '@material-ui/core/styles'; 6 | import AppBar from '@material-ui/core/AppBar'; 7 | import Toolbar from '@material-ui/core/Toolbar'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import { BrowserRouter as Router, Route, Link, NavLink } from "react-router-dom"; 10 | 11 | import NewFundraiser from './NewFundraiser' 12 | import Home from './Home' 13 | import Receipts from './Receipts' 14 | 15 | import "./App.css"; 16 | 17 | const App = () => { 18 | const [state, setState] = useState({web3: null, accounts: null, contract: null}); 19 | const [storageValue, setStorageValue] = useState(0); 20 | 21 | useEffect(() => { 22 | const init = async() => { 23 | try { 24 | // Get network provider and web3 instance. 25 | const web3 = await getWeb3(); 26 | 27 | // Use web3 to get the user's accounts. 28 | const accounts = await web3.eth.getAccounts(); 29 | 30 | // Get the contract instance. 31 | const networkId = await web3.eth.net.getId(); 32 | const deployedNetwork = FactoryContract.networks[networkId]; 33 | const instance = new web3.eth.Contract( 34 | FactoryContract.abi, 35 | deployedNetwork && deployedNetwork.address, 36 | ); 37 | 38 | // Set web3, accounts, and contract to the state, and then proceed with an 39 | setState({web3, accounts, contract: instance}); 40 | 41 | } catch(error) { 42 | // Catch any errors for any of the above operations. 43 | alert( 44 | `Failed to load web3, accounts, or contract. Check console for details.`, 45 | ); 46 | console.error(error); 47 | } 48 | } 49 | init(); 50 | }, []); 51 | 52 | const useStyles = makeStyles({ 53 | root: { 54 | flexGrow: 1, 55 | }, 56 | }); 57 | 58 | const classes = useStyles(); 59 | 60 | const runExample = async () => { 61 | const { accounts, contract } = state; 62 | }; 63 | 64 | return ( 65 |
66 | 67 | 68 | 69 | 70 | Home 71 | 72 | New 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | ) 82 | } 83 | 84 | 85 | export default App; 86 | -------------------------------------------------------------------------------- /chapter-9/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /chapter-9/src/FundraiserCard.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardActionArea from '@material-ui/core/CardActionArea'; 5 | import CardActions from '@material-ui/core/CardActions'; 6 | import CardContent from '@material-ui/core/CardContent'; 7 | import CardMedia from '@material-ui/core/CardMedia'; 8 | import Button from '@material-ui/core/Button'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import Dialog from '@material-ui/core/Dialog'; 11 | import DialogActions from '@material-ui/core/DialogActions'; 12 | import DialogContent from '@material-ui/core/DialogContent'; 13 | import DialogContentText from '@material-ui/core/DialogContentText'; 14 | import DialogTitle from '@material-ui/core/DialogTitle'; 15 | 16 | import FilledInput from '@material-ui/core/FilledInput'; 17 | import FormControl from '@material-ui/core/FormControl'; 18 | import FormHelperText from '@material-ui/core/FormHelperText'; 19 | import Input from '@material-ui/core/Input'; 20 | import InputLabel from '@material-ui/core/InputLabel'; 21 | import OutlinedInput from '@material-ui/core/OutlinedInput'; 22 | 23 | import getWeb3 from "./utils/getWeb3"; 24 | import FundraiserContract from "./contracts/Fundraiser.json"; 25 | import Web3 from 'web3' 26 | 27 | import { Link } from 'react-router-dom' 28 | 29 | const cc = require('cryptocompare') 30 | 31 | const getModalStyle =() => { 32 | const top = 50; 33 | const left = 50; 34 | 35 | return { 36 | top, 37 | left, 38 | }; 39 | } 40 | 41 | const useStyles = makeStyles(theme => ({ 42 | container: { 43 | display: 'flex', 44 | flexWrap: 'wrap', 45 | }, 46 | formControl: { 47 | margin: theme.spacing(1), 48 | display: 'table-cell' 49 | }, 50 | card: { 51 | maxWidth: 450, 52 | height: 400 53 | }, 54 | media: { 55 | height: 140, 56 | }, 57 | paper: { 58 | position: 'absolute', 59 | width: 500, 60 | backgroundColor: theme.palette.background.paper, 61 | border: 'none', 62 | boxShadow: 'none', 63 | padding: 4, 64 | }, 65 | })); 66 | 67 | const FundraiserCard = (props) => { 68 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 69 | 70 | const [ contract, setContract] = useState(null) 71 | const [ accounts, setAccounts ] = useState(null) 72 | const [ fund, setFundraiser ] = useState(null) 73 | const [ fundName, setFundname ] = useState(null) 74 | const [ bio, setBio ] = useState(null) 75 | const [ totalDonations, setTotalDonations ] = useState(null) 76 | const [ imageURL, setImageURL ] = useState(null) 77 | const [ url, setURL ] = useState(null) 78 | const [ open, setOpen] = React.useState(false); 79 | const [ donationAmount, setDonationAmount] = useState(null) 80 | const [ exchangeRate, setExchangeRate ] = useState(null) 81 | const [ userDonations, setUserDonations ] = useState(null) 82 | const [ isOwner, setIsOwner ] = useState(false) 83 | 84 | const ethAmount = (donationAmount / exchangeRate || 0).toFixed(4) 85 | 86 | const { fundraiser } = props 87 | 88 | const classes = useStyles(); 89 | 90 | useEffect(() => { 91 | if (fundraiser) { 92 | init(fundraiser) 93 | } 94 | }, [fundraiser]); 95 | 96 | const init = async (fundraiser) => { 97 | try { 98 | const fund = fundraiser 99 | const networkId = await web3.eth.net.getId(); 100 | const deployedNetwork = FundraiserContract.networks[networkId]; 101 | const accounts = await web3.eth.getAccounts(); 102 | const instance = new web3.eth.Contract( 103 | FundraiserContract.abi, 104 | fund 105 | ); 106 | setContract(instance) 107 | setAccounts(accounts) 108 | 109 | const name = await instance.methods.name().call() 110 | const bio = await instance.methods.bio().call() 111 | const totalDonations = await instance.methods.totalDonations().call() 112 | const imageURL = await instance.methods.imageURL().call() 113 | const url = await instance.methods.url().call() 114 | 115 | const exchangeRate = await cc.price('ETH', ['USD']) 116 | setExchangeRate(exchangeRate.USD) 117 | const eth = web3.utils.fromWei(totalDonations, 'ether') 118 | const dollarDonationAmount = exchangeRate.USD * eth 119 | 120 | setTotalDonations(dollarDonationAmount.toFixed(2)) 121 | setFundname(name) 122 | setBio(bio) 123 | setImageURL(imageURL) 124 | setURL(url) 125 | 126 | const userDonations = await instance.methods.myDonations().call({ from: accounts[0]}) 127 | console.log(userDonations) 128 | setUserDonations(userDonations) 129 | 130 | const isUser = accounts[0] 131 | const isOwner = await instance.methods.owner().call() 132 | if (isOwner === accounts[[0]]) { 133 | setIsOwner(true) 134 | } 135 | } 136 | catch(error) { 137 | alert( 138 | `Failed to load web3, accounts, or contract. Check console for details.`, 139 | ); 140 | console.error(error); 141 | } 142 | } 143 | 144 | window.ethereum.on('accountsChanged', function (accounts) { 145 | window.location.reload() 146 | }) 147 | 148 | const handleOpen = () => { 149 | setOpen(true); 150 | }; 151 | 152 | const handleClose = () => { 153 | setOpen(false); 154 | }; 155 | 156 | const submitFunds = async () => { 157 | const fundraisercontract = contract 158 | const conversionRate = 18460; 159 | const ethRate = exchangeRate 160 | const ethTotal = donationAmount / ethRate 161 | const donation = web3.utils.toWei(ethTotal.toString()) 162 | 163 | await contract.methods.donate(conversionRate).send({ 164 | from: accounts[0], 165 | value: donation, 166 | gas: 650000 167 | }) 168 | setOpen(false); 169 | } 170 | 171 | const showReceipt = (amount, date) => { 172 | 173 | } 174 | 175 | const renderDonationsList = () => { 176 | var donations = userDonations 177 | if (donations === null) {return null} 178 | 179 | const totalDonations = donations.values.length 180 | let donationList = [] 181 | var i 182 | for (i = 0; i < totalDonations; i++) { 183 | const ethAmount = web3.utils.fromWei(donations.values[i]) 184 | const userDonation = exchangeRate * ethAmount 185 | const donationDate = donations.dates[i] 186 | donationList.push({ donationAmount: userDonation.toFixed(2), date: donationDate}) 187 | } 188 | 189 | return donationList.map((donation) => { 190 | return ( 191 |
192 |

${donation.donationAmount}

193 | 194 | 199 |
200 | ) 201 | }) 202 | } 203 | 204 | const withdrawalFunds = async () => { 205 | await contract.methods.withdraw().send({ 206 | from: accounts[0], 207 | }) 208 | 209 | alert('Funds Withdrawn!') 210 | } 211 | 212 | return ( 213 |
214 | 215 | 216 | Donate to {fundName} 217 | 218 | 219 | 220 | 221 |

{bio}

222 | 223 |
224 | 225 | $ 226 | setDonationAmount(e.target.value)} 230 | placeholder="0.00" 231 | /> 232 | 233 | 234 |

Eth: {ethAmount}

235 |
236 | 237 | 240 | 241 |
242 |

My donations

243 | {renderDonationsList()} 244 |
245 |
246 |
247 | 248 | 251 | {isOwner && 252 | 259 | } 260 | 261 |
262 | 263 | 264 | 265 | 270 | 271 | 272 | {fundName} 273 | 274 | 275 |

{bio}

276 |

Total Donations: ${totalDonations}

277 |
278 |
279 |
280 | 281 | 287 | 288 |
289 |
290 | ) 291 | } 292 | 293 | export default FundraiserCard; 294 | -------------------------------------------------------------------------------- /chapter-9/src/Home.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import FundraiserCard from './FundraiserCard' 5 | import getWeb3 from "./utils/getWeb3"; 6 | import FactoryContract from "./contracts/Factory.json"; 7 | import Web3 from 'web3' 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | button: { 11 | margin: theme.spacing(1), 12 | }, 13 | input: { 14 | display: 'none', 15 | }, 16 | })); 17 | 18 | 19 | const Home = () => { 20 | const classes = useStyles(); 21 | const [ contract, setContract] = useState(null) 22 | const [ accounts, setAccounts ] = useState(null) 23 | const [ funds, setFunds ] = useState([]) 24 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 25 | 26 | useEffect(() => { 27 | init() 28 | }, []); 29 | 30 | const init = async () => { 31 | try { 32 | const networkId = await web3.eth.net.getId(); 33 | const deployedNetwork = FactoryContract.networks[networkId]; 34 | const accounts = await web3.eth.getAccounts(); 35 | const instance = new web3.eth.Contract( 36 | FactoryContract.abi, 37 | deployedNetwork && deployedNetwork.address, 38 | ); 39 | setContract(instance) 40 | setAccounts(accounts) 41 | 42 | const funds = await instance.methods.fundraisers().call() 43 | setFunds(funds) 44 | } 45 | catch(error) { 46 | alert( 47 | `Failed to load web3, accounts, or contract. Check console for details.`, 48 | ); 49 | console.error(error); 50 | } 51 | } 52 | 53 | const displayFundraisers = () => { 54 | return funds.map((fundraiser) => { 55 | return ( 56 | 60 | ) 61 | }) 62 | } 63 | 64 | return ( 65 |
66 | {displayFundraisers()} 67 |
68 | ) 69 | } 70 | 71 | export default Home; 72 | -------------------------------------------------------------------------------- /chapter-9/src/NewFundraiser.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import FormControl from '@material-ui/core/FormControl'; 4 | import { makeStyles } from '@material-ui/core/styles'; 5 | import Button from '@material-ui/core/Button'; 6 | import MenuItem from '@material-ui/core/MenuItem'; 7 | import TextField from '@material-ui/core/TextField'; 8 | import getWeb3 from "./utils/getWeb3"; 9 | import FactoryContract from "./contracts/Factory.json"; 10 | import Web3 from 'web3' 11 | 12 | const useStyles = makeStyles(theme => ({ 13 | container: { 14 | display: 'flex', 15 | flexWrap: 'wrap', 16 | }, 17 | textField: { 18 | marginLeft: theme.spacing(1), 19 | marginRight: theme.spacing(1), 20 | }, 21 | dense: { 22 | marginTop: theme.spacing(2), 23 | }, 24 | menu: { 25 | width: 200, 26 | }, 27 | })); 28 | 29 | const NewFundraiser = () => { 30 | const [labelWidth, setLabelWidth] = React.useState(0); 31 | const labelRef = React.useRef(null); 32 | const classes = useStyles(); 33 | const [ web3, setWeb3 ] = useState(null) 34 | 35 | useEffect(() => { 36 | const init = async() => { 37 | try { 38 | const web3 = await getWeb3(); 39 | const networkId = await web3.eth.net.getId(); 40 | const deployedNetwork = FactoryContract.networks[networkId]; 41 | const accounts = await web3.eth.getAccounts(); 42 | const instance = new web3.eth.Contract( 43 | FactoryContract.abi, 44 | deployedNetwork && deployedNetwork.address, 45 | ); 46 | 47 | setWeb3(web3) 48 | setContract(instance) 49 | setAccounts(accounts) 50 | 51 | } catch(error) { 52 | alert( 53 | `Failed to load web3, accounts, or contract. Check console for details.`, 54 | ); 55 | console.error(error); 56 | } 57 | } 58 | init(); 59 | }, []); 60 | 61 | const [ name, setFundraiserName ] = useState(null) 62 | const [ website, setFundraiserWebsite ] = useState(null) 63 | const [ description, setFundraiserDescription ] = useState(null) 64 | const [ image, setImage ] = useState(null) 65 | const [ address, setAddress ] = useState(null) 66 | const [ custodian, setCustodian ] = useState(null) 67 | const [ contract, setContract] = useState(null) 68 | const [ accounts, setAccounts ] = useState(null) 69 | 70 | const handleSubmit = async () => { 71 | const imageURL = image 72 | const bio = description 73 | const beneficiary = address 74 | const transaction = await contract.methods.createFundraiser( 75 | name, 76 | website, 77 | imageURL, 78 | bio, 79 | beneficiary, 80 | custodian 81 | ).send({ from: accounts[0] }) 82 | 83 | alert('Successfully created fundraiser') 84 | } 85 | 86 | return ( 87 |
88 |

Create A New Fundraiser

89 | 90 | 91 | setFundraiserName(e.target.value)} 97 | variant="outlined" 98 | inputProps={{ 'aria-label': 'bare' }} 99 | /> 100 | 101 | 102 | setFundraiserWebsite(e.target.value)} 108 | variant="outlined" 109 | inputProps={{ 'aria-label': 'bare' }} 110 | /> 111 | 112 | 113 | setFundraiserDescription(e.target.value)} 119 | variant="outlined" 120 | inputProps={{ 'aria-label': 'bare' }} 121 | /> 122 | 123 | 124 | setImage(e.target.value)} 130 | variant="outlined" 131 | inputProps={{ 'aria-label': 'bare' }} 132 | /> 133 | 134 | 135 | setAddress(e.target.value)} 141 | variant="outlined" 142 | inputProps={{ 'aria-label': 'bare' }} 143 | /> 144 | 145 | 146 | setCustodian(e.target.value)} 152 | variant="outlined" 153 | inputProps={{ 'aria-label': 'bare' }} 154 | /> 155 | 156 | 162 |
163 | ) 164 | } 165 | 166 | 167 | export default NewFundraiser; 168 | -------------------------------------------------------------------------------- /chapter-9/src/Receipts.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | 3 | const Receipts = (props) => { 4 | const [ donation, setDonation ] = useState(null) 5 | const [ fundName, setFundName ] = useState(null) 6 | const [ date, setDate ] = useState(null) 7 | 8 | useEffect(() => { 9 | const { donation, date, fund } = props.location.state 10 | 11 | const formattedDate = new Date(parseInt(date)) 12 | 13 | setDonation(donation) 14 | setDate(formattedDate.toString()) 15 | setFundName(fund) 16 | }, []); 17 | 18 | return ( 19 |
20 |
21 |

Thank you for your donation to {fundName}

22 |
23 | 24 |
25 |
Date of Donation: {date}
26 |
Donation Value: ${donation}
27 |
28 |
29 | ) 30 | } 31 | 32 | export default Receipts; 33 | -------------------------------------------------------------------------------- /chapter-9/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /chapter-9/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom' 4 | import App from './App'; 5 | 6 | ReactDOM.render(( 7 | // 8 | 9 | 10 | 11 | // 12 | ), document.getElementById('root')) 13 | -------------------------------------------------------------------------------- /chapter-9/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-9/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /chapter-9/src/utils/getWeb3.js: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | const getWeb3 = () => 4 | new Promise((resolve, reject) => { 5 | // Wait for loading completion to avoid race conditions with web3 injection timing. 6 | window.addEventListener("load", async () => { 7 | // Modern dapp browsers... 8 | if (window.ethereum) { 9 | const web3 = new Web3(window.ethereum); 10 | try { 11 | // Request account access if needed 12 | await window.ethereum.enable(); 13 | // Acccounts now exposed 14 | resolve(web3); 15 | } catch (error) { 16 | reject(error); 17 | } 18 | } 19 | // Legacy dapp browsers... 20 | else if (window.web3) { 21 | // Use Mist/MetaMask's provider. 22 | const web3 = window.web3; 23 | console.log("Injected web3 detected."); 24 | resolve(web3); 25 | } 26 | // Fallback to localhost; use dev console port by default... 27 | else { 28 | const provider = new Web3.providers.HttpProvider( 29 | "http://127.0.0.1:8545" 30 | ); 31 | const web3 = new Web3(provider); 32 | console.log("No web3 instance injected, using Local web3."); 33 | resolve(web3); 34 | } 35 | }); 36 | }); 37 | 38 | export default getWeb3; 39 | --------------------------------------------------------------------------------