LoginSignup
20
12

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-08

はじめに

Reactとweb3を使って宝くじのdappを作ってみました。

宝くじdapp

イメージはこんな感じです。

Screen Shot 2018-07-08 at 16.36.39.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です。

Screen Shot 2018-07-06 at 13.51.49.png

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

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

compileファイル作成

lottery-reactとは別のフォルダを作成します。
前回作ったフォルダをコピーしてlotteryに変更します。
前回記事
ディレクトリ構造はこのような感じ。
*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を押します。

Screen Shot 2018-07-08 at 16.36.39.png

Waiting on transaction success...がETH送金が完了すると、
You have been entered!に変わります。
リロードするとentry人数と金額が更新されます。
Screen Shot 2018-07-08 at 16.37.27.png

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

Screen Shot 2018-07-08 at 16.38.01.png

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

Screen Shot 2018-07-08 at 16.39.18.png

Ethescanにもしっかり取引履歴が残ってます。
Screen Shot 2018-07-08 at 16.41.28.png

終わりに

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

参考にさせて頂きました

Ethereum and Solidity: The Complete Developer's Guide
async/await 入門(JavaScript)

20
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
12