VIVITABLOG

VIVITAで活躍するメンバーの情報発信サイト

意外と簡単?Ethereum Blockchain アプリのローカル開発手順。

こんにちは、デザイナーの @imatomix です。

突然ですが、「子供たちとブロックチェーンでなんか出来ないかなー」って漠然と考えてみてたんです。当然ですが、「そもそも僕自身がブロックチェーンのこと何も知らない。」ってとこに着地しました。なので、実際にブロックチェーンに触ってみようと思います。百聞は一見に、百見は一験に如かずです。

Ethereum

f:id:imatomix:20180810120830p:plain

Ethereum は、スマートコントラクトという自動契約プログラムやそれを活用したアプリケーションを構築するためのプラットフォームです。Bitcoin が人対人の取引なのに対して、Ethereum は人対プログラム(スマートコントラクト)の取引も可能なのが特徴で、ブロックチェーン界隈のエンジニア達の中では、今最も勢いがあるらしいです。

エンジニアかぶれのデザイナーとしては、素直にその勢いに身を委ね、 今回はローカルの Ethereum ブロックチェーンネットワーク上に独自通貨(トークン)を作成し、Web アプリから操作するまでをやってみました。

Ganache

f:id:imatomix:20180801232725p:plain

「ワンクリックブロックチェーン」というだけあって、Ganache は起動するだけでローカル環境に Ethereum ブロックチェーンネットワークを構築し、アカウント(アドレス)10個とそれぞれに 100 ETH ずつを生成してくれます。さらに、 GUI からブロックやトランザクションを参照できるので、アプリケーションの動作を確認することがとても簡単にできます。

インストール

truffleframework.com

上記サイトからインストーラをダウンロードしてインストールするだけです。 Ganache には JavaScript で実装された Ethereum ブロックチェーン が組み込まれているので、geth などの Ethereum クライアントのインストールも不要です。

僕は最初に geth から始めたんですが、geth では

  • ブロックチェーンを起動
  • アカウントを作成
  • マイニング開始
  • マイニング停止

などの1つ1つのアクションをCLIから実行 & 管理する必要があるので、アカウントやトランザクションが増えてくるとホントめんどくさくて、、、Ganache の方が劇的に楽です。


というわけで、これでローカルにブロックチェーンネットワークが出来ました。次に開発環境です。

Truffle

f:id:imatomix:20180801233033p:plain

Truffle は、Ethereum のスマートコントラクト開発フレームワークです。開発に必要なコンパイルやマイグレーション、テストなど、諸々の面倒をみてくれます。

インストール

truffleframework.com

上記サイトにあるようにコンソールから以下のコマンドを実行し、インストールします。

$ npm install -g truffle 

プロジェクトの作成

新しくプロジェクトを作成するには、空のディレクトリを用意して、そのディレクトリ内で以下のコマンドを実行します。

$ truffle init

すると、以下のような構成のファイル一式がダウンロードされます。コントラクトとマイグレーションとテストのための最小構成です。

f:id:imatomix:20180801234505p:plain:h200

ちなみに、以下のコマンドから、Truffle Boxes | Truffle Suite に用意されている様々な構成テンプレート( BOX )をダウンロードすることも可能です。

$ truffle unbox 任意のBOX名

ネットワーク設定

f:id:imatomix:20180803225451p:plain

↑Ganache にあるネットワーク情報を参照し、プロジェクトディレクトリ直下にある truffle.js を以下のように書き換えます。

module.exports = {
  networks: {
    development: {
      host: '127.0.0.1',
      port: 7545,
      network_id: '*'
    }
  }
}

これで、truffle が Ganache のブロックチェーンネットワークを向きます。

OpenZepperin

f:id:imatomix:20180801235438p:plain

今回は独自通貨(トークン)を作ってみるのですが、Ethereum のトークンにはいくつかの規格があります。トークンを作成する際は、 ERC20 という標準規格に準拠することで、その他の異なる ERC20 準拠トークンと価値を移転することが可能になります。

OpenZeppelin は、Ethereum のスマートコントラクト開発の補助ライブラリで、ERC20 トークンの実装もサポートしてくれます。

インストール

プロジェクトディレクトリにて、以下のコマンドを実行し、インストールします。

