├── README.md ├── SimpleStore.sol ├── deploy-contract.js ├── index.html ├── package.json ├── simple-store.json └── test-contract.js /README.md: -------------------------------------------------------------------------------- 1 | Tutorial: Simple Store 2 | ====================== 3 | 4 | *This file is very much a work in progress. This file is still under substantial 5 | development. If you are new to Ethereum, this is likely not ready for you yet. It 6 | is posted mostly to get some early feedback. Thanks!* 7 | 8 | This is a simple tutorial to expose new blockchain developers to Ethereum 9 | and ramp up existing blockchain developers with the [ethers.io](https://ethers.io) 10 | ecosystem. 11 | 12 | Simple Store is a simple on-chain contract which can store a single value. Anyone 13 | may send a transaction to the Ethereum network to update this value. 14 | 15 | 16 | Overview 17 | -------- 18 | 19 | This tutorial provides a full, ready-to-use dApp, with deployment scripts, a test suite and 20 | admin scripts. The initial contract we will be using has already been deployed to the 21 | network. 22 | 23 | We will then explore each component individually to update user interace, alter and deploy 24 | a custom version of the Ethereum contract, and add test cases. 25 | 26 | 27 | Before We Start 28 | --------------- 29 | 30 | You must [install git](https://git-scm.com/downloads) and [install node.js](https://nodejs.org/en/download/) to follow this tutorial. 31 | 32 | Once you have *node.js* installed, we can install the ethers toolchain. From your terminal, type: 33 | 34 | ``` 35 | /home/ricmoo> npm install -g ethers-cli 36 | ``` 37 | 38 | You may wish to [install Ganache](http://truffleframework.com/ganache/) for later steps, but that is not necessary for now. 39 | 40 | 41 | Getting Started 42 | --------------- 43 | 44 | In your terminal, from your home direcotry, we will download the tutorial 45 | from GitHub and prepare our project. 46 | 47 | ```bash 48 | # Download the Tutorial 49 | /home/ricmoo> git clone git@github.com:ethers-io/tutorial-simeplstore.git 50 | Cloning into 'tutorial-simeplstore'... 51 | remote: Counting objects: done 52 | remote: Compressing objects: done. 53 | Receiving objects: done. 54 | Resolving deltas: done. 55 | 56 | # Enter the Tutorial directory 57 | /home/ricmoo> cd tutorial-simplestore 58 | 59 | # Install the dependencies (ethers-cli, ethers) 60 | /home/ricmoo/tutorial-simplestore> npm install 61 | # ... This may be a few minutes, but you can ignore the output 62 | ``` 63 | 64 | Now that we have downloaded the tutorial and all of our dependencies, we 65 | can start up our application. 66 | 67 | ```bash 68 | # Start a local webserver to serve your dapp 69 | /home/ricmoo/tutorial-simplestore> npm start 70 | Serving content from file:///Users/ricmoo/Development/ethers/tutorials/simplestore 71 | Listening on port: 8080 72 | Local Application Test URL: 73 | mainnet: http://ethers.io/#!/app-link-insecure/localhost:8080/ 74 | ropsten: http://ropsten.ethers.io/#!/app-link-insecure/localhost:8080/ 75 | rinkeby: http://rinkeby.ethers.io/#!/app-link-insecure/localhost:8080/ 76 | kovan: http://kovan.ethers.io/#!/app-link-insecure/localhost:8080/ 77 | ``` 78 | 79 | The dapp is now running locally. Ethereum has multiple networks; a production network 80 | (mainnet) and several test networks (ropsten, rinkeby, kovan). We will try out the dapp on 81 | the Ropsten test network, so follow [this link](http://ropsten.ethers.io/#!/app-link-insecure/localhost:8080/) to load your dApp in ropsten. 82 | 83 | 84 | Exploring Your Application 85 | -------------------------- 86 | 87 | You may use any editor your are comfortable with. We are going to start 88 | exploring the various files in the tutorial, before we start making changes. 89 | 90 | 91 | ## index.html 92 | 93 | This is the front end to your application, made up of basic HTML, CSS and JavaScript. 94 | 95 | The Application is currently very simple. We will get more into what each line 96 | in the javascript does later, but basically we connect to an existing contract 97 | on the blockchain using its **address** and its **Application Binary Interface** (ABI) 98 | which is just a high-level description of what the functions and events the contract has. 99 | 100 | ## SimpleStore.sol 101 | 102 | This is the Ethereum contract. We are currently using a version already deployed to the 103 | Ethereum network, but will soon be making changes to it and deploying our own instances 104 | to the blockchain. 105 | 106 | ## deploy-contract.js 107 | 108 | This is a script we will use to help deploy our smart contract to the Ethereum network. 109 | It is very simple for now, but will make it easier for us as our contract becomes more 110 | complex. 111 | 112 | ## test-contract.js 113 | 114 | This is our test suite. It is important to always test functionality of your contracts 115 | since once they are deployed, they cannot be changed. Instead you have to re-deploy a 116 | whole new instance and migrate your data and tools to use the new instance. It is better 117 | to test thoroughly first. 118 | 119 | The test cases are currently very basic, and we will be adding to them soon. 120 | 121 | ## simple-store.json 122 | 123 | This file is automatically generated in the deploy-contract.js file. It contains 124 | the address and the interface of the SimpleStore contract in a format that can 125 | be eaily loaded by the ethers-app library. 126 | 127 | ## pacakge.json 128 | 129 | This is a file used by *node.js* and *npm* to manage your applications dependencies and 130 | handle some admin tasks. 131 | 132 | ----- 133 | 134 | Exercises 135 | ========= 136 | 137 | Now will will work through som simple exercises to familiarize ourselves with 138 | how a dApp works. 139 | 140 | Make the UI Prettier 141 | -------------------- 142 | 143 | Add CSS transitions. Support pressing enter. 144 | 145 | @TODO: Include code 146 | 147 | Modify the Contract 148 | ------------------- 149 | 150 | Make it charge ether to update the contract value. 151 | 152 | @TODO: Include code 153 | 154 | Add Test Cases 155 | -------------- 156 | 157 | Add a test case for both sending sufficient ether to change the state and 158 | insufficient ether which fails to update state. 159 | 160 | @TODO: Include code and command lines to run tests 161 | 162 | Debugging the Test Case 163 | ----------------------- 164 | 165 | @TODO: Intentionally introduce a bug to demonstrate the real-time console.log 166 | 167 | Deploy Updated Contract 168 | ----------------------- 169 | 170 | @TODO: Include command lines to deploy the updated contract 171 | 172 | ----- 173 | 174 | Sharing your Application 175 | ------------------------ 176 | 177 | Now that we are happy with our application on our local computer, we 178 | can publish it to the internet for everyone to use. 179 | 180 | Ethers.space is a free service provided by ethers to host small dApps 181 | on unique secure HTTPS URLs so that browser security content-policies 182 | can keep you and your customers safe. 183 | 184 | Ethers.space accounts are managed with standard Ethereum keys and do 185 | not require a username or password. 186 | 187 | Create a new private key to manage your ethers.space account. 188 | 189 | ``` 190 | /home/ricmoo/simplestore> ethers-deploy init 191 | Do NOT lose or forget this password. It cannot be reset. 192 | New Account Password: ****** 193 | Confirm Password: ****** 194 | Encrypting Account... (this may take a few seconds) 195 | Account successfully created. Keep this file SAFE. Do NOT check it into source control 196 | ``` 197 | 198 | Make sure your latest changes are checked into the Git repo: 199 | 200 | ``` 201 | /home/ricmoo> git add index.html simple-store.json SimpleStore.sol 202 | /home/ricmoo> git commit -m 'Updated my dApp.' 203 | ``` 204 | 205 | ``` 206 | # Deploy your application to the internet 207 | /home/ricmoo/simplestore> ethers-deploy publish 208 | Sign Message: 209 | Message: ... 210 | Sign Message? [y/n]: y 211 | 212 | Successfully deployed! 213 | 214 | Application URLs: 215 | Mainnet: https://ethers.io/#!/app-link/0xf01ee6669078e5ec9a452fd60b7d18d41b53163e.ethers.space/ 216 | Ropsten: https://ropsten.ethers.io/#!/app-link/0xf01ee6669078e5ec9a452fd60b7d18d41b53163e.ethers.space/ 217 | Rinkebey: https://rinkeby.ethers.io/#!/app-link/0xf01ee6669078e5ec9a452fd60b7d18d41b53163e.ethers.space/ 218 | Kovan: https://kovan.ethers.io/#!/app-link/0xf01ee6669078e5ec9a452fd60b7d18d41b53163e.ethers.space/ 219 | ``` 220 | 221 | You may now share the Ropsten URL with your friends. 222 | 223 | Advanced Exercises 224 | ================== 225 | 226 | Working with Ganache (testrpc) 227 | ------------------------------ 228 | 229 | There may be times where you wish to test against a local node... Explain more here. 230 | 231 | Run your Ganache development node on port 7545 (the default). 232 | 233 | ``` 234 | # Deploy the SimpleStore contract to your Ganache node 235 | /home/ricmoo/simplestore> npm start -- --rpc http://localhost:7545 \ 236 | --local-accounts run deploy-contract.js 237 | ``` 238 | 239 | Copy the `contract address` from the output of the above command and edit 240 | `index.html`, placing your address in the `contractAddress` variable near the 241 | top of the script tag. 242 | 243 | ``` 244 | # Run your local application against your Ganache node 245 | /home/ricmoo/simplestore> npm start -- --rpc http://localhost:7545 serve 246 | ``` 247 | 248 | To import your first account from Ganache into ethers.io, copy your mnemonic 249 | from the top of the Ganache UI. In the Ethers container, select add account, 250 | then select import. Paste your mnemonic in the text field and follow the 251 | instructions. 252 | 253 | ``` 254 | # Deploy your appliaction to the Ropsten testnet 255 | /home/ricmoo/simplestore> ethers-deploy --network ropsten run deploy-contract 256 | ``` 257 | 258 | Update your `index.html` to point to the new contract on the ropsten network 259 | 260 | ``` 261 | # Run your local application against the Ropsten network (select the ropsten link below) 262 | /home/ricmoo/simplestore> npm start 263 | ``` 264 | 265 | Once you are happy, you can publish to ethers.space. 266 | 267 | ``` 268 | # Run your local application against the Ropsten network (select the ropsten link below) 269 | /home/ricmoo/simplestore> git init 270 | /home/ricmoo/simplestore> git add index.html 271 | /home/ricmoo/simplestore> git commit -m 'My first dapp.' 272 | /home/ricmoo/simplestore> ethers-deploy publish 273 | ``` 274 | 275 | 276 | Deploying to Rinkeby and Kovan 277 | ------------------------------ 278 | 279 | @TODO 280 | 281 | ``` 282 | /home/ricmoo/simplestore> npm run deploy -- --network rinkeby 283 | /home/ricmoo/simplestore> npm run deploy -- --network kovan 284 | ``` 285 | -------------------------------------------------------------------------------- /SimpleStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.20; 2 | 3 | contract SimpleStore { 4 | 5 | event ValueChanged(address indexed author, string oldValue, string newValue); 6 | 7 | string _value = "First!!1"; 8 | 9 | function setValue(string value) public { 10 | ValueChanged(msg.sender, _value, value); 11 | _value = value; 12 | } 13 | 14 | function value() public constant returns (string) { 15 | return _value; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /deploy-contract.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(builder) { 4 | 5 | // Compile the SimpleStore.sol Solidity contract with optimizations enabled 6 | var codes = builder.compile('./SimpleStore.sol', true); 7 | 8 | // A Solidity file can contain multiple contracts, so we need to select 9 | // the specific contract code object 10 | var code = codes.SimpleStore; 11 | 12 | // Deploy the contract 13 | return code.deploy().then(function(contract) { 14 | 15 | // Save the contract details to disk 16 | builder.saveContract('simple-store.json', contract); 17 | 18 | // By returning the contract we simplify our test cases, which will 19 | // use this deploy-contract JavaScript file to deploy the contract 20 | // to a short-lived fake Ethereum network, which is destoryed and 21 | // recreated between each test case. 22 | return contract; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 24 | 25 |
26 |

27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethers-tutorial-simeplstore", 3 | "version": "0.0.1", 4 | "description": "A simple demonstration of using ethers to build a Simple Store dApp.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "ethers": "^3.0.6", 8 | "ethers-cli": "^3.0.7" 9 | }, 10 | "devDependencies": { 11 | "mocha": "^5.0.4" 12 | }, 13 | "scripts": { 14 | "deploy": "ethers-deploy run deploy-contract.js", 15 | "start": "ethers-deploy serve", 16 | "test": "mocha test-contract.js" 17 | }, 18 | "keywords": [ 19 | "ethers", 20 | "ethereum", 21 | "dapp", 22 | "tutorial", 23 | "simple", 24 | "store" 25 | ], 26 | "author": "Richard Moore ", 27 | "license": "MIT" 28 | } 29 | -------------------------------------------------------------------------------- /simple-store.json: -------------------------------------------------------------------------------- 1 | {"ropsten":{"address":"0x531B68a07354CeFCb7E056047b217F93342EF5d6","abi":"[{\"constant\":true,\"inputs\":[],\"name\":\"value\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"value\",\"type\":\"string\"}],\"name\":\"setValue\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"author\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"oldValue\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"newValue\",\"type\":\"string\"}],\"name\":\"ValueChanged\",\"type\":\"event\"}]","interface":"[{\"constant\":true,\"inputs\":[],\"name\":\"value\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"value\",\"type\":\"string\"}],\"name\":\"setValue\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"author\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"oldValue\",\"type\":\"string\"},{\"indexed\":false,\"name\":\"newValue\",\"type\":\"string\"}],\"name\":\"ValueChanged\",\"type\":\"event\"}]"}} -------------------------------------------------------------------------------- /test-contract.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var tools = require('ethers-cli'); 6 | 7 | 8 | // We use the exact same deployment script for our test cases as we use to 9 | // actually deploy the contract. 10 | var deployContract = require('./deploy-contract'); 11 | 12 | // If "beforeEach", "describe" and "it" are unfamiliar to you, please check 13 | // out the Mocha documentation: https://mochajs.org 14 | 15 | 16 | var builder = null; 17 | beforeEach(function() { 18 | 19 | // Compiling the Solidity source and setting up an ephemeral testnet 20 | // can take more than the 2 seconds Mocha uses by default 21 | this.timeout(120000); 22 | 23 | // Create a new TestBuilder, which creates a brand new blockchain to work with 24 | builder = new tools.TestBuilder(deployContract); 25 | 26 | // Execute the deploy function passed into the TestBuilder 27 | return builder.deploy(); 28 | }); 29 | 30 | 31 | describe('Basic Testing', function() { 32 | 33 | it('Sets and Gets a value', function () { 34 | 35 | // These testcases can take a while 36 | this.timeout(120000); 37 | 38 | // Our deployContract function returned a reference to the deployed 39 | // contract, which has the Signer attached to it that was used to 40 | // deploy it; i.e. builder.accounts[0] 41 | var contract = builder.deployed; 42 | 43 | var contractAccount1 = contract.connect(builder.accounts[1]); 44 | var contractReadOnly = contract.connect(builder.provider); 45 | 46 | var initialValue = "First!!1"; 47 | var newValue = "Hello World!" 48 | 49 | var seq = Promise.resolve(); 50 | 51 | // Set up an event listener to test the event 52 | var testEvent = new Promise(function(resolve, reject) { 53 | contractReadOnly.onvaluechanged = function(author, oldValue, value) { 54 | // Remove the listener, otherwise the test case cannot exit (think unref) 55 | this.removeListener(); 56 | 57 | assert.equal(author, builder.accounts[1].address, 'correct author attributed in event'); 58 | assert.equal(oldValue, initialValue, 'old value is the initial string in event'); 59 | assert.equal(value, newValue, 'new value matches updated value in event'); 60 | 61 | // This testcase can resolve ( 62 | resolve(); 63 | }; 64 | }); 65 | 66 | // Get the initial value 67 | seq = seq.then(function() { 68 | return contractReadOnly.value().then(function(value) { 69 | assert.equal(value, initialValue, 'initial value is the empty string'); 70 | }); 71 | }); 72 | 73 | // Set the new value 74 | seq = seq.then(function() { 75 | return contractAccount1.setValue(newValue).then(function(tx) { 76 | return builder.provider.waitForTransaction(tx.hash); 77 | }); 78 | }); 79 | 80 | // Verify the new value 81 | seq = seq.then(function() { 82 | return contractReadOnly.value().then(function(value) { 83 | assert.equal(value, newValue, 'new value matches updated value'); 84 | }); 85 | }); 86 | 87 | // Wait for the event test to resolve 88 | seq = seq.then(function() { 89 | return testEvent; 90 | }); 91 | 92 | return seq; 93 | }); 94 | }); 95 | --------------------------------------------------------------------------------