#はじめに
Reactとweb3を使って宝くじのdappを作ってみました。
##宝くじdapp
イメージはこんな感じです。
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です。
次に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
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に変更します。
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();
##スマートコントラクト
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);
を付け足します。
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もこのように変更します。
import Web3 from 'web3';
const web3 = new Web3(window.web3.currentProvider);
export default web3;
##フロント
そして最後にフロント部の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を押します。
Waiting on transaction success...がETH送金が完了すると、
You have been entered!に変わります。
リロードするとentry人数と金額が更新されます。
そして集まったらオーナーアカウントでpick a winnerボタンを押します。
Waiting on transaction success...から
A winner has been picked!になれば成功です。
#終わりに
初めてReactを触ってdappを作ってみました。
フロントとsolidityの理解が深まれば応用したdappが作れそうです。
#参考にさせて頂きました
Ethereum and Solidity: The Complete Developer's Guide
async/await 入門(JavaScript)