人生なんて暇つぶし

Webエンジニアの雑記ブログ。技術の話題はDApps開発、NEMアプリ開発がメイン。

移転しました。

約3秒後に自動的にリダイレクトします。

DApps開発ギルドでデプロイしたアプリを1から作ってみる

先日、オオキマキさん(@ookimaki_JP)が主催するDApps開発ギルドの勉強会に参加してきました。

勉強会は2時間という限られた時間だったので、今回作ったアプリを復習がてら自分で1から作成してみました。

勉強会で行った内容はこちら。

givinglog.com

それでは作ってみます。

開発環境

MacBook Pro(macOS High Sierra 10.13.6)

MetaMask 4.8.0

Ganache 1.2.1

$ npm -v
6.1.0

$ node -v
v10.7.0

$ truffle version
Truffle v4.1.13 (core: 4.1.13)
Solidity v0.4.24 (solc-js)

環境構築が済んでいない方はこちらへ。

givinglog.com

プロジェクト初期化

はじめにプロジェクト用のフォルダを作成し移動しましょう。

$ cd ~/Document
$ mkdir ~/dapps
$ mkdir ~/dapps/turotial/
$ cd ~/dapps/tutorial/

今回はtutorialというフォルダで作業を行います。

プロジェクトフォルダに移動したら、truffleというフレームワークを初期化します。

$ truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

tutorial直下にファイルが作成されていることを確認しましょう。

f:id:maroemon58:20180728111325p:plain

コントラクトを作成

contractsフォルダの下にSimpleStore.solを作成します。

ファイルの中身は次のコードを記載します。

pragma solidity ^0.4.23;

contract SimpleStore {
    string value;

    function set(string _value) public {
        value = _value;
    }

    function get() public view returns (string) {
        return (value);
    }
}

Migrationsを作成

先ほど書いたコントラクトをデプロイするためのmigrationを作成します。

migrationsフォルダに2_deploy_contracts.jsを作成します。

ファイルの中身は次のコードを記載します。

var SimpleStore = artifacts.require('../contracts/SimpleStore.sol');

module.exports = function(deployer) {
  deployer.deploy(SimpleStore);
};

ネットワーク設定

次にデプロイするネットワークを設定ファイルに追記します。

truffle.jsに次のコードを記載します。

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
  networks: {
    development: {
      host: '127.0.0.1',
      port: 8545,
      network_id: '*'
    }
  }
};

host: 127.0.0.1port: 8545という接続先はプライベートチェーンGanacheを指しています。

※Ganacheのポート番号を8545に変更しています。Ganacheの設定画面から変更可能です。

デプロイ

準備が整ったのでデプロイします。コマンドを実行する前にGanacheを起動しておきましょう。

$ truffle migrate
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/SimpleStore.sol...
Writing artifacts to ./build/contracts

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x55a8c1d05804136fb1670127c8bf4f08b6832a74a44e8a0006b8108ffb355d46
  Migrations: 0x562f587d7241ca995b50476adbbdc4bf3b7fa75f
Saving successful migration to network...
  ... 0xa1d9bb5b7594aea5b49ea110e2648dc5859c49c18b05aad555fab6c4dc2b6658
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying SimpleStore...
  ... 0x4dd1bdb65839e6eaade2cb3a45b9d63fa403bf66f8609d014d54eb09eaf07c13
  SimpleStore: 0x15a40f91de5f6797721266c6a6daf83758d10e00
Saving successful migration to network...
  ... 0xe1b4d2a0a3aba6861b6e2c79fa5285af636d04d2ce030a0fa85db53a250ac229
Saving artifacts...

SimpleStore: 0x15a40f91de5f6797721266c6a6daf83758d10e00がコントラクトのアドレスになります。 重要なのでメモっておきましょう。※これって後で確認したいときどうするんでしょう?

無事にデプロイできたらbuildというフォルダが作成されているはずです。

f:id:maroemon58:20180728111540p:plain

フロントアプリを作成

コントラクトがデプロイできたので、これを利用するためのフロントアプリを作成します。

tutorial直下にsrcフォルダを作成し、下記3つのファイルを追加していきます。

f:id:maroemon58:20180728111642p:plain

web3.min.jsは下ページから最新バージョンをダウンロードし、dist/web3.min.jsを所定の位置に配置します。

github.com

contract_abi.jsは新規に作成し、下記コードを記載しましょう。

変数の中身はbuild/SimpleStore.jsonのabiから取ってきたものです。

var contractABI = [
  {
    // build/SimpleStore.jsonのabiをコピペ
    constant: false,
    inputs: [
      {
        name: '_value',
        type: 'string'
      }
    ],
    name: 'set',
    outputs: [],
    payable: false,
    stateMutability: 'nonpayable',
    type: 'function'
  },
  {
    constant: true,
    inputs: [],
    name: 'get',
    outputs: [
      {
        name: '',
        type: 'string'
      }
    ],
    payable: false,
    stateMutability: 'view',
    type: 'function'
  }
];

