├── .nvmrc
├── app
├── actions
│ ├── contracts
│ │ ├── index.js
│ │ └── SimpleStorage.js
│ ├── index.js
│ ├── ipfs.js
│ └── accounts.js
├── init
│ ├── storage.js
│ ├── web3.js
│ ├── contracts.js
│ └── index.js
├── reducers
│ ├── index.js
│ ├── ipfs.js
│ ├── accounts.js
│ └── contracts
│ │ └── SimpleStorage.js
├── index.html
├── index.js
├── style
│ └── app.css
├── store.js
└── components
│ └── App
│ └── index.js
├── .eslintignore
├── scripts
├── start_services.sh
└── build_docker.sh
├── docker-compose.yml
├── migrations
├── 1_initial_migration.js
└── 2_deploy_contracts.js
├── .babelrc
├── .gitignore
├── truffle.js
├── contracts
├── SimpleStorage.sol
└── Migrations.sol
├── .travis.yml
├── test
└── simplestorage.js
├── spec
└── actions
│ └── accounts.spec.js
├── README.md
├── webpack.config.js
├── .eslintrc
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 7.6.0
2 |
--------------------------------------------------------------------------------
/app/actions/contracts/index.js:
--------------------------------------------------------------------------------
1 | export * from './SimpleStorage'
2 |
--------------------------------------------------------------------------------
/app/init/storage.js:
--------------------------------------------------------------------------------
1 | import ipfs from 'ipfs-api'
2 |
3 | export {
4 | ipfs
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | tmp/**
2 | build/**
3 | node_modules/**
4 | contracts/**
5 | submodules/**
6 |
--------------------------------------------------------------------------------
/app/actions/index.js:
--------------------------------------------------------------------------------
1 | export * from './accounts'
2 | export * from './contracts'
3 | export * from './ipfs'
4 |
--------------------------------------------------------------------------------
/scripts/start_services.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SCRIPT_DIR="$(pwd -P)/$(dirname $0)"
4 |
5 | echo "Starting Docker..."
6 | docker-compose up -d
7 | echo "Docker initialised..."
8 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | testrpc:
5 | image: freddielindsey/ethereumjs-testrpc
6 | container_name: testrpc
7 | ports:
8 | - 8545:8545
9 |
--------------------------------------------------------------------------------
/migrations/1_initial_migration.js:
--------------------------------------------------------------------------------
1 | const Migrations = artifacts.require('../contracts/Migrations.sol')
2 |
3 | module.exports = function(deployer) {
4 | deployer.deploy(Migrations)
5 | }
6 |
--------------------------------------------------------------------------------
/migrations/2_deploy_contracts.js:
--------------------------------------------------------------------------------
1 | const SimpleStorage = artifacts.require('../contracts/SimpleStorage.sol')
2 |
3 | module.exports = function(deployer) {
4 | deployer.deploy(SimpleStorage);
5 | };
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "latest",
4 | "stage-0",
5 | "react"
6 | ],
7 | "env": {
8 | "development": {
9 | "presets": ["react-hmre"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Caches and packages
2 | .truffle-solidity-loader
3 | .cache
4 | node_modules
5 | .node-xml*
6 | chains.json
7 |
8 | # Logs
9 | yarn-error.log
10 | npm-debug.log
11 |
12 | # Build
13 | build
14 |
--------------------------------------------------------------------------------
/truffle.js:
--------------------------------------------------------------------------------
1 | // Allows us to use ES6 in our migrations and tests.
2 | require('babel-register')
3 |
4 | module.exports = {
5 | networks: {
6 | development: {
7 | host: 'localhost',
8 | port: 8545,
9 | network_id: '*' // Match any network id
10 | }
11 | },
12 | mocha: {
13 | useColors: true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import accounts from './accounts'
4 | import ipfs from './ipfs'
5 |
6 | // Contracts
7 | import SimpleStorage from './contracts/SimpleStorage'
8 |
9 | const reducers = combineReducers({
10 | accounts,
11 | ipfs,
12 | SimpleStorage
13 | });
14 |
15 | export default reducers;
16 |
--------------------------------------------------------------------------------
/contracts/SimpleStorage.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 | contract SimpleStorage {
4 | uint public storedData;
5 |
6 | function SimpleStorage() {
7 | storedData = 10;
8 | }
9 |
10 | function set(uint x) {
11 | storedData = x;
12 | }
13 |
14 | function get() constant returns (uint retVal) {
15 | return storedData;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 |
3 | language: node_js
4 |
5 | cache: yarn
6 |
7 | notification:
8 | email:
9 | success: never
10 | failure: always
11 |
12 | install:
13 | - yarn install
14 |
15 | before_script:
16 | - yarn run testrpc > ./testrpc.log &
17 |
18 | script:
19 | - yarn run build
20 | - yarn run test-contracts
21 | - yarn run test-web
22 |
--------------------------------------------------------------------------------
/scripts/build_docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | DOCKER_ROOT_TAG=freddielindsey/ethereumjs-testrpc
4 | DOCKER_TAG=$DOCKER_ROOT_TAG:$(date +%Y-%m-%d)
5 | SCRIPT_DIR="$(pwd -P)/$(dirname $0)"
6 |
7 | docker build -t $DOCKER_TAG "$SCRIPT_DIR/../submodules/testrpc"
8 | docker tag $DOCKER_TAG $DOCKER_ROOT_TAG:latest
9 | docker push $DOCKER_TAG
10 | docker push $DOCKER_ROOT_TAG:latest
11 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simple Storage Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/simplestorage.js:
--------------------------------------------------------------------------------
1 | const SimpleStorage = artifacts.require('./SimpleStorage.sol')
2 |
3 | contract('SimpleStorage', (accounts) => {
4 |
5 | it("should put 10 as the default value", () => {
6 | return SimpleStorage.deployed()
7 | .then((instance) => {
8 | return instance.get.call()
9 | })
10 | .then((storedData) => {
11 | assert.equal(storedData.valueOf(), 10, "10 wasn't stored by default")
12 | })
13 | })
14 |
15 | })
16 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | // Import the page's CSS. Webpack will know what to do with it.
2 | import './style/app.css'
3 |
4 | import React from 'react'
5 | import { Provider } from 'react-redux'
6 | import { render } from 'react-dom'
7 |
8 | import './init'
9 |
10 | import store from './store'
11 |
12 | import App from './components/App/index.js'
13 |
14 | render(
15 |
16 |
17 | ,
18 | document.getElementById('app')
19 | )
20 |
--------------------------------------------------------------------------------
/app/init/web3.js:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3'
2 |
3 | let web3;
4 | if (typeof window !== 'undefined' &&
5 | typeof window.web3 !== 'undefined') {
6 | // Use Mist/MetaMask's provider
7 | web3 = new Web3(window.web3.currentProvider)
8 | } else {
9 | // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
10 | web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
11 | }
12 |
13 | export {
14 | web3
15 | }
16 |
--------------------------------------------------------------------------------
/app/init/contracts.js:
--------------------------------------------------------------------------------
1 | import contract from 'truffle-contract'
2 |
3 | import SimpleStorage from '../../contracts/SimpleStorage.sol'
4 |
5 | export const contractArtifacts = {
6 | SimpleStorage
7 | }
8 |
9 | export const initialise = (contractArtifacts, web3) => {
10 | let contracts = {};
11 | for (const k in contractArtifacts) {
12 | const contract_ = contract(contractArtifacts[k])
13 | contract_.setProvider(web3.currentProvider)
14 | contracts[k] = contract_
15 | }
16 | return contracts
17 | }
18 |
--------------------------------------------------------------------------------
/app/reducers/ipfs.js:
--------------------------------------------------------------------------------
1 | const ipfs = (ipfsState = {}, action) => {
2 | // Pre-checks
3 | let state = ipfsState
4 | if (!action.info || !state.instance) {
5 | state = getIpfs(state)
6 | }
7 | if (!state.instance) return state
8 |
9 | // Handler for actions
10 | switch (action.type) {
11 |
12 | }
13 | return state
14 | }
15 |
16 | const getIpfs = (state) => {
17 | return (typeof window.ipfs === 'object') ?
18 | { ...state, instance: window.ipfs } :
19 | { ...state, error: 'No IPFS found!' }
20 | }
21 |
22 | export default ipfs
23 |
--------------------------------------------------------------------------------
/app/init/index.js:
--------------------------------------------------------------------------------
1 | import { contractArtifacts, initialise } from './contracts'
2 | import { web3 } from './web3'
3 | // import { ipfs } from './storage'
4 |
5 | window.web3 = web3
6 | window.contracts = initialise(contractArtifacts, web3)
7 | // window.ipfs = ipfs
8 | window.ipfs = window.IpfsApi()
9 |
10 | if (module.hot) {
11 | let { contractArtifacts } = require('./contracts')
12 | // let { ipfs } = require('./storage')
13 |
14 | window.contracts = initialise(contractArtifacts, web3)
15 | // window.ipfs = ipfs
16 | window.ipfs = window.IpfsApi()
17 | }
18 |
--------------------------------------------------------------------------------
/contracts/Migrations.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 |
3 | contract Migrations {
4 | address public owner;
5 | uint public last_completed_migration;
6 |
7 | modifier restricted() {
8 | if (msg.sender == owner) _;
9 | }
10 |
11 | function Migrations() {
12 | owner = msg.sender;
13 | }
14 |
15 | function setCompleted(uint completed) restricted {
16 | last_completed_migration = completed;
17 | }
18 |
19 | function upgrade(address new_address) restricted {
20 | Migrations upgraded = Migrations(new_address);
21 | upgraded.setCompleted(last_completed_migration);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/actions/ipfs.js:
--------------------------------------------------------------------------------
1 | // Write file to IPFS
2 | export const IPFS_WRITE_FILE_PENDING = 'IPFS_WRITE_FILE_PENDING'
3 | export const IPFS_WRITE_FILE_SUCCESS = 'IPFS_WRITE_FILE_SUCCESS'
4 | export const IPFS_WRITE_FILE_ERROR = 'IPFS_WRITE_FILE_ERROR'
5 |
6 | export const ipfsWriteFile = () => (dispatch) => {
7 | dispatch(ipfsWriteFilePending())
8 |
9 | }
10 |
11 | const ipfsWriteFilePending = () => {
12 | return {
13 | type: IPFS_WRITE_FILE_PENDING
14 | }
15 | }
16 |
17 | const ipfsWriteFileSuccess = (fileMetadata) => {
18 | return {
19 | type: IPFS_WRITE_FILE_SUCCESS,
20 | fileMetadata
21 | }
22 | }
23 |
24 | const ipfsWriteFileError = (error) => {
25 | return {
26 | type: IPFS_WRITE_FILE_ERROR,
27 | error
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/spec/actions/accounts.spec.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 |
3 | import { web3 } from '../../app/init/web3'
4 |
5 | import {
6 | accountsInitPending,
7 | accountsInitSuccess
8 | } from '../../app/actions/accounts'
9 |
10 | describe('Actions: accounts', () => {
11 | it('should provide a blank account init pending', () => {
12 | const expected = {
13 | type: 'ACCOUNTS_INIT_PENDING'
14 | }
15 | expect(accountsInitPending()).to.deep.equal(expected)
16 | })
17 |
18 | it('should provide a filled init success', () => {
19 | const accounts = [{ random: 1 }]
20 | const expected = {
21 | type: 'ACCOUNTS_INIT_SUCCESS',
22 | accounts
23 | }
24 | expect(accountsInitSuccess(accounts)).to.deep.equal(expected)
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/app/reducers/accounts.js:
--------------------------------------------------------------------------------
1 | import {
2 | ACCOUNTS_INIT_PENDING,
3 | ACCOUNTS_INIT_SUCCESS,
4 | ACCOUNTS_INIT_ERROR
5 | } from '../actions'
6 |
7 | const accounts = (state = {}, action) => {
8 | switch (action.type) {
9 | case ACCOUNTS_INIT_SUCCESS:
10 | return handleAccountsInitSuccess(state, action.accounts)
11 | case ACCOUNTS_INIT_ERROR:
12 | return handleAccountsInitError(state, action.error)
13 | }
14 | return state
15 | }
16 |
17 | // Accounts has to have at least one value
18 | const handleAccountsInitSuccess = (state, accounts) => {
19 | return {
20 | ...state,
21 | all: accounts,
22 | default: accounts[0]
23 | }
24 | }
25 |
26 | const handleAccountsInitError = (state, error) => {
27 | return {
28 | ...state,
29 | error
30 | }
31 | }
32 |
33 | export default accounts
34 |
--------------------------------------------------------------------------------
/app/style/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: "Open Sans", sans-serif;
4 | }
5 |
6 | label {
7 | display: inline-block;
8 | width: 100px;
9 | }
10 |
11 | input {
12 | width: 500px;
13 | padding: 5px;
14 | font-size: 16px;
15 | }
16 |
17 | button {
18 | font-size: 16px;
19 | padding: 5px;
20 | }
21 |
22 | h1, h2 {
23 | display: block;
24 | vertical-align: middle;
25 | margin-top: 0px;
26 | margin-bottom: 10px;
27 | }
28 |
29 | h2 {
30 | color: #AAA;
31 | font-size: 32px;
32 | }
33 |
34 | h3 {
35 | font-weight: normal;
36 | color: #AAA;
37 | font-size: 24px;
38 | }
39 |
40 | .app {
41 | margin: auto;
42 | padding: 50px 0;
43 | max-width: 500px;
44 | }
45 |
46 | .black {
47 | color: black;
48 | }
49 |
50 | #balance {
51 | color: black;
52 | }
53 |
54 | .hint {
55 | color: #666;
56 | }
57 |
--------------------------------------------------------------------------------
/app/store.js:
--------------------------------------------------------------------------------
1 | // Redux store
2 | import { applyMiddleware, compose, createStore } from 'redux'
3 | import thunk from 'redux-thunk'
4 | import logger from 'redux-logger'
5 |
6 | import reducers from './reducers'
7 | import {
8 | accountsInit
9 | } from './actions'
10 |
11 | let middleware = (process.env.NODE_ENV !== 'production')
12 | ? applyMiddleware(thunk, logger())
13 | : applyMiddleware(thunk)
14 |
15 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
16 |
17 | const store = createStore(reducers, {}, composeEnhancer(middleware))
18 |
19 | // Get accounts on store load
20 | store.dispatch(accountsInit())
21 |
22 | if (module.hot) {
23 | // Enable Webpack hot module replacement for reducers
24 | module.hot.accept('./reducers', () => {
25 | const nextReducers = require('./reducers/index')
26 | store.replaceReducer(nextReducers)
27 | })
28 | }
29 |
30 | export default store
31 |
--------------------------------------------------------------------------------
/app/actions/accounts.js:
--------------------------------------------------------------------------------
1 | // Accounts initialise
2 | export const ACCOUNTS_INIT_PENDING = 'ACCOUNTS_INIT_PENDING'
3 | export const ACCOUNTS_INIT_SUCCESS = 'ACCOUNTS_INIT_SUCCESS'
4 | export const ACCOUNTS_INIT_ERROR = 'ACCOUNTS_INIT_ERROR'
5 |
6 | export const accountsInit = () => (dispatch) => {
7 | dispatch(accountsInitPending())
8 | if (window.web3) {
9 | const web3 = window.web3
10 | web3.eth.getAccounts((err, accs) => {
11 | if (err != null) {
12 | dispatch(accountsInitError(err))
13 | } else if (accs.length == 0) {
14 | dispatch(accountsInitError('No accounts found!'))
15 | } else {
16 | dispatch(accountsInitSuccess(accs))
17 | }
18 | })
19 | } else {
20 | dispatch(accountsInitError('No web3 object!'))
21 | }
22 | }
23 |
24 | export const accountsInitPending = () => {
25 | return {
26 | type: ACCOUNTS_INIT_PENDING
27 | }
28 | }
29 |
30 | export const accountsInitSuccess = (accounts) => {
31 | return {
32 | type: ACCOUNTS_INIT_SUCCESS,
33 | accounts
34 | }
35 | }
36 |
37 | export const accountsInitError = (error) => {
38 | return {
39 | type: ACCOUNTS_INIT_ERROR,
40 | error
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Truffle with Webpack Boilerplate
2 |
3 | [](https://travis-ci.com/FreddieLindsey/truffle-webpack-boilerplate)
4 |
5 | ## Purpose
6 |
7 | There are quite a number of truffle boilerplates out there, but none that really completely worked out of the box. This is particularly the case with Truffle version 3.x. This boilerplate works completely with version 3.x as far as I know.
8 |
9 | Here, just run the following:
10 |
11 | ```bash
12 | yarn run services
13 | yarn start
14 | ```
15 |
16 | This should get you going with a testrpc instance running on Docker. Starting yarn then brings up webpack which migrates and hot-reloads your contracts as you edit them.
17 |
18 | Have fun!
19 |
20 | ## Docker
21 |
22 | I couldn't find the Docker image referenced in the [testrpc repository](https://github.com/ethereumjs/testrpc). I have therefore built it and pushed it publicly to Docker. Please kindly recognise that this work is not my own, but that of [ethereumjs](https://github.com/ethereumjs).
23 |
24 | ## Issues
25 |
26 | Please feel free to contact me or submit a PR if you find any issues you'd like to share.
27 |
--------------------------------------------------------------------------------
/app/reducers/contracts/SimpleStorage.js:
--------------------------------------------------------------------------------
1 | import {
2 | // SIMPLESTORAGE_ADDRESS_GET_PENDING,
3 | SIMPLESTORAGE_ADDRESS_GET_SUCCESS,
4 | SIMPLESTORAGE_ADDRESS_GET_ERROR,
5 | // SIMPLESTORAGE_VALUE_GET_PENDING,
6 | SIMPLESTORAGE_VALUE_GET_SUCCESS,
7 | SIMPLESTORAGE_VALUE_GET_ERROR,
8 | // SIMPLESTORAGE_VALUE_SET_PENDING,
9 | SIMPLESTORAGE_VALUE_SET_SUCCESS,
10 | SIMPLESTORAGE_VALUE_SET_ERROR
11 | } from '../../actions'
12 |
13 | const SimpleStorage = (state = {}, action) => {
14 | switch (action.type) {
15 | case SIMPLESTORAGE_ADDRESS_GET_SUCCESS:
16 | return handleAddressGetSuccess(state, action.address)
17 | case SIMPLESTORAGE_ADDRESS_GET_ERROR:
18 | return handleAddressGetError(state, action.error)
19 | case SIMPLESTORAGE_VALUE_GET_SUCCESS:
20 | return handleValueGetSuccess(state, action.value)
21 | case SIMPLESTORAGE_VALUE_GET_ERROR:
22 | return handleValueGetError(state, action.error)
23 | case SIMPLESTORAGE_VALUE_SET_SUCCESS:
24 | return handleValueSetSuccess(state, action.value)
25 | case SIMPLESTORAGE_VALUE_SET_ERROR:
26 | return handleValueSetError(state, action.error)
27 | }
28 | return state
29 | }
30 |
31 | const handleAddressGetSuccess = (state, address) => {
32 | return {
33 | ...state,
34 | error: undefined,
35 | address
36 | }
37 | }
38 |
39 | const handleAddressGetError = (state, error) => {
40 | return {
41 | ...state,
42 | error
43 | }
44 | }
45 |
46 | const handleValueGetSuccess = (state, value) => {
47 | return {
48 | ...state,
49 | error: undefined,
50 | value
51 | }
52 | }
53 |
54 | const handleValueGetError = (state, error) => {
55 | return {
56 | ...state,
57 | error
58 | }
59 | }
60 |
61 | const handleValueSetSuccess = (state, value) => {
62 | return {
63 | ...state,
64 | error: undefined,
65 | value
66 | }
67 | }
68 |
69 | const handleValueSetError = (state, error) => {
70 | return {
71 | ...state,
72 | error
73 | }
74 | }
75 |
76 | export default SimpleStorage
77 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint strict: 0 */
2 | 'use strict'
3 |
4 | var webpack = require('webpack')
5 | var path = require('path')
6 | var CopyWebpackPlugin = require('copy-webpack-plugin')
7 |
8 | module.exports = {
9 | cache: true,
10 | devtool: 'source-map',
11 | devServer: {
12 | stats: 'errors-only',
13 | },
14 | entry: './app/index.js',
15 | output: {
16 | path: path.resolve(__dirname, 'build'),
17 | filename: 'app.js'
18 | },
19 | resolve: {
20 | extensions: ['.js', '.jsx']
21 | },
22 | module: {
23 | rules: [
24 | {
25 | enforce: 'pre',
26 | test: /\.js(x)?$/,
27 | loader: 'eslint-loader'
28 | },
29 | {
30 | test: /\.js(x)?$/,
31 | exclude: /(node_modules)/,
32 | loader: 'babel-loader'
33 | },
34 | {
35 | test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/,
36 | loader: 'url-loader'
37 | },
38 | {
39 | test: /\.json$/,
40 | loader: 'json-loader'
41 | },
42 | {
43 | test: /\.(s)?css$/,
44 | use: [
45 | {
46 | loader: 'style-loader'
47 | }, {
48 | loader: 'css-loader',
49 | options: {
50 | camelCase: true,
51 | importLoaders: 1,
52 | localIdentName: '[path][name]__[local]--[hash:base64:5]',
53 | modules: true,
54 | sourceMap: true
55 | }
56 | }, {
57 | loader: 'sass-loader',
58 | options: { sourceMap: true }
59 | }
60 | ]
61 | },
62 | {
63 | test: /\.(png|jpg)$/,
64 | loader: 'url-loader?limit=8192'
65 | },
66 | {
67 | test: /\.sol/,
68 | loaders: ['json-loader', 'truffle-solidity-loader?network=development']
69 | }
70 | ],
71 | noParse: /lie\.js|[\s\S]*.(svg|ttf|eot)/
72 | },
73 | node: {
74 | fs: 'empty',
75 | net: 'empty',
76 | tls: 'empty'
77 | },
78 | plugins: [
79 | // Copy our app's index.html to the build folder.
80 | new CopyWebpackPlugin([
81 | { from: './app/index.html', to: 'index.html' }
82 | ]),
83 | new webpack.NoEmitOnErrorsPlugin(),
84 | new webpack.DefinePlugin({
85 | '__DEV__': true,
86 | 'process.env': {
87 | 'NODE_ENV': JSON.stringify('development')
88 | }
89 | })
90 | ],
91 | stats: {
92 | children: false
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "node": true,
5 | "browser": true,
6 | "es6": true,
7 | "mocha": true
8 | },
9 | "globals": {
10 | "React": true
11 | },
12 | "plugins": [
13 | "react"
14 | ],
15 | "ecmaFeatures": {
16 | "arrowFunctions": true,
17 | "blockBindings": true,
18 | "classes": true,
19 | "defaultParams": true,
20 | "destructuring": true,
21 | "forOf": true,
22 | "generators": false,
23 | "modules": true,
24 | "objectLiteralComputedProperties": true,
25 | "objectLiteralDuplicateProperties": false,
26 | "objectLiteralShorthandMethods": true,
27 | "objectLiteralShorthandProperties": true,
28 | "spread": true,
29 | "superInFunctions": true,
30 | "templateStrings": true,
31 | "jsx": true
32 | },
33 | "rules": {
34 | "no-bitwise": 1,
35 | "indent": [2, 2, { "SwitchCase": 1 }],
36 | "max-depth": [1, 4],
37 | "max-len": ["error", 100],
38 | "max-statements": [1, 30],
39 | "complexity": [1, 30],
40 | "quotes": 0,
41 | "eol-last": 1,
42 | "dot-notation": 0,
43 | "strict": [2, "never"],
44 | "react/display-name": 1,
45 | "react/jsx-boolean-value": 2,
46 | "react/jsx-no-undef": 2,
47 | "react/jsx-sort-props": 0,
48 | "react/jsx-sort-prop-types": 0,
49 | "react/jsx-uses-react": 2,
50 | "react/jsx-uses-vars": 2,
51 | "react/no-did-mount-set-state": 2,
52 | "react/no-did-update-set-state": 2,
53 | "react/no-multi-comp": 2,
54 | "react/no-unknown-property": 2,
55 | "react/prop-types": 2,
56 | "react/react-in-jsx-scope": 2,
57 | "react/self-closing-comp": 2,
58 | "react/jsx-wrap-multilines": 2,
59 | "react/sort-comp": [2, {
60 | "order": [
61 | "displayName",
62 | "propTypes",
63 | "contextTypes",
64 | "childContextTypes",
65 | "mixins",
66 | "statics",
67 | "defaultProps",
68 | "constructor",
69 | "getDefaultProps",
70 | "getInitialState",
71 | "getChildContext",
72 | "componentWillMount",
73 | "componentDidMount",
74 | "componentWillReceiveProps",
75 | "shouldComponentUpdate",
76 | "componentWillUpdate",
77 | "componentDidUpdate",
78 | "componentWillUnmount",
79 | "/^on.+$/",
80 | "/^get.+$/",
81 | "/^handle.+$/",
82 | "everything-else",
83 | "/^render.+$/",
84 | "render"
85 | ]
86 | }]
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "truffle_boilerplate",
3 | "version": "0.0.1",
4 | "description": "Frontend with tested contracts using webpack",
5 | "scripts": {
6 | "lint": "eslint ./",
7 | "build": "webpack",
8 | "build-docker": "./scripts/build_docker.sh",
9 | "clean": "rm -rf .node-xml* node_modules",
10 | "logs-ipfs": "docker logs ipfs --follow",
11 | "logs-testrpc": "docker logs testrpc --follow",
12 | "services": "./scripts/start_services.sh",
13 | "start": "webpack-dev-server --host 0.0.0.0 --port 3000 --hot --content-base build",
14 | "test": "yarn run test-contracts; yarn run test-web",
15 | "test-contracts": "truffle test",
16 | "test-web": "mocha --compilers js:babel-register spec/**/*.spec.js"
17 | },
18 | "author": "Frederick Lindsey",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "babel-cli": "^6.22.2",
22 | "babel-core": "^6.22.1",
23 | "babel-eslint": "^6.1.2",
24 | "babel-loader": "^6.2.10",
25 | "babel-plugin-transform-runtime": "^6.22.0",
26 | "babel-polyfill": "^6.23.0",
27 | "babel-preset-env": "^1.1.8",
28 | "babel-preset-latest": "^6.22.0",
29 | "babel-preset-react": "^6.23.0",
30 | "babel-preset-react-hmre": "^1.1.1",
31 | "babel-preset-stage-0": "^6.22.0",
32 | "babel-register": "^6.23.0",
33 | "chai": "^3.5.0",
34 | "copy-webpack-plugin": "^4.0.1",
35 | "css-loader": "^0.26.1",
36 | "eslint": "^3.14.0",
37 | "eslint-config-standard": "^6.0.0",
38 | "eslint-loader": "^1.6.3",
39 | "eslint-plugin-babel": "^4.0.0",
40 | "eslint-plugin-mocha": "^4.8.0",
41 | "eslint-plugin-promise": "^3.0.0",
42 | "eslint-plugin-react": "^6.10.0",
43 | "eslint-plugin-standard": "^2.0.0",
44 | "html-webpack-plugin": "^2.28.0",
45 | "ipfs-api": "^12.1.7",
46 | "json-loader": "^0.5.4",
47 | "mocha": "^3.2.0",
48 | "node-sass": "^4.5.0",
49 | "react": "^15.4.2",
50 | "react-dom": "^15.4.2",
51 | "react-redux": "^5.0.3",
52 | "redux": "^3.6.0",
53 | "redux-logger": "^2.8.1",
54 | "redux-thunk": "^2.2.0",
55 | "sass-loader": "^6.0.2",
56 | "style-loader": "^0.13.1",
57 | "truffle": "^3.2.4",
58 | "truffle-contract": "^1.1.6",
59 | "truffle-solidity-loader": "git+https://github.com/sogoiii/truffle-solidity-loader.git#1f1e213d52f033b6863218307b8968ae68220fe1",
60 | "web3": "^0.18.2",
61 | "webpack": "^2.2.1",
62 | "webpack-dev-server": "^2.4.1"
63 | },
64 | "dependencies": {
65 | "ethereumjs-testrpc": "^3.0.5"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/components/App/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { connect } from 'react-redux'
3 | import Web3 from 'web3'
4 |
5 | import {
6 | simpleStorageAddressGet,
7 | simpleStorageValueGet,
8 | simpleStorageValueSet
9 | } from '../../actions'
10 |
11 | const mapStateToProps = (state) => {
12 | return {
13 | accounts: state.accounts,
14 | SimpleStorage: state.SimpleStorage
15 | }
16 | }
17 |
18 | const mapDispatchToProps = (dispatch) => {
19 | return {
20 | handleAddressGet: () => simpleStorageAddressGet(dispatch),
21 | handleValueGet: () => simpleStorageValueGet(dispatch),
22 | handleValueSet: (v, a) => simpleStorageValueSet(dispatch, v, a)
23 | }
24 | }
25 |
26 | class App extends Component {
27 |
28 | static displayName = 'App'
29 | static propTypes = {
30 | accounts: PropTypes.shape({
31 | all: PropTypes.arrayOf(PropTypes.string),
32 | default: PropTypes.string
33 | }).isRequired,
34 | SimpleStorage: PropTypes.shape({
35 | address: PropTypes.string,
36 | value: PropTypes.any
37 | }).isRequired,
38 |
39 | handleAddressGet: PropTypes.func.isRequired,
40 | handleValueGet: PropTypes.func.isRequired,
41 | handleValueSet: PropTypes.func.isRequired
42 | }
43 |
44 | componentDidMount () {
45 | this.props.handleAddressGet()
46 | this.props.handleValueGet()
47 | }
48 |
49 | handleValueSet () {
50 | let newValue = this.textInput.value
51 | if (!newValue || newValue === '') return
52 | this.props.handleValueSet(newValue, this.props.accounts.default)
53 | }
54 |
55 | render () {
56 | const {
57 | address,
58 | value
59 | } = this.props.SimpleStorage
60 | return (
61 |
62 |
Simple Storage
63 |
64 | { address ?
65 |
66 |
67 | SimpleStorage deployed at { address }
68 |
69 |
70 | { value &&
71 |
72 |
73 |
74 | Current value: { value.toString() }
75 |
76 | }
77 |
78 | { value ?
79 |
80 | { this.textInput = i }} />
81 |
84 |
85 | :
86 |
89 | }
90 |
91 | :
92 |
95 | }
96 |
97 |
98 |
99 |
100 |
101 | Hint:
102 | Open the browser developer console to
103 | view redux state changes, errors and warnings.
104 |
105 |
106 | )
107 | }
108 |
109 | }
110 |
111 | export default connect(mapStateToProps, mapDispatchToProps)(App)
112 |
--------------------------------------------------------------------------------
/app/actions/contracts/SimpleStorage.js:
--------------------------------------------------------------------------------
1 | // Get Address
2 | export const SIMPLESTORAGE_ADDRESS_GET_PENDING = 'SIMPLESTORAGE_ADDRESS_GET_PENDING'
3 | export const SIMPLESTORAGE_ADDRESS_GET_SUCCESS = 'SIMPLESTORAGE_ADDRESS_GET_SUCCESS'
4 | export const SIMPLESTORAGE_ADDRESS_GET_ERROR = 'SIMPLESTORAGE_ADDRESS_GET_ERROR'
5 |
6 | export const simpleStorageAddressGet = (dispatch) => {
7 | dispatch(simpleStorageAddressGetPending())
8 | window.contracts.SimpleStorage.deployed()
9 | .then((instance) => {
10 | dispatch(simpleStorageAddressGetSuccess(instance.address))
11 | })
12 | .catch((err) => {
13 | dispatch(simpleStorageAddressGetError(err))
14 | })
15 | }
16 |
17 | const simpleStorageAddressGetPending = () => {
18 | return {
19 | type: SIMPLESTORAGE_ADDRESS_GET_PENDING
20 | }
21 | }
22 |
23 | const simpleStorageAddressGetSuccess = (address) => {
24 | return {
25 | type: SIMPLESTORAGE_ADDRESS_GET_SUCCESS,
26 | address
27 | }
28 | }
29 |
30 | const simpleStorageAddressGetError = (error) => {
31 | return {
32 | type: SIMPLESTORAGE_ADDRESS_GET_ERROR,
33 | error
34 | }
35 | }
36 |
37 | // Get Value
38 | export const SIMPLESTORAGE_VALUE_GET_PENDING = 'SIMPLESTORAGE_VALUE_GET_PENDING'
39 | export const SIMPLESTORAGE_VALUE_GET_SUCCESS = 'SIMPLESTORAGE_VALUE_GET_SUCCESS'
40 | export const SIMPLESTORAGE_VALUE_GET_ERROR = 'SIMPLESTORAGE_VALUE_GET_ERROR'
41 |
42 | export const simpleStorageValueGet = (dispatch) => {
43 | dispatch(simpleStorageValueGetPending())
44 | window.contracts.SimpleStorage.deployed()
45 | .then((instance) => {
46 | return instance.get()
47 | })
48 | .then((value) => {
49 | dispatch(simpleStorageValueGetSuccess(value))
50 | })
51 | .catch((error) => {
52 | dispatch(simpleStorageValueGetError(error))
53 | })
54 | }
55 |
56 | const simpleStorageValueGetPending = () => {
57 | return {
58 | type: SIMPLESTORAGE_VALUE_GET_PENDING
59 | }
60 | }
61 |
62 | const simpleStorageValueGetSuccess = (value) => {
63 | return {
64 | type: SIMPLESTORAGE_VALUE_GET_SUCCESS,
65 | value
66 | }
67 | }
68 |
69 | const simpleStorageValueGetError = (error) => {
70 | return {
71 | type: SIMPLESTORAGE_VALUE_GET_ERROR,
72 | error
73 | }
74 | }
75 |
76 | // Get Value
77 | export const SIMPLESTORAGE_VALUE_SET_PENDING = 'SIMPLESTORAGE_VALUE_SET_PENDING'
78 | export const SIMPLESTORAGE_VALUE_SET_SUCCESS = 'SIMPLESTORAGE_VALUE_SET_SUCCESS'
79 | export const SIMPLESTORAGE_VALUE_SET_ERROR = 'SIMPLESTORAGE_VALUE_SET_ERROR'
80 |
81 | export const simpleStorageValueSet = (dispatch, value, address) => {
82 | dispatch(simpleStorageValueSetPending())
83 | window.contracts.SimpleStorage.deployed()
84 | .then((instance) => {
85 | return instance.set(value, { from: address })
86 | })
87 | .then((receipt) => {
88 | console.dir(receipt)
89 | dispatch(simpleStorageValueSetSuccess(value))
90 | })
91 | .catch((error) => {
92 | dispatch(simpleStorageValueSetError(error))
93 | })
94 | }
95 |
96 | const simpleStorageValueSetPending = () => {
97 | return {
98 | type: SIMPLESTORAGE_VALUE_SET_PENDING
99 | }
100 | }
101 |
102 | const simpleStorageValueSetSuccess = (value) => {
103 | return {
104 | type: SIMPLESTORAGE_VALUE_SET_SUCCESS,
105 | value
106 | }
107 | }
108 |
109 | const simpleStorageValueSetError = (error) => {
110 | return {
111 | type: SIMPLESTORAGE_VALUE_SET_ERROR,
112 | error
113 | }
114 | }
115 |
--------------------------------------------------------------------------------