├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── contract └── MyToken.sol ├── dapp ├── js │ ├── component │ │ ├── App.jsx │ │ ├── MyToken.jsx │ │ └── Web3.jsx │ └── main.jsx └── static │ └── index.html ├── mocha-webpack.opts ├── package.json ├── test └── contract │ └── MyToken.test.js ├── webpack.config.js └── webpack.config.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "mocha": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | /lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | /coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | /build 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | .tmp 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "5" 5 | - "4" 6 | env: 7 | - CXX=g++-4.8 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-4.8 14 | before_script: 15 | - npm install -g ethereumjs-testrpc 16 | - testrpc & 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Ethereum Dapp Template 2 | 3 | [![Build Status](https://travis-ci.org/uzyn/react-ethereum-dapp-template.svg?branch=master)](https://travis-ci.org/uzyn/react-ethereum-dapp-template) 4 | 5 | Template for React-based Ethereum decentralized app (Dapp). 6 | 7 | This is an **opinionated version** of [ethereum-webpack-example-dapp](https://github.com/uzyn/ethereum-webpack-example-dapp) and largely intended for personal use, unless you share the same opinion as mine. 8 | 9 | It is largely a combination of: 10 | 11 | - [react-webpack-airbnbjs-boilerplate](https://github.com/uzyn/react-webpack-airbnbjs-boilerplate), and 12 | - [ethereum-webpack-example-dapp](https://github.com/uzyn/ethereum-webpack-example-dapp) 13 | 14 | ## What does this include 15 | 16 | - Webpack build script with Webpack dev server 17 | - ES2015/ES6 18 | - ESlint for ES2015 using Airbnb JS style guide 19 | - React for front-end view 20 | - Solidity for Ethereum smart contracts 21 | - Test suite for smart contract testing 22 | 23 | 24 | ## How to run 25 | 26 | 1. Run a local Ethereum node with JSON-RPC listening at port 8545 _(default)_. [testrpc](https://github.com/ethereumjs/testrpc) would be the most straight-forward method. 27 | 28 | ```bash 29 | # Using testrpc (recommended) 30 | testrpc 31 | 32 | # If you are running Geth, 33 | # make sure to run in testnet or private net and enable rpc 34 | geth --testnet --rpc 35 | ``` 36 | 37 | 1. Install dependencies 38 | 39 | ```bash 40 | npm install 41 | ``` 42 | 43 | 1. Start the dev server, code and enjoy! Browser should automatically refresh if you make any changes to the code. 44 | 45 | ```bash 46 | npm start 47 | ``` 48 | 49 | Load [http://localhost:8080/](http://localhost:8080/) on your web browser. 50 | 51 | 1. For deployment, run `npm build` and upload `build/` to your server. 52 | 53 | -------------------------------------------------------------------------------- /contract/MyToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * This file has 2 contracts: tokenRecipient and MyToken 3 | * and is reproduced directly from https://www.ethereum.org/token 4 | */ 5 | contract tokenRecipient { 6 | function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData); 7 | } 8 | 9 | contract MyToken { 10 | /* Public variables of the token */ 11 | string public name; 12 | string public symbol; 13 | string public version; 14 | uint8 public decimals; 15 | uint256 public totalSupply; 16 | 17 | /* This creates an array with all balances */ 18 | mapping (address => uint256) public balanceOf; 19 | mapping (address => mapping (address => uint256)) public allowance; 20 | mapping (address => mapping (address => uint256)) public spentAllowance; 21 | 22 | /* This generates a public event on the blockchain that will notify clients */ 23 | event Transfer(address indexed from, address indexed to, uint256 value); 24 | 25 | /* Initializes contract with initial supply tokens to the creator of the contract */ 26 | function MyToken( 27 | uint256 initialSupply, 28 | string tokenName, 29 | uint8 decimalUnits, 30 | string tokenSymbol, 31 | string versionOfTheCode 32 | ) { 33 | balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens 34 | totalSupply = initialSupply; // Update total supply 35 | name = tokenName; // Set the name for display purposes 36 | symbol = tokenSymbol; // Set the symbol for display purposes 37 | decimals = decimalUnits; // Amount of decimals for display purposes 38 | version = versionOfTheCode; 39 | } 40 | 41 | /* Send coins */ 42 | function transfer(address _to, uint256 _value) { 43 | if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough 44 | if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows 45 | balanceOf[msg.sender] -= _value; // Subtract from the sender 46 | balanceOf[_to] += _value; // Add the same to the recipient 47 | Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place 48 | } 49 | 50 | /* Allow another contract to spend some tokens in your behalf */ 51 | function approveAndCall(address _spender, uint256 _value, bytes _extraData) 52 | returns (bool success) { 53 | allowance[msg.sender][_spender] = _value; 54 | tokenRecipient spender = tokenRecipient(_spender); 55 | spender.receiveApproval(msg.sender, _value, this, _extraData); 56 | return true; 57 | } 58 | 59 | /* A contract attempts to get the coins */ 60 | function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { 61 | if (balanceOf[_from] < _value) throw; // Check if the sender has enough 62 | if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows 63 | if (spentAllowance[_from][msg.sender] + _value > allowance[_from][msg.sender]) throw; // Check allowance 64 | balanceOf[_from] -= _value; // Subtract from the sender 65 | balanceOf[_to] += _value; // Add the same to the recipient 66 | spentAllowance[_from][msg.sender] += _value; 67 | Transfer(_from, _to, _value); 68 | return true; 69 | } 70 | 71 | /* This unnamed function is called whenever someone tries to send ether to it */ 72 | function () { 73 | throw; // Prevents accidental sending of ether 74 | } 75 | } -------------------------------------------------------------------------------- /dapp/js/component/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Web3 from './Web3'; 3 | import MyToken from './MyToken'; 4 | 5 | 6 | export default function App() { 7 | return ( 8 |
9 |

Sample Ethereum Dapp with Webpack

10 |

Example The Coin Dapp with smart contract code from https://www.ethereum.org/token

11 | 12 |
13 | 14 | 15 |
16 | 17 | 18 |
19 |

tokenRecipient

20 |

21 | If a .sol file contains more than one contracts, 22 | individual instantiated contracts are also available. 23 | See console.log(tokenRecipient). 24 |

25 | 26 |
27 |

You can find more information on this sample dapp at its GitHub repository and @uzyn.

28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /dapp/js/component/MyToken.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MyToken } from '../../../contract/MyToken.sol'; 3 | 4 | export default function Web3() { 5 | return ( 6 |
7 |

MyToken

8 |

Instantiated (deployed) MyToken is available as MyToken.

9 |
10 |
Token name
11 |
{MyToken.name()}
12 |
Token symbol
13 |
{MyToken.symbol()}
14 |
Total supply of token
15 |
{MyToken.totalSupply().toString()}
16 |
Version
17 |
{MyToken.version()}
18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /dapp/js/component/Web3.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { web3 } from '../../../contract/MyToken.sol'; 3 | 4 | export default function Web3() { 5 | return ( 6 |
7 |

Web3

8 |

Initialized Web3 object for easy interfacing of Ethereum JavaScript API.

9 |
10 |
Connected Ethereum node (Web3 provider)
11 |
{web3.currentProvider.host}
12 |
Latest block
13 |
{web3.eth.blockNumber}
14 |
Accounts
15 |
16 | {web3.eth.accounts.map( 17 | (account) =>
{account}
18 | )} 19 |
20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /dapp/js/main.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import App from './component/App'; 5 | 6 | render( 7 | , 8 | document.getElementById('root') 9 | ); 10 | -------------------------------------------------------------------------------- /dapp/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample React Ethereum Dapp 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /mocha-webpack.opts: -------------------------------------------------------------------------------- 1 | --colors 2 | --webpack-config webpack.config.test.js 3 | --require source-map-support/register 4 | --timeout 5000 5 | test/**/*.js 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ethereum-dapp-template", 3 | "version": "0.0.1", 4 | "description": "Template for Ethereum Dapp based on React with test suite", 5 | "scripts": { 6 | "start": "webpack-dev-server --inline --content-base build/", 7 | "lint": "eslint --ext=js --ext=jsx --ignore-path .gitignore .", 8 | "build": "webpack", 9 | "test": "mocha-webpack" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/uzyn/react-ethereum-dapp-template.git" 14 | }, 15 | "author": "U-Zyn Chua ", 16 | "license": "UNLICENSED", 17 | "devDependencies": { 18 | "babel-core": "^6.7.7", 19 | "babel-loader": "^6.2.4", 20 | "babel-preset-es2015": "^6.6.0", 21 | "babel-preset-react": "^6.11.1", 22 | "chai": "^3.5.0", 23 | "clean-webpack-plugin": "^0.1.8", 24 | "copy-webpack-plugin": "^2.1.1", 25 | "eslint": "^2.8.0", 26 | "eslint-config-airbnb": "^7.0.0", 27 | "eslint-loader": "^1.3.0", 28 | "eslint-plugin-jsx-a11y": "^0.6.2", 29 | "eslint-plugin-react": "^4.3.0", 30 | "json-loader": "^0.5.4", 31 | "mocha": "^2.5.3", 32 | "mocha-webpack": "^0.4.0", 33 | "solc-loader": "1.x", 34 | "source-map-support": "^0.4.1", 35 | "web3": "^0.15.3", 36 | "web3-loader": "1.x", 37 | "webpack": "^1.13.0", 38 | "webpack-node-externals": "^1.2.0" 39 | }, 40 | "dependencies": { 41 | "babel-polyfill": "^6.7.4", 42 | "react": "^15.0.1", 43 | "react-dom": "^15.0.1", 44 | "webpack-dev-server": "^1.14.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/contract/MyToken.test.js: -------------------------------------------------------------------------------- 1 | import { MyToken, web3 } from '../../contract/MyToken.sol'; 2 | 3 | const assert = require('chai').assert; 4 | 5 | describe('MyToken', () => { 6 | describe('totalSupply()', () => { 7 | it('should return the correct value', () => { 8 | assert.equal(MyToken.totalSupply().toNumber(), 250000); 9 | }); 10 | }); 11 | 12 | describe('transfer()', () => { 13 | it('should successfully transfer tokens', () => { 14 | const account1 = web3.eth.accounts[0]; 15 | const account2 = web3.eth.accounts[1]; 16 | MyToken.transfer(account2, 100, { from: account1, gas: 100000 }); 17 | 18 | assert.equal(MyToken.balanceOf(account2).toNumber(), 100); 19 | assert.equal(MyToken.balanceOf(account1).toNumber(), 250000 - 100); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: path.resolve(__dirname, 'dapp/js/main'), 7 | devServer: { 8 | outputPath: path.join(__dirname, 'build'), 9 | }, 10 | resolve: { 11 | extensions: ['', '.js', '.jsx'], 12 | }, 13 | output: { 14 | path: path.resolve(__dirname, 'build/js'), 15 | publicPath: '/js/', 16 | filename: 'bundle.js', 17 | }, 18 | web3Loader: { 19 | constructorParams: { 20 | MyToken: [ 250000, 'The Coin', 2, 'TC$', '1.0.0' ], 21 | } 22 | }, 23 | plugins: [ 24 | new CleanWebpackPlugin(['build']), 25 | new CopyWebpackPlugin([ 26 | { 27 | context: path.resolve(__dirname, 'dapp/static'), 28 | from: '**/*', 29 | to: path.resolve(__dirname, 'build'), 30 | }, 31 | ]), 32 | ], 33 | module: { 34 | preLoaders: [ 35 | { 36 | test: /\.jsx?$/, 37 | exclude: /(node_modules|bower_components)/, 38 | loaders: ['eslint'], 39 | }, 40 | ], 41 | loaders: [ 42 | { 43 | test: /\.sol$/, 44 | loaders: ['web3', 'solc'], 45 | }, 46 | { 47 | test: /\.json$/, 48 | loaders: ['json'], 49 | }, 50 | { 51 | test: /\.jsx?$/, 52 | exclude: /(node_modules|bower_components)/, 53 | loaders: ['babel'], 54 | }, 55 | ], 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /webpack.config.test.js: -------------------------------------------------------------------------------- 1 | const nodeExternals = require('webpack-node-externals'); 2 | const config = require('./webpack.config.js'); 3 | 4 | config.output = { 5 | devtoolModuleFilenameTemplate: '[absolute-resource-path]', 6 | devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]', 7 | }; 8 | 9 | // Set target to node to be runable within Mocha 10 | config.target = 'node'; 11 | 12 | config.externals = [nodeExternals()]; 13 | 14 | // Enable source maps 15 | config.devtool = 'cheap-module-source-map'; 16 | 17 | module.exports = config; 18 | --------------------------------------------------------------------------------