TruffleとVue.jsとTypeScriptでブロックチェーンアプリを作ってみる

自分の備忘録としてDappの作り方の手順をまとめてみました。 最近やり始めたので間違いもあるかもしれません。ご承知ください。

環境構築

Macでやっています。

solidityのインストール

$ brew install solidity

truffleのインストールとプロジェクトの作成

$ npm install -g truffle
$ mkdir project && cd project
$ truffle init
$ vue create front

vue create時にtypescriptを選択します。

*****現状の環境*****

$ node -v
v11.9.0
$ npm -v
6.5.0
$ vue -V
3.3.0
$ solc --version
solc, the solidity compiler commandline interface
Version: 0.5.3+commit.10d17f24.Darwin.appleclang
$ truffle version
Truffle v5.0.3 (core: 5.0.3)
Solidity v0.5.0 (solc-js)
Node v11.9.0

macOS Mojave version10.14

truffleプロジェクトの構造

上記に従ってプロジェクトの作成を行うと、以下のような構造を持つディレクトリが作られます。

-----contracts
 |---migrations
 |---test
 |---truffle-config.js
 |---front

ディレクトリの構造を理解するために、Dapp開発の流れを説明します。

Dapp開発の手順

  1. contractディレクトリにsolファイルを作成してコントラクトコードを書く。
  2. migrationディレクトリにデプロイのためのjsファイルを作成。
  3. testディレクトリにコントラクトコードをテストするファイルを作成。
  4. ganacheの導入
  5. terminalでtruffle migrateを行い、プライベートチェーン上にコントラクトをデプロイ
  6. フロント側でweb3.jsの設定を行う
  7. metamaskの導入
  8. 動作確認

contractディレクトリにsolファイルを作成してコントラクトコードを書く。

 solファイルはsolidityで書かれたファイルのことです。このファイルにコントラクトを書き込んでいきます。  コントラクトはブロックチェーンのネットワーク上で利用できるアカウントであり、このコントラクト上で実行されるコードをコントラクトコードと呼びます。  ここではブロックチェーンに関する解説は省きますが、ブロックチェーンのネットワークには送金などを行うアカウントコードとして実行されるアカウントが存在し、Dappでは後者を利用されています。

// Dogs.sol

pragma solidity ^0.5.0;

contract Dogs {
  // 犬のインスタンスを作っていきます
  struct Dog {
    uint id;
    string name;
  }

  // mappingはいわゆる連想配列のこと。ここではuint(id)をDogインスタンスに割り当てている。
  mapping(uint => Dog) public dogs; // Dogインスタンスを格納
  uint public dogsCount; // インスタンス作成時のidとして利用(毎インスタンス生成時に+1していく)

  constructor() public { // 初期インスタンス生成
    createProgress("pochi");
  }

  function createProgress(string memory _name) private {
    dogsCount++;
    dogs[dogsCount] = Dog(dogsCount, _name);
  }
}

migrationディレクトリにデプロイのためのjsファイルを作成。

migrationディレクトリには、deployのためのファイルを作成します。

// migration/2_deploy_contract.js

let Dogs = artifacts.require("./Dogs.sol");
module.exports = function(deployer) { deployer.deploy(Dogs); };

上記のようにファイルを作成し、以下のコマンドを実行すると、contractsディレクトリに作成されたコントラクトをブロックチェーン上にデプロイします。

$ truffle migrate

また、今後開発を行なっていく上で、一度デプロイしたコントラクトコードを修正したいと思うことがあると思います。 その時は以下のコマンドを実行して新しいコントラクトコードでデプロイし直します。

$ truffle migrate --reset

railsをやったことがある人であれば、db:migrate:resetと同じような感じで考えていただければ良いと思います。

testディレクトリにコントラクトコードをテストするファイルを作成。