index.htmlの中身は最低限動くレベルで書いておきます。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DApps Dev Guild Tutorial</title>
  <script language="javascript" type="text/javascript" src="./js/web3.min.js"></script>
  <script language="javascript" type="text/javascript" src="./js/contract_abi.js"></script>

  <script>
    var contract;
    var userAccount;

    function startApp() {
      var contractAddress = "0xdc30da2490373416e7bf7467bb297bae624287b4";

      contract = new web3js.eth.Contract(contractABI, contractAddress);

      var accountInterval = setInterval(function () {
        web3.eth.getAccounts((error, accounts) => {
          if (accounts[0] !== userAccount) {
            userAccount = accounts[0];
          }
        });
      }, 100);
    }

    window.addEventListener('load', function () {
      // Checking if Web3 has been injected by the browser(Mist/MetaMask)
      if (typeof web3 !== 'undefined') {
        // Use Mist/MetaMask's provider
        web3js = new Web3(web3.currentProvider);
      } else {
        // Handle the case where the user doesn't have Metamask installed
        // Probably show them a message prompting them to install Metamask
      }
      // Now you can start your app & access web3 freely;
      startApp();
    });

    function set() {
      var _val = document.getElementById("val").value;
      contract.methods.set(_val)
        .send({ from: userAccount })
        .on("transactionHash", function (txhash) {
          alert("Txhash: " + txhash);
        })
        .on("receipt", function (receipt) {
          console.log(receipt);
        })
        .on("error", function (error) {
          console.log(error);
        });
    }

    function get() {
      contract.methods.get().call().then(function (_val) {
        alert(_val);
      });
    }
  </script>
</head>

<body>
  <h1>DApps Dev Guild Tutorial</h1>

  <input type="text" name="val" id="val">
  <input type="button" value="Set" onclick="set()">
  <input type="button" value="Get" onclick="get()">
</body>

</html>

MetaMaskにethを用意する

アプリを実行するためにはMetaMaskにethがないと利用できないので、Ganacheで用意されているアドレスをインポートしましょう。

適当なアドレスのshow keysをクリックします。

f:id:maroemon58:20180728111843p:plain

プライベートキーが表示されるのでクリップボードにコピーします。

f:id:maroemon58:20180728112002p:plain

MetaMaskの人マークをクリックすると、アカウントメニューが表示されるのでImport Accountをクリックします。

f:id:maroemon58:20180728120749p:plain

先程コピーしたプライベートキーを入力し、Importをクリックするとアカウントが追加されます。

f:id:maroemon58:20180728120933p:plain

動作確認

動作確認するためにlive-server(ローカルWebサーバ)を利用します。

導入されていない方はnpm installでインストールしましょう。

$ npm install -g live-server

エディタのプラグインで提供されている場合もあるので、そっちを使っても大丈夫です。

srcフォルダに移動して実行します。

$ cd src
$ live-server
Serving "/Users/hoge/Document/dapps/tutorial/src" at http://127.0.0.1:8080
Ready for changes

Chromeでhttp://127.0.0.1:8080にアクセスするとフロントアプリの画面が表示されます。

f:id:maroemon58:20180728112358p:plain

MetaMaskを立ち上げネットワークLocalhost 8545を選択します。

f:id:maroemon58:20180728112504p:plain

実際に動かしてみます。

f:id:maroemon58:20180728112525g:plain

無事に動きました!お疲れ様でした!!

おまけ

ちょっと見た目が野暮ったいので、機能は変えずにデザインをオシャレにアレンジしてみます。

MaterializeというCSSフレームワークを利用します。

materializecss.com

ファイルをダウンロードし、materialize.min.cssmaterialize.min.jsを配置します。

f:id:maroemon58:20180728112715p:plain

index.htmlをゴリゴリ修正します。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DApps Dev Guild Tutorial</title>
  <script language="javascript" type="text/javascript" src="./js/web3.min.js"></script>
  <script language="javascript" type="text/javascript" src="./js/contract_abi.js"></script>
  <script language="javascript" type="text/javascript" src="./js/materialize.min.js"></script>

  <!--Import Google Icon Font-->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <!--Import materialize.css-->
  <link type="text/css" rel="stylesheet" href="./css/materialize.min.css" media="screen,projection" />

  <script>
    var contract;
    var userAccount;

    function startApp() {
      var contractAddress = "0xdc30da2490373416e7bf7467bb297bae624287b4";

      contract = new web3js.eth.Contract(contractABI, contractAddress);

      var accountInterval = setInterval(function () {
        web3.eth.getAccounts((error, accounts) => {
          if (accounts[0] !== userAccount) {
            userAccount = accounts[0];
          }
        });
      }, 100);
    }

    window.addEventListener('load', function () {
      // Checking if Web3 has been injected by the browser(Mist/MetaMask)
      if (typeof web3 !== 'undefined') {
        // Use Mist/MetaMask's provider
        web3js = new Web3(web3.currentProvider);
      } else {
        // Handle the case where the user doesn't have Metamask installed
        // Probably show them a message prompting them to install Metamask
      }
      // Now you can start your app & access web3 freely;
      startApp();
    });

    function set() {
      var _val = document.getElementById("val").value;
      contract.methods.set(_val)
        .send({ from: userAccount })
        .on("transactionHash", function (txhash) {
          alert("Txhash: " + txhash);
        })
        .on("receipt", function (receipt) {
          console.log(receipt);
        })
        .on("error", function (error) {
          console.log(error);
        });
    }

    function get() {
      contract.methods.get().call().then(function (_val) {
        alert(_val);
      });
    }
  </script>
</head>

<body>
  <nav>
    <div class="nav-wrapper blue-grey ">
      <a href="" class="brand-logo">DApps Dev Guild Tutorial</a>
    </div>
  </nav>

  <div class="container">
    <input type="text" name="val" id="val">
    <div class="center">
      <a class="waves-effect waves-light btn" onclick="set()">
        <i class="material-icons left">file_upload</i>Set</a>
      <a class="waves-effect waves-light btn" onclick="get()">
        <i class="material-icons right">file_download</i>Get</a>
    </div>
  </div>
</body>

</html>

f:id:maroemon58:20180728112818g:plain

まとめ

デプロイするまでDApps開発に敷居を高く感じていましたが、手順を教えてもらい一通りできるようになったので取っ掛かりは掴めたかなって感じです。 所々理解できていない部分もありますが、今は教えられたことを全部飲み込むフェーズにし、アプリの開発をドシドシやろうと思います。ではまた。

twitter.com