├── 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 |
11 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
23 | Starter Kit Tutorial
24 |
25 |
26 | You need to enable JavaScript to run this app.
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 |
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 | requestAuth(context)}>Request Access
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 |
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 |
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 |
56 |
57 |
67 |
68 |
69 |
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 |
168 | );
169 | }
170 |
171 | return (
172 |
173 |
Counter Instance
174 | {lib && !instance && renderNoDeploy()}
175 | {lib && instance && (
176 |
177 |
178 |
Instance address:
179 |
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 | increase(1)} size="small">
203 | {sending ? : Increase Counter by 1 }
204 |
205 | decrease(1)} disabled={!(methods && methods.decreaseCounter)} size="small">
206 | {sending ? : Decrease Counter by 1 }
207 |
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 |
32 |
33 |
window.location.reload()}>Reload
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 |
window.location.reload()}>Reload
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 |
--------------------------------------------------------------------------------