$ npm install zeppelin-solidity --save



さて、

これで準備は整いました。次はスマートコントラクトの作成です。

スマートコントラクトの作成

基本的な流れは以下の通りです。

  • コントラクト作成
  • コンパイル
  • デプロイ
  • テスト(今回は省略)

コントラクト作成

以下のコマンドからコントラクトファイルを作成します。ここでは独自のトークンを作ってみます。

$ truffle create contract MyToken

contract ディレクトリに MyToken.sol というファイルが作成されます。Solidity という Ethereum のスマートコントラクトを記述するプログラミング言語で書かれており、独自トークンもこのファイルを編集して作ります。

f:id:imatomix:20180803150109p:plain

MyToken.sol の中身を以下のように編集します。

pragma solidity ^0.4.22; 
import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";

contract MyToken is StandardToken { 
  string public name = "MyToken";  
  string public symbol = "MT"; 
  uint public decimals = 18; 

  constructor(uint initialSupply) public {
    totalSupply_ = initialSupply;
    balances[msg.sender] = initialSupply; 
  }
}

簡単な解説をすると、

まず冒頭でコンパイルに必要なsolidtyのバージョンを指定しています。

paragma solidity ^0.4.22;

OpenZeppelin から ERC20 の実装クラス、StandardToken をインポートし、それを継承してオリジナルトークンのクラスを作成します。

import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";

contract MyToken is StandardToken {
...
}

クラスの中身では、トークンの名前、単位、小数点の桁数を指定しています。 decimals = 18 とは 1MT あたり 100000000000000000 wei、つまり 1MT = 1ETH ということになります。

コンストラクタでは、初期発行トークンをコンストラクタ実行者に付与しています。totalSupply_ はトークンの総発行量です。msg.senderはコンストラクタの実行者のアドレスなので、balances[msg.sender] で実行者のバランス(口座)を取得し、そこに初期発行トークンを入てれいます。

  string public name = "MyToken"; 
  string public symbol = "MT"; 
  uint public decimals = 18;

  constructor(uint initialSupply)  {
    totalSupply_ = initialSupply;
    balances[msg.sender] = initialSupply; 
  }

コンパイル

以下のコマンドを実行してコンパイルします。

$ truffle compile

コンパイルが成功すると、build/contracts ディレクトリ下に JSON ファイルが出力されます。

この JSON の中には、コントラクトのアドレスや、ABI (Application Binary Interface) というコントラクトの変数や関数、パラメーターなどのインターフェースの情報が入っており、この情報を元にトークンを操作していくことになります。

f:id:imatomix:20180803223536p:plain

マイグレーションファイルの作成

以下のコマンドからマイグレーションファイルを作成します。

$ truffle create migration deploy_my_token

migration ディレクトリに 数字_deploy_my_token.js というファイルが作成されます。

数字の部分は作成した時の timestamp が入っています。ここの数字を書き換えてマイグレーションを順番を指定することが可能です。

f:id:imatomix:20180803224138p:plain

作成されたファイル以下のように編集します。

const MyToken = artifacts.require('MyToken.sol')

module.exports = (deployer) => {
  const initialSupply = 300e18
  deployer.deploy(MyToken, initialSupply)
}

ここでは、トークンの初期発行数を指定して、 MyToken のコンストラクタへ値を渡しています。

デプロイ

以下のコマンドでコントラクトをブロックチェーンネットワーク上にデプロイします。

$ truffle migratie 

truffle deploytruffle migrate のエイリアスなので truffle deploy でもいいです。以下のように出力されればデプロイは成功です。

f:id:imatomix:20180803225726p:plain

MyToken: 0xb3b0dccba2e6112bf0b00f104cdaf9e7c54f4273 の部分が、今回独自トークンのために作成したスマートコントラクトのアドレスになります。

余談

この段階で Ganache のアカウントリストの最初のアドレスの Balance が 100ETH から 99.82 ETHに減っています。

f:id:imatomix:20180803233318p:plain

これはデプロイにかかった手数料が引かれたためです。デプロイの手数料は Coinbase のアドレス( Ganache では一番上のアドレス) から引かれます。 手数料に関する詳細は Ganache の ブロックリストやトランザクションリストのページから確認できます。

