Ethereum入門　〜Reactとweb3で宝くじdappを作る 〜

#はじめに

Reactとweb3を使って宝くじのdappを作ってみました。
##宝くじdapp
イメージはこんな感じです。

<img width="1282" alt="Screen Shot 2018-07-08 at 16.36.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/635a9095-1a89-10a5-a42b-ce49e08d3c26.png">

0.1ETH以上でエントリーし、ETHが集まったらランダムで当選者を決定し、
その当選者に集まったETHを配当するというスマートコントラクトです。
Ethの送金はMetamaskを使います。


#宝くじdapp作りかた
##Reactインストール

アプリを作りたいフォルダに移動し下記コマンドを実行します。

```
$sudo install -g create-react-app
$create-react-app lottery-react
$ls
lottery-react
```
lottery-reactが出来ていればOKです。
lottery-reactに移動してrunします。

```
$cd lottery-react
$npm run start
```
ブラウザ上(localhost:3000)で下記が表示されればOKです。

<img width="475" alt="Screen Shot 2018-07-06 at 13.51.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/1965b9d6-7875-af4b-0607-0df2305ef1fa.png">

次にyarn と web3をインストールします。

```
$ npm install -g yarn
$npm install --save web3@1.0.0-beta.26
```


##compileファイル作成
lottery-reactとは別のフォルダを作成します。
前回作ったフォルダをコピーしてlotteryに変更します。
[前回記事](https://qiita.com/kolife/items/990fdbaaa970d06293fa)
ディレクトリ構造はこのような感じ。
*change部を変更していきます。

```
lottery
├── compile.js //*change
├── contracts
│   └── Lottery.sol  //*change
├── deploy.js //*change
├── node_modules
│   ├── abstract-leveldown
│   ├── ... 
│   └── ...
├── package-lock.json
├── package.json
```

```compile.js
const path = require('path');
const fs = require('fs');
const solc = require('solc');

const lotteryPath = path.resolve(__dirname, 'contracts', 'Lottery.sol');
const source = fs.readFileSync(lotteryPath, 'utf8');

module.exports = solc.compile(source,1).contracts[':Lottery'];
```
2箇所inboxをlotteryに変更します。

```deploy.js
const HDWalletProvider = require('truffle-hdwallet-provider');
const Web3 = require('web3');
const { interface, bytecode } = require('./compile');

const provider = new HDWalletProvider(
  'twelve word mnemonic...', //Metamaskのニーモニックを記入
  'https://rinkeby.infura.io/...' //infraで取得したURLを記入
    
);

const web3 = new Web3(provider);

const deploy = async () => {
    const accounts = await web3.eth.getAccounts();

    console.log('Attempting to deploy from account', accounts[0]);
    const result = await new web3.eth.Contract(JSON.parse(interface))
        .deploy({data: bytecode }) //arguments以下が不要なため削除
        .send({ gas: '1000000', from: accounts[0] });

    console.log(interface);
    console.log('Contract deployed to', result.options.address);
    
};
deploy();
```


##スマートコントラクト

```Lottery.sol
pragma solidity ^0.4.17;

contract Lottery{
    address public manager;
    address[] public players;
    
    function Lottery () public {
        manager = msg.sender;
    }
    
    function enter () public payable {
        require(msg.value > .01 ether);
        
        players.push(msg.sender);
    }
    
    function random () private view returns (uint) {
        return uint(keccak256(block.difficulty, now, players));
    }
    
    function pickWinner() public restricted{
        uint index = random() % players.length;
        players[index].transfer(this.balance);
        players = new address[](0);
        
    }
    
    modifier restricted(){
        require(msg.sender == manager);
        _;
    }
    
    function getPlayers() public view returns (address[]){
        return players;
    }
}

```

modifer はセキュリティを強化するため、managerしか実行出来ない関数につけます。


##デプロイ

準備が出来たらデプロイをします。
デプロイが完了できると、下記のようにABIとContract addressが表示されます。

```
$node deploy.js
Attempting to deploy from account 0x686BF528CA3793954547070EbE25649e08197805
[{"constant":true,"inputs":[],"name":"manager","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pickWinner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPlayers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"enter","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"players","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
Contract deployed to 0xaC24CD668b4A6d2935b09596F1558c1E305F62F1
```


lotter-reactに戻り、src内にlottery.jsを作成します。
ディレクトリ構造はこちら

```
lotter-react
├── README.md
├── node_modules
│   ├── abab
│   ├── accepts
│   ├── acorn
│   ├── ・・・
│   ├── ・・・
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── lottery.js
│   ├── registerServiceWorker.js
│   └── web3.js
└── yarn.lock
```


lottery.jsに先ほどのaddress、abiを貼り付けます。
最後に　`export default new web3.eth.Contract(abi, address);`　を付け足します。

```lottery.js
import web3 from './web3';

const address = '0xaC24CD668b4A6d2935b09596F1558c1E305F62F1';


const abi = [{
    "constant": true,
    "inputs": [],
    "name": "manager",
    "outputs": [{
        "name": "",
        "type": "address"
    }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": false,
    "inputs": [],
    "name": "pickWinner",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}, {
    "constant": true,
    "inputs": [],
    "name": "getPlayers",
    "outputs": [{
        "name": "",
        "type": "address[]"
    }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "constant": false,
    "inputs": [],
    "name": "enter",
    "outputs": [],
    "payable": true,
    "stateMutability": "payable",
    "type": "function"
}, {
    "constant": true,
    "inputs": [{
        "name": "",
        "type": "uint256"
    }],
    "name": "players",
    "outputs": [{
        "name": "",
        "type": "address"
    }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "inputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "constructor"
}];


export default new web3.eth.Contract(abi, address);
```

ちなみにvscodeの場合abiの整形はbeatifyで出来ます。
https://marketplace.visualstudio.com/items?itemName=HookyQR.beautify
F1 →　beatify選択で綺麗に整形してくれます。


web3.jsもこのように変更します。

```web3.js
import Web3 from 'web3';

const web3 = new Web3(window.web3.currentProvider);

export default web3;
```

##フロント
そして最後にフロント部のApp.jsを下記のように変更していきます。

```App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import web3 from './web3';
import lottery from './lottery';

class App extends Component {
  state = {
    manager: '',
    players: [],
    balance: '',
    value: '',
    messaget: ''
  };

  async componentDidMount(){
    const manager = await lottery.methods.manager().call();
    const players = await lottery.methods.getPlayers().call();
    const balance = await web3.eth.getBalance(lottery.options.address);

    this.setState({ manager, players, balance });
  }

  onSubmit = async (event) => {
    event.preventDefault();

    const accounts = await web3.eth.getAccounts();

    this.setState({ message: 'Waiting on transaction success...' });

    await lottery.methods.enter().send({
      from: accounts[0],
      value: web3.utils.toWei(this.state.value, 'ether')
    });

    this.setState({ message: 'You have been entered!'});
  };

  onClick = async () => {
    const accounts = await web3.eth.getAccounts();

    this.setState({ message: 'Waiting on transaction success...'});

    await lottery.methods.pickWinner().send({
      from: accounts[0]
    });

    this.setState({ message: 'A winner has been picked!'});
  };

  render() {
    return (
      <div>
        <h2>Lottery Cntract</h2>
        <p>
          This contract is managed by {this.state.manager}.
          There are currently {this.state.players.length} peple entered,
          competing to win {web3.utils.fromWei(this.state.balance, 'ether')} ether!
        </p>
        <hr />

        <form onSubmit={this.onSubmit}>
            <h4>Want to try your luck?</h4>
            <div>
              <label>Amount of ether to enter</label>
              <input
              value = {this.state.value}
                onChange={event => this.setState({ value: event.target.value })}
              />
            </div>
            <button>Enter</button>
        </form>

        <hr />

        <h4>Ready to pick a winner?</h4>
        <button onClick={this.onClick}>Pick a winner!</button>

        <hr />
        <h1>{this.state.message}</h1>
      </div>
    );
  }
}

export default App;
```

これで、`$npm run start` を実行してlottery画面が表示されれば成功です。

##動作確認

0.1ETH以上を入力しEntryを押します。

<img width="1282" alt="Screen Shot 2018-07-08 at 16.36.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/ed842031-dd84-a602-4def-6a8b7fc0ed1d.png">

Waiting on transaction success...がETH送金が完了すると、
You have been entered!に変わります。
リロードするとentry人数と金額が更新されます。
<img width="897" alt="Screen Shot 2018-07-08 at 16.37.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/18ffd0e7-a361-ff8a-a6ba-df1ff7da55ce.png">



そして集まったらオーナーアカウントでpick a winnerボタンを押します。



<img width="1260" alt="Screen Shot 2018-07-08 at 16.38.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/f9124f26-5188-866f-705b-b83188559a6c.png">

Waiting on transaction success...から
A winner has been picked!になれば成功です。

<img width="472" alt="Screen Shot 2018-07-08 at 16.39.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/758d7295-b262-ae2d-2062-e95a355e1ab7.png">


Ethescanにもしっかり取引履歴が残ってます。
<img width="1204" alt="Screen Shot 2018-07-08 at 16.41.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/6df37f5f-16e9-1fd1-000e-e87a3ba45648.png">


#終わりに
初めてReactを触ってdappを作ってみました。
フロントとsolidityの理解が深まれば応用したdappが作れそうです。

#参考にさせて頂きました
[Ethereum and Solidity: The Complete Developer's Guide](https://www.udemy.com/ethereum-and-solidity-the-complete-developers-guide/learn/v4/overview)
[async/await 入門（JavaScript)](https://qiita.com/soarflat/items/1a9613e023200bbebcb3)