Help us understand the problem. What is going on with this article?

分散アプリケーション開発をはじめから最後まで( Ethereum × TypeScript(Vue.js) )

More than 1 year has passed since last update.

はじめに

前書き

Satoshi Nakamotoの論文により、開発されたBitcoinは中央管理局を必要としない通貨として注目を集めましたが、本当に特筆すべきは、その過程で生まれたBlockchain技術です。
分散型大衆決定のためのその概念はまさに革新的な発明であったと言えるでしょう。

現在、Blockchain技術は様々な分野への応用が期待されており、その一つが分散アプリケーションです。
※ Ethereumを用いた分散アプリケーションがどのように動き、どのようなメリットがあるかはこちらに書いてありますので、併せてお読みください :pray:

作ったもの

リポジトリはこちらになります

AwesomeTeamCoinDemo.gif

今回は、その分散アプリケーション開発にフォーカスを当て、「独自の仮想通貨」を作り、プライベートブロックチェーン環境にデプロイ、Vue.js × TypeScriptで作ったviewからアクセスを行い、独自仮想通貨取引を可視化するところまでご案内いたします。 :smile:

分散アプリケーションの実装

見慣れないものが多いと思いますが、使う言語、フレームワークやライブラリの概要です

名前 バージョン 概要
Solidity 0.4.18 コントラクト指向言語と呼ばれ、コントラクトの実装を行う言語。
Truffle 4.0.6 Ethereum開発のフレームワーク。Solidityのコンパイルやデプロイ、テストの実行など、様々なことができる
Ganache 1.0.2 プライベートブロックチェーン環境を作り、アンロックされたユーザを生成、GUIでトランザクションやETHが見れる
zeppelin-solidity 1.6.0 コントラクトのライブラリで、token発行や認証などを継承することで簡単に行える。

環境構築

コントラクト(Ethereumネットワーク上で動くプログラム)はSolidityと呼ばれるコントラクト指向言語を用い実装します。
まず、フレームワークであるTruffleを用い、開発環境を整えます。

mkdir AwesomeTeamCoin && cd AwesomeTeamCoin
npm i -g truffle
truffle init

これで、インストールできます

以下のようなファイルができていればokです

AwesomeTeamCoin
       |- contracts // Solidityで実装されたコントラクトのフォルダー
       |- migrations // コントラクトのデプロイファイルを入れるフォルダー
       |- test // テストコードを入れるフォルダー
       |- truffle-config.js // デプロイ先のEthereumネットワークなどを指定するconfigファイル

このcontracts下に〇〇.solというファイルを作り、実装を行います。
実装を行う前に、使うライブラリの説明を行います。

ライブラリ(zeppelin-solidity)の概要

zeppelin-solidityという便利なライブラリがあるので、それを入れて、StandardTokenというファイルを継承して実装を行います。
これは、独自の仮想通貨を簡単に作れるもので

BasicToken.sol
...

contract BasicToken is ERC20Basic {
  mapping(address => uint256) balances; // 連想配列でアドレスをキーにそのアカウントの独自仮想通貨残高を記録する
  uint256 totalSupply_; // 独自仮想通貨の総数

  /**
   * @dev 独自仮想通貨の総数を返す
   */
  function totalSupply() public view returns (uint256) {
    return totalSupply_; 
  }

  /**
   * @dev 独自仮想通貨の移動
   * @dev なお、送り元アドレスは、msg.senderという予約語でfunctionを呼び出した人が取得できる
   * @param _to 宛先アドレス
   * @param _value 移動する量
   */
  function transfer(address _to, uint256 _value) public returns (bool) {
    ...
    return true; //成功するとtrueが返り、失敗するとrevertされる
  }

  /**
   * @dev 所持している独自仮想通貨の確認
   * @param _owner 確認したいアカウントのアドレス
   */
  function balanceOf(address _owner) public view returns (uint256 balance) {
    return balances[_owner];
  }

}

このようなファイルになっています。
紙面の都合上あまり多くはかけないので、詳しくはこちらをご確認ください
すごくわかりやすくまとまっていると思います !
https://qiita.com/amachino/items/8cf609f6345959ffc450

ここでは、このファイルを継承すると、

