├── .eslintignore ├── .gitignore ├── README.md ├── config ├── .eslintrc ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── contracts ├── ConvertLib.sol ├── MetaCoin.sol ├── Migrations.sol └── SimpleWallet.sol ├── docs └── img │ ├── dapp-dev-env.png │ └── react-redux-dapp.png ├── license.MD ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package.json ├── public ├── css │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ └── styles.css ├── favicon.ico ├── index.html └── js │ ├── bootstrap.min.js │ └── jquery.min.js ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── actions │ └── index.js ├── components │ ├── Account.js │ ├── AccountItem.js │ ├── AccountTable.js │ ├── AccountsList.js │ ├── AddressDropdown.js │ ├── AmountInput.js │ ├── CryptoDropdown.js │ ├── HomePage.js │ ├── TransactionTable.js │ ├── nav │ │ ├── NavBar.js │ │ └── NavItem.js │ ├── services │ │ └── HttpService.js │ └── uport │ │ └── UPortView.jsx ├── containers │ ├── App.js │ └── YAEEContainer.js ├── index.js ├── middleware │ ├── crypto.js │ ├── cryptos.json │ ├── ethereum.js │ └── metacoin.js └── reducers │ ├── accounts.js │ ├── cryptos.js │ ├── index.js │ ├── transactions.js │ └── transfer.js ├── test ├── SimpleWalletTest.js ├── TestMetacoin.sol └── metacoin.js └── truffle.js /.eslintignore: -------------------------------------------------------------------------------- 1 | migrations/** 2 | test/SimpleWalletTest.js 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | 17 | # truffle compiles 18 | .truffle-solidity-loader 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-redux-dapp 2 | This is a template project that can be used as the basis for an Ethereum Dapp. 3 | 4 | 5 | 6 | # Prerequisites 7 | You'll want to have the following installed globally 8 | 9 | - [nodejs](https://nodejs.org/en/) 10 | - [testrpc](https://github.com/ethereumjs/testrpc) 11 | - [truffle v3.1.1+](http://truffleframework.com/) 12 | 13 | # This is how to use it 14 | In the project directory, run these commands: 15 | ``` 16 | npm install 17 | npm start 18 | ``` 19 | 20 | # Environment 21 | This is an overview of the development environment I use. 22 | 23 | 24 | 25 | 26 | # General Redux Data Flows in react-redux-dapp 27 | For reference, consider these depictions of generic redux flows taken from [ReachJS Issue #653](https://github.com/reactjs/redux/issues/653]. 28 | 29 | 30 | 31 | 32 | 33 | # Redux Data Flows in react-redux-dapp 34 | This section describes the reducers, state model and actions used in the YAEE sample application included in this project. 35 | 36 | ## Reducers 37 | The reducers maintain state 38 | 39 | ## Middleware 40 | 41 | ### Thunk 42 | The [redux-thunk](https://github.com/gaearon/redux-thunk) middleware component is used to incercept the asynchronous server calls from `web3.filter()` events. 43 | 44 | ### 45 | The [redux-logger](https://www.npmjs.com/package/redux-logger) middleware component provides clean logging of state before, actions and state after for debugging. 46 | 47 | 48 | ## State Model 49 | The state model represents on-chain data including `accounts` and transactions `transactions` and off-chain data like the list of `cryptos` or tokens that can be transfered and `transfer` details captured from the view. 50 | 51 | ## Actions 52 | The application state is initialized by calling actions `getAllAccounts`, `getCryptos` and `fetchTransactions`. For now, `fetchTransactions` initalizes the `web3.filter()` to listen for the latest transacations. 53 | 54 | 55 | 56 | # ---- STOP - Outdated Notes below ----- 57 | # - project has been update to ES6 since these notes started 58 | The notes below this point capture some of the journey to get here and need to be updated. 59 | 60 | # This is how it was built 61 | So now we've seen how easy it is to create a react application and we've seen how easy it is to create a Dapp with Truffle. 62 | 63 | So how do we combine these into a cohesive development environment? 64 | 65 | ```Linux 66 | create-react-app react-truffle 67 | cd react-truffle 68 | npm run eject # to 'unhide' the transitive dependencies 69 | ``` 70 | 71 | Now let's start configuring our react application with truffle integration. 72 | 73 | Following along with this Truffle tutorial [Bundling with Webpack](http://truffleframework.com/tutorials/bundling-with-webpack), we'll 74 | install some dependencies. Since the ejected React app already has webpack and 75 | webpack-dev-server installed, we just install the truffle-solidity-loader to 76 | enable Solidity support. 77 | 78 | ```Linux 79 | npm install truffle-solidity-loader --save-dev 80 | ``` 81 | 82 | ## Configure Webpack 83 | To configure the development environment so Webpack will use the `truffle-solidity-loader` to recompile and migrate updates to Solidity contracts, edit `config\webpack.config.dev.js` to load `.sol` files from your project. 84 | 85 | ```Javascript 86 | module: { 87 | ... 88 | loaders: [ 89 | ... 90 | }, 91 | { 92 | test: /\.sol/, loader: 'truffle-solidity' 93 | } 94 | ] 95 | }, 96 | ... 97 | ``` 98 | 99 | Next, create a truffle folder so we can isolate the Ethereum portfion of our Dapp. 100 | 101 | Create a truffle.js 102 | Your truffle.js will not have any build configuration since webpack will handle 103 | building your web application. (What happens if you run truffle build?) 104 | 105 | The only configuration in truffle.js is non-web configuration such as rpc. 106 | 107 | ```javascript 108 | module.exports = { 109 | rpc: { 110 | host: "localhost", 111 | port: 8545 112 | } 113 | } 114 | 115 | `babel-preset-react-app` conflicts with the `es2015` preset so used by Truffle so 116 | replace that with `babel-preset-react` so both will play nice. 117 | 118 | ```Linux 119 | npm uninstall --save-dev babel-preset-react-app 120 | npm install --save-dev babel-preset-react 121 | ``` 122 | 123 | ``` 124 | Edit `package.json` babel preset settings to `es2015`. Without this, 125 | running truffle commands will error. 126 | 127 | ```Javascript 128 | "babel": { 129 | "presets": [ 130 | "react", 131 | "es2015" 132 | ] 133 | }, 134 | ``` 135 | 136 | * Copy example contracts from truffle-basic project to our truffle folder 137 | * Copy migrations folder from truffle-basic project to our truffle folder 138 | * Copy test folder from truffle-basic project to our truffle folder 139 | 140 | We need to test that all our truffle functionality still works. 141 | 142 | # React 143 | Now let's add some structure to the React portion of this react-dapp project. 144 | The next few sections will walk through creating a healthy base structure 145 | to support a reactive Dapp application. 146 | 147 | ## Bootstrap 148 | Download the latest [Bootstrap libraries](http://getbootstrap.com/) and save 149 | `bootstrap.min.css` and `bootstrap-theme.min.css` to `public/css` folder. Save 150 | the corresponding `boostrap.min.js` to `public/js` folder as well as a version 151 | [jquery](https://jquery.com/). It's unlikely you'll need `jquery` anywhere 152 | else in your project but some of the `Bootstrap` components use it. 153 | 154 | Now update the `/public/index.html` to link the stylesheets and include the 155 | stylesheets. 156 | 157 | ``` 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | ... 166 | 167 | 168 | 169 | 170 | ``` 171 | 172 | ## Remove default React src 173 | Delete the `App.css`, `App.js`, `App.test.js`, `index.css`, and `logo.svg` from 174 | the `src` folder. 175 | 176 | ## Leverage react-router 177 | This project will leverage react-router. 178 | 179 | ```Linux 180 | npm install --save-dev react-router 181 | ``` 182 | 183 | Update `src/index.js`. 184 | 185 | ```javascript 186 | import React from 'react'; 187 | import ReactDOM from 'react-dom'; 188 | import Routes from './routes/Routes.jsx'; 189 | 190 | ReactDOM.render( , document.getElementById('root')); 191 | ``` 192 | 193 | ## What I like about react 194 | Once you have a base project structure in place, React development involves 195 | creating a collection of reusable components. 196 | 197 | Create the `src/components/Routes.jsx' 198 | 199 | ```JSX 200 | import React from 'react'; 201 | import { hashHistory, Router, Route, IndexRoute } from 'react-router'; 202 | 203 | import BasePage from './BasePage.jsx'; 204 | import HomePage from './HomePage.jsx'; 205 | import SimpleWallet from './wallets/SimpleWallet.jsx'; 206 | 207 | var Routes = React.createClass({ 208 | render: function() { 209 | return ( 210 | 211 | 212 | 213 | 214 | 215 | 216 | ) 217 | } 218 | }); 219 | 220 | module.exports = Routes; 221 | ``` 222 | 223 | ## Create BasePage.jsx 224 | This creates the common components of our application such as a Navigation Bar. 225 | 226 | ```JSX 227 | var React = require('react'); 228 | var NavBar = require('./nav/NavBar.jsx'); 229 | 230 | var navLinks = [{title: "Simple Wallet", href: "/simplewallet"}]; 231 | 232 | var BasePage = React.createClass({ 233 | render: function() { 234 | return ( 235 |
236 | 237 | {this.props.children} 238 |
239 | ); 240 | } 241 | }); 242 | 243 | module.exports = BasePage; 244 | ``` 245 | 246 | ## Create a bootstrap NavBar and associated NavItem components 247 | ```JSX 248 | var React = require('react'); 249 | var NavItem = require('./NavItem.jsx'); 250 | var ReactRouter = require('react-router'); 251 | var Link = ReactRouter.Link; 252 | 253 | var NavBar = React.createClass({ 254 | render: function() { 255 | 256 | var navStyle = { 257 | WebkitBoxShadow: "0 0 4px rgba(0,0,0,0.4)", 258 | MozBoxShadow: "0 0 4px rgba(0,0,0,0.4)", 259 | boxShadow: "0 0 4px rgba(0,0,0,0.4)", 260 | borderRadius: 0 261 | }; 262 | 263 | var createLinkItem = function(item, index) { 264 | return 265 | }; 266 | 267 | return ( 268 |
269 | 284 |
285 | ); 286 | } 287 | }); 288 | 289 | module.exports = NavBar; 290 | ``` 291 | 292 | ```JSX 293 | var React = require('react'); 294 | var ReactRouter = require('react-router'); 295 | var Link = ReactRouter.Link; 296 | 297 | var NavItem = React.createClass({ 298 | getInitialState: function() { 299 | return {hover: false}; 300 | }, 301 | mouseOver: function(e) { 302 | this.setState({hover: true}); 303 | }, 304 | mouseOut: function(e) { 305 | this.setState({hover: false}); 306 | }, 307 | render: function() { 308 | return ( 309 |
  • 310 | {this.props.title} 311 |
  • 312 | ); 313 | } 314 | }); 315 | 316 | module.exports = NavItem; 317 | ``` 318 | 319 | ## Create the HomePage.jsx component 320 | ```JSX 321 | var React = require('react'); 322 | var ReactRouter = require('react-router'); 323 | var Link = ReactRouter.Link; 324 | 325 | var HomePage = React.createClass({ 326 | render: function() { 327 | return ( 328 |
    329 |

    Welcome to react-dapp.

    330 |

    If you're wondering how you got here, 331 | see react-dapp 332 |

    333 |
    334 | ); 335 | } 336 | }); 337 | 338 | module.exports = HomePage; 339 | ``` 340 | 341 | ## Create the SimpleWallet.jsx component 342 | 343 | ## Introduce RefluxJS 344 | This will introduce a unidirectional dataflow architecture which enables any 345 | registered components to 'react' to events they are registered to. We'll use 346 | this along with Solidity Smart Contract events to make some dynamic components 347 | in our Dapp. 348 | 349 | See [React.js + Reflux Example](https://blog.ochronus.com/react-js-reflux-example-2d46a4d8faf0#.qujzxaj9d) 350 | for a good explanation. 351 | 352 | 353 | Install reflux. 354 | ```Linux 355 | npm install --save-dev reflux 356 | ``` 357 | 358 | Create `src/components/reflux/actions.jsx` 359 | 360 | ```JSX 361 | import Reflux from 'reflux'; 362 | 363 | var Actions = Reflux.createActions([ 364 | 'getAccounts' 365 | ]); 366 | 367 | module.exports = Actions; 368 | ``` 369 | 370 | ## Create AccountStore 371 | AccountStore will implement `getAccounts` and other Account related services. 372 | 373 | # Creating the SimpleWallet contract 374 | 375 | ----------------------------- 376 | 377 | ## Sending Feedback 378 | 379 | We are always open to [your feedback](https://github.com/facebookincubator/create-react-app/issues). 380 | 381 | 382 | In the project directory, you can run: 383 | 384 | ### `npm start` 385 | 386 | Runs the app in the development mode.
    387 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 388 | 389 | The page will reload if you make edits.
    390 | You will also see any lint errors in the console. 391 | 392 | ### `npm test` 393 | 394 | Launches the test runner in the interactive watch mode.
    395 | See the section about [running tests](#running-tests) for more information. 396 | 397 | ### `npm run build` 398 | 399 | Builds the app for production to the `build` folder.
    400 | It correctly bundles React in production mode and optimizes the build for the best performance. 401 | 402 | The build is minified and the filenames include the hashes.
    403 | Your app is ready to be deployed! 404 | 405 | See the section about [deployment](#deployment) for more information. 406 | 407 | ### `npm run eject` 408 | 409 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 410 | 411 | 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. 412 | 413 | 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. 414 | 415 | 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. 416 | 417 | ## Syntax Highlighting in the Editor 418 | 419 | To configure the syntax highlighting in your favorite text editor, head to the [Babel's docs](https://babeljs.io/docs/editors) and follow the instructions. Some of the most popular editors are covered. 420 | 421 | ## Displaying Lint Output in the Editor 422 | 423 | >Note: this feature is available with `react-scripts@0.2.0` and higher. 424 | 425 | Some editors, including Sublime Text, Atom, and Visual Studio Code, provide plugins for ESLint. 426 | 427 | They are not required for linting. You should see the linter output right in your terminal as well as the browser console. However, if you prefer the lint results to appear right in your editor, there are some extra steps you can do. 428 | 429 | You would need to install an ESLint plugin for your editor first. 430 | 431 | >**A note for Atom `linter-eslint` users** 432 | 433 | >If you are using the Atom `linter-eslint` plugin, make sure that **Use global ESLint installation** option is checked: 434 | 435 | > 436 | 437 | Then add this block to the `package.json` file of your project: 438 | 439 | ```js 440 | { 441 | // ... 442 | "eslintConfig": { 443 | "extends": "react-app" 444 | } 445 | } 446 | ``` 447 | 448 | Finally, you will need to install some packages *globally*: 449 | 450 | ```sh 451 | npm install -g eslint-config-react-app@0.3.0 eslint@3.8.1 babel-eslint@7.0.0 eslint-plugin-react@6.4.1 eslint-plugin-import@2.0.1 eslint-plugin-jsx-a11y@2.2.3 eslint-plugin-flowtype@2.21.0 452 | ``` 453 | 454 | We recognize that this is suboptimal, but it is currently required due to the way we hide the ESLint dependency. The ESLint team is already [working on a solution to this](https://github.com/eslint/eslint/issues/3458) so this may become unnecessary in a couple of months. 455 | 456 | ## Installing a Dependency 457 | 458 | The generated project includes React and ReactDOM as dependencies. It also includes a set of scripts used by Create React App as a development dependency. You may install other dependencies (for example, React Router) with `npm`: 459 | 460 | ``` 461 | npm install --save 462 | ``` 463 | 464 | ## Importing a Component 465 | 466 | This project setup supports ES6 modules thanks to Babel.
    467 | While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](http://exploringjs.com/es6/ch_modules.html) instead. 468 | 469 | For example: 470 | 471 | ### `Button.js` 472 | 473 | ```js 474 | import React, { Component } from 'react'; 475 | 476 | class Button extends Component { 477 | render() { 478 | // ... 479 | } 480 | } 481 | 482 | export default Button; // Don’t forget to use export default! 483 | ``` 484 | 485 | ### `DangerButton.js` 486 | 487 | 488 | ```js 489 | import React, { Component } from 'react'; 490 | import Button from './Button'; // Import a component from another file 491 | 492 | class DangerButton extends Component { 493 | render() { 494 | return 30 | react-dapp 31 | 32 | 35 | 36 | 37 | 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/nav/NavItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | export default class NavItem extends Component { 5 | constructor() { 6 | super() 7 | this.state = {hover: false} 8 | this.mouseOver = this.mouseOver.bind(this) 9 | this.mouseOut = this.mouseOut.bind(this) 10 | } 11 | 12 | mouseOver(e) { 13 | this.setState({hover: true}) 14 | } 15 | 16 | mouseOut(e) { 17 | this.setState({hover: false}) 18 | } 19 | 20 | render() { 21 | return ( 22 |
  • 23 | {this.props.title} 24 |
  • 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/services/HttpService.js: -------------------------------------------------------------------------------- 1 | // Thank you, Mark Price for sharing this with me in your lesson on reflux. 2 | 3 | var baseUrl = 'http://localhost:6069'; 4 | 5 | var service = { 6 | get: function(url) { 7 | return fetch(baseUrl + url).then(function(response) { 8 | return response.json(); 9 | }); 10 | }, 11 | post: function(url, data) { 12 | return fetch(baseUrl + url, { 13 | headers: { 14 | 'Accept': 'text/plain', 15 | 'Content-Type': 'application/json' 16 | }, 17 | method: 'post', 18 | body: JSON.stringify(data) 19 | }).then(function(response) { 20 | return response; 21 | }); 22 | } 23 | } 24 | 25 | module.exports = service; 26 | -------------------------------------------------------------------------------- /src/components/uport/UPortView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // import { Uport } from 'uport-lib'; 4 | // 5 | // let uport = new Uport('MyDApp'); 6 | // let web3 = uport.getWeb3(); 7 | // 8 | // uport.getUserPersona() 9 | // .then((persona) => { 10 | // let profile = persona.profile; 11 | // console.log(profile); 12 | // }); 13 | 14 | var UPortView = React.createClass({ 15 | render: function() { 16 | return ( 17 |
    18 | ) 19 | } 20 | }); 21 | 22 | module.exports = UPortView; 23 | -------------------------------------------------------------------------------- /src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NavBar from '../components/nav/NavBar' 3 | 4 | const navdata = [{"title": "Home", "href": "/"}, {"title": "YAEE", "href": "/yaee"}] 5 | 6 | class App extends React.Component { 7 | 8 | render() { 9 | return ( 10 |
    11 | 12 |
    13 | {this.props.children} 14 |
    15 | ) 16 | } 17 | } 18 | 19 | export default App 20 | -------------------------------------------------------------------------------- /src/containers/YAEEContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { getAllAccounts } from '../reducers/accounts' 4 | import { getSupportedCryptos } from '../reducers/cryptos' 5 | import { selectFromAddress, selectToAddress, selectCrypto, enterAmount, emitTransfer } from '../actions' 6 | import AddressDropdown from '../components/AddressDropdown' 7 | import CryptoDropdown from '../components/CryptoDropdown' 8 | import AmountInput from '../components/AmountInput' 9 | import AccountTable from '../components/AccountTable' 10 | import TransactionTable from '../components/TransactionTable' 11 | 12 | const YAEEContainer = ({ accounts, cryptos, transactions, selectFromAddress, selectToAddress, selectCrypto, enterAmount, emitTransfer }) => { 13 | return ( 14 |
    15 |
    16 |
    17 |
    18 |

    YAEE - Yet Another Ethereum Explorer

    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 | 26 | 27 | 28 |
    29 |
    30 |
    31 |
    32 | 33 |
    34 |
    35 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 | 42 |
    43 | 44 |
    45 |
    46 |
    47 | )} 48 | 49 | const mapStateToProps = state => ({ 50 | accounts: getAllAccounts(state.accounts), 51 | cryptos: getSupportedCryptos(state.cryptos), 52 | transactions: state.transactions 53 | }) 54 | 55 | export default connect( 56 | mapStateToProps, 57 | { selectFromAddress, selectToAddress, selectCrypto, enterAmount, emitTransfer } 58 | )(YAEEContainer) 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Router, Route, browserHistory } from 'react-router' 4 | import { createStore, applyMiddleware } from 'redux' 5 | import { Provider } from 'react-redux' 6 | import createLogger from 'redux-logger' 7 | import thunk from 'redux-thunk' 8 | import thunkMiddleware from 'redux-thunk' 9 | import reducer from './reducers' 10 | import { getAllAccounts } from './actions' 11 | import { getCryptos } from './actions' 12 | import { fetchTransactions } from './actions' 13 | import App from './containers/App' 14 | import YAEEContainer from './containers/YAEEContainer' 15 | 16 | const middleware = [ thunk ]; 17 | if (process.env.NODE_ENV !== 'production') { 18 | middleware.push(createLogger()); 19 | } 20 | 21 | const store = createStore( 22 | reducer, 23 | applyMiddleware(thunkMiddleware, ...middleware), 24 | ) 25 | store.dispatch(getAllAccounts()) 26 | store.dispatch(getCryptos()) 27 | store.dispatch(fetchTransactions()) 28 | 29 | render( 30 | 31 | 32 | 33 | 34 | 35 | 36 | , 37 | document.getElementById('root') 38 | ) 39 | -------------------------------------------------------------------------------- /src/middleware/crypto.js: -------------------------------------------------------------------------------- 1 | import _cryptos from './cryptos.json' 2 | 3 | const TIMEOUT = 100 4 | 5 | export default { 6 | getCryptos: (cb, timeout) => setTimeout(() => cb(_cryptos), timeout || TIMEOUT), 7 | } 8 | -------------------------------------------------------------------------------- /src/middleware/cryptos.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": 1, "name": "ether", "symbol": "ETH"}, 3 | {"id": 2, "name": "coin", "symbol": "META"} 4 | ] 5 | -------------------------------------------------------------------------------- /src/middleware/ethereum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ethereum service API 3 | */ 4 | import Web3 from 'web3' 5 | import { getBalance, sendCoin } from './metacoin' 6 | 7 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 8 | 9 | const CADcoin = 68 10 | 11 | export default { 12 | getAccounts: (cb) => { 13 | web3.eth.getAccounts(async function(error, addresses) { 14 | let _accounts = []; 15 | 16 | for (var i = 0; i < addresses.length; i++) { 17 | let eth = web3.fromWei(web3.eth.getBalance(addresses[i]), 'ether') 18 | _accounts.push({ 19 | address: addresses[i], 20 | balance: eth, 21 | cadCoin: eth * CADcoin, 22 | meta: await getBalance(addresses[i]) 23 | }) 24 | } 25 | cb(_accounts); 26 | }) 27 | }, 28 | transfer: (transaction) => { 29 | 30 | if (transaction.crypto === 'ETH') { 31 | const amount = web3.toWei(transaction.amount, "ether") 32 | 33 | return web3.eth.sendTransaction({from:transaction.from, to:transaction.to, value: amount}) 34 | } else { 35 | return sendCoin(transaction) 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/middleware/metacoin.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3' 2 | import { default as contract } from 'truffle-contract' 3 | // Import our contract artifacts and turn them into usable abstractions. 4 | import metacoin_artifacts from '../../build/contracts/MetaCoin.json' 5 | 6 | var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); 7 | 8 | // MetaCoin is our usable abstraction, which we'll use through the code below. 9 | var MetaCoin = contract(metacoin_artifacts); 10 | 11 | // Bootstrap the MetaCoin abstraction for Use. 12 | MetaCoin.setProvider(web3.currentProvider); 13 | 14 | export const sendCoin = async (transaction) => { 15 | 16 | // let result = MetaCoin.deployed().then(function(instance) { 17 | // return instance.sendCoin(transaction.to, transaction.amount, {from: transaction.from}); 18 | // }) 19 | let meta = await MetaCoin.deployed() 20 | let result = await meta.sendCoin(transaction.to, transaction.amount, {from: transaction.from}); 21 | 22 | console.log(`result: ${result}`) 23 | return result 24 | 25 | } 26 | 27 | export const getBalance = async (address) => { 28 | try { 29 | let meta = await MetaCoin.deployed() 30 | let balance = await meta.getBalance.call(address) 31 | 32 | return balance.toNumber() 33 | } catch (err) { 34 | return 'Not Deployed' 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/reducers/accounts.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import deepfreeze from 'deep-freeze' 3 | import expect from 'expect' 4 | 5 | export const RECEIVE_ACCOUNTS = 'RECEIVE_ACCOUNTS' 6 | export const ADD_ACCOUNT = 'ADD_ACCOUNT' 7 | 8 | const accounts = (state = {}, action) => { 9 | switch (action.type) { 10 | case ADD_ACCOUNT: 11 | return { 12 | ...state, 13 | accounts: action.accounts 14 | } 15 | default: 16 | return state 17 | } 18 | } 19 | 20 | const byAddress = (state = {}, action) => { 21 | switch (action.type) { 22 | case RECEIVE_ACCOUNTS: 23 | return { 24 | ...state, 25 | ...action.accounts.reduce((obj, account) => { 26 | obj[account.address] = account 27 | return obj 28 | }, {}) 29 | } 30 | default: 31 | const { accountAddress } = action 32 | if (accountAddress) { 33 | return { 34 | ...state, 35 | [accountAddress]: accounts(state[accountAddress], action) 36 | } 37 | } 38 | return state 39 | } 40 | } 41 | 42 | const allAccounts = (state = [], action) => { 43 | switch (action.type) { 44 | case RECEIVE_ACCOUNTS: 45 | return action.accounts.map(account => account.address) 46 | default: 47 | return state 48 | } 49 | } 50 | 51 | export default combineReducers({ 52 | byAddress, 53 | allAccounts 54 | }) 55 | 56 | export const getAccount = (state, address) => 57 | state.byAddress[address] 58 | 59 | export const getAllAccounts = state => 60 | state.allAccounts.map(address => getAccount(state, address)) 61 | 62 | 63 | const testAccounts = () => { 64 | const stateBefore = []; 65 | const action = { 66 | type: RECEIVE_ACCOUNTS, 67 | accounts: [ 68 | { address: 'abc', balance: 100} 69 | ] 70 | } 71 | const stateAfter = { 72 | abc: { address: 'abc', balance: 100} 73 | }; 74 | 75 | deepfreeze(stateBefore); 76 | deepfreeze(action); 77 | // expect( 78 | // accounts(stateBefore, action) 79 | // ).toEqual(stateAfter); 80 | 81 | expect( 82 | byAddress(stateBefore, action) 83 | ).toEqual(stateAfter); 84 | }; 85 | 86 | testAccounts(); 87 | console.log('All tests passed'); 88 | -------------------------------------------------------------------------------- /src/reducers/cryptos.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | export const RECEIVE_CRYPTOS = 'RECEIVE_CRYPTOS' 4 | 5 | const byId = (state = {}, action) => { 6 | //debugger; 7 | switch (action.type) { 8 | case RECEIVE_CRYPTOS: 9 | return { 10 | ...state, 11 | ...action.cryptos.reduce((obj, crypto) => { 12 | obj[crypto.id] = crypto 13 | return obj 14 | }, {}) 15 | } 16 | default: 17 | return state 18 | } 19 | } 20 | 21 | const allCryptos = (state = [], action) => { 22 | //debugger; 23 | switch (action.type) { 24 | case RECEIVE_CRYPTOS: 25 | return action.cryptos.map(crypto => crypto.id) 26 | default: 27 | return state 28 | } 29 | } 30 | 31 | export const getCryptoById = (state, id) => 32 | state.byId[id] 33 | 34 | export const getSupportedCryptos = state => 35 | state.allCryptos.map(id => getCryptoById(state, id)) 36 | 37 | export default combineReducers({ 38 | byId, 39 | allCryptos 40 | }) 41 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | import accounts from './accounts' 4 | import cryptos from './cryptos' 5 | import transfer from './transfer' 6 | import transactions from './transactions' 7 | 8 | export default combineReducers({ accounts, transactions, transfer, cryptos }) 9 | -------------------------------------------------------------------------------- /src/reducers/transactions.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3' 2 | 3 | export const SHOW_TRANSACTIONS = 'SHOW_TRANSACTIONS' 4 | export const ADD_TRANSACTION = 'ADD_TRANSACTION' 5 | 6 | const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) 7 | 8 | const transaction = (state, action) => { 9 | switch (action.type) { 10 | case ADD_TRANSACTION: 11 | return { 12 | blockHash: action.transaction.blockHash, 13 | blockNumber: action.transaction.blockNumber, 14 | hash: action.transaction.hash, 15 | from: action.transaction.from, 16 | to: action.transaction.to, 17 | nonce: action.transaction.nonce, 18 | gasUsed: action.transaction.receipt.gasUsed, 19 | gasPrice: action.transaction.gasPrice, 20 | cost: action.transaction.receipt.gasUsed * action.transaction.gasPrice.toNumber(), 21 | costEth: web3.fromWei(action.transaction.receipt.gasUsed * action.transaction.gasPrice.toNumber(), 'ether'), 22 | costCAD: web3.fromWei(action.transaction.receipt.gasUsed * action.transaction.gasPrice.toNumber(), 'ether') * 68 23 | } 24 | default: 25 | return state 26 | } 27 | } 28 | 29 | const transactions = (state = [], action) => { 30 | switch (action.type) { 31 | case ADD_TRANSACTION: 32 | return [ 33 | transaction(undefined, action), 34 | ...state 35 | ] 36 | default: 37 | return state 38 | } 39 | } 40 | 41 | export default transactions 42 | -------------------------------------------------------------------------------- /src/reducers/transfer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | export const SELECT_CRYPTO = 'SELECT_CRYPTO' 4 | export const SELECT_FROM_ADDRESS = 'SELECT_FROM_ADDRESS' 5 | export const SELECT_TO_ADDRESS = 'SELECT_TO_ADDRESS' 6 | export const ENTER_AMOUNT = 'ENTER_AMOUNT' 7 | 8 | const detail = (state = {}, action) => { 9 | switch (action.type) { 10 | case SELECT_FROM_ADDRESS: 11 | console.log(`reducer: SELECT_FROM_ADDRESS ${action.fromAddress}`) 12 | return { 13 | ...state, 14 | from: action.address 15 | } 16 | case SELECT_TO_ADDRESS: 17 | return { 18 | ...state, 19 | to: action.address 20 | } 21 | case SELECT_CRYPTO: 22 | return { 23 | ...state, 24 | crypto: action.crypto 25 | } 26 | case ENTER_AMOUNT: 27 | return { 28 | ...state, 29 | amount: action.amount 30 | } 31 | default: 32 | return state 33 | } 34 | } 35 | 36 | export default combineReducers({ 37 | detail 38 | }) 39 | -------------------------------------------------------------------------------- /test/SimpleWalletTest.js: -------------------------------------------------------------------------------- 1 | contract('SimpleWallet', function(accounts) { 2 | it('the owner is allowed to send funds', function() { 3 | var wallet = SimpleWallet.deployed() 4 | return wallet.isAllowedToSend.call(accounts[0]).then(function(isAllowed) { 5 | assert.equal(isAllowed, true, 'the owner should have been allowed to send funds'); 6 | }) 7 | }); 8 | 9 | it('the other account should not be allowed to send funds', function() { 10 | var wallet = SimpleWallet.deployed(); 11 | return wallet.isAllowedToSend.call(accounts[2]).then(function(isAllowed) { 12 | assert.equal(isAllowed, false, 'the other account was allowed'); 13 | }) 14 | }); 15 | 16 | it('adding accounts to the allowed list', function() { 17 | var wallet = SimpleWallet.deployed(); 18 | 19 | return wallet.isAllowedToSend.call(accounts[1]).then(function(isAllowed) { 20 | assert.equal(isAllowed, false, 'the other account was allowed'); 21 | }).then(function() { 22 | return wallet.allowAddressToSendMoney(accounts[1]); 23 | }).then(function() { 24 | return wallet.isAllowedToSend.call(accounts[1]); 25 | }).then(function(isAllowed) { 26 | assert.equal(isAllowed, true, 'the other account was not allowed'); 27 | }).then(function() { 28 | return wallet.disallowAddressToSendMoney(accounts[1]); 29 | }).then(function() { 30 | return wallet.isAllowedToSend.call(accounts[1]); 31 | }).then(function(isAllowed) { 32 | assert.equal(isAllowed, false, 'the account was allowed'); 33 | }) 34 | }); 35 | 36 | it('should check Deposit events', function(done) { 37 | var wallet = SimpleWallet.deployed(); 38 | 39 | var event = wallet.allEvents(); 40 | event.watch(function(error, result) { 41 | if (error) { 42 | console.err(error); 43 | } else { 44 | // now we'll check that the events are correctly 45 | assert.equal(result.event, "Deposit"); 46 | assert.equal(web3.fromWei(result.args.amount.valueOf(), 'ether'), 1); 47 | assert.equal(result.args._sender.valueOf(), web3.eth.accounts[0]); 48 | event.stopWatching(); 49 | done(); 50 | } 51 | }); 52 | // send ether 53 | web3.eth.sendTransaction({from: web3.eth.accounts[0], to: wallet.address, value: web3.toWei(1, 'ether')}); 54 | }); 55 | 56 | it('should check not allowed Deposit events', function(done) { 57 | var wallet = SimpleWallet.deployed(); 58 | 59 | // send ether 60 | web3.eth.sendTransaction({from: web3.eth.accounts[1], to: wallet.address, value: web3.toWei(1, 'ether')}, function(error, result) { 61 | if (error) { 62 | done(); 63 | } else { 64 | done(result); 65 | } 66 | }); 67 | 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /test/TestMetacoin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | import "truffle/Assert.sol"; 4 | import "truffle/DeployedAddresses.sol"; 5 | import "../contracts/MetaCoin.sol"; 6 | 7 | contract TestMetacoin { 8 | 9 | function testInitialBalanceUsingDeployedContract() { 10 | MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin()); 11 | 12 | uint expected = 10000; 13 | 14 | Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially"); 15 | } 16 | 17 | function testInitialBalanceWithNewMetaCoin() { 18 | MetaCoin meta = new MetaCoin(); 19 | 20 | uint expected = 10000; 21 | 22 | Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /test/metacoin.js: -------------------------------------------------------------------------------- 1 | var MetaCoin = artifacts.require("./MetaCoin.sol"); 2 | 3 | contract('MetaCoin', function(accounts) { 4 | it("should put 10000 MetaCoin in the first account", function() { 5 | return MetaCoin.deployed().then(function(instance) { 6 | return instance.getBalance.call(accounts[0]); 7 | }).then(function(balance) { 8 | assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account"); 9 | }); 10 | }); 11 | it("should call a function that depends on a linked library", function() { 12 | var meta; 13 | var metaCoinBalance; 14 | var metaCoinEthBalance; 15 | 16 | return MetaCoin.deployed().then(function(instance) { 17 | meta = instance; 18 | return meta.getBalance.call(accounts[0]); 19 | }).then(function(outCoinBalance) { 20 | metaCoinBalance = outCoinBalance.toNumber(); 21 | return meta.getBalanceInEth.call(accounts[0]); 22 | }).then(function(outCoinBalanceEth) { 23 | metaCoinEthBalance = outCoinBalanceEth.toNumber(); 24 | }).then(function() { 25 | assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken"); 26 | }); 27 | }); 28 | it("should send coin correctly", function() { 29 | var meta; 30 | 31 | // Get initial balances of first and second account. 32 | var account_one = accounts[0]; 33 | var account_two = accounts[1]; 34 | 35 | var account_one_starting_balance; 36 | var account_two_starting_balance; 37 | var account_one_ending_balance; 38 | var account_two_ending_balance; 39 | 40 | var amount = 10; 41 | 42 | return MetaCoin.deployed().then(function(instance) { 43 | meta = instance; 44 | return meta.getBalance.call(account_one); 45 | }).then(function(balance) { 46 | account_one_starting_balance = balance.toNumber(); 47 | return meta.getBalance.call(account_two); 48 | }).then(function(balance) { 49 | account_two_starting_balance = balance.toNumber(); 50 | return meta.sendCoin(account_two, amount, {from: account_one}); 51 | }).then(function() { 52 | return meta.getBalance.call(account_one); 53 | }).then(function(balance) { 54 | account_one_ending_balance = balance.toNumber(); 55 | return meta.getBalance.call(account_two); 56 | }).then(function(balance) { 57 | account_two_ending_balance = balance.toNumber(); 58 | 59 | assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender"); 60 | assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver"); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /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 | } 13 | --------------------------------------------------------------------------------