f:id:imatomix:20180803234331p:plain

f:id:imatomix:20180803234347p:plain



さて次は、web フロントです。

web3.js

f:id:imatomix:20180802215724p:plain

web3.js は HTTP や IPCから Ethereum とのやりとりを可能にするライブラリ集です。 ここでは フロントに Vue.js を使い、web3.js 経由で Ethereum を操作してみます。

インストール

Vueプロジェクトを作成し、以下のコマンドから web3.js をインストールします。

$ npm install web3

オブジェクトの作成

Vue プロジェクトの src ディレクトリ直下に web3.js ファイルを作成し、以下を記述します。

import Web3 from 'web3'

var web3 = new Web3('ws://localhost:7545')

export default web3

ws://localhost:7545は Ganache のネットワークです。これで Ganache に接続した web3 オブジェクトが作成されます。

MetaMask などで、グローバル空間にすでに web3 オブジェクトが存在し、それを使いたいときは (new Web3(Web3.givenProvider || 'ws://localhost:7545')) とするといいです。

所持金(ETH)を表示する

まずは自分( Coinbase のアドレス)の所持金( ETH )を表示してみます。 Home.vue を以下のように書き換えます。

<template>
  <div>
    <h1>{{Balance}} ETH</h1>
  </div>
</template>

<script>
import web3 from '@/web3.js'

export default {
  data () {
    return {
      Balance: null
    }
  },
  beforeMount () {
    web3.eth.getCoinbase().then(address => {
      web3.eth.getBalance(address).then(balance => {
        this.Balance = Math.round(balance / 1e18 * 100) / 100
      })
    })
  }
}
</script>

npm run serveでwebを起動してみましょう。以下のように Ganache の 一番上のアドレスと同じ所持金額が表示されるはずです。

f:id:imatomix:20180804000246p:plain

簡単な解説をすると、

まず先ほど作成した web3.js から web3 オブジェクトをインポートして、

import web3 from '@/web3.js'

web3.eth.getCoinbase() で、Coinbaseとなるアカウントのアドレスを取得し、web3.eth.getBalance(address)で、そのアカウントが持つ所持金( Balace )を取得しています。

getCoinbasegetBalanceなどの処理は Ethereum とやりとりしているので非同期処理となるので、Vue.js で取り扱うときも注意が必要です。

    web3.eth.getCoinbase().then(address => {
      web3.eth.getBalance(address).then(balance => {
        this.Balance = Math.round(balance / 1e18 * 100) / 100
      })
    })

所持金(独自トークン)を表示する

Home.vue を以下のように書き換えます。

<template>
  <div>
    <h1>{{balanceEth}} ETH</h1>
    <h1>{{balanceMt}} MT</h1>
  </div>
</template>

<script>
import web3 from '@/web3.js'
import artifact from '../build/contracts/MyToken.json'

export default {
  data () {
    return {
      tokenContract: null,
      balanceEth: null,
      balanceMt: null,
    }
  },
  beforeMount () {
    this.tokenContract = new web3.eth.Contract(artifact.abi, artifact.networks[5777].address)
    web3.eth.getCoinbase().then(address => {
      web3.eth.getBalance(address).then(balance => {
        this.balanceEth = Math.round(balance / 1e18 * 100) / 100
      })
      this.tokenContract.methods.balanceOf(address).call((error, balance) => {
        this.balanceMt = Math.round(balance / 1e18 * 100) / 100
      })
    })
  }
}
</script>

これでETH に加えて、独自で発行した MT が表示されます。トークン額はマイグレーションファイルに記載した初期発行額になっているはずです。

f:id:imatomix:20180804031601p:plain

ここも簡単に解説すると、

まず、コントラクトをコンパイルして出来た build/contracts/MyToken.json をインポートします。

import artifact from '../build/contracts/MyToken.json'

このファイルの中には、web3.js から独自トークンを操作するのに必要な、コントラクトのアドレスや、コントラクトが持つメソッドと引数といったのインターフェースの情報( ABI )が入っています。

このファイルから、以下のようにして new web3.eth.Contract() に コントラクトの ABI とアドレスを渡してコントラクトオブジェクトを作成しています。

    this.tokenContract = new web3.eth.Contract(artifact.abi, artifact.networks[5777].address)