関数名 概要
totalSupply() 独自通貨の総量を返す
transfer(_to, _value) この関数を呼び出した人から_toのアドレスへ_valueだけ独自通貨を支払う
balanceOf(_owner) _ownerの独自通貨の所持量を返す

という関数を呼び出すことができるということだけ認識してください。

独自通貨の実装

では、早速先ほどのファイルを使い、実際に実装を行います。

yarn add --dev zeppelin-solidity
touch contracts/AwesomeTeamCoin.sol
AwesomeTeamCoin.sol
pragma solidity ^0.4.18;


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


contract AwesomeTeamCoin is StandardToken {
    string public constant name = "AwesomeTeamCoin";
    string public constant symbol = "ATC";
    uint8 public constant decimals = 18;

    /**
    * @dev Constructor that gives msg.sender all of existing tokens.
    * @param _initialSupply : initial tokens.
    */
    function AwesomeTeamCoin(uint256 _initialSupply) public {
        totalSupply_ = _initialSupply;
        balances[msg.sender] = _initialSupply;
        Transfer(0x0, msg.sender, _initialSupply);
    }
}

これだけで簡単な仮想通貨の実装は終わりです。
説明を行います。

contract AwesomeTeamCoin is StandardToken {
    // スクリプト群(function群)
}

この中にスクリプト群(function群)を記述していきます。
多言語でいう、クラスみたいなものですね
isのあとは継承するコントラクトを指定します

contract AwesomeTeamCoin is StandardToken {
        string public constant name = "AwesomeTeamCoin";
    ...
}

Publicで宣言した変数は、外部からinstance.nameのような形でアクセスができます。

contract AwesomeTeamCoin is StandardToken {
    function AwesomeTeamCoin(uint256 _initialSupply) public {
        totalSupply_ = _initialSupply;
        balances[msg.sender] = _initialSupply;
    }
}

Contract名と同じ名前のfunctionなので、コンストラクターです。Javaによく似ていますね
superであるStandardToken.solのtotalSupply_というStandardTokenの変数に_initialSupplyをセットします。
次の行のmsg.senderは予約語です。
このfunctionを実行した人(今回の場合、コンストラクターなので、コントラクトをデプロイした人)のアドレスが入ります。
つまり、balances[msg.sender] = _initialSupply はコントラクトをデプロイした人に全ての仮想通貨を付与するということです。

簡単ですが、これで自分だけの仮想通貨が出来上がりました。
それではこれをコンパイルして、プライベートブロックチェーンネットワークであるGanacheにデプロイしましょう!

GanacheでプライベートなEthereumネットワークの構築

gitのサブモジュールとしてganacheを入れて、そこに先ほど作った独自通貨をデプロイします。

git submodule add https://github.com/trufflesuite/ganache
cd ganache
yarn install
yarn start

プライベートなEthereumネットワークの準備ができました :smile_cat:
画像では、localhostの8545ポート、ネットワークIDは15026...で立ち上がっていますね!

Ganacheのいいところは、画面を見ればわかるように、あらかじめ10つのアカウントが発行されていて、100ETHずつ用意されているので、開発する上では最適です!
それでは、このネットワークに先ほどのコントラクトをデプロイしましょう

truffle-config.jsを以下のように編集します

truffle-config.js
module.exports = {
  networks: {
    ganache: {
      host: 'localhost',
      port: 8545, // Ganacheで表示されたポートに設定
      network_id: '*' // ワイルドカード
    }
  }
};

次に、デプロイのためのマイグレーションファイルを作成します。

touch migrations/2_deploy_contracts.js
2_deploy_contracts.js
const DEPLOYER = 'Ganacheでデプロイした時に表示される10つのアドレスのうちから一つ';
const AwesomeTeamCoin = artifacts.require('AwesomeTeamCoin');

module.exports = (deployer) => {
  deployer.deploy(AwesomeTeamCoin, 10000, { from : DEPLOYER });
};

Ganacheで表示されている10このアカウントから適当に1つ選んでDEPLOYERを設定します。
先ほど、AwesomeTeamCoinのConstructorを呼び出した人に全ての独自仮想通貨が付与されるように実装したので、このアカウントに全ての独自仮想通貨が付与されます。

準備は完了しましたので、truffleからコンパイルとデプロイを行いましょう
truffleコンソールに入る時に先ほど、--networkオプションでtruffle-config.jsで指定したganacheを指定します。

