はじめてのDapp開発 チュートリアル
Dappをつくるための全体像を掴むことができるチュートリアルをやってみました。
どうやってアプリをつくって最終的にイーサリアムにデプロイするのかを掴むことができるのでいい感じです
内容は以下の英語のチュートリアルをやってみました。
・どうやっていちからスマートコントラクトを作成するか
・どうやってスマートコントラクトをRopsteにデプロイするか
・どうやってDappのフロント(見た目)を実装するか
・どうやってそのコントラクトをアプリにひもづけるか
・どうやって最終的にDAppをIPFS(分散型ホスティング)にデプロイするか
・どうやってIPFSでカスタムドメインを使用するか
完成形はこんな感じです。
このチュートリアルをおえれば、あなたのスマートコントラクトをもった分散型のWebサイトをつくることができます。アプリはブラウザを通して、MetaMaskやMistでブロックチェーンと相互作用させてつかうことができます。
チュートリアル前には、ブロックチェーンについてやWebアプリの作成についての基礎知識を身に着けておいたほうがいいです。
おすすめはProgateさんでJavaScriptの基礎を学んで
Solidityはクリプトゾンビーがいいと思います。
チュートリアルで使用する技術は以下です。
・Database: The Ethereum’s Testnet Ropsten blockchain.
・Hosting: IPFS to get free hosting forever in a decentralized platform.
・Frontend: React.js with webpack.
・Domain name: Godaddy.
・Contract’s programing language: Solidity 0.4.11,
・Frontend contracts: web3.js
・Frameworks: Truffle
・Development server: Node.js
・Metamask: wallet
チュートリアルは以下の手順で進めていきます
1. プロジェクトのセットアップをする
2. Solidityでスマートコントラクトを書いていく
3. アプリのフロントを実装する
4. アプリをIPFSにデプロイする
5. アプリにカスタムドメインを割り当てる
6. アプリをつかってみる!!
1. プロジェクトのセットアップをする
このチュートリアルでは、カジノ アプリを作成します。ユーザーは1から10の数字にベットします。ベットした数字が正しければすべてのイーサが支払われます。(全100ベット後)
このチュートリアルで、DAppを作成する全プロセスを確認することができます。
① まずはnode.js の最新バージョンをダウンロードしてください。
② casino-ethereum フォルダを任意のディレクトリを作成する。
作成後はTerminalを開いて cd でディレクトリに移動する。
③ その後、Terminalで以下を実行する.
開発フレームワークのTruffleをインストールします。(-D: Devendency, -g: globally)
$ npm init -y
{
"name": "casino-ethereum",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
$ npm i -D -g truffle
※ truffleインストールで下記のエラーが出た場合はこちらの記事を参照したらなおりました。
$ npm i -D -g truffle
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules
④以下のコマンドを実行して、DApp開発に必要なファイルをいれます。
$ npm init -y
$ truffle init
⑤ フロントエンドの開発に必要なものを準備します。
npm i -D webpack react react-dom babel-core
babel-loader babel-preset-react babel-preset-env
css-loader style-loader json-loader web3@0.20.0
/* 以下がインストールされました (ここはTerminalに入力しないでください)
+ babel-preset-react@6.24.1
+ react-dom@16.4.2
+ web3@0.20.0
+ css-loader@1.0.0
+ json-loader@0.5.7
+ react@16.4.2
+ babel-loader@7.1.5
+ style-loader@0.22.1
+ babel-preset-env@1.7.0
+ babel-core@6.26.3
+ webpack@4.16.5
*/
主には、webpack, react, babel and web3 をインストールしました。web3はweb3@0.20.0 をインストールしました。現時点では、最新バージョンのweb3 1.0 はbeta版で不安定だからです。
⑥ サーバーをインストールします。ローカルホスト localhost:8080
$ npm i -g http-server
⑦ プロジェクトフォルダーにいって、webpack.config.js ファイルを作成します。JSファイルやCSSファイルを人るのファイル(buiid.js)にバンドル(まとめ)します。webpack.config.jsには以下のコードを書きます。
const path = require('path')
module.exports = {
entry: path.join(__dirname, 'src/js', 'index.js'), // Our frontend will be inside the src folder
output: {
path: path.join(__dirname, 'dist'),
filename: 'build.js' // The final file will be created in dist/build.js
},
module: {
loaders: [{
test: /\.css$/, // To load the css in react
use: ['style-loader', 'css-loader'],
include: /src/
}, {
test: /\.jsx?$/, // To load the js and jsx files
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react', 'stage-2']
}
}, {
test: /\.json$/, // To load the json files
loader: 'json-loader'
}]
}
}
⑧ "src"フォルダをプロジェクトの中に作成します。"src"フォルダの中に、"js", "css"フォルダをそれぞれ作成します。それぞれのフォルダの中に、"index.js", "index.css"を作成します。
そして、プロジェクト内に"dist"フォルダを作成します。その中に、"index.html"ファイルを作成します。
ディレクトリ構成はこんな感じです。
contracts/
-- Migrations.sol
migrations/
node_modules/
test/
src/
-- css/index.css
-- js/index.js
dist/
-- index.html
package.json
truffle-config.js
truffle.js
webpack.config.js
⑨ index.html にコードを書いていく
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
<title>Casino Ethereum Dapp</title>
</head>
<body>
<div id="root"></div>
<script trc="build.js"></script>
// build
</body>
</html>
※フォントにopen sansをつかってます。
<div id="root"></div> は、reactコードが挿入されます。
<script trc="build.js"></script> は、webpackでつくられるbuildファイルです。
これでやっと下準備は終わりです!!!!
次回から、コントラクトを書いていきます!
2. Solidityでスマートコントラクトを書いていく
ここから実際にメインのスマートコントラクトを書いていきます。
① Casino.sol ファイルを contractsフォルダ配下に作成します。つぎからは、その "Casino.sol"ファイル内にSolidityでコードを書いていきます。
② solidityのcompiler version を指定する。
pragma solidity 0.4.24;
③ コード追記
pragma solidity 0.4.24;
contract Casino {
address public owner;
function Casino() public {
owner = msg.sender;
}
function kill() public {
if(msg.sender == owner) selefdestruct(owner);
}
}
・function Casino( ) は、コンストラクターです。コントラクトのオーナーをセットします。
・function kill( )は、コントラクトをオーナーの好きなときに削除します。残っているetherはオーナーのアドレスに送り返されます。ハッキングされて損害が出たときなどに実行されます。
よし!ここで、このアプリで実装したい機能をかんがえてみましょう!
カジノっぽいアプリ
ユーザーは1から10の数字にお金をかけることができる。賭けた数字が当たれば、100回のベットの後、全てのetherがかった分前として配分される。
これを実現させるには以下のタスクの実装が必要
Taskリスト
・ユーザーがどの番号にどれくらいetherをかけたかの記録の保持
・賭け金の最低金額
・すでにゲームに参加しているユーザーかをチェックする
・累積の合計ether
・どれくらいのベット(賭け)があったのかをstoreする変数
・いつベットを止め、当選番号をあきらかにするか
・買ったぶんの配当ehterを各ユーザーに送る関数
必要機能
・番号に賭ける
・ランダムな数の生成
・勝者にetherを送る
さて、一個ずつやっていきましょーか!!
・ユーザーがどの番号にどれくらいetherをかけたかの記録の保持
Structは構造体、mapping は配列なようなもの。 mappingでユーザーのアドレスとstoreします。
pragma solidity 0.4.20;
contract Casino {
address public owner;
/* ここから追加
uint256 public minimumBet;
uint256 public totalBet;
uint256 public numberOfBets;
uint256 public maxAmountOfBets = 100;
address[] public players;
struct Player {
uint256 amountBet;
uint256 numberSelected;
}
// The address of the player and => the user info
mapping(address => Player) public playerInfo;
*/
function Casino() public {
owner = msg.sender;
}
function kill() public {
if(msg.sender == owner) selfdestruct(owner);
}
}
Player というstruct は、ベットの総量( amountBet ) と 選ばれた番号 ( numberSelected )をもつ。このアプリでは、どれくらいのetherをユーザーが何番にベットしたのかの情報を持っておく必要があるからです。
player の配列も必要です。勝者に配当をわたすために、だれがゲームに参加しているかを把握する必要があるからです。
・賭け金の最低金額
さて、次は、コンストラクター(Casino contract)を少し変えていきます。
最低賭け金を決めれるようにします。 function Casino( )をつぎのように編集します。0 以外であれば最低ベットとします。
function Casino(uint256 _minimumBet){
owner = msg.sender;
if(_minimumBet != 0 ) minimumBet = _minimumBet;
}
・ 番号に賭ける
function kill ( )の下に、1から10の数字にベットできる関数をつくっていきます。
// To bet for a number between 1 to 10 both inclusive.
function bet(uint256 numberSelected) public payable {
require(!checkPlayerExists(msg.sender));
require(numberSelected >= 1 && numberSelected <= 10);
require(msg.value >= minimubBet);
playerInfo[msg.sender].amountBet = msg.value;
playerInfo[msg.sender].numberSelected = numberSelected;
players.push(msg.sender);
totalBet += msg.value;
}
payable は modifierで、この関数を実行するとether がかかります。
require( )は、絶対にtrueを返すべき前提条件です。
1. すでにプレイしていないプレーヤーであること
2. 選択した番号が1以上10以下であること
3. 賭け金が最低賭け金以上であること
playerInfo[msg.sender].amountBet = msg.sender;
. msg.sender は、この関数を実行したユーザーのアドレスです。
・すでにゲームに参加しているユーザーかをチェックする
上のコードで、checkPlayerExists関数 を使っています。これは、このユーザーがすでにカジノをプレーしてるプレーヤーかどうかを判別する関数です。まだ実装していないので、function bet ( )の上に書きましょう。
function checkPlayerExists(address player) public constant returns(bool){
for(uint256 i = 0; i < players.length; i++){
if(players[i] == player) return true;
}
return false;
}
※constant: これを関数につけると、この関数の実行によってgasを消費することはなくなります。だってすでに存在しているデータを参照して、bool型でtrueかfalseを返すだけのためにgasはいらないからです。つまり、データを読み取るだけだからです。
・ランダムな数の生成
もし99ベットまで来ていたら、つぎで100ベットになるので、そのときには、賞金発表しなきゃいけないです。
なので、bet ( ) の下に、以下のコードを書き足します。
function generateNumberWinner() public {
uint256 numberGenerated = block.number % 10 + 1;
distributePrizes(numberGenerated);
}
ブロックの数を10で割ったあまりに1を足して、てきとうな数字を発生させる。ぶっちゃけ、あまり安全な手ではないです。
・勝者にetherを送る
// Sends the corresponding ether to each winner depending on the total bets
function distributePrizes(uint256 numberWinner) public {
address[100] memory winners; // We have to create a temporary in memory array with fixed size
uint256 count = 0; // This is the count for the array of winners
for(uint256 i = 0; i < players.length; i++){
address playerAddress = players[i];
if(playerInfo[playerAddress].numberSelected == numberWinner){
winners[count] = playerAddress;
count++;
}
delete playerInfo[playerAddress]; // Delete all the players
}
players.length = 0; // Delete all the players array
uint256 winnerEtherAmount = totalBet / winners.length; // How much each winner gets
for(uint256 j = 0; j < count; j++){
if(winners[j] != address(0)) // Check that the address in this fixed array is not empty
winners[j].transfer(winnerEtherAmount);
}
}
1. winners 配列を作成します。プレイヤーが選んだ番号が勝ち番号かどうかをチェックします。 winners配列は, memory配列です。関数が実行されたら消去されるものです。
2. 勝者に送られる合計のetherは、total bet amountと winnersの数によって変わってくる。
3. それぞれの勝者に、以下のコードで それに対応したetherが送られる。
winners[ j ].transfer
以上で、シンプルに数字にベットするというカジノのコントラクトはできました。
最後に、fallback function を書いていきます。
これによって、あなたがコントラクトに送信したetherが保存されます。
// Fallback function in case someone sends ether to the contract so it doesn't get lost and to increase the treasury of this contract that will be distributed in each game
function() public payable {}
いままで書いてきたコードはこんな感じになりました。
pragma solidity 0.4.24;
contract Casino {
address public owner;
uint256 public minimumBet;
uint256 public totalBet;
uint256 public numberOfBets;
uint256 public maxAmountOfBets = 100;
address[] public players;
struct Player {
uint256 amountBet;
uint256 numberSelected;
}
// The address of the player and => the user info
mapping(address => Player) public playerInfo;
function() public payable {}
function Casino(uint256 _minimumBet) public {
owner = msg.sender;
if(_minimumBet != 0 ) minimumBet = _minimumBet;
}
function kill() public {
if(msg.sender == owner) selfdestruct(owner);
}
function checkPlayerExists(address player) public constant returns(bool){
for(uint256 i = 0; i < players.length; i++){
if(players[i] == player) return true;
}
return false;
}
// To bet for a number between 1 and 10 both inclusive
function bet(uint256 numberSelected) public payable {
require(!checkPlayerExists(msg.sender));
require(numberSelected >= 1 && numberSelected <= 10);
require(msg.value >= minimumBet);
playerInfo[msg.sender].amountBet = msg.value;
playerInfo[msg.sender].numberSelected = numberSelected;
numberOfBets++;
players.push(msg.sender);
totalBet += msg.value;
}
// Generates a number between 1 and 10 that will be the winner
function generateNumberWinner() public {
uint256 numberGenerated = block.number % 10 + 1; // This isn't secure
distributePrizes(numberGenerated);
}
// Sends the corresponding ether to each winner depending on the total bets
function distributePrizes(uint256 numberWinner) public {
address[100] memory winners; // We have to create a temporary in memory array with fixed size
uint256 count = 0; // This is the count for the array of winners
for(uint256 i = 0; i < players.length; i++){
address playerAddress = players[i];
if(playerInfo[playerAddress].numberSelected == numberWinner){
winners[count] = playerAddress;
count++;
}
delete playerInfo[playerAddress]; // Delete all the players
}
players.length = 0; // Delete all the players array
uint256 winnerEtherAmount = totalBet / winners.length; // How much each winner gets
for(uint256 j = 0; j < count; j++){
if(winners[j] != address(0)) // Check that the address in this fixed array is not empty
winners[j].transfer(winnerEtherAmount);
}
}
}
コントラクトのテスト
コントラクトを書き終わったので、testをしていきます。2つのやり方がアリアmす。
・truffleでtestsを書くやりかた。 testrpc
・Solidity IDEのRemixを使って testを書いてデプロイするやり方
今回は簡易的なRemixの方でやっていきます。
・metamaskの設定
・・・とその前に、metamaskをダウンロードしておいてください。スマートコントラクトを実行するにも、イーサリアムのウォレットとしても必要になるので。
metamaskの使い方はこちらも参考になります。
metamaskの12フレーズは必ずメモしておいてください。
Remix IDEに"Casino.sol"のコントラクトをコピペする。
Remixの右側のRunをクリックする。
metamaskにいって、ネットワークをRopsten Test Networkを選択する。
faucetで1 etherを無料でゲットします。
Remix IDE で、Environment を Injected Web3 を選択します。
Deplyをおして実行します。そしたら、metamaskのポップアップがでてくるので、そのポップアップに表示される"確認" を押します
無事にテストネットにデプロイされたら、Remixのコンソールに実行結果がでてるはずです。
Remixの右の Deployed Contracts のアドレスをコピーします。あとで使うので、どこかにメモをしておいてください。
Terminal で truffle compile を実行します。
$ truffle compile
3. アプリのフロントを実装する
さぁ、これからフロント側を実装していきます! "index.js"のファイルをReactでつくっていきます。
import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import './../css/index.css'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
lastWinner: 0,
timer: 0
}
}
voteNumber(number) {
console.log(number)
}
render() {
return(
<div className="main-container">
<h1>Bet for your best number and win huge amounts of Ether</h1>
<div className="block">
<h4>Timer:</h4>
<span ref="timer"> {this.state.timer} </span>
</div>
<div className="block">
<h4>Last winner:</h4>
<span ref="last-winner"> {this.state.lastWinner} </span>
</div>
<hr/>
<h2>Vote for the next number</h2>
<ul>
<li onCLick={() => {this.voteNumber(1)}}>1</li>
<li onCLick={() => {this.voteNumber(2)}}>2</li>
<li onCLick={() => {this.voteNumber(3)}}>3</li>
<li onCLick={() => {this.voteNumber(4)}}>4</li>
<li onCLick={() => {this.voteNumber(5)}}>5</li>
<li onCLick={() => {this.voteNumber(6)}}>6</li>
<li onCLick={() => {this.voteNumber(7)}}>7</li>
<li onCLick={() => {this.voteNumber(8)}}>8</li>
<li onCLick={() => {this.voteNumber(9)}}>9</li>
<li onCLick={() => {this.voteNumber(10)}}>10</li>
</ul>
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector('#root')
)
・React, ReactDOM, Web3をimportします。
・メイン画面をつくる<App />コンポーネントを書いていきます
・コンストラクターを書いていきます。voteNumber(number)メソッドでコントラクトに投票します。そして、render ( ) メソッドで、基本的なhtmlをonClickイベントを実行させます。
つぎにCSSで見た目を整えます。
body {
font-family: 'open sans';
margin: 0;
}
ul {
list-style-type: none;
padding-left: 0;
display: flex;
}
li {
padding: 40px;
border: 2px solid rgb(30,134,255);
margin-right: 5px;
border-radius: 10px;
cursor: pointer;
}
li:hover {
background-color: rgb(30,134,255);
color: white;
}
li:active {
opacity: 0.7;
}
* {
color: #444444;
}
.main-container {
padding: 20px;
}
.block {
display: flex;
align-items: center;
}
.number-selected {
background-color: rgb(30,134,255);
color: white;
}
.bet-input {
padding: 15px;
border-radius: 10px;
border: 1px solid lightgrey;
font-size: 15pt;
margin: 0 10px;
}
見た目も整えたので、コントラクトと紐づけます。簡易的にRemix IDEでやっていきます。
Remix IDEでコントラクトをデプロイしてください。
Remixにデプロイ後にCompilie タブを選んで、Detailを押します。
そうするとポップアップページでてくるので、ABI Interfaceのコピーボタンを押して、コードをコピーします。
index.js のコンストラクターを編集します。
constructor(props){
super(props)
this.state = {
lastWinner: 0,
numberOfBets: 0,
minimumBet: 0,
totalBet: 0,
maxAmountOfBets: 0,
}
if(typeof web3 != 'undefined'){
console.log("Using web3 detected from external source like Metamask")
this.web3 = new Web3(web3.currentProvider)
}else{
this.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
}
const MyContract = web3.eth.contract([ABI interface コピーしたものを貼り付けます])
this.state.ContractInstance = MyContract.at("0x925d81c01d878899adbb7d38f84ce9d5284fa2e7")
}
・コンストラクタのstateをセットします。
・Web3 変数のチェック
・コントラクトABIのセットアップ
・MyContract.at( )にaddressを格納します。MyContract.ad( ) がinstanceを返します。
よーし、index.jsにコードを加えた最終版がこちらです。
import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import './../css/index.css'
class App extends React.Component {
constructor(props){
super(props)
this.state = {
lastWinner: 0,
numberOfBets: 0,
minimumBet: 0,
totalBet: 0,
maxAmountOfBets: 0,
}
if(typeof web3 != 'undefined'){
console.log("Using web3 detected from external source like Metamask")
this.web3 = new Web3(web3.currentProvider)
}else{
console.log("No web3 detected. Falling back to http://localhost:8545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
this.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
}
const MyContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"generateNumberWinner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"numberOfBets","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"player","type":"address"}],"name":"checkPlayerExists","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"resetData","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"maxBets","type":"uint256"}],"name":"setMaxAmountOfBets","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"number","type":"uint256"}],"name":"bet","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[],"name":"distributePrizes","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"numberWinner","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"minimumBet","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"maxAmountOfBets","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"players","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalBet","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"_minimumBet","type":"uint256"},{"name":"_maxAmountOfBets","type":"uint256"}],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"}])
this.state.ContractInstance = MyContract.at("0x7a684de06f473636e03e2d951c78d190eeecb411")
}
componentDidMount(){
this.updateState()
this.setupListeners()
setInterval(this.updateState.bind(this), 10e3)
}
updateState(){
this.state.ContractInstance.minimumBet((err, result) => {
if(result != null){
this.setState({
minimumBet: parseFloat(web3.fromWei(result, 'ether'))
})
}
})
this.state.ContractInstance.totalBet((err, result) => {
if(result != null){
this.setState({
totalBet: parseFloat(web3.fromWei(result, 'ether'))
})
}
})
this.state.ContractInstance.numberOfBets((err, result) => {
if(result != null){
this.setState({
numberOfBets: parseInt(result)
})
}
})
this.state.ContractInstance.maxAmountOfBets((err, result) => {
if(result != null){
this.setState({
maxAmountOfBets: parseInt(result)
})
}
})
}
// Listen for events and executes the voteNumber method
setupListeners(){
let liNodes = this.refs.numbers.querySelectorAll('li')
liNodes.forEach(number => {
number.addEventListener('click', event => {
event.target.className = 'number-selected'
this.voteNumber(parseInt(event.target.innerHTML), done => {
// Remove the other number selected
for(let i = 0; i < liNodes.length; i++){
liNodes[i].className = ''
}
})
})
})
}
voteNumber(number, cb){
let bet = this.refs['ether-bet'].value
if(!bet) bet = 0.1
if(parseFloat(bet) < this.state.minimumBet){
alert('You must bet more than the minimum')
cb()
} else {
this.state.ContractInstance.bet(number, {
gas: 300000,
from: web3.eth.accounts[0],
value: web3.toWei(bet, 'ether')
}, (err, result) => {
cb()
})
}
}
render(){
return (
<div className="main-container">
<h1>Bet for your best number and win huge amounts of Ether</h1>
<div className="block">
<b>Number of bets:</b>
<span>{this.state.numberOfBets}</span>
</div>
<div className="block">
<b>Last number winner:</b>
<span>{this.state.lastWinner}</span>
</div>
<div className="block">
<b>Total ether bet:</b>
<span>{this.state.totalBet} ether</span>
</div>
<div className="block">
<b>Minimum bet:</b>
<span>{this.state.minimumBet} ether</span>
</div>
<div className="block">
<b>Max amount of bets:</b>
<span>{this.state.maxAmountOfBets} ether</span>
</div>
<hr/>
<h2>Vote for the next number</h2>
<label>
<b>How much Ether do you want to bet? <input className="bet-input" ref="ether-bet" type="number" placeholder={this.state.minimumBet}/></b> ether
<br/>
</label>
<ul ref="numbers">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector('#root')
)
4. アプリをIPFSにデプロイする
ようやくここまで来ました。フリーホスティングサービスIPFSをつかって、オンラインにアプリをのせます。まずは以下からIPFSをインストールします。
インストールできたら以下をTerminalで実行していきます。
$ ipfs init
$ ipfs daemon
daemon is ready になったら、以下を実行します。
$ ipfs swarm peers
プロジェクトがあるディレクトリに移動します。(以下はぼくの場合です)
$ cd /Users/takezo/casino-ethereum
こうして、distフォルダがあるディレクトリで、次に、distフォルダをネットワークに追加します。
$ ipfs add -r dist/
added QmPeEmvkooecb1QL4Aj6ivGdiuFmEy6RK3DGBhkQzBxPdm dist/index.html
added QmSG5W5EL86ZQvGE4RVpDSiabMBhr4UxywdpArwinMw3Ug dist
長いハッシュが生成されます。最後のハッシュがそのフォルダのidentifierです。
最後のハッシュをコピーして以下のように実行します。
$ ipfs name publish QmSG5W5EL86ZQvGE4RVpDSiabMBhr4UxywdpArwinMw3Ug
そうすればこんなふうにかえってきます。
Published to QmYbVjHT9jZEa8B9HEsnXhE1FRJNWaWvbMDHznBJEckGU9: /ipfs/QmSG5W5EL86ZQvGE4RVpDSiabMBhr4UxywdpArwinMw3Ug
最後に、Publish to の後のハッシュ値で、
gateway.ipfs.io/ipns/<your-hash-here> とブラウザで叩けば表示が確認できます。こんなかんじですー!
gateway.ipfs.io/ipns/QmYbVjHT9jZEa8B9HEsnXhE1FRJNWaWvbMDHznBJEckGU9
以上です。
勉強することばっかりで頭パンクしそうですが、DApp作成の教材としてはよかったです。
Twitterでは, WebアプリのプログラミングやdApps関連をつぶやいています。よければフォローしてください^^