├── .gitignore
├── .prettierrc.js
├── README.md
├── inbox
├── .env.example
├── .gitignore
├── README.md
├── compile.js
├── contracts
│ └── Inbox.sol
├── deploy.js
├── package-lock.json
├── package.json
└── test
│ └── Inbox.test.js
├── kickstart
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── README.md
├── ethereum
│ ├── .env
│ ├── .env.example
│ ├── build
│ │ ├── Campaign.json
│ │ └── CampaignFactory.json
│ ├── compile.js
│ ├── contracts
│ │ └── Campaign.sol
│ └── deploy.js
├── next.config.js
├── package.json
├── public
│ └── favicon.ico
├── src
│ └── pages
│ │ ├── _app.js
│ │ └── index.js
├── styles
│ └── globals.css
├── test
│ └── Campaign.test.js
└── yarn.lock
├── lottery-react
├── .gitignore
├── README.md
├── config-overrides.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── utils
│ ├── lottery.js
│ └── web3.js
└── lottery
├── .env.example
├── .gitignore
├── README.md
├── compile.js
├── contracts
└── Lottery.sol
├── deploy.js
├── package-lock.json
├── package.json
├── test
└── Lottery.test.js
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *node_modules
2 |
3 | # Logs
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # IDEs and editors (shamelessly copied from @angular/cli's .gitignore)
9 | /.idea
10 | .project
11 | .classpath
12 | .c9/
13 | *.launch
14 | .settings/
15 | *.sublime-workspace
16 |
17 | # IDE - VSCode
18 | .vscode/*
19 |
20 | ### Linux ###
21 | *~
22 |
23 | # temporary files which can be created if a process still has a handle open of a deleted file
24 | .fuse_hidden*
25 |
26 | # KDE directory preferences
27 | .directory
28 |
29 | # Linux trash folder which might appear on any partition or disk
30 | .Trash-*
31 |
32 | # .nfs files are created when an open file is removed but is still being accessed
33 | .nfs*
34 |
35 | ### OSX ###
36 | *.DS_Store
37 | .AppleDouble
38 | .LSOverride
39 |
40 | # Icon must end with two \r
41 | Icon
42 |
43 | # Thumbnails
44 | ._*
45 |
46 | # Files that might appear in the root of a volume
47 | .DocumentRevisions-V100
48 | .fseventsd
49 | .Spotlight-V100
50 | .TemporaryItems
51 | .Trashes
52 | .VolumeIcon.icns
53 | .com.apple.timemachine.donotpresent
54 |
55 | # Directories potentially created on remote AFP share
56 | .AppleDB
57 | .AppleDesktop
58 | Network Trash Folder
59 | Temporary Items
60 | .apdisk
61 |
62 | ### Windows ###
63 | # Windows thumbnail cache files
64 | Thumbs.db
65 | ehthumbs.db
66 | ehthumbs_vista.db
67 |
68 | # Folder config file
69 | Desktop.ini
70 |
71 | # Recycle Bin used on file shares
72 | $RECYCLE.BIN/
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: true,
6 | singleQuote: false,
7 | trailingComma: "none",
8 | bracketSpacing: true,
9 | arrowParens: "avoid",
10 | endOfLine: "auto"
11 | };
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ethereum and Solidity: The Complete Developer's Guide (Community Contributed Code Updates)
2 |
3 | ## Note: This repo is no longer maintained
4 |
5 | Hi, for anyone who has stumbled upon this repo in hope of finding up-to-date Solidity/web3.js/Node.js/React/Next.js code for the udemy.com course [Ethereum and Solidity: The Complete Developer's Guide](https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/), a course that I was a student of and NOT the lecturer/creator, unfortunately while some parts of the repo do provide up-to-date code and explanations, I have not been able to afford the time to keep maintaining this repo as I would have liked and so I have decided to archive it.
6 |
7 | ## Purpose of this Repo
8 |
9 | Up-to-date Solidity/web3.js/Node.js/React/Next.js code for the udemy.com course [Ethereum and Solidity: The Complete Developer's Guide](https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/).
10 |
11 | ## The Reason
12 |
13 | Toward the end of 2019 I became very interested in entering the blockchain development space and so I embarked on a journey to learn as much as I can, as quickly as I can, within this ever-evolving tech space, and to be more specific, the **Ethereum ecosystem**. Of course, I quickly realised that the development tools and packages being used to build, develop and deploy dApps and tech within this ecosystem all share a common trend: **rapid change and evolution, sometimes introducing breaking changes through iterations of their releases**.
14 |
15 | I make heavy use of the online learning website [udemy.com](https://www.udemy.com/) and find it to be a great supplementary learning tool. So naturally I bought a few courses on Ethereum and Solidity. The problem is, many of these courses target outdated versions of [Solidity](https://docs.soliditylang.org), [web3.js](https://web3js.readthedocs.io/) and [Truffle](https://www.trufflesuite.com/) in their course lessons and code examples. In the course creators' defense, remember, this is rapidly evolving tech we're dealing with here and the respective effort required to keep their video course content up-to-date with current software releases can be rather challenging.
16 |
17 | _And so, that's where I decided to lend a bit of a helping hand_.
18 |
19 | ## Let the Code speak
20 |
21 | I figured that if I wanted the online courses I enrolled in to provide up-to-date code then **other developers also had to want this**. So, I decided to take action and just write the updated code myself, starting with the Udemy course _Ethereum and Solidity: The Complete Developer's Guide_, the one I found most enjoyable and acceptable.
22 |
23 | ## Repository structure
24 |
25 | This repository was setup as a monolithic repository (without the full monorepo structure so as not to introduce unnecessary extra complexity beyond the scope of the udemy.com course), allowing me to keep the updated versions of the isolated bits of the course's code and tests well organized all within a single repository.
26 |
27 | ### Smart Contracts
28 |
29 | The smart contracts created in the course are:
30 |
31 | - [The Inbox Contract](/inbox/contracts/Inbox.sol)
32 | - [The Lottery Contract](/lottery/contracts/Lottery.sol)
33 | - [The CampaignFactory and Campaign contracts](/kickstart/ethereum/contracts/Campaign.sol)
34 |
35 | ### Working with the latest React tooling
36 |
37 | The course sections that cover building out a front-end application using React make use of outdated versions of [_Create React App_](https://create-react-app.dev) and [_Next.js_](https://nextjs.org).
38 |
39 | For Create React App, the previous approach of installing globally via `npm install -g create-react-app` is no longer the recommended approach. As such if you have already used this command and installed create-react-app globally then you should uninstall the package using `npm uninstall -g create-react-app` or `yarn global remove create-react-app`. To create a new React app you may now use one of the following methods to ensure that you always use the latest React version:
40 |
41 | - **npx**: `npx create-react-app my-app`
42 | - **npm**: `npm init react-app my-app`
43 | - **Yarn**: `yarn create react-app my-app`
44 |
45 | For more details on the above methods, see [https://create-react-app.dev/docs/getting-started](https://create-react-app.dev/docs/getting-started).
46 |
47 | **The Kickstart/CrowdCoin app implemented in this repo is itself currently being updated to the latest version of Next.js (v13).**
48 |
49 | ### The lottery-react App
50 |
51 | To create the `lottery-react` app I chose to use the npx command option, as follows:
52 |
53 | ```bash
54 | npx create-react-app lottery-react
55 | ```
56 |
57 | - [Browse the lottery-react App code files](/lottery-react)
58 | - [lottery-react App README](/lottery-react/README.md)
59 | - [Live Demo of the app](https://lottery-react.onrender.com)
60 |
61 | ### The Kickstart/CrowdCoin App
62 |
63 | - [Browse the app code files](/kickstart)
64 | - Live Demo of the app (_update in progress_)
65 |
66 | ## Acknowledgement
67 |
68 | I would like to give credit to [Stephen Grider](https://www.udemy.com/user/sgslo/) for creating the [excellent course](https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/) for which I created this repository as my own personal add-on. If any mistakes or errors are found within any of this repository's content they should be attributed to an oversight on my part, and in no part should be deemed any fault of the Udemy course author, Stephen Grider.
69 |
--------------------------------------------------------------------------------
/inbox/.env.example:
--------------------------------------------------------------------------------
1 | ACCOUNT_MNEMONIC=""
2 | GOERLI_ENDPOINT=""
--------------------------------------------------------------------------------
/inbox/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Logs
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # IDEs and editors (shamelessly copied from @angular/cli's .gitignore)
9 | /.idea
10 | .project
11 | .classpath
12 | .c9/
13 | *.launch
14 | .settings/
15 | *.sublime-workspace
16 |
17 | # IDE - VSCode
18 | .vscode/*
19 | !.vscode/settings.json
20 | !.vscode/tasks.json
21 | !.vscode/launch.json
22 | !.vscode/extensions.json
23 |
24 | ### Linux ###
25 | *~
26 |
27 | # temporary files which can be created if a process still has a handle open of a deleted file
28 | .fuse_hidden*
29 |
30 | # KDE directory preferences
31 | .directory
32 |
33 | # Linux trash folder which might appear on any partition or disk
34 | .Trash-*
35 |
36 | # .nfs files are created when an open file is removed but is still being accessed
37 | .nfs*
38 |
39 | ### OSX ###
40 | *.DS_Store
41 | .AppleDouble
42 | .LSOverride
43 |
44 | # Icon must end with two \r
45 | Icon
46 |
47 | # Thumbnails
48 | ._*
49 |
50 | # Files that might appear in the root of a volume
51 | .DocumentRevisions-V100
52 | .fseventsd
53 | .Spotlight-V100
54 | .TemporaryItems
55 | .Trashes
56 | .VolumeIcon.icns
57 | .com.apple.timemachine.donotpresent
58 |
59 | # Directories potentially created on remote AFP share
60 | .AppleDB
61 | .AppleDesktop
62 | Network Trash Folder
63 | Temporary Items
64 | .apdisk
65 |
66 | ### Windows ###
67 | # Windows thumbnail cache files
68 | Thumbs.db
69 | ehthumbs.db
70 | ehthumbs_vista.db
71 |
72 | # Folder config file
73 | Desktop.ini
74 |
75 | # Recycle Bin used on file shares
76 | $RECYCLE.BIN/
77 |
78 | # Environment file
79 | .env
--------------------------------------------------------------------------------
/inbox/README.md:
--------------------------------------------------------------------------------
1 | # Up-to-date Inbox Smart Contract, Node.js Scripts & Unit Tests
2 |
3 | > Section 1 of the udemy.com course [Ethereum and Solidity: The Complete Developer's Guide](https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/) by [Stephen Grider](https://www.udemy.com/user/sgslo/) implements an Inbox smart contract, along with a Node.js compile script, deploy script and unit tests for that contract. In this repo I provide up-to-date equivalents (along with detailed explanations) for each of these, for the benefit of students who have enrolled in Stephen's course.
4 |
5 | ## Contents
6 |
7 | - [Inbox smart contract](#inbox-smart-contract)
8 | - [Compile Script](#compile-script)
9 | - [Update your package.json](#update-your-packagejson)
10 | - [Unit Tests](#unit-tests)
11 | - [Deploy Script](#deploy-script)
12 |
13 |
14 |
15 | ## Inbox Smart Contract
16 |
17 | An up-to-date equivalent to the course's Inbox smart contract can be found [here](./contracts/Inbox.sol) and is shown immediately below:
18 |
19 | ```solidity
20 | // SPDX-License-Identifier: GPL-3.0-or-later
21 | pragma solidity >=0.5.0 <0.9.0;
22 |
23 | contract Inbox {
24 | string public message;
25 |
26 | constructor(string memory initialMessage) {
27 | message = initialMessage;
28 | }
29 |
30 | function setMessage(string memory newMessage) public {
31 | message = newMessage;
32 | }
33 |
34 | // Because we declared the `message` state variable above with
35 | // keyword `public`, the compiler automatically generates a getter
36 | // function for us equivalent to:
37 | //
38 | // function message() external view returns (string memory) { return message; }
39 | //
40 | // ...so we do **NOT** need to define a getter function ourselves.
41 | }
42 | ```
43 |
44 | This smart contract is extremely simple but a good one for showing the changes that need to be made from the course version to bring it up-to-date with the latest Solidity version.
45 |
46 | The first line:
47 |
48 | ```solidity
49 | // SPDX-License-Identifier: GPL-3.0-or-later
50 | ```
51 |
52 | is an [SPDX license identifier](https://docs.soliditylang.org/en/latest/layout-of-source-files.html?highlight=spdx#spdx-license-identifier), _introduced from Solidity 0.6.8_, which allows developers to specify the [license](https://spdx.org/licenses) the smart contract uses. **Every Solidity source file should start with a comment indicating its license** and it should be one of the identifiers listed at https://spdx.org/licenses. In this case I've specified that the smart contract uses the [GNU General Public License v3.0 or later](https://spdx.org/licenses/GPL-3.0-or-later.html)
53 |
54 | The next line:
55 |
56 | ```solidity
57 | pragma solidity >=0.5.0 <0.9.0;
58 | ```
59 |
60 | specifies that this smart contract's source code is written for Solidity version 0.5.0 up to, but not including version 0.9.0. In the udemy.com course, the author uses a version pragma of ^0.4.17, so _the course's version of Inbox will not compile on a Solidity compiler earlier than version 0.4.17 nor will it compile on a compiler starting from version 0.5.0_.
61 |
62 | Within the contract body definition, we will keep the line that declares the `message` state variable as is:
63 |
64 | ```solidity
65 | string public message;
66 | ```
67 |
68 | Now since we're declaring this variable using the `public` visibility keyword, the compiler will automatically generate a getter function that allows the current value of the `message` to be read from outside of the contract. The code of this function is equivalent to the following:
69 |
70 | ```solidity
71 | function message() external view returns (string memory) { return message; }
72 | ```
73 |
74 | This makes the `getMessage` function that the course author adds to his Inbox contract definition redundant, so there is no need for your Inbox contract to have this function.
75 |
76 | ### Change in syntax for definining the Contructor
77 |
78 | In the course example, a constructor for Inbox is defined as follows:
79 |
80 | ```solidity
81 | function Inbox(string initialMessage) public {
82 | message = initialMessage;
83 | }
84 | ```
85 |
86 | **This style of constructor definition is no longer valid**. As of Solidity 0.5.0, constructors [must be defined using the `constructor` keyword](https://docs.soliditylang.org/en/latest/050-breaking-changes.html#constructors). As of Solidity 0.7.0, [visibility (public / internal) is not needed for constructors anymore](https://docs.soliditylang.org/en/latest/050-breaking-changes.html#constructors). To prevent a contract from being created, it can be marked `abstract`, thereby making the visibility concept for constructors obsolete.
87 |
88 | The constructor function for the Inbox contract therefore needs to be changed to:
89 |
90 | ```solidity
91 | constructor(string memory initialMessage) {
92 | message = initialMessage;
93 | }
94 | ```
95 |
96 | Note that with this change, we also use the `memory` keyword when declaring the constructor's `initialMessage` string parameter. This is because, as of Solidity 0.5.0, explicit data location for all variables of struct, array or mapping types is now mandatory (see https://docs.soliditylang.org/en/latest/050-breaking-changes.html#explicitness-requirements), and this applies to function parameters and return variables as well. So just for the sake of a bit more clarity:
97 |
98 | - Variables of type `string` are special arrays in Solidity. You can check out the official documentation on arrays [here](https://docs.soliditylang.org/en/latest/types.html#arrays).
99 | - Since `string`s are arrays we have to specifiy an explicit data location, so we specify the `memory` location. The Ethereum Virtual Machine (EVM) has three areas where it can store data: **storage**, **memory** and the **stack**. See https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html#storage-memory-and-the-stack if you want to learn more about these data locations.
100 |
101 | The final change to the contract was to also add the `memory` data location to the `newMessage` parameter of the `setMessage` function.
102 |
103 |
104 |
105 | ## Compile Script
106 |
107 | An up-to-date equivalent to the course's compile script for the Inbox contract can be found [here](./compile.js) and is shown immediately below:
108 |
109 | ```js
110 | const path = require("path");
111 | const fs = require("fs");
112 | const solc = require("solc");
113 |
114 | const inboxPath = path.resolve(__dirname, "contracts", "Inbox.sol");
115 | const source = fs.readFileSync(inboxPath, "utf8");
116 |
117 | const input = {
118 | language: "Solidity",
119 | sources: {
120 | "Inbox.sol": {
121 | content: source
122 | }
123 | },
124 | settings: {
125 | metadata: {
126 | useLiteralContent: true
127 | },
128 | outputSelection: {
129 | "*": {
130 | "*": ["*"]
131 | }
132 | }
133 | }
134 | };
135 |
136 | const output = JSON.parse(solc.compile(JSON.stringify(input)));
137 |
138 | module.exports = output.contracts["Inbox.sol"].Inbox;
139 | ```
140 |
141 | There's alot to digest with respect to the changes made to this script to bring it up-to-date. So let's dive in and explain what's going on:
142 |
143 | ### Compiler Input and Output JSON Description
144 |
145 | The recommended way to interface with the Solidity compiler, especially when developing more complex and automated setups is the so-called JSON-input-output interface. In summary, the compiler API expects a JSON formatted input and outputs the compilation result in a JSON formatted output. For details on this approach, including thorough descriptions of the input and output formats, check out the Solidity docs [here](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description).
146 |
147 | In our `compile.js` above, we declare a variable named `input` to hold a JavaScript object representation of the input that will be passed to the compiler after it's JSON stringified. In this object we define the single Solidity source file that has to be compiled, `Inbox.sol`, passing the fully loaded source code of the contract as the content source of the contract.
148 |
149 | The lines
150 |
151 | ```js
152 | const output = JSON.parse(solc.compile(JSON.stringify(input)));
153 |
154 | module.exports = output.contracts["Inbox.sol"].Inbox;
155 | ```
156 |
157 | parse the output returned by the call to `solc.compile(...)` and store it in the `output` variable. We then extract the `Inbox` contract object only and set that as the only export from `compile.js`.
158 |
159 |
160 |
161 | ## Update your package.json
162 |
163 | Before I get into the unit tests and deploy script, I think it's important to first explain the updates that need to be made to this project's [package.json](./package.json), specificially the dependencies being used. **All** of the dependencies should be updated to their latest versions and the `ganache-cli` dependency replaced with `ganache`, since the `ganache-cli` package has been deprecated and is now just `ganache` (as explained [here](https://github.com/trufflesuite/ganache/blob/develop/UPGRADE-GUIDE.md)).
164 |
165 | Here is what my package.json looks like:
166 |
167 | ```json
168 | {
169 | "name": "inbox",
170 | "version": "2.0.2",
171 | "description": "Inbox smart contract Node.js project.",
172 | "main": "compile.js",
173 | "scripts": {
174 | "test": "mocha"
175 | },
176 | "author": "Owan Hunte",
177 | "license": "ISC",
178 | "dependencies": {
179 | "@truffle/hdwallet-provider": "^2.1.6",
180 | "dotenv": "^16.0.3",
181 | "solc": "^0.8.18",
182 | "web3": "^1.8.2"
183 | },
184 | "devDependencies": {
185 | "ganache": "^7.7.4",
186 | "mocha": "^10.2.0"
187 | }
188 | }
189 | ```
190 |
191 | Note that I installed the `ganache` and `mocha` packages as development dependencies since both are only used in the [unit tests](./test/Inbox.test.js).
192 |
193 |
194 |
195 | ## Unit Tests
196 |
197 | An up-to-date equivalent to the course's unit tests (`Inbox.test.js`) can be found [here](./test/Inbox.test.js) and is shown immediately below:
198 |
199 | ```js
200 | const assert = require("assert");
201 | const ganache = require("ganache");
202 | const Web3 = require("web3");
203 | const provider = ganache.provider();
204 | const web3 = new Web3(provider);
205 | const { abi, evm } = require("../compile");
206 |
207 | const message = "Hi there!";
208 | let accounts;
209 | let inbox;
210 |
211 | beforeEach(async () => {
212 | // Get a list of all accounts.
213 | accounts = await web3.eth.getAccounts();
214 |
215 | // Use one of those accounts to deploy the contract.
216 | inbox = await new web3.eth.Contract(abi)
217 | .deploy({ data: "0x" + evm.bytecode.object, arguments: [message] })
218 | .send({ from: accounts[0], gas: "1000000" });
219 | });
220 |
221 | describe("Inbox", () => {
222 | it("deploys a contract", () => {
223 | assert.ok(inbox.options.address);
224 | });
225 |
226 | it("has a default message", async () => {
227 | const msg = await inbox.methods.message().call();
228 | assert.strictEqual(msg, message);
229 | });
230 |
231 | it("can change the message", async () => {
232 | const newMsg = "bye";
233 | await inbox.methods.setMessage(newMsg).send({ from: accounts[0] });
234 |
235 | const msg = await inbox.methods.message().call();
236 | assert.strictEqual(msg, newMsg);
237 | });
238 | });
239 | ```
240 |
241 | There are 2 main changes happening with the above tests file. First we have the line:
242 |
243 | ```js
244 | const ganache = require("ganache");
245 | ```
246 |
247 | which replaces the line from the course's version that uses the now deprecated `ganache-cli`.
248 |
249 | Second, the line
250 |
251 | ```js
252 | const { abi, evm } = require("../compile");`
253 | ```
254 |
255 | imports the compiled `Inbox` contract object that the compile script exports and stores the `abi` and `evm` object values as variables. The import line which the course has, `const { interface, bytecode } = require("../compile");`, will not work with the latest Solidity compiler versions. The `abi` object replaces the `interface` object, and we can access the contract's bytecode object via `evm.bytecode.object`, as shown above.
256 |
257 |
258 |
259 | ## Deploy Script
260 |
261 | An up-to-date equivalent to the course's deploy script for the Inbox contract can be found [here](./deploy.js) and is shown immediately below:
262 |
263 | ```js
264 | require("dotenv").config();
265 |
266 | const HDWalletProvider = require("@truffle/hdwallet-provider");
267 | const Web3 = require("web3");
268 | const { abi, evm } = require("./compile");
269 | const mnemonicPhrase = process.env.ACCOUNT_MNEMONIC;
270 | const network = process.env.GOERLI_ENDPOINT;
271 |
272 | const provider = new HDWalletProvider({
273 | mnemonic: {
274 | phrase: mnemonicPhrase
275 | },
276 | providerOrUrl: network
277 | });
278 |
279 | const web3 = new Web3(provider);
280 | const message = "Hi there!";
281 |
282 | const deploy = async () => {
283 | const accounts = await web3.eth.getAccounts();
284 | console.log("Attempting to deploy from account", accounts[0]);
285 |
286 | const result = await new web3.eth.Contract(abi)
287 | .deploy({ data: "0x" + evm.bytecode.object, arguments: [message] })
288 | .send({ from: accounts[0] });
289 |
290 | console.log("Contract deployed to", result.options.address);
291 | provider.engine.stop();
292 | };
293 |
294 | deploy();
295 | ```
296 |
297 | The changes in this script from the course's version are as follows:
298 |
299 | - The line `const { abi, evm } = require("./compile");` replaces the `const { interface, bytecode } = require("./compile");` line that's found in the course example, and we access the bytecode object via `evm.bytecode.object`.
300 | - Instead of hard-coding the account mnemonic and Infura endpoint as is done in the course's deploy script, I'm storing and referencing these via environment variables.
301 | - A [Goerli Infura](https://app.infura.io) endpoint (stored in the `process.env.GOERLI_ENDPOINT` environment variable) is passed to `HDWalletProvider` instead of a Rinkeby endpoint since the Rinkeby network no longer exists. So when copying your endpoint from the Infura dashboard, remember to grab the Goerli Ethereum endpoint.
302 | - The `dotenv` package is used to read these environment variables from a `.env` file. Create that file locally in the root of your inbox folder and copy the contents of [`.env.example`](./.env.example) into your `.env` file. Set `ACCOUNT_MNEMONIC` and `GOERLI_ENDPOINT` in your `.env` file appropriately. **DO NOT use a mnemonic for an account/wallet with real money or Ether associated with it!**
303 | - To prevent the deployment from hanging, the statement `provider.engine.stop();` is added at the end of the `deploy` function definition.
304 |
305 | ## That's all for now
306 |
307 | That about covers things where the updates to the Inbox smart contract and Node.js project are concerned. As always, I sincerely hope my contributions prove useful to all students of the course who find their way to this repository.
308 |
--------------------------------------------------------------------------------
/inbox/compile.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const fs = require("fs");
3 | const solc = require("solc");
4 |
5 | const inboxPath = path.resolve(__dirname, "contracts", "Inbox.sol");
6 | const source = fs.readFileSync(inboxPath, "utf8");
7 |
8 | /***
9 | * The recommended way to interface with the Solidity compiler, especially for more
10 | * complex and automated setups is the so-called JSON-input-output interface.
11 | *
12 | * See https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description
13 | * for more details.
14 | */
15 | const input = {
16 | language: "Solidity",
17 | sources: {
18 | // Each Solidity source file to be compiled must be specified by defining either
19 | // a URL to the file or the literal file content.
20 | // See https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description
21 | "Inbox.sol": {
22 | content: source
23 | }
24 | },
25 | settings: {
26 | metadata: {
27 | useLiteralContent: true
28 | },
29 | outputSelection: {
30 | "*": {
31 | "*": ["*"]
32 | }
33 | }
34 | }
35 | };
36 |
37 | const output = JSON.parse(solc.compile(JSON.stringify(input)));
38 |
39 | module.exports = output.contracts["Inbox.sol"].Inbox;
40 |
--------------------------------------------------------------------------------
/inbox/contracts/Inbox.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0-or-later
2 | pragma solidity >=0.5.0 <0.9.0;
3 |
4 | contract Inbox {
5 | // The keyword `public` automatically generates a function that
6 | // allows you to access the current value of the state variable
7 | // from outside of the contract (see lines 22 - 28).
8 | string public message;
9 |
10 | // Note: Removing `public` visibility specifier. Visibility (public / internal)
11 | // is not needed for constructors anymore: To prevent a contract from being
12 | // created, it can be marked abstract. This makes the visibility concept
13 | // for constructors obsolete.
14 | constructor(string memory initialMessage) {
15 | message = initialMessage;
16 | }
17 |
18 | function setMessage(string memory newMessage) public {
19 | message = newMessage;
20 | }
21 |
22 | // Because we declared the `message` state variable above with
23 | // keyword `public`, the compiler automatically generates a getter
24 | // function for us equivalent to:
25 | //
26 | // function message() external view returns (string memory) { return message; }
27 | //
28 | // ...so we do **NOT** need to define a getter function ourselves.
29 | }
30 |
--------------------------------------------------------------------------------
/inbox/deploy.js:
--------------------------------------------------------------------------------
1 | // Load environment variables.
2 | require("dotenv").config();
3 |
4 | const HDWalletProvider = require("@truffle/hdwallet-provider");
5 | const Web3 = require("web3");
6 | const { abi, evm } = require("./compile");
7 | const mnemonicPhrase = process.env.ACCOUNT_MNEMONIC;
8 | const network = process.env.GOERLI_ENDPOINT;
9 |
10 | const provider = new HDWalletProvider({
11 | mnemonic: {
12 | phrase: mnemonicPhrase
13 | },
14 | providerOrUrl: network
15 | });
16 |
17 | const web3 = new Web3(provider);
18 | const message = "Hi there!";
19 |
20 | const deploy = async () => {
21 | const accounts = await web3.eth.getAccounts();
22 | console.log("Attempting to deploy from account", accounts[0]);
23 |
24 | const result = await new web3.eth.Contract(abi)
25 | .deploy({ data: "0x" + evm.bytecode.object, arguments: [message] })
26 | .send({ from: accounts[0] });
27 |
28 | console.log("Contract deployed to", result.options.address);
29 | provider.engine.stop();
30 | };
31 |
32 | deploy();
33 |
--------------------------------------------------------------------------------
/inbox/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "inbox",
3 | "version": "2.0.2",
4 | "description": "Inbox smart contract Node.js project.",
5 | "main": "compile.js",
6 | "scripts": {
7 | "test": "mocha"
8 | },
9 | "author": "Owan Hunte",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@truffle/hdwallet-provider": "^2.1.6",
13 | "dotenv": "^16.0.3",
14 | "solc": "^0.8.18",
15 | "web3": "^1.8.2"
16 | },
17 | "devDependencies": {
18 | "ganache": "^7.7.4",
19 | "mocha": "^10.2.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/inbox/test/Inbox.test.js:
--------------------------------------------------------------------------------
1 | const assert = require("assert");
2 | const ganache = require("ganache");
3 | const Web3 = require("web3");
4 | const provider = ganache.provider();
5 | const web3 = new Web3(provider);
6 | const { abi, evm } = require("../compile");
7 |
8 | const message = "Hi there!";
9 | let accounts;
10 | let inbox;
11 |
12 | beforeEach(async () => {
13 | // Get a list of all accounts.
14 | accounts = await web3.eth.getAccounts();
15 |
16 | // Use one of those accounts to deploy the contract.
17 | inbox = await new web3.eth.Contract(abi)
18 | .deploy({ data: "0x" + evm.bytecode.object, arguments: [message] })
19 | .send({ from: accounts[0], gas: "1000000" });
20 | });
21 |
22 | describe("Inbox", () => {
23 | it("deploys a contract", () => {
24 | assert.ok(inbox.options.address);
25 | });
26 |
27 | it("has a default message", async () => {
28 | const msg = await inbox.methods.message().call();
29 | assert.strictEqual(msg, message);
30 | });
31 |
32 | it("can change the message", async () => {
33 | const newMsg = "bye";
34 | await inbox.methods.setMessage(newMsg).send({ from: accounts[0] });
35 |
36 | const msg = await inbox.methods.message().call();
37 | assert.strictEqual(msg, newMsg);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/kickstart/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals", "prettier"]
3 | }
4 |
--------------------------------------------------------------------------------
/kickstart/.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 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/kickstart/.prettierignore:
--------------------------------------------------------------------------------
1 | # dependencies folder/files:
2 | node_modules
3 | .pnp
4 | .pnp.js
5 |
6 | # artifacts:
7 | build
8 | coverage
9 |
10 | # next.js and vercel
11 | .next
12 | .vercel
13 | out
14 |
15 | # debug files:
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 |
20 | # misc:
21 | .DS_Store
22 | *.pem
23 |
24 | # env files:
25 | .env
26 | .env.*
--------------------------------------------------------------------------------
/kickstart/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: "es5",
4 | singleQuote: true,
5 | printWidth: 100,
6 | tabWidth: 2,
7 | useTabs: false,
8 | };
9 |
--------------------------------------------------------------------------------
/kickstart/README.md:
--------------------------------------------------------------------------------
1 | # The Kickstart/CrowdCoin App
2 |
3 | This README is for my version of the Kickstart/CrowdCoin app developed in the udemy.com course [Ethereum and Solidity: The Complete Developer's Guide](https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/). For this app I used the latest version of [Next.js](https://nextjs.org) when I created this repo, and this is now being updated to the currently latest version of Next.js (**v12**) as part of my current efforts to bring this repo up-to-date. So bear with me as I work towards completing this upgrade over the next week.
4 |
--------------------------------------------------------------------------------
/kickstart/ethereum/.env:
--------------------------------------------------------------------------------
1 | ACCOUNT_MNEMONIC="question cream ensure tackle lyrics filter chat blue weasel whale dinner current"
2 | RINKEBY_ENDPOINT="https://rinkeby.infura.io/v3/103b800ab3a64b9f94500919bbaeb94a"
--------------------------------------------------------------------------------
/kickstart/ethereum/.env.example:
--------------------------------------------------------------------------------
1 | ACCOUNT_MNEMONIC=""
2 | RINKEBY_ENDPOINT=""
--------------------------------------------------------------------------------
/kickstart/ethereum/compile.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const solc = require("solc");
3 | const fs = require("fs-extra");
4 |
5 | const buildPath = path.resolve(__dirname, "build");
6 | const contractFileName = "Campaign.sol";
7 |
8 | // Delete the current build folder.
9 | fs.removeSync(buildPath);
10 |
11 | const campaignPath = path.resolve(__dirname, "contracts", contractFileName);
12 | const source = fs.readFileSync(campaignPath, "utf8");
13 |
14 | /***
15 | * The recommended way to interface with the Solidity compiler, especially for more
16 | * complex and automated setups is the so-called JSON-input-output interface.
17 | *
18 | * See https://docs.soliditylang.org/en/v0.8.6/using-the-compiler.html#compiler-input-and-output-json-description
19 | * for more details.
20 | */
21 | const input = {
22 | language: "Solidity",
23 | sources: {},
24 | settings: {
25 | metadata: {
26 | useLiteralContent: true,
27 | },
28 | outputSelection: {
29 | "*": {
30 | "*": ["*"],
31 | },
32 | },
33 | },
34 | };
35 |
36 | input.sources[contractFileName] = {
37 | content: source,
38 | };
39 |
40 | const output = JSON.parse(solc.compile(JSON.stringify(input)));
41 | const contracts = output.contracts[contractFileName];
42 |
43 | // Create the build folder.
44 | fs.ensureDirSync(buildPath);
45 |
46 | // Extract and write the JSON representations of the contracts to the build folder.
47 | for (let contract in contracts) {
48 | if (contracts.hasOwnProperty(contract)) {
49 | fs.outputJsonSync(path.resolve(buildPath, `${contract}.json`), contracts[contract]);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/kickstart/ethereum/contracts/Campaign.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 | pragma solidity >=0.5.0 <0.9.0;
3 |
4 | contract CampaignFactory {
5 | Campaign[] public deployedCampaigns;
6 |
7 | function createCampaign(uint256 minimum) public {
8 | Campaign newCampaign = new Campaign(minimum, msg.sender);
9 | deployedCampaigns.push(newCampaign);
10 | }
11 |
12 | function getDeployedCampaigns() public view returns (Campaign[] memory) {
13 | return deployedCampaigns;
14 | }
15 | }
16 |
17 | contract Campaign {
18 | struct Request {
19 | string description;
20 | uint256 value;
21 | address payable recipient;
22 | bool complete;
23 | uint256 approvalCount;
24 | mapping(address => bool) approvals;
25 | }
26 |
27 | address public manager;
28 | uint256 public minimumContribution;
29 | mapping(address => bool) public approvers;
30 | uint256 public approversCount;
31 |
32 | // As of Solidity 0.7.0, an unsafe feature related to mappings in Structs was removed.
33 | // If a struct or array contains a mapping, it can only be used in storage. Prior to 0.7.0,
34 | // mapping members were silently skipped in memory, which is confusing and error-prone.
35 | //
36 | // So the line which is given in the course code:
37 | //
38 | // Request[] public requests;
39 | //
40 | // now becomes the following 2 lines of code. Also in the createRequest
41 | // function, instead of creating a memory Struct, which the code was proviously
42 | // doing, we now have to create a storage Struct.
43 | //
44 | // And finally, wherever the course code has the line `requests.length` gets replaced
45 | // by `numRequests`.
46 | //
47 | // https://docs.soliditylang.org/en/v0.7.0/070-breaking-changes.html#mappings-outside-storage
48 | // https://docs.soliditylang.org/en/v0.8.6/types.html?highlight=struct#structs
49 | //
50 | uint256 numRequests;
51 | mapping(uint256 => Request) public requests;
52 |
53 | constructor(uint256 minimum, address creator) {
54 | manager = creator;
55 | minimumContribution = minimum;
56 | }
57 |
58 | function contribute() public payable {
59 | require(
60 | msg.value >= minimumContribution,
61 | "A minumum contribution is required."
62 | );
63 | approvers[msg.sender] = true;
64 | approversCount++;
65 | }
66 |
67 | function createRequest(
68 | string memory description,
69 | uint256 value,
70 | address payable recipient
71 | ) public onlyManager {
72 | Request storage r = requests[numRequests++];
73 | r.description = description;
74 | r.value = value;
75 | r.recipient = recipient;
76 | r.complete = false;
77 | r.approvalCount = 0;
78 | }
79 |
80 | function approveRequest(uint256 index) public {
81 | Request storage request = requests[index];
82 | require(
83 | approvers[msg.sender],
84 | "Only contributors can approve a specific payment request"
85 | );
86 | require(
87 | !request.approvals[msg.sender],
88 | "You have already voted to approve this request"
89 | );
90 |
91 | request.approvals[msg.sender] = true;
92 | request.approvalCount++;
93 | }
94 |
95 | function finalizeRequest(uint256 index) public onlyManager {
96 | Request storage request = requests[index];
97 | require(
98 | request.approvalCount > (approversCount / 2),
99 | "This request needs more approvals before it can be finalized"
100 | );
101 | require(!(request.complete), "This request has already been finalized");
102 |
103 | request.recipient.transfer(request.value);
104 | request.complete = true;
105 | }
106 |
107 | function getSummary()
108 | public
109 | view
110 | returns (
111 | uint256,
112 | uint256,
113 | uint256,
114 | uint256,
115 | address
116 | )
117 | {
118 | return (
119 | minimumContribution,
120 | address(this).balance,
121 | numRequests,
122 | approversCount,
123 | manager
124 | );
125 | }
126 |
127 | function getRequestsCount() public view returns (uint256) {
128 | return numRequests;
129 | }
130 |
131 | modifier onlyManager() {
132 | require(
133 | msg.sender == manager,
134 | "Only the campaign manager can call this function."
135 | );
136 | _;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/kickstart/ethereum/deploy.js:
--------------------------------------------------------------------------------
1 | // Load environment variables.
2 | require("dotenv").config();
3 |
4 | const HDWalletProvider = require("@truffle/hdwallet-provider");
5 | const Web3 = require("web3");
6 | const compiledFactory = require("./build/CampaignFactory.json");
7 | const mnemonicPhrase = process.env.ACCOUNT_MNEMONIC;
8 | const network = process.env.RINKEBY_ENDPOINT;
9 |
10 | const provider = new HDWalletProvider({
11 | mnemonic: {
12 | phrase: mnemonicPhrase
13 | },
14 | providerOrUrl: network
15 | });
16 |
17 | const web3 = new Web3(provider);
18 |
19 | const deploy = async () => {
20 | const accounts = await web3.eth.getAccounts();
21 | console.log("Attempting to deploy from account", accounts[0]);
22 |
23 | const result = await new web3.eth.Contract(compiledFactory.abi)
24 | .deploy({ data: "0x" + compiledFactory.evm.bytecode.object })
25 | .send({ from: accounts[0] });
26 |
27 | console.log("Contract deployed to", result.options.address);
28 | provider.engine.stop();
29 | };
30 |
31 | deploy();
32 |
--------------------------------------------------------------------------------
/kickstart/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | eslint: {
3 | dirs: ["src"], // Run ESLint on the 'src' directory during production builds (next build)
4 | },
5 | reactStrictMode: true,
6 | };
7 |
--------------------------------------------------------------------------------
/kickstart/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kickstart-js",
3 | "version": "3.0.0",
4 | "description": "Kickstart/CrowdCoin App based on the udemy.com course Ethereum and Solidity: The Complete Developer's Guide",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint",
11 | "test": "mocha"
12 | },
13 | "browserslist": [
14 | ">0.3%",
15 | "not ie 11",
16 | "not dead",
17 | "not op_mini all"
18 | ],
19 | "dependencies": {
20 | "@truffle/hdwallet-provider": "^1.5.1",
21 | "dotenv": "^10.0.0",
22 | "fs-extra": "^10.0.0",
23 | "ganache-cli": "^6.12.2",
24 | "mocha": "^9.1.3",
25 | "next": "12.0.2",
26 | "react": "17.0.2",
27 | "react-dom": "17.0.2",
28 | "semantic-ui-css": "^2.4.1",
29 | "semantic-ui-react": "^2.0.4",
30 | "solc": "^0.8.9",
31 | "web3": "^1.6.0"
32 | },
33 | "devDependencies": {
34 | "eslint": "7.32.0",
35 | "eslint-config-next": "12.0.2",
36 | "eslint-config-prettier": "^8.3.0",
37 | "prettier": "^2.4.1"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/kickstart/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/owanhunte/ethereum-solidity-course-updated-code/ca00b11721ccd0074f3aa5f4c508d4607f139d5f/kickstart/public/favicon.ico
--------------------------------------------------------------------------------
/kickstart/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'semantic-ui-css/semantic.min.css';
2 | import '../../styles/globals.css';
3 |
4 | function MyApp({ Component, pageProps }) {
5 | return ;
6 | }
7 |
8 | export default MyApp;
9 |
--------------------------------------------------------------------------------
/kickstart/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 |
3 | export default function CampaignIndex() {
4 | return (
5 |
244 | This contract is managed by {manager}.
245 | {players.length === 1
246 | ? ` There is currently ${players.length} person entered, `
247 | : ` There are currently ${players.length} people entered, `}
248 | competing to win {web3.utils.fromWei(balance, "ether")} ether!
249 |