truffle console --network ganache
> compile
> migrate

これで、ganacheネットワークにコントラクトがデプロイされました。
これだけだと面白くないので、フロントを作成し、仮想通貨取引をしてみましょう :laughing:

フロント側の実装

開発環境の構築

npmを使い、関連パッケージを入れていきます

npm i --save-dev vue vue-class-component vue-router vuex-class
npm i --save-dev typescript ts-loader
npm i --save-dev laravel-mix cross-env
npm i --save-dev bulma
npm i --save-dev web3 @types/node truffle-contract

あまり見ないのは、laravel-mixですかね?
PHP framework のLaravelで使われているwebpackのラッパープラグインみたいなものです。
設定ファイルが少なくて済むので、これを使います。

src/
 |- assets/
 |    |- ts/
 |    |   |- application.ts
 |    |  ...
 |    |- sass/
 |    |   |- applications.scss
 |- public/
 |    |- js/
 |    |- css/
 |    |- index.html
 |- package.json
 |- tsconfig.json
 |- webpack.mix.js

全体のディレクトリ構成はこのような形でいきます。
assets/に実装ファイルを置き、laravel-mixでコンパイルしたファイルをpublic/に置きます

Vue.js × TypeScriptの実装

まずディレクトリ構成と概要から

ts
|- views/ // 各view(vue-router表示)
|    |- Home.vue
|    |- Balances.vue
|    |- Trading.vue
|- components/
|      |- Transfer.vue // Tradingで使うcomponent
|- api/
|   |- getWeb3.ts
|- application.ts // indexファイル(vue-routerやbootstrapなどを読み込む)
|- router.ts // vue-routerの設定ファイル
|- bootstrap.ts // 初期設定を行うファイル(今回はweb3を読み込む)
|- sfc.d.ts // typescriptで*.vueや.jsonを読み込むための型定義ファイル

Vue.js × TypeScriptを同時に使う時に注意しないといけないのは、sfc.d.tsです
TypeScriptでは型定義をきちんと行わなければならないですが、.vueファイルや.jsonファイルを読み込みたいので、型定義を行います

sfc.d.ts
declare module "*.vue" {
    import Vue from 'vue'
    export default Vue
}
declare module "*.json" {
    const value: any;
    export default value;
}

Home.vueを作り、それをvue-routerでroutingを行うことで表示します
また、Vue.js × TypeScriptの時には、Vue.component()でコンポーネントを定義する必要がありますが、vue-class-componentを使うことで、@Componentプロパティをクラスに付与するとVue.component()と同じ扱いになります。

Home.vue
<template>
    ...
</template>

<script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'

    @Component
    export default class Home extends Vue {
        ...
    }
</script>
router.ts
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './views/Home.vue'

Vue.use(VueRouter)

export default new VueRouter({
    routes: [
        {
            path: '/',
            component: Home,
            name: 'home'
        }
    ]
})

これをapplication.tsで読み込み、index.html#appidにマウントします

application.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import router from './router'

@Component<App>({
    router
})
class App extends Vue {

}

const vm = new App()

vm.$mount('#app')

細かいコードはリポジトリを見ていただければと思いますが、ここまでで以下のような画面が表示できました

スクリーンショット 2018-03-05 4.50.29.png

Ethereumネットワークとの接続

それでは、いよいよWeb3を使って、Ganache(Ethereumネットワーク)と接続を試みましょう

getWeb3.ts
import Web3 from 'web3'

export const getWeb3 = function () {
    let web3 : Web3 | undefined

    if (typeof web3 !== 'undefined') {
        web3 = new Web3(web3.currentProvider)

        console.log('Injected web3 detected.')
    } else {
        let provider = new Web3.providers.HttpProvider('http://localhost:7545')

        web3 = new Web3(provider)

        console.log('No web3 instance injected, using Local web3.')
    }
    return web3
}

Web3を取得し、プロバイダー(ネットワークやポート)の設定を行っています
これを最初に呼び出し、使いやすいようにwindow['web3']に入れておきます

bootstrap.ts
import Web3 from 'web3'
import { getWeb3 } from './api/getWeb3';

declare global {
    interface Window {
        web3: Web3
    }
}