migrate --resetでやり直せるにしても、ブロックチェーンにデプロイされたものは修正が効かず、それゆえに改竄不可能性を持つわけですが、そうなるとバグや予想しない動作を含むものをデプロイするわけにはいきません。 こういった不具合を未然に防ぐためにもコントラクトコードのテストを行う方が良いです。 テストはsolidityでもjavascriptでも書くことができます。

// 飽くまで例です
// jsの場合
const Dogs = artifacts.require("./Dogs.sol");
contract("Dogs", function(accounts) {
  it("initializes with a dog", function() {
    return Dogs.deployed().then(function(instance) {
      return instance.dogsCount();
    }).then(function(count) {
      assert.equal(count, 1); // solファイルのconstructorで作成したデフォルトのインスタンス
    });
  });
});

ganacheの導入

ganache ganacheはプライベートチェーン上に自動的に模擬アカウントを作成してくれるツールです。 以下のリンクからインストールできます。 https://truffleframework.com/ganache

ganacheを起動させてからコントラクトコードをプライベートチェーンにデプロイしてあげると、ganacheで生成されたアカウントがマイニングを行なってくれるので、すぐにコントラクトコードを実行できるようになります。

terminalでtruffle migrateを行い、プライベートチェーン上にコントラクトをデプロイ

ganacheを起動させた状態で、且つ、contractsディレクトリにsolidityファイル、migrationsディレクトリにdeploy用のjsファイル(testのファイルはここでの動作に影響はありませんが、soldityファイルにエラーがあればデプロイができなくなります)があることを確認してください。 その上で次のコマンドを実行してください。

$ truffle migrate

1_initial_migration.js
======================

   Replacing 'Migrations'
   ----------------------
   > transaction hash:    0xc3006c7464bb1e931d818226170e743976ca068ce15c92c0ad074f6a2685f71c
   > Blocks: 0            Seconds: 0
   > contract address:    0x8bBDf86104324ff2bA12d34F4D9D38eEe9e83748
   > account:             0x0f85Ddf6555a85e7bfb107faD34Fc13DF63475A4
   > balance:             99.92727632
   > gas used:            284908
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00569816 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00569816 ETH


2_deploy_contract.js
====================

   Replacing 'Dogs'
   ------------------
   > transaction hash:    0xb2e38fc03105dc5089418a24ede9f54559ca78264ce2ab9753f521e2338ee50f
   > Blocks: 0            Seconds: 0
   > contract address:    0x1EEB22378Ae20b4c18179E46F9C89D11B1f8Aa57
   > account:             0x0f85Ddf6555a85e7bfb107faD34Fc13DF63475A4
   > balance:             99.90698292
   > gas used:            972636
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.01945272 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.01945272 ETH


Summary
=======
> Total deployments:   2
> Final cost:          0.02515088 ETH

デプロイができれば上記のようになります。

他の方法でデプロイができていることを確認するには、

$ truffle console

でtruffleのコンソールに入って、以下のようにコントラクトコードを実行してみることができます。

$ truffle console
truffle(ganache)> Dogs.deployed().then(function(dogs) { app = dogs })
undefined
truffle(ganache)> app.dogs(1).then(function(d) { dog = d })
undefined
truggle(ganache)> dog[1]
'pochi'

Dogs.deployed().then(function(dogs) { app = dogs })の部分で、appにdogsコントラクトを渡し、そのappを使って、app.dogs(1)のように、コントラクトコード上に定義したmappingにidを渡して該当のインスタンスを抜き出します。 最後にdog = dの部分で抜き出したインスタンスを変数に格納し、最後にそれの[1]番目の要素、つまりnameを参照しています。

このように名前を参照することができればデプロイできている証拠です。 次にフロント側の設定を行なっていきます。

フロント側でweb3.jsの設定を行う

それでは今度はフロント側を実装していきます。 ここではvue.jsとtypescriptを使っていきます。

まず、vue createでプロジェクトを作成してください。 作成時にtypescriptを利用する選択を行なってください。 csscssプリプロセッサを使うことで楽に記述できるようになるのでおすすめです。

