├── migrations └── .gitkeep ├── .gitattributes ├── client ├── .env.example ├── public │ ├── pic_bg.png │ ├── favicon-32x32.png │ ├── manifest.json │ └── index.html ├── src │ ├── utils │ │ ├── sleep.js │ │ └── getTransactionReceipt.js │ ├── components │ │ ├── Hero │ │ │ ├── pic_bg.png │ │ │ ├── Hero.module.scss │ │ │ └── index.js │ │ ├── Footer │ │ │ ├── logo-red.png │ │ │ ├── mail.svg │ │ │ ├── index.js │ │ │ ├── pencil.svg │ │ │ ├── footer.module.scss │ │ │ ├── twitter.svg │ │ │ ├── github.svg │ │ │ └── zeppelin_logo.svg │ │ ├── Header │ │ │ ├── logo-red.png │ │ │ ├── stater-kits-logo.png │ │ │ ├── index.js │ │ │ └── header.module.scss │ │ ├── Web3Info │ │ │ ├── Web3Info.module.scss │ │ │ └── index.js │ │ ├── Instructions │ │ │ ├── Instructions.module.scss │ │ │ └── index.js │ │ └── Counter │ │ │ ├── Counter.module.scss │ │ │ └── index.js │ ├── App.test.js │ ├── index.js │ ├── layout │ │ ├── variables.scss │ │ └── index.scss │ ├── App.module.scss │ ├── images │ │ └── logo.svg │ ├── App.js │ └── serviceWorker.js ├── .prettierrc.js ├── netlify.toml ├── .eslintrc.json ├── .gitignore ├── config │ └── webpack.js ├── config-overrides.js ├── package.json └── README.md ├── .env.example ├── solhint.json ├── kit.json ├── test └── counter.js ├── truffle-config.js ├── LICENSE ├── .gitignore ├── package.json ├── contracts └── Counter.sol └── README.md /migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_INFURA_TOKEN=YOURTOKEN 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Create a .env file 2 | MNEMONIC="front .... wolf" 3 | INFURA_ID="d67...d" -------------------------------------------------------------------------------- /client/public/pic_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/starter-kit-gsn/HEAD/client/public/pic_bg.png -------------------------------------------------------------------------------- /client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/starter-kit-gsn/HEAD/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /client/src/utils/sleep.js: -------------------------------------------------------------------------------- 1 | export default function sleep(ms) { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /client/src/components/Hero/pic_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/starter-kit-gsn/HEAD/client/src/components/Hero/pic_bg.png -------------------------------------------------------------------------------- /client/src/components/Footer/logo-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/starter-kit-gsn/HEAD/client/src/components/Footer/logo-red.png -------------------------------------------------------------------------------- /client/src/components/Header/logo-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/starter-kit-gsn/HEAD/client/src/components/Header/logo-red.png -------------------------------------------------------------------------------- /client/src/components/Header/stater-kits-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenZeppelin/starter-kit-gsn/HEAD/client/src/components/Header/stater-kits-logo.png -------------------------------------------------------------------------------- /client/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 2, 7 | }; 8 | -------------------------------------------------------------------------------- /client/netlify.toml: -------------------------------------------------------------------------------- 1 | # The following redirect is intended for use with most SPAs that handle 2 | # routing internally. 3 | [[redirects]] 4 | from = "/*" 5 | to = "/index.html" 6 | status = 200 7 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app"], 3 | "plugins": ["prettier", "react-hooks"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "react-hooks/rules-of-hooks": "error", 7 | "react-hooks/exhaustive-deps": "warn" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "default", 3 | "rules": { 4 | "indent": ["error", 4], 5 | 6 | "bracket-align": false, 7 | "compiler-fixed": false, 8 | "no-simple-event-func-name": false, 9 | "two-lines-top-level-separator": false 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 | -------------------------------------------------------------------------------- /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.* 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /client/src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './header.module.scss'; 3 | import logo from './stater-kits-logo.png'; 4 | 5 | const Header = () => ( 6 |
7 | 14 |
15 | ); 16 | 17 | export default Header; 18 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './layout/index.scss'; 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 | -------------------------------------------------------------------------------- /client/src/layout/variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $primary: #3c4653; 3 | $blue: #39e6e0; 4 | $purple: #4420d8; 5 | $purple-aux: #917dff; 6 | $yellow: #ffbe44; 7 | $pink: #ff2972; 8 | $white: #fff; 9 | $black: #000; 10 | 11 | // Fonts (use typography.js) 12 | $font-size-base: 16px; 13 | $font-size-h1: 3.375em; 14 | // $font-family-sans-serif: 'Open Sans', sans-serif; 15 | $line-height-base: 1.42; 16 | 17 | // Responsive 18 | $screen-xs: 576px; 19 | $screen-sm-min: 768px; 20 | $screen-sm-max: 992px; 21 | $screen-md-max: 1280px; 22 | -------------------------------------------------------------------------------- /client/config/webpack.js: -------------------------------------------------------------------------------- 1 | const solidityLoaderOptions = { 2 | network: 'development', 3 | // you can stop loader from automatic compile/push/updgrade 4 | // action by setting disabled flag to true, but it will still 5 | // serve .json files from file system 6 | disabled: true, 7 | }; 8 | 9 | module.exports = { 10 | solidityLoader: { 11 | test: /\.sol$/, 12 | use: [ 13 | { loader: 'json-loader' }, 14 | { 15 | loader: '@openzeppelin/solidity-loader', 16 | options: solidityLoaderOptions, 17 | }, 18 | ], 19 | }, 20 | solidityLoaderOptions, 21 | }; 22 | -------------------------------------------------------------------------------- /kit.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "0.1.0", 3 | "message": "\nQuick Start\nRun your local blockchain:\n> ganache-cli --deterministic\nInitialize the OpenZeppelin SDK project:\n> npx openzeppelin init app\nGo to the client directory:\n> cd client\nRun the React app:\n> npm run start\nContinue in your browser!\nMore at https://docs.openzeppelin.com/starter-kits/gsnkit", 4 | "files": [ 5 | ".gitignore", 6 | "LICENSE", 7 | "client", 8 | "contracts", 9 | "migrations", 10 | "package.json", 11 | "solhint.json", 12 | "test", 13 | "truffle-config.js" 14 | ], 15 | "hooks": { 16 | "post-unpack": "npm install && cd client && npm install" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/config-overrides.js: -------------------------------------------------------------------------------- 1 | /* config-overrides.js */ 2 | const { solidityLoader } = require('./config/webpack'); 3 | 4 | module.exports = function override(config, env) { 5 | //do stuff with the webpack config... 6 | 7 | // allow importing from outside of app/src folder, ModuleScopePlugin prevents this. 8 | const scope = config.resolve.plugins.findIndex(o => o.constructor.name === 'ModuleScopePlugin'); 9 | if (scope > -1) { 10 | config.resolve.plugins.splice(scope, 1); 11 | } 12 | 13 | // add solidity loader support 14 | // have to insert before last loader, because CRA user 'file-loader' as default one 15 | config.module.rules.splice(config.module.rules - 2, 0, solidityLoader); 16 | 17 | return config; 18 | }; 19 | -------------------------------------------------------------------------------- /client/src/utils/getTransactionReceipt.js: -------------------------------------------------------------------------------- 1 | import sleep from './sleep'; 2 | 3 | export default async function getTransactionReceipt(web3, hash, pollInterval = 500) { 4 | let receipt = null; 5 | while (receipt === null) { 6 | // we are going to check every second if transation is mined or not, once it is mined we'll leave the loop 7 | receipt = await getTransactionReceiptPromise(web3, hash); 8 | await sleep(pollInterval); 9 | } 10 | return receipt; 11 | } 12 | 13 | function getTransactionReceiptPromise(web3, hash) { 14 | // here we just promisify getTransactionReceipt function for convenience 15 | return new Promise((resolve, reject) => { 16 | web3.eth.getTransactionReceipt(hash, function(err, data) { 17 | if (err !== null) reject(err); 18 | else resolve(data); 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /client/src/components/Footer/mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic_mail 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/src/components/Web3Info/Web3Info.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../layout/variables.scss'; 2 | 3 | .web3 { 4 | padding: 40px 0; 5 | display: flex; 6 | flex-flow: column wrap; 7 | justify-content: flex-start; 8 | align-items: flex-start; 9 | width: 380px; 10 | height: auto; 11 | padding: 40px 20px; 12 | border: 1px solid #ccc; 13 | box-shadow: 0px 2px 2px #ccc; 14 | 15 | h3 { 16 | border-bottom: 1px dotted #ffbe44; 17 | padding-bottom: 5px; 18 | } 19 | 20 | .dataPoint { 21 | display: flex; 22 | flex-flow: column nowrap; 23 | align-items: flex-start; 24 | margin: 5px 0; 25 | width: 100%; 26 | 27 | .value { 28 | font-weight: bold; 29 | display: flex; 30 | flex-flow: row nowrap; 31 | justify-content: space-between; 32 | align-items: baseline; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/counter.js: -------------------------------------------------------------------------------- 1 | const { BN, constants, expectEvent, shouldFail } = require('openzeppelin-test-helpers'); 2 | const should = require('chai').should(); 3 | 4 | const Counter = artifacts.require('Counter'); 5 | 6 | contract("counter", async ([_, owner, ...otherAccounts]) => { 7 | let counter; 8 | const value = new BN(9999); 9 | const add = new BN(1); 10 | 11 | beforeEach(async function () { 12 | counter = await Counter.new(); 13 | counter.initialize(value, { from: owner }); 14 | }); 15 | 16 | it("should have proper owner", async () => { 17 | (await counter.owner()).should.equal(owner); 18 | }); 19 | 20 | it("should have proper default value", async () => { 21 | (await counter.getCounter()).should.bignumber.equal(value); 22 | }); 23 | 24 | it("should increase counter value", async () => { 25 | await counter.increaseCounter(add); 26 | (await counter.getCounter()).should.bignumber.equal(value.add(add)); 27 | }); 28 | 29 | }); -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const mnemonic = process.env.MNEMONIC; 3 | const HDWalletProvider = require("truffle-hdwallet-provider"); 4 | // Create your own key for Production environments (https://infura.io/) 5 | const INFURA_ID = process.env.INFURA_ID || 'd6760e62b67f4937ba1ea2691046f06d'; 6 | 7 | 8 | const configNetwok = (network, networkId, path = "m/44'/60'/0'/0/", gas = 4465030, gasPrice = 1e10) => ({ 9 | provider: () => new HDWalletProvider( 10 | mnemonic, `https://${network}.infura.io/v3/${INFURA_ID}`, 11 | 0, 1, true, path 12 | ), 13 | networkId, 14 | gas, 15 | gasPrice, 16 | }); 17 | 18 | module.exports = { 19 | // See 20 | // to customize your Truffle configuration! 21 | networks: { 22 | development: { 23 | host: "127.0.0.1", 24 | port: 8545, 25 | network_id: "*", 26 | }, 27 | ropsten: configNetwok('ropsten', 3), 28 | kovan: configNetwok('kovan', 42), 29 | rinkeby: configNetwok('rinkeby', 4), 30 | main: configNetwok('mainnet', 1), 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zeppelin Solutions 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 | -------------------------------------------------------------------------------- /client/src/components/Instructions/Instructions.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../layout/variables.scss'; 2 | 3 | .instructions { 4 | padding: 60px 20px; 5 | width: 100%; 6 | max-width: 800px; 7 | display: flex; 8 | flex-flow: column nowrap; 9 | align-items: center; 10 | text-align: left; 11 | 12 | .question { 13 | font-weight: 400; 14 | font-size: 24px; 15 | margin: 30px 0 10px; 16 | color: rgba(0, 0, 0, 0.9); 17 | } 18 | 19 | h2 { 20 | color: rgba(0, 0, 0, 0.87); 21 | } 22 | 23 | .separator { 24 | margin: 5px 0 20px 0; 25 | width: 60%; 26 | height: 2px; 27 | background: #d8d8d8; 28 | } 29 | 30 | .step { 31 | width: 100%; 32 | margin: 10px 0; 33 | 34 | .instruction { 35 | width: 100%; 36 | font-size: 1em; 37 | opacity: 0.8; 38 | } 39 | 40 | span.inline { 41 | color: $pink; 42 | background: #efefef; 43 | padding: 2px; 44 | display: inline-block; 45 | } 46 | 47 | .code { 48 | width: 100%; 49 | display: flex; 50 | flex-flow: row nowrap; 51 | align-items: center; 52 | margin: 10px 0; 53 | 54 | code { 55 | background: rgba(231, 231, 238, 0.5); 56 | border-radius: 30px; 57 | } 58 | } 59 | button { 60 | margin: 5px 0; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /client/src/components/Counter/Counter.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../layout/variables.scss'; 2 | 3 | .loader { 4 | } 5 | 6 | .code { 7 | width: 100%; 8 | display: flex; 9 | flex-flow: row nowrap; 10 | align-items: center; 11 | margin: 10px 0; 12 | 13 | code { 14 | background: rgba(231, 231, 238, 0.5); 15 | border-radius: 0px; 16 | } 17 | } 18 | 19 | .counter { 20 | padding: 40px 0; 21 | display: flex; 22 | flex-flow: column wrap; 23 | justify-content: flex-start; 24 | align-items: flex-start; 25 | width: 380px; 26 | height: auto; 27 | padding: 40px 20px; 28 | border: 1px solid #ccc; 29 | box-shadow: 0px 2px 2px #ccc; 30 | 31 | h3 { 32 | border-bottom: 1px dotted #ffbe44; 33 | padding-bottom: 5px; 34 | } 35 | 36 | .dataPoint { 37 | display: flex; 38 | flex-flow: column nowrap; 39 | align-items: flex-start; 40 | margin: 5px 0; 41 | width: 100%; 42 | 43 | .value { 44 | font-weight: bold; 45 | display: flex; 46 | flex-flow: row nowrap; 47 | justify-content: space-between; 48 | align-items: baseline; 49 | } 50 | } 51 | 52 | .buttons { 53 | display: flex; 54 | width: 100%; 55 | flex-flow: column nowrap; 56 | align-items: center; 57 | button { 58 | margin: 5px 0; 59 | width: 100%; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.* 60 | 61 | # next.js build output 62 | .next 63 | 64 | # zos sessions 65 | zos.dev-* 66 | 67 | # contracts build 68 | build 69 | 70 | .zos.session 71 | 72 | 73 | .openzeppelin/.session 74 | 75 | .vscode -------------------------------------------------------------------------------- /client/src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './footer.module.scss'; 3 | // import logo from './logo-red.png' 4 | import mail from './mail.svg'; 5 | import twitter from './twitter.svg'; 6 | import github from './github.svg'; 7 | import zeppelin from './zeppelin_logo.svg'; 8 | 9 | const Footer = () => ( 10 | 31 | ); 32 | 33 | export default Footer; 34 | -------------------------------------------------------------------------------- /client/src/components/Footer/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic_medium 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@openzeppelin/gsn-provider": "^0.1.6", 7 | "@openzeppelin/network": "^0.2.10", 8 | "@openzeppelin/solidity-loader": "^1.4.6", 9 | "classnames": "^2.2.6", 10 | "json-loader": "^0.5.7", 11 | "node-sass": "^4.13.0", 12 | "prop-types": "^15.7.2", 13 | "react": "^16.8.6", 14 | "react-app-rewired": "^2.1.0", 15 | "react-dom": "^16.8.6", 16 | "react-scripts": "^3.1.1", 17 | "rimble-ui": "^0.9.7", 18 | "styled-components": "^4.4.1", 19 | "web3": "^1.2.1" 20 | }, 21 | "scripts": { 22 | "start": "react-app-rewired start", 23 | "build": "react-app-rewired build", 24 | "test": "react-app-rewired test", 25 | "eject": "react-scripts eject", 26 | "lint": "prettier 'src/**/*.js' --write && eslint 'src/**/*.js' --fix", 27 | "pre-push": "", 28 | "pre-commit": "lint-staged" 29 | }, 30 | "lint-staged": { 31 | "*.js": [ 32 | "prettier --write", 33 | "eslint --fix", 34 | "git add" 35 | ], 36 | "*.scss": [ 37 | "prettier --write", 38 | "git add" 39 | ] 40 | }, 41 | "browserslist": [ 42 | ">0.2%", 43 | "not dead", 44 | "not ie <= 11", 45 | "not op_mini all" 46 | ], 47 | "devDependencies": { 48 | "eslint": "^6.2.2", 49 | "eslint-config-prettier": "^6.9.0", 50 | "eslint-plugin-import": "^2.18.2", 51 | "eslint-plugin-prettier": "^3.1.2", 52 | "eslint-plugin-react-hooks": "^2.3.0", 53 | "lint-staged": "^9.2.5", 54 | "prettier": "^1.18.2", 55 | "pretty-quick": "^1.11.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/App.module.scss: -------------------------------------------------------------------------------- 1 | @import './layout/variables.scss'; 2 | 3 | .App { 4 | text-align: center; 5 | display: flex; 6 | flex-flow: column nowrap; 7 | justify-content: flex-start; 8 | align-items: center; 9 | } 10 | 11 | code { 12 | background: #fefefe; 13 | box-shadow: 0 0px 2px 0px #ccc; 14 | padding: 8px 16px; 15 | font-size: 14px; 16 | width: 100%; 17 | font-family: source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace; 18 | } 19 | 20 | .wrapper { 21 | min-height: 80vh; 22 | width: 100%; 23 | display: flex; 24 | flex-flow: column nowrap; 25 | align-items: center; 26 | } 27 | 28 | .setup, 29 | .contracts { 30 | width: 100%; 31 | padding: 40px 0; 32 | display: flex; 33 | flex-flow: column nowrap; 34 | align-items: center; 35 | 36 | .notice { 37 | margin: 15px 0; 38 | 39 | p { 40 | padding: 10px; 41 | background: #ededed; 42 | box-shadow: 0 1px 1px 2px #ccc; 43 | border: 1px solid #ddd; 44 | color: #444; 45 | } 46 | } 47 | 48 | .widgets { 49 | display: flex; 50 | max-width: 800px; 51 | width: 100%; 52 | flex-flow: row wrap; 53 | justify-content: space-between; 54 | align-items: flex-start; 55 | margin: 20px 0; 56 | } 57 | } 58 | 59 | .loader { 60 | width: 100%; 61 | display: flex; 62 | flex-flow: column nowrap; 63 | align-items: center; 64 | justify-content: center; 65 | min-height: 80vh; 66 | padding: 40px 0; 67 | h3 { 68 | margin-top: 30px; 69 | } 70 | } 71 | 72 | @keyframes App-logo-spin { 73 | from { 74 | transform: rotate(0deg); 75 | } 76 | to { 77 | transform: rotate(360deg); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-kit-gsn", 3 | "version": "1.0.2", 4 | "description": "An OpenZeppelin starter kit focused on GSN.", 5 | "main": "index.js", 6 | "scripts": { 7 | "build-contracts": "openzeppelin compile", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "pre-push": "cd client && npm run pre-push", 10 | "pre-commit": "cd client && npm run pre-commit" 11 | }, 12 | "husky": { 13 | "hooks": { 14 | "pre-push": "npm run pre-push", 15 | "pre-commit": "npm run pre-commit" 16 | } 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/OpenZeppelin/starter-kit-gsn" 21 | }, 22 | "keywords": [ 23 | "openzeppelin", 24 | "truffle", 25 | "react", 26 | "dapp", 27 | "infura", 28 | "metamask", 29 | "web3", 30 | "ganache", 31 | "web3js", 32 | "ethereum", 33 | "smart-contracts" 34 | ], 35 | "author": "Igor Yalovoy ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/OpenZeppelin/starter-kit-gsn/issues" 39 | }, 40 | "homepage": "https://github.com/OpenZeppelin/starter-kit-gsn#readme", 41 | "dependencies": { 42 | "@openzeppelin/contracts-ethereum-package": "^2.4.0", 43 | "@openzeppelin/upgrades": "^2.6.0", 44 | "dotenv": "^6.2.0", 45 | "truffle-hdwallet-provider": "^1.0.3" 46 | }, 47 | "devDependencies": { 48 | "@openzeppelin/cli": "^2.6.0", 49 | "@openzeppelin/cli": "^2.5.3", 50 | "@openzeppelin/gsn-helpers": "^0.2.3", 51 | "chai": "^4.2.0", 52 | "husky": "^3.1.0", 53 | "openzeppelin-test-helpers": "^0.4.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/components/Footer/footer.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../layout/variables.scss'; 2 | .footer { 3 | width: 100%; 4 | display: flex; 5 | flex-flow: row wrap; 6 | justify-content: space-between; 7 | align-items: flex-start; 8 | background: $white; 9 | border-top: 1px solid #d8d8d8; 10 | padding: 25px 100px; 11 | 12 | .brand { 13 | display: flex; 14 | flex-flow: column nowrap; 15 | color: $primary; 16 | .created { 17 | width: 100%; 18 | display: flex; 19 | flex-flow: row nowrap; 20 | justify-content: flex-start; 21 | align-items: center; 22 | width: 400px; 23 | color: $primary; 24 | 25 | img { 26 | width: 33px; 27 | margin-right: 14px; 28 | } 29 | 30 | a { 31 | color: $white; 32 | margin-top: 4px; 33 | } 34 | 35 | span { 36 | color: $white; 37 | margin-left: 60px; 38 | line-height: 30px; 39 | 40 | a { 41 | color: $purple-aux; 42 | } 43 | } 44 | } 45 | 46 | .copyright { 47 | font-size: 14px; 48 | margin-top: 10px; 49 | text-align: left; 50 | color: rgba(0, 0, 0, 0.6); 51 | } 52 | } 53 | 54 | .links { 55 | display: flex; 56 | flex-flow: row wrap; 57 | align-items: center; 58 | justify-content: flex-start; 59 | a { 60 | margin: 0 10px; 61 | } 62 | img { 63 | width: 21px; 64 | } 65 | } 66 | } 67 | 68 | @media only screen and (max-width: $screen-sm-max) { 69 | .footer { 70 | padding: 80px 40px; 71 | 72 | .links { 73 | margin-top: 20px; 74 | width: 100%; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /client/src/components/Footer/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic_twitter 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 23 | Starter Kit Tutorial 24 | 25 | 26 | 27 |
28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /contracts/Counter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol"; 4 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 5 | 6 | contract Counter is Initializable, GSNRecipient { 7 | //it keeps a count to demonstrate stage changes 8 | uint private count; 9 | address private _owner; 10 | 11 | function initialize(uint num) public initializer { 12 | GSNRecipient.initialize(); 13 | _owner = _msgSender(); 14 | count = num; 15 | } 16 | 17 | // accept all requests 18 | function acceptRelayedCall( 19 | address, 20 | address, 21 | bytes calldata, 22 | uint256, 23 | uint256, 24 | uint256, 25 | uint256, 26 | bytes calldata, 27 | uint256 28 | ) external view returns (uint256, bytes memory) { 29 | return _approveRelayedCall(); 30 | } 31 | 32 | function _preRelayedCall(bytes memory context) internal returns (bytes32) { 33 | } 34 | 35 | function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal { 36 | } 37 | 38 | function owner() public view returns (address) { 39 | return _owner; 40 | } 41 | 42 | // getter 43 | function getCounter() public view returns (uint) { 44 | return count; 45 | } 46 | 47 | //and it can add to a count 48 | function increaseCounter(uint256 amount) public { 49 | count = count + amount; 50 | } 51 | 52 | //We'll upgrade the contract with this function after deploying it 53 | //Function to decrease the counter 54 | function decreaseCounter(uint256 amount) public returns (bool) { 55 | require(count > amount, "Cannot be lower than 0"); 56 | count = count - amount; 57 | return true; 58 | } 59 | 60 | function setRelayHubAddress() public { 61 | if(getHubAddr() == address(0)) { 62 | _upgradeRelayHub(0xD216153c06E857cD7f72665E0aF1d7D82172F494); 63 | } 64 | } 65 | 66 | function getRecipientBalance() public view returns (uint) { 67 | return IRelayHub(getHubAddr()).balanceOf(address(this)); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /client/src/components/Header/header.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../layout/variables.scss'; 2 | 3 | .header { 4 | padding: 10px 0; 5 | width: 100%; 6 | background: #3c4653; 7 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 8 | height: auto; 9 | display: flex; 10 | justify-content: center; 11 | 12 | nav { 13 | width: 100%; 14 | max-width: 1180px; 15 | padding: 0 30px; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | 20 | .brand { 21 | display: flex; 22 | flex-flow: row nowrap; 23 | justify-content: flex-start; 24 | align-items: center; 25 | width: 300px; 26 | 27 | img { 28 | height: 1.5em; 29 | vertical-align: middle; 30 | } 31 | } 32 | 33 | ul { 34 | margin: 0; 35 | padding: 0; 36 | display: flex; 37 | flex-flow: row nowrap; 38 | height: 60px; 39 | list-style: none; 40 | 41 | li { 42 | padding: 15px; 43 | margin: 0; 44 | margin-right: 60px; 45 | text-decoration: none; 46 | a { 47 | color: $white; 48 | text-transform: uppercase; 49 | font-size: 14px; 50 | letter-spacing: 2px; 51 | text-decoration: none; 52 | font-family: 'Roboto Mono', monospace; 53 | letter-spacing: 0.8px; 54 | opacity: 0.8; 55 | 56 | &:hover { 57 | opacity: 1; 58 | text-decoration: none; 59 | border-bottom: 2px solid $white; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | @media only screen and (max-width: $screen-sm-max) { 68 | .header nav ul .brand { 69 | width: 100%; 70 | margin-right: 0px; 71 | justify-content: flex-start; 72 | } 73 | } 74 | @media only screen and (max-width: $screen-sm-min) { 75 | .header { 76 | nav { 77 | flex-flow: row wrap; 78 | margin-top: 10px; 79 | } 80 | 81 | nav ul { 82 | flex-flow: row wrap; 83 | height: auto; 84 | li { 85 | width: 100%; 86 | text-align: left; 87 | padding: 3px 0; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /client/src/components/Footer/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic_github 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/src/components/Hero/Hero.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../layout/variables.scss'; 2 | 3 | .Hero { 4 | padding: 60px 20px; 5 | width: 100%; 6 | color: white; 7 | display: flex; 8 | flex-flow: row wrap; 9 | align-items: flex-start; 10 | justify-content: space-around; 11 | background: linear-gradient(to bottom, #3c4653, #212830); 12 | 13 | .hwrapper { 14 | display: flex; 15 | flex-flow: row wrap; 16 | max-width: 1180px; 17 | justify-content: space-between; 18 | align-items: center; 19 | width: 100%; 20 | } 21 | 22 | .left, 23 | .right { 24 | display: flex; 25 | flex-flow: column nowrap; 26 | width: calc(50% - 80px); 27 | min-width: 400px; 28 | justify-content: flex-end; 29 | align-items: flex-start; 30 | text-align: left; 31 | margin-top: 20px; 32 | margin-bottom: 20px; 33 | padding: 0 20px; 34 | 35 | h1 { 36 | font-size: 46px; 37 | line-height: 50px; 38 | margin-top: 0px; 39 | font-weight: 300; 40 | } 41 | 42 | h2 { 43 | font-size: 1em; 44 | line-height: 1.6; 45 | font-weight: 500; 46 | max-width: 400px; 47 | color: #39e6e0; 48 | } 49 | 50 | img { 51 | width: 92%; 52 | } 53 | } 54 | 55 | .sellingpoints { 56 | display: flex; 57 | width: 100%; 58 | justify-content: flex-end; 59 | flex-flow: column nowrap; 60 | 61 | .feature { 62 | margin: 6px 0; 63 | font-size: 1em; 64 | font-weight: 400; 65 | opacity: 0.9; 66 | 67 | a { 68 | color: $white; 69 | font-weight: 600; 70 | text-decoration: none; 71 | 72 | &:hover { 73 | text-decoration: none; 74 | border-bottom: 2px solid #fff; 75 | } 76 | } 77 | } 78 | } 79 | 80 | .ctas { 81 | display: flex; 82 | flex-flow: row wrap; 83 | margin: 40px 0 0; 84 | justify-content: flex-start; 85 | 86 | .mainLink { 87 | text-decoration: none; 88 | color: $blue; 89 | font-weight: bold; 90 | 91 | &:hover { 92 | text-decoration: underline; 93 | } 94 | } 95 | } 96 | } 97 | 98 | @media only screen and (max-width: 480px) { 99 | .Hero .left, 100 | .Hero .right { 101 | min-width: 92%; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /client/src/components/Web3Info/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import { PublicAddress, Button } from 'rimble-ui'; 3 | import styles from './Web3Info.module.scss'; 4 | 5 | export default function Web3Info(props) { 6 | const { context } = props; 7 | const { networkId, networkName, accounts, providerName, lib } = context; 8 | 9 | const [balance, setBalance] = useState(0); 10 | 11 | const getBalance = useCallback(async () => { 12 | let balance = 13 | accounts && accounts.length > 0 ? lib.utils.fromWei(await lib.eth.getBalance(accounts[0]), 'ether') : 'Unknown'; 14 | setBalance(balance); 15 | }, [accounts, lib.eth, lib.utils]); 16 | 17 | useEffect(() => { 18 | getBalance(); 19 | }, [accounts, getBalance, networkId]); 20 | 21 | const requestAuth = async web3Context => { 22 | try { 23 | await web3Context.requestAuth(); 24 | } catch (e) { 25 | console.error(e); 26 | } 27 | }; 28 | 29 | return ( 30 |
31 |