window.web3 = getWeb3()
application.ts
import './bootstrap.ts'
...

Balances.vue(全アカウントの残高を確認するview)の実装

では、簡単に、全アカウントの残高を確認するBalances.vueを作成しましょう

Balances.vue
<template>
    ...
</template>

<script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import AwesomeTeamCoin from ~/build/contracts/AwesomeTeamCoin.json'

    interface Account {
        address: string,
        balances: number
    }

    @Component
    export default class Balances extends Vue {
        showingAccounts : any[] = []

        async created () {
            let accounts = await window['web3'].eth.getAccounts()
            const contract = require('truffle-contract')
            const atc = contract(AwesomeTeamCoin)
            atc.setProvider(window.web3.currentProvider)
            const instance = await atc.deployed()
            for (let account of accounts) {
                const balances = await instance.balanceOf(account)
                let accountData = {} as Account
                accountData.address = account
                accountData.balances = balances.c[0]
                this.showingAccounts.push(accountData)
            }
        }
    }
</script>

説明します

let accounts = await window['web3'].eth.getAccounts()

で全アカウントが取得できます

import AwesomeTeamCoin from '~build/contracts/AwesomeTeamCoin.json'

const contract = require('truffle-contract')
const atc = contract(AwesomeTeamCoin)
atc.setProvider(window.web3.currentProvider)
const instance = await atc.deployed()

これで、デプロイされたコントラクトにアクセスできます

const balances = await instance.balanceOf(account)

上のコントラクトBasicTokenのところで説明したbalanceOf()を呼び出しそれぞれのアカウントの独自仮想通貨量を取得します

これで、以下のようなViewを作ることができました

きちんと先ほどGanacheにコントラクトをデプロイしたアカウントに独自仮想通貨量が付与されていますね :smiley:

Transfer.vue(独自仮想通貨取引を行うview)の作成

最後に、独自仮想通貨取引を行うviewを作成します

Transfer.vue
<template>
    ...
</template>

<script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import AwesomeTeamCoin from '~/build/contracts/AwesomeTeamCoin.json'

    @Component
    export default class CreateAccount extends Vue {
        senderAddress = '0x627306090abaB3A6e1400e9345bC60c78a8BEf57'
        receiverAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'
        amount = 10

        async submit () {
            const contract = require('truffle-contract')
            const atc = contract(AwesomeTeamCoin)
            atc.setProvider(window.web3.currentProvider)
            const instance = await atc.deployed()
            const response = await instance.transfer(this.receiverAddress, this.amount, { from: this.senderAddress })
        }
    }
</script>

先ほどとほとんど同じですが、呼び出すコントラクトの関数がinstance.transfer()になっています。

関数呼び出しの際、引数の最後に{ from: アドレス }を指定するとそのアドレスからfunctionを呼び出すことができます。

これで、以下のようなviewを作成することができました

実際にsubmitボタンを押すと、transfer関数が呼び出され、062...のアカウントから0xf17...のアカウントに10移動されます

以上が分散アプリケーション開発の基本的な流れになります

最後に

Truffleを用いたコントラクトの実装からコンパイル、デプロイの流れを示し、さらに、Web3を用い、Webからのアクセスを行いました

今回作った独自の仮想通貨は簡単な取引ができるものでしたが、Ethereumを用いれば、もっと色々なデータをブロックチェーンに記録することができます
例えば、Stream Tokenはまだ開発段階ですが、Ethereumを用いて動画ストリーミング配信を行うことができるサービスを目指しています。

※ 動画なんか大きいファイルがブロックチェーンに記録することができるの??という疑問を抱く方もいらっしゃるかと思いますが、
そこで、ブロックチェーンで画像や動画を管理するときは、storageが用意されています(いわゆる分散Dropboxみたいな形です)

これからどんどん発展していく分野だと思いますので、皆さんもぜひ一度触って見てください

a-hikkoshi
当社は、親会社である株式会社エイチームの経営理念をそのままに、引越しの比較サービス開始以降、大切にしてきた「三方よし」の理念を基本として、世の中に求められるサービスの創造を目指します。 一緒に働けるエンジニアを募集しております。下記URLよりご応募ください。 https://bit.ly/3lwf7QJ
https://hikkoshi.a-tm.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away