$ vue create frontend

プロジェクトが作成されれば、中に入ってweb3とtruffle-contractのモジュールをインストールします。

$ npm install --save-dev web3@1.0.0-beta.37 truffle-contract

ここではweb3のバージョンを指定していますが、私の環境ではweb3だけでnpm installすると、このあと紹介するmetamaskというツールを利用した時にエラーが発生したので、これはその対処として行なっています。

vueとtypescriptの説明は行わないので、コンポーネントのファイルに書くブロックチェーンとやりとりするためのコードを解説します。 el-系のinputタグやbuttonタグはelementUIをインストールすると利用できるようになります。非常に使いやすいのでお勧めです。 https://element.eleme.io/#/en-US

*templateにはpugを利用しています。npm install --save-dev pug pug-plain-loaderで使えます。

<template lang="pug">
  #Contract
    .list(v-for="dog in dogs")
      p {{ dog.name }}
    .form
      el-input(v-model="name")
      el-button(@click="createDog") 作成
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import artifacts from '../../../build/contracts/Dogs.json' // デプロイするとbuild/contracts配下に作成されます。
import $ from 'jquery'
const Dogs = TruffleContract(artifacts)

@Component({ components: { Dogs } })
export default class ProgressChainIndex extends Vue {
  private ownAddress: string = ""
  private name: string = ""
  private dogs: Array<object> = []

  created() {
    if (typeof web3 !== 'undefined') {
      web3 = new Web3(web3.currentProvider)
    } else {
      console.warn('No web3 detected.')
      web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:7545'))
    }

    Dogs.setProvider(web3.currentProvider)
    web3.eth.getCoinbase().then((account) => {
      this.ownerAddress = account
      Dogs.defaults({ from: account })
    });
  }

  beforeMount() {
    // ここでブロックチェーン上に記録されたデータを取得して画面上に表示する
    let dogsInstance
    let self = this
    Dogs.deployed().then(function(instance) {
      dogsInstance = instance
      return dogsInstance.dogsCount()
    }).then(function(dogsCount) {
      for (let i = 1; i <= progressId; i++) {
        // ブロックチェーン上のdogインスタンスを一つ一つ拾ってきて、そのidとnameをまとめたオブジェクトを作成、配列dogsに格納する
        dogsInstance.progresses(i).then(function(dog) {
          self.dogs.push({
            id: dog[0],
            name: dog[1]
          })
        })
      }
    }).catch(function(err) {
      console.warn(err)
    })
  }

  createDog() {
    // ブロックチェーンのコントラクトコードにインスタンスを記録
    let self = this
    Dogs.deployed().then(function(instance) {
      return instance.createProgress(
        self.name,
        { from: self.ownerAddress })
    }).catch(function(err) {
      console.error(err)
    })
  }
}
</script>

このコンポーネントを好きなように親コンポーネントに組み込んであげてください。

metamaskの導入

metamask metamaskはブラウザとブロックチェーンを繋げるツールです。 このツールが実行されていれば、先ほどのコード上でweb3を認識することができます。 https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=ja

アプリケーションの実行

上記のようにコードを作成し、vueのディレクトリでnpm run serveを行います。 localhost:8080を開き、コンポーネントで配置したinputにdogの名前を入力して、作成ボタンを押します。 すると、ブロックチェーンへのアクセスを承認するか拒否するかの選択が表示されるので、承認ボタンを押しましょう。 承認後、作成したインスタンスをブラウザ上に反映させるためには一度リロードを行う必要があります。

また、ここでエラーが発生するようであれば、コードに何らかの原因があるか、ganacheやmetamaskの問題が考えられます。 ganacheの再起動やmetamaskでアカウントのリセットを行うと改善されることがあるので、その辺を確認してみましょう。

以下の動画は非常に参考になります。実際に動かしながら説明しているのでこのページを読むより楽かもしれません。 https://www.youtube.com/watch?v=3681ZYbDSSk