{props.title}

32 |
33 |
Network:
34 |
{networkId ? `${networkId} – ${networkName}` : 'No connection'}
35 |
36 |
37 |
Your address:
38 |
39 | 40 |
41 |
42 |
43 |
Your ETH balance:
44 |
{balance}
45 |
46 |
47 |
Provider:
48 |
{providerName}
49 |
50 | {accounts && accounts.length ? ( 51 |
52 |
Accounts & Signing Status
53 |
Access Granted
54 |
55 | ) : !!networkId && providerName !== 'infura' ? ( 56 |
57 |
58 | 59 |
60 | ) : ( 61 |
62 | )} 63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /client/src/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | import { useWeb3Network, useEphemeralKey, useWeb3Injected } from '@openzeppelin/network/react'; 5 | 6 | import Header from './components/Header/index.js'; 7 | import Footer from './components/Footer/index.js'; 8 | import Hero from './components/Hero/index.js'; 9 | import Web3Info from './components/Web3Info/index.js'; 10 | import Counter from './components/Counter/index.js'; 11 | 12 | import styles from './App.module.scss'; 13 | 14 | // eslint-disable-next-line no-unused-vars 15 | const infuraToken = process.env.REACT_APP_INFURA_TOKEN || '95202223388e49f48b423ea50a70e336'; 16 | 17 | function App() { 18 | // get ephemeralKey 19 | // eslint-disable-next-line no-unused-vars 20 | const signKey = useEphemeralKey(); 21 | 22 | // get GSN web3 23 | // const context = useWeb3Network(`wss://rinkeby.infura.io/ws/v3/${infuraToken}`, { 24 | // pollInterval: 15 * 1000, 25 | // gsn: { 26 | // signKey, 27 | // }, 28 | // }); 29 | 30 | const context = useWeb3Network('http://127.0.0.1:8545', { 31 | gsn: { 32 | dev: true, 33 | signKey, 34 | }, 35 | }); 36 | 37 | // load Counter json artifact 38 | let counterJSON = undefined; 39 | try { 40 | // see https://github.com/OpenZeppelin/solidity-loader 41 | counterJSON = require('../../contracts/Counter.sol'); 42 | } catch (e) { 43 | console.log(e); 44 | } 45 | 46 | // load Counter instance 47 | const [counterInstance, setCounterInstance] = useState(undefined); 48 | let deployedNetwork = undefined; 49 | if (!counterInstance && context && counterJSON && counterJSON.networks && context.networkId) { 50 | deployedNetwork = counterJSON.networks[context.networkId.toString()]; 51 | if (deployedNetwork) { 52 | setCounterInstance(new context.lib.eth.Contract(counterJSON.abi, deployedNetwork.address)); 53 | } 54 | } 55 | 56 | function renderNoWeb3() { 57 | return ( 58 |
59 |

Web3 Provider Not Found

60 |

Please, install and run Ganache.

61 |
62 | ); 63 | } 64 | 65 | return ( 66 |
67 |
68 | 69 |
70 | {!context.lib && renderNoWeb3()} 71 |
72 |

BUIDL with GSN Kit!

73 |
74 | 75 | 76 |
77 |
78 |
79 |
80 |
81 | ); 82 | } 83 | 84 | export default App; 85 | -------------------------------------------------------------------------------- /client/src/components/Hero/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styles from './Hero.module.scss'; 3 | import cx from 'classnames'; 4 | import logos from './pic_bg.png'; 5 | 6 | export default class Hero extends Component { 7 | renderLogo(name, imgUrl) { 8 | return ( 9 |
10 | zeppelin 11 |
12 | ); 13 | } 14 | render() { 15 | return ( 16 |
17 |
18 |
19 |

GSN Kit

20 |

21 | The easiest way to build a Web3 application and onboard users with the most trusted tools in Ethereum. 22 |

23 |
24 |
25 | - Upgradeable smart contracts with{' '} 26 | 27 | OpenZeppelin SDK 28 | 29 | . 30 |
31 |
32 | - Includes{' '} 33 | 34 | Infura 35 | {' '} 36 | setup for easy deployments & connection. 37 |
38 |
39 | -{' '} 40 | 41 | Truffle 42 | {' '} 43 | to compile & test smart contracts. 44 |
45 |
46 | -{' '} 47 | 48 | React 49 | 50 | & 51 | 52 | Rimble 53 | {' '} 54 | to build usable and friendly interfaces. 55 |
56 |
57 | 67 |
68 |
69 | Starter Kit Tutorial 70 |
71 |
72 |
73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /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 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /client/src/layout/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | 3 | html { 4 | -ms-text-size-adjust: 100%; 5 | -webkit-text-size-adjust: 100%; 6 | } 7 | 8 | html, 9 | body { 10 | overflow-x: hidden; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | font: 112.5%/1.45em 'Roboto', sans-serif; 17 | box-sizing: border-box; 18 | overflow-y: scroll; 19 | background: $white; 20 | color: $primary; 21 | } 22 | 23 | a { 24 | color: $blue; 25 | text-decoration: none; 26 | cursor: pointer; 27 | 28 | &:hover { 29 | text-decoration: underline; 30 | } 31 | } 32 | 33 | * { 34 | box-sizing: inherit; 35 | } 36 | *:before { 37 | box-sizing: inherit; 38 | } 39 | *:after { 40 | box-sizing: inherit; 41 | } 42 | 43 | body { 44 | color: hsla(0, 0%, 0%, 0.8); 45 | font-weight: normal; 46 | word-wrap: break-word; 47 | font-kerning: normal; 48 | -moz-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 49 | -ms-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 50 | -webkit-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 51 | font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 52 | } 53 | 54 | img { 55 | max-width: 100%; 56 | margin-left: 0; 57 | margin-right: 0; 58 | margin-top: 0; 59 | padding-bottom: 0; 60 | padding-left: 0; 61 | padding-right: 0; 62 | padding-top: 0; 63 | margin-bottom: 0; 64 | } 65 | h1 { 66 | margin-left: 0; 67 | margin-right: 0; 68 | margin-top: 0; 69 | padding-bottom: 0; 70 | padding-left: 0; 71 | padding-right: 0; 72 | padding-top: 0; 73 | margin-bottom: 1.45rem; 74 | color: inherit; 75 | font-weight: bold; 76 | text-rendering: optimizeLegibility; 77 | line-height: 1.1; 78 | font-size: 2em; 79 | font-weight: 300; 80 | } 81 | h2 { 82 | margin-left: 0; 83 | margin-right: 0; 84 | margin-top: 0; 85 | padding-bottom: 0; 86 | padding-left: 0; 87 | padding-right: 0; 88 | padding-top: 0; 89 | margin-bottom: 1.45rem; 90 | color: inherit; 91 | font-weight: bold; 92 | text-rendering: optimizeLegibility; 93 | font-size: 1.62671rem; 94 | line-height: 1.1; 95 | } 96 | h3 { 97 | margin-left: 0; 98 | margin-right: 0; 99 | margin-top: 0; 100 | padding-bottom: 0; 101 | padding-left: 0; 102 | padding-right: 0; 103 | padding-top: 0; 104 | margin-bottom: 1.45rem; 105 | color: inherit; 106 | font-weight: bold; 107 | text-rendering: optimizeLegibility; 108 | font-size: 1.38316rem; 109 | line-height: 1.1; 110 | } 111 | h4 { 112 | margin-left: 0; 113 | margin-right: 0; 114 | margin-top: 0; 115 | padding-bottom: 0; 116 | padding-left: 0; 117 | padding-right: 0; 118 | padding-top: 0; 119 | margin-bottom: 1.45rem; 120 | color: inherit; 121 | font-weight: bold; 122 | text-rendering: optimizeLegibility; 123 | font-size: 1rem; 124 | line-height: 1.1; 125 | } 126 | h5 { 127 | margin-left: 0; 128 | margin-right: 0; 129 | margin-top: 0; 130 | padding-bottom: 0; 131 | padding-left: 0; 132 | padding-right: 0; 133 | padding-top: 0; 134 | margin-bottom: 1.45rem; 135 | color: inherit; 136 | font-weight: bold; 137 | text-rendering: optimizeLegibility; 138 | font-size: 0.85028rem; 139 | line-height: 1.1; 140 | } 141 | h6 { 142 | margin-left: 0; 143 | margin-right: 0; 144 | margin-top: 0; 145 | padding-bottom: 0; 146 | padding-left: 0; 147 | padding-right: 0; 148 | padding-top: 0; 149 | margin-bottom: 1.45rem; 150 | color: inherit; 151 | font-weight: bold; 152 | text-rendering: optimizeLegibility; 153 | font-size: 0.78405rem; 154 | line-height: 1.1; 155 | } 156 | 157 | button { 158 | box-shadow: none; 159 | outline: none; 160 | color: $white; 161 | padding: 10px 15px; 162 | border: 1px solid $white; 163 | font-size: 16px; 164 | font-weight: 400; 165 | cursor: pointer; 166 | 167 | &.purple { 168 | background: $primary; 169 | &:hover { 170 | background: $white; 171 | color: $primary; 172 | border-color: $primary; 173 | } 174 | } 175 | 176 | &.yellow { 177 | background: $yellow; 178 | &:hover { 179 | background: $white; 180 | color: $yellow; 181 | border-color: $yellow; 182 | } 183 | } 184 | 185 | &.pink { 186 | background: $pink; 187 | &:hover { 188 | background: $white; 189 | color: $pink; 190 | border-color: $pink; 191 | } 192 | } 193 | } 194 | 195 | input { 196 | width: 100%; 197 | border: 1px solid #ccc; 198 | padding: 5px; 199 | color: $primary; 200 | margin: 5px 0; 201 | } 202 | 203 | @media only screen and (max-width: 480px) { 204 | html { 205 | font-size: 100%; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /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(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), 19 | ); 20 | 21 | export function register(config) { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Let's check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl, config); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit http://bit.ly/CRA-PWA', 45 | ); 46 | }); 47 | } else { 48 | // Is not localhost. Just register service worker 49 | registerValidSW(swUrl, config); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl, config) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | if (installingWorker == null) { 62 | return; 63 | } 64 | installingWorker.onstatechange = () => { 65 | if (installingWorker.state === 'installed') { 66 | if (navigator.serviceWorker.controller) { 67 | // At this point, the updated precached content has been fetched, 68 | // but the previous service worker will still serve the older 69 | // content until all client tabs are closed. 70 | console.log( 71 | 'New content is available and will be used when all ' + 72 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.', 73 | ); 74 | 75 | // Execute callback 76 | if (config && config.onUpdate) { 77 | config.onUpdate(registration); 78 | } 79 | } else { 80 | // At this point, everything has been precached. 81 | // It's the perfect time to display a 82 | // "Content is cached for offline use." message. 83 | console.log('Content is cached for offline use.'); 84 | 85 | // Execute callback 86 | if (config && config.onSuccess) { 87 | config.onSuccess(registration); 88 | } 89 | } 90 | } 91 | }; 92 | }; 93 | }) 94 | .catch(error => { 95 | console.error('Error during service worker registration:', error); 96 | }); 97 | } 98 | 99 | function checkValidServiceWorker(swUrl, config) { 100 | // Check if the service worker can be found. If it can't reload the page. 101 | fetch(swUrl) 102 | .then(response => { 103 | // Ensure service worker exists, and that we really are getting a JS file. 104 | const contentType = response.headers.get('content-type'); 105 | if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) { 106 | // No service worker found. Probably a different app. Reload the page. 107 | navigator.serviceWorker.ready.then(registration => { 108 | registration.unregister().then(() => { 109 | window.location.reload(); 110 | }); 111 | }); 112 | } else { 113 | // Service worker found. Proceed as normal. 114 | registerValidSW(swUrl, config); 115 | } 116 | }) 117 | .catch(() => { 118 | console.log('No internet connection found. App is running in offline mode.'); 119 | }); 120 | } 121 | 122 | export function unregister() { 123 | if ('serviceWorker' in navigator) { 124 | navigator.serviceWorker.ready.then(registration => { 125 | registration.unregister(); 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /client/src/components/Counter/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import { PublicAddress, Button, Loader } from 'rimble-ui'; 3 | 4 | import styles from './Counter.module.scss'; 5 | 6 | import getTransactionReceipt from '../../utils/getTransactionReceipt'; 7 | import { utils } from '@openzeppelin/gsn-provider'; 8 | const { isRelayHubDeployedForRecipient, getRecipientFunds } = utils; 9 | 10 | export default function Counter(props) { 11 | const { instance, accounts, lib, networkName, networkId, providerName } = props; 12 | const { _address, methods } = instance || {}; 13 | 14 | // GSN provider has only one key pair 15 | const isGSN = providerName === 'GSN'; 16 | 17 | const [balance, setBalance] = useState(0); 18 | 19 | const getBalance = useCallback(async () => { 20 | let balance = 21 | accounts && accounts.length > 0 ? lib.utils.fromWei(await lib.eth.getBalance(accounts[0]), 'ether') : 'Unknown'; 22 | setBalance(Number(balance)); 23 | }, [accounts, lib.eth, lib.utils]); 24 | 25 | useEffect(() => { 26 | if (!isGSN) getBalance(); 27 | }, [accounts, getBalance, isGSN, lib.eth, lib.utils, networkId]); 28 | 29 | const [, setIsDeployed] = useState(false); 30 | const [funds, setFunds] = useState(0); 31 | 32 | const getDeploymentAndFunds = useCallback(async () => { 33 | if (instance) { 34 | if (isGSN) { 35 | // if GSN check how much funds recipient has 36 | const isDeployed = await isRelayHubDeployedForRecipient(lib, _address); 37 | 38 | setIsDeployed(isDeployed); 39 | if (isDeployed) { 40 | const funds = await getRecipientFunds(lib, _address); 41 | setFunds(Number(funds)); 42 | } 43 | } 44 | } 45 | }, [_address, instance, isGSN, lib]); 46 | 47 | useEffect(() => { 48 | getDeploymentAndFunds(); 49 | }, [getDeploymentAndFunds, instance]); 50 | 51 | const [count, setCount] = useState(0); 52 | 53 | const getCount = useCallback(async () => { 54 | if (instance) { 55 | // Get the value from the contract to prove it worked. 56 | const response = await instance.methods.getCounter().call(); 57 | // Update state with the result. 58 | setCount(response); 59 | } 60 | }, [instance]); 61 | 62 | useEffect(() => { 63 | getCount(); 64 | }, [getCount, instance]); 65 | 66 | const [sending, setSending] = useState(false); 67 | const [transactionHash, setTransactionHash] = useState(''); 68 | 69 | const increase = async number => { 70 | try { 71 | if (!sending) { 72 | setSending(true); 73 | 74 | const tx = await instance.methods.increaseCounter(number).send({ from: accounts[0] }); 75 | const receipt = await getTransactionReceipt(lib, tx.transactionHash); 76 | setTransactionHash(receipt.transactionHash); 77 | 78 | getCount(); 79 | getDeploymentAndFunds(); 80 | 81 | setSending(false); 82 | } 83 | } catch (e) { 84 | setSending(false); 85 | console.log(e); 86 | } 87 | }; 88 | 89 | const decrease = async number => { 90 | try { 91 | if (!sending) { 92 | setSending(true); 93 | 94 | const receipt = await instance.methods.decreaseCounter(number).send({ from: accounts[0] }); 95 | setTransactionHash(receipt.transactionHash); 96 | 97 | getCount(); 98 | getDeploymentAndFunds(); 99 | 100 | setSending(false); 101 | } 102 | } catch (e) { 103 | setSending(false); 104 | console.log(e); 105 | } 106 | }; 107 | 108 | function renderNoDeploy() { 109 | return ( 110 |
111 |

112 | Can't Load Deployed Counter Instance 113 |

114 |

Please, run `oz create` to deploy an counter instance.

115 |
116 | ); 117 | } 118 | 119 | function renderNoFunds() { 120 | return ( 121 |
122 |

123 | The recipient has no funds 124 |

125 |

Please, run:

126 |
127 | 128 | npx oz-gsn fund-recipient --recipient {_address} 129 | 130 |
131 |

to fund the recipient on local network.

132 |
133 | ); 134 | } 135 | 136 | function renderNoBalance() { 137 | return ( 138 |
139 |

140 | Fund your Metamask account 141 |

142 |

You need some ETH to be able to send transactions. Please, run:

143 |
144 | 145 | openzeppelin transfer --to {accounts[0]} 146 | 147 |
148 |

to fund your Metamask.

149 |
150 | ); 151 | } 152 | 153 | function renderTransactionHash() { 154 | return ( 155 |
156 |

157 | Transaction{' '} 158 | 163 | {transactionHash.substr(0, 6)} 164 | {' '} 165 | has been mined on {networkName} network. 166 |

167 |
168 | ); 169 | } 170 | 171 | return ( 172 |
173 |

Counter Instance

174 | {lib && !instance && renderNoDeploy()} 175 | {lib && instance && ( 176 | 177 |
178 |
Instance address:
179 |
180 | 181 |
182 |
183 |
184 |
Counter Value:
185 |
{count}
186 |
187 | {isGSN && ( 188 |
189 |
Recipient Funds:
190 |
{lib.utils.fromWei(funds.toString(), 'ether')} ETH
191 |
192 | )} 193 | {isGSN && !funds && renderNoFunds()} 194 | {!isGSN && !balance && renderNoBalance()} 195 | 196 | {(!!funds || !!balance) && ( 197 | 198 |
199 | Counter Actions 200 |
201 |
202 | 205 | 208 |
209 |
210 | )} 211 | {transactionHash && networkName !== 'Private' && renderTransactionHash()} 212 |
213 | )} 214 |
215 | ); 216 | } 217 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: This project is **deprecated**. We are no longer actively developing new features nor addressing issues. Read [here](https://forum.openzeppelin.com/t/doubling-down-in-security/2712) for more info, and reach out if you are interested in taking over maintenance. We suggest looking into [create-eth-app](https://github.com/PaulRBerg/create-eth-app) for a popular alternative to this project. 2 | 3 | # OpenZeppelin GSN Starter Kit 4 | 5 | An OpenZeppelin Starter Kit GSN containing React, OpenZeppelin CLI, OpenZeppelin Contracts, Gas Station Network, Truffle and Infura. 6 | 7 | OpenZeppelin GSN Starter Kit comes with everything you need to start using Gas Station Network contracts inside your applications. It also includes all the GSN Providers & Web3 connectors that you need to abstract gas for your users. 8 | 9 | In addition to the contents included in the [vanilla Starter Kit](https://github.com/OpenZeppelin/starter-kit/blob/master/README.md), this kit contains a step-by-step tutorial on how to enable Gas Station Network for a simple Counter Contract. 10 | 11 | ## Requirements 12 | 13 | Install Ganache, and Truffle 14 | 15 | ``` 16 | npm install -g truffle@5.0.41 ganache-cli@6.7.0 17 | ``` 18 | 19 | ## Installation 20 | 21 | Ensure you are in a new and empty directory, and run the `unpack` command with `gsn` to create a starter project: 22 | 23 | ```javascript 24 | npx @openzeppelin/cli unpack gsn 25 | ``` 26 | 27 | ## Run 28 | 29 | In a new terminal window, run your local blockchain: 30 | 31 | ``` 32 | ganache-cli --deterministic 33 | ``` 34 | 35 | In your original terminal window, at the top level of your folder, initialize the project 36 | and follow the prompts: 37 | 38 | ```javascript 39 | npx openzeppelin init 40 | ``` 41 | 42 | In a new terminal window, in the `client` directory, run the React app: 43 | 44 | ```javascript 45 | cd client 46 | npm run start 47 | ``` 48 | 49 | Follow the instructions in the browser. 50 | 51 | ## Test 52 | 53 | Truffle can run tests written in Solidity or JavaScript against your smart contracts. Note the command varies slightly if you're in or outside of the truffle development console. 54 | 55 | ```javascript 56 | // inside the development console. 57 | test 58 | 59 | // outside the development console.. 60 | truffle test 61 | ``` 62 | 63 | Jest is included for testing React components. Compile your contracts before running Jest, or you may receive some file not found errors. 64 | 65 | ```javascript 66 | // ensure you are inside the client directory when running this 67 | npm run test 68 | ``` 69 | 70 | ## Build 71 | 72 | To build the application for production, use the build script. A production build will be in the `client/build` folder. 73 | 74 | ```javascript 75 | // ensure you are inside the client directory when running this 76 | npm run build 77 | ``` 78 | 79 | ## Why this kit? 80 | 81 | This kit leverages GSN to create dapps that are ready for mass adoption. Free your users from 82 | the initial burden of installing Metamask and obtaining Ether. Create blockchain applications 83 | that are indistinguishable from Web2.0 apps. 84 | 85 | This documents assumes familiarity with the [Gas Station Network](https://gsn.openzeppelin.com/). Check out our [GSN getting started guide](https://docs.openzeppelin.com/learn/sending-gasless-transactions) and [GSN Kit Tutorial](https://forum.openzeppelin.com/t/using-gas-station-network-starter-kit-on-a-local-network/1213) to learn more. 86 | 87 | ### How does it use Web3 with GSN? 88 | 89 | This kit uses Open Zeppelin [network.js](https://github.com/OpenZeppelin/openzeppelin-network.js) to create the connection to Web3. Using a couple of flags for development and production you can see how the dapp obtains a context that is aware of Gas Station Network. 90 | 91 | ```javascript 92 | // get GSN web3 93 | const context = useWeb3Network("http://127.0.0.1:8545", { 94 | gsn: { dev: true } 95 | }); 96 | ``` 97 | 98 | ### How are the contracts modified to use GSN? 99 | 100 | The `Counter` contract is modified to inherit from `RelayRecipient`. Also, the `Counter` contract is going to 101 | naively pay for all the transactions that are submitted. Note how the `acceptRelayedCall` determines this by returning 0. Also `_preRelayedCall` and `_postRelayedCall` methods must be implemented because they are defined as abstract in `GSNRecipient`. 102 | 103 | ```solidity 104 | pragma solidity ^0.5.0; 105 | 106 | import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol"; 107 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 108 | 109 | contract Counter is Initializable, GSNRecipient { 110 | //it keeps a count to demonstrate stage changes 111 | uint private count; 112 | address private _owner; 113 | 114 | function initialize(uint num) public initializer { 115 | GSNRecipient.initialize(); 116 | _owner = _msgSender(); 117 | count = num; 118 | } 119 | 120 | // accept all requests 121 | function acceptRelayedCall( 122 | address, 123 | address, 124 | bytes calldata, 125 | uint256, 126 | uint256, 127 | uint256, 128 | uint256, 129 | bytes calldata, 130 | uint256 131 | ) external view returns (uint256, bytes memory) { 132 | return _approveRelayedCall(); 133 | } ... 134 | } 135 | 136 | function _preRelayedCall(bytes memory context) internal returns (bytes32) { 137 | // solhint-disable-previous-line no-empty-blocks 138 | } 139 | 140 | function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal { 141 | // solhint-disable-previous-line no-empty-blocks 142 | } 143 | 144 | ``` 145 | 146 | ### How to know if my recipient has funds? 147 | 148 | The frontend also has some functions to help you see how much remaining balance you have left. 149 | Once it runs out, transactions will stop working because your dapp won't be able to pay the gas fee 150 | on behalf of its users. 151 | 152 | ```js 153 | const getDeploymentAndFunds = async () => { 154 | if (instance) { 155 | const isDeployed = await isRelayHubDeployedForRecipient(lib, _address); 156 | setIsDeployed(isDeployed); 157 | if (isDeployed) { 158 | const funds = await getRecipientFunds(lib, _address); 159 | setFunds(funds); 160 | } 161 | } 162 | }; 163 | ``` 164 | 165 | You can top your balance by sending funds to your contract using `npx oz-gsn fund-recipient --recipient ADDRESS` command or heading to the [dapp tool](https://gsn.openzeppelin.com/recipients). 166 | 167 | ### How is the RelayHub deployed locally? 168 | 169 | When you run `npx oz-gsn fund-recipient`, the following [code](https://github.com/OpenZeppelin/openzeppelin-gsn-helpers/blob/master/src/fund.js) gets executed: 170 | 171 | ```js 172 | // Ensure relayHub is deployed on the local network 173 | if (options.relayHubAddress.toLowerCase() === data.relayHub.address.toLowerCase()) { 174 | await deployRelayHub(web3, options.from); 175 | } 176 | ``` 177 | 178 | Note that on both mainnet and testnets, as well as local blockchain (`ganache`) environments, the address of the `RelayHub` contract is always `0xD216153c06E857cD7f72665E0aF1d7D82172F494`. 179 | 180 | ## FAQ 181 | 182 | - **How do I use this with the Ganache-CLI?** 183 | 184 | It's as easy as modifying the config file! [Check out our documentation on adding network configurations](http://truffleframework.com/docs/advanced/configuration#networks). 185 | 186 | - **Where is my production build?** 187 | 188 | The production build will be in the `client/build` folder after running `npm run build` in the `client` folder. 189 | 190 | - **Where can I find more documentation?** 191 | 192 | Check out the [OpenZeppelin Starter Kits documentation](https://docs.openzeppelin.com/starter-kits/). 193 | -------------------------------------------------------------------------------- /client/src/components/Footer/zeppelin_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OZ_logo_color 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/src/components/Instructions/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'rimble-ui'; 3 | import styles from './Instructions.module.scss'; 4 | 5 | export default class Instructions extends Component { 6 | renderCounterSetup() { 7 | return ( 8 |
9 |

Build your first app with Starter Kit Tutorial

10 |
11 |
Initialize your OpenZeppelin SDK project
12 |
13 | openzeppelin init {'<>'} 14 |
15 |
16 |
17 |
18 | Create an instance of the Counter contract and deploy it using the create command, follow the cli prompts. 19 |
20 |
21 | openzeppelin create Counter 22 |
23 |
24 |
25 |
26 | Done! Refresh the page to interact with your instance of the counter contract. 27 |
28 |
29 | Learn more about OpenZeppelin SDK or ask a question in the{' '} 30 | Forum. 31 |
32 |
33 | 34 |
35 |
36 | ); 37 | } 38 | 39 | renderSetup() { 40 | return ( 41 |
42 |

Starter Kit Tutorial is running!

43 |
44 |
Congratulations! Your application is correctly setup.
45 |
46 | 47 |
48 |
49 | Visit the tutorials. Start with the Counter page to deploy and interact with your 50 | first contract. 51 |
52 |
53 |
54 | ); 55 | } 56 | 57 | renderMetamask() { 58 | const code = this.props.accounts[0]; 59 | return ( 60 |
61 |

Fund your Metamask account

62 |

You need some ETH to be able to send transactions.

63 |
64 |
1. Open a terminal and type
65 |
66 | openzeppelin transfer 67 |
68 |
69 |
70 |
71 | 2. Send 0.5 ETH from one of your ganache accounts to your Metamask account. 72 |
73 |
74 | {code} 75 |
76 |
77 |
78 |
79 | 3. Congratulations!! You can now interact with the contract and increase the counter. 80 |
81 |
82 |
83 | ); 84 | } 85 | 86 | renderUpgrade() { 87 | return ( 88 |
89 |

Upgrading your contract

90 |

Thanks to OpenZeppelin SDK, you can upgrade the code of your contract to add more functionality.

91 |
92 |
93 | 1. Open contracts/Counter.sol and uncomment the decreaseCounter method (lines 32-36). 94 |
95 |
96 | {`// function decreaseCounter(uint256 amount) public returns (bool) {`} 97 |
98 |
99 |
100 |
101 | 2. Save the changes and compile, push, and update the new changes to the network. 102 |
103 |
104 | openzeppelin update Counter 105 |
106 |
107 |
108 |
109 | 3. Congratulations! You have upgraded your contract and you can now decrease the counter. 110 |
111 |
112 | 113 |
114 |
115 | ); 116 | } 117 | 118 | renderAutoUpgrade() { 119 | return ( 120 |
121 |

Upgrading on Development Network

122 |

123 | {' '} 124 | Thanks to OpenZeppelin SDK and Solidity Loader your smart contracts would reload automatically after you save 125 | a .sol file while preserving a state.{' '} 126 |

127 |
128 |
129 | 1. Open contracts/Counter.sol and uncomment the decreaseCounter method (lines 32-36). 130 |
131 |
132 | {`// function decreaseCounter(uint256 amount) public returns (bool) {`} 133 |
134 |
135 |
136 |
137 | 2. Save the changes and wait for the .sol files to compile. Upon completion, Solidity Loader will push and 138 | update your smart contracts 139 |
140 |
141 |
142 |
143 | 3. Congratulations! You have upgraded your contract and you can now decrease the counter. 144 |
145 |
146 |
147 |

148 | {' '} 149 | * On a non development network you would have to run openzeppelin update command manually.{' '} 150 |

151 |
152 | ); 153 | } 154 | 155 | renderFAQ() { 156 | return ( 157 |
158 |

FAQ

159 |
Q: How do I deploy to other networks?
160 |
161 |
162 |
163 | 1. Enter the mnemonic of the account you want to use to deploy in the{' '} 164 | .env file located in the top level folder. Add your network to the{' '} 165 | networks.js file. 166 |
167 |
168 | mnemonic='fill' 169 |
170 |
171 |
172 |
3. Push the project contracts and deploy the dependencies (if any).
173 |
174 | openzeppelin push 175 |
176 |
177 |
178 |
179 | 4. Create the instances and follow the cli prompts 180 |
181 |
182 | openzeppelin create CONTRACT_NAME 183 |
184 |
185 |
Q: How do I run tests?
186 |
187 |
188 |
1. To execute smart contract tests run:
189 |
190 | truffle test 191 |
192 |
193 |
194 |
2. To test your React components, (inside the client folder) run:
195 |
196 | npm run test 197 |
198 |
199 |
Q: How do I connect to other networks from my local website?
200 |
201 |
202 |
203 | 1. Change the fallback provider by switching REACT_APP_NETWORK {' '} 204 | inside the .env file located in the client folder. 205 |
206 |
207 | REACT_APP_NETWORK = https://mainnet.infura.io/v3/d6760e62b67f4937ba1ea2691046f06d 208 |
209 |
210 |
211 | Take into account that this only switches the default provider. If you are using Metamask, you only need to 212 | switch network from the extension. 213 |
214 |
215 | ); 216 | } 217 | 218 | getDefaultAddress() { 219 | const { ganacheAccounts } = this.props; 220 | return (ganacheAccounts && ganacheAccounts.length) > 2 ? ganacheAccounts[2] : '
'; 221 | } 222 | 223 | render() { 224 | const { name } = this.props; 225 | switch (name) { 226 | case 'setup': 227 | return this.renderSetup(); 228 | case 'metamask': 229 | return this.renderMetamask(); 230 | case 'upgrade': 231 | return this.renderUpgrade(); 232 | case 'upgrade-auto': 233 | return this.renderAutoUpgrade(); 234 | case 'counter': 235 | return this.renderCounterSetup(); 236 | case 'faq': 237 | return this.renderFAQ(); 238 | case 'evm': 239 | return this.renderEVM(); 240 | default: 241 | return this.renderSetup(); 242 | } 243 | } 244 | } 245 | --------------------------------------------------------------------------------