最後に、作成したコントラクトオブジェクトに対し、methods.balanceOf() で指定したアドレスの所持トークン額を取得しています。

      this.tokenContract.methods.balanceOf(address).call((error, balance) => {
        this.balanceMt = Math.round(balance / 1e18 * 100) / 100
      })

トークンを送金する

ではいよいよ、トークンを送金してみます。Home.vue を以下のように書き換えます。

<template>
  <div>
    <div>
      From:
      <h1>{{From.balanceEth}} ETH</h1>
      <h1>{{From.balanceMt}} MT</h1>
    </div>
    <form @submit.prevent="SendToken">
      To:
      <div>
        <select v-model="To.address" @change="getBalanceMt(To)">
          <option v-for="(account, index) in Accounts" v-if="account.toLowerCase() != From.address" :key="index">{{account}}</option>
        </select>
      </div>
      <h1>{{To.balanceMt}} MT</h1>
      <div>
        <input type="number" v-model="Value"/> MT
        <input type="submit" value="Send"/>
      </div>
    </form>
  </div>
</template>

<script>
import web3 from '@/web3.js'
import artifact from '../../build/contracts/MyToken.json'

export default {
  data () {
    return {
      From: {
        address: null,
        balanceEth: 0,
        balanceMt: 0
      },
      To: {
        address: null,
        balanceMt: 0
      },
      Accounts: [],
      Value: 10,
      tokenContract: null,
    }
  },
  beforeMount () {
    this.tokenContract = new web3.eth.Contract(artifact.abi, artifact.networks[5777].address)

    web3.eth.getAccounts().then(accounts => {
      this.Accounts = accounts
    })

    web3.eth.getCoinbase().then(address => {
      this.From.address = address
      web3.eth.getBalance(address).then(balance => {
        this.From.balanceEth = Math.round(balance / 1e18 * 100) / 100
      })
      this.getBalanceMt(this.From)
    })
  },
  methods: {
    getBalanceMt(account) {
      this.tokenContract.methods.balanceOf(account.address).call((error, balance) => {
        account.balanceMt = Math.round(balance / 1e18 * 100) / 100
      })
    },
    SendToken () {
      this.tokenContract.methods.transfer(this.To.address, this.Value*1e18)
        .send({
          from: this.From.address,
          gas: 50000,
          gasLimit:100000
        }, (error, transactionHash) => {
        this.getBalanceMt(this.To)
        this.getBalanceMt(this.From)
      })
    }
  }
}
</script>

web から宛先のアドレスと送金額を指定し、send ボタンを押すとトークンが送金されるようになります。

f:id:imatomix:20180804123934p:plain

コードが少々長くなりましたが、ほぼ Vue.js の部分なので、その辺の解説は省きます。送金部分は以下です。

      this.tokenContract.methods.transfer(this.To.address, this.Value*1e18)
        .send({
          from: this.From.address,
          gas: 50000,
          gasLimit:100000
        }, (error, transactionHash) => {
      })

methods.transfer() にアドレスと額を渡して、トークン額を送金します。send()のオプションでは、以下を指定しています。

  • from: 送信元アドレス
  • gasPrice: トランザクションにかかるガス量
  • gas: トランザクションにかかるガス量の最大値

トークンを送金したら、Ganache を確認してみましょう。トランザクションの詳細が確認できます。gasPrice などの設定も反映されているはずです。

f:id:imatomix:20180804232535p:plain

感想

ひとまず今回はこれで、無事ゴールに辿り着きました。実際は迷宮入りしかけたりもしたので、それなりに道のりはありましたが、こうやって必要なところだけまとめてみると、意外と最短距離は短く、ブロックチェーンに手を出してみるハードルもだいぶ下がってきているような気がします。それだけ、ブロックチェーン界隈はエンジニアが集まってるんでしょうね。有難いです。

さて、僕はというと、ようやく「子供たちとブロックチェーンでなんか出来ないかなー」を考えるスタートラインに立てた(いやまだ向かい始めただけ?)ような気がするので、もう少し真面目に「なんか」を考えます。あと、できれば本業のデザイン業務の方でも頑張りたいので、一緒に考えたり、作ったりしてくれる開発メンバー募集中です!

recruit.jobcan.jp