コンテンツにスキップするには Enter キーを押してください

Truffle+Drizzle+Reactのチュートリアルをやってみる

こんにちは。
Fusic吉野です。
メリークリスマスですね!

今回は、Ethereumを用いたDapps開発のフレームワークである「Truffle」の公式サイトにのっている

「GETTING STARTED WITH DRIZZLE AND REACT」
をやってみたので、紹介したいと思います。

基本チュートリアルの手順通りに進めていますが、一部順番を前後させている部分があります。

Drizzleとは

Drizzle is the newest member of the Truffle Suite and our first front-end development tool. At its core, Drizzle takes care of synchronizing your contract data, transaction data and more from the blockchain to a Redux store.

多分、重要なのはここですかね

Drizzle takes care of synchronizing your contract data, transaction data and more from the blockchain to a Redux store

DrizzleはブロックチェーンのコントラクトやトランザクションのデータをRedux Storeで管理してくれるもの、のようです。

つまりは、ブロックチェーンのデータの状態管理をしてくれるもの、という認識でよさそうですね。

なんだか便利そう。

実際にチュートリアルを進めてみます。

環境

$ truffle version                                              
Truffle v5.0.0 (core: 5.0.0)
Solidity v0.5.0 (solc-js)
Node v10.11.0

$ npm --version
6.4.1

スマートコントラクトを書く

まずは、Truffleを使って簡単なスマートコントラクトを書いていきます。

Truffleやganache=cliのインストールがまだな方は、以下のコマンドでインストールしましょう。

$ npm install -g truffle
$ npm install -g ganache-cli

プロジェクトディレクトリを作成します。

$ mkdir truffle-drizzle-react-practice

作成できたら、さっそくTruffleをはじめます。

$ truffle init

今回のチュートリアルのスマートコントラクトは、ただ文字列をセットするだけの、とても単純なものになります。
contracts/ 配下に MyStringStore.solという名前でファイルを作成します。

pragma solidity ^0.5.0; // 公式チュートリアルでは^0.4.24ですが、それだと動かなかった、、、

contract MyStringStore {
  string public myString = "Hello World";
	// こちらも公式チュートリアルにはないですが、memoryを追記します
  function set(string memory x) public { 
    myString = x;
  }
}

次にMigrationファイルを作成します。
2_deploy_contracts.jsという名前のファイルをmigrations配下に作成します。

const MyStringStore = artifacts.require('MyStringStore')

module.exports = function(deployer) {
	deployer.deploy(MyStringStore)
}

次に、接続するブロックチェーンの設定を行います。
truffle-config.jsのdevelopmentの箇所のコメントアウトを外します。

development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
},

host、portなどは使用するganacheに合わせて適宜変更してください。(基本そのままでいいはず)

ブロックチェーンにMigrationする

次に書いたコードをローカルチェーンにMigrationします。

まずは新しくコンソールを開き、別プロセスでganacheを立ち上げます。

$ ganache-cli -b 3

スマートコントラクトをcompileします。

$ truffle compile

次にブロックチェーン上にmigrateします。

$ truffle migrate

これでMigrationが完了です。

スマートコントラクトをテストする

スマートコントラクトにはテストコードが必須です。
ここでもテストコードを書いていきます。

MyStringStore.jsというファイルをtests/配下に設置します。

const MyStringStore = artifacts.require("./MyStringStore.sol");

contract("MyStringStore", accounts => {
  it("should store the string 'Hey there!'", async () => {
    const myStringStore = await MyStringStore.deployed();

    // "Hey there!"という文字列をセット
    await myStringStore.set("Hey there!", { from: accounts[0] });

    // 文字列を取得
    const storedString = await myStringStore.myString.call();

        // 文字列が等しいかどうかチェック
    assert.equal(storedString, "Hey there!", "The string was not stored");
  });
});

テストを実行します。

$ truffle test
Using network 'development'.



  Contract: MyStringStore
    ✓ should store the string 'Hey there!' (3080ms)


  1 passing (3s)

passしたら、成功です。

これで簡単なスマートコントラクトのコードを書くことができました。

Reactのプロジェクトをつくる

次にReactのプロジェクトを作成します。
Reactのプロジェクトをclientディレクトリ配下に作成します。

$ npx create-react-app client

Reactプロジェクトのsrc配下に、contractsへのシンボリックリンクをはります。

$ cd src
$ ln -s ../../build/contracts contracts

これでReactプロジェクトが作成できました。

drizzleを導入する

遂に、drizzleの導入です。
例のごとく、npmでインストールします。

$ npm install drizzle

一度ここでReactプロジェクトを立ち上げてみます。

$ npm start

すると以下のようなエラーがでます。

There might be a problem with the project dependency tree.
It is likely not a bug in Create React App, but something you need to fix locally.

The react-scripts package provided by Create React App requires a dependency:

  "babel-eslint": "9.0.0"

Don't try to install it manually: your package manager does it automatically.
However, a different version of babel-eslint was detected higher up in the tree:

  /Users/ayasamind/ethereum/node_modules/babel-eslint (version: 8.2.3)

Manually installing incompatible versions is known to cause hard-to-debug issues.

If you would prefer to ignore this check, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project.
That will permanently disable this message but you might encounter other issues.

Reactとethereumのbabel-eslintのバージョンが異なることによるエラーのようです。

今回は一旦動かす、という方針で進めます。

client配下に.envファイルを作成します。

SKIP_PREFLIGHT_CHECK=true

この状態で、再度、npm startを実行すると

Reactのスタート画面が表示されます。

storeの設定をする

次に、drizzleをstoreで使用する設定を書いていきます。

src/index.jsファイルを以下のように変更します。(公式のチュートリアルを若干異なるので注意)

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

// 追加
import { Drizzle, generateStore } from "drizzle";
import MyStringStore from "./contracts/MyStringStore.json";
const options = { contracts: [MyStringStore] };
const drizzleStore = generateStore(options);
const drizzle = new Drizzle(options, drizzleStore);

ReactDOM.render(<App drizzle={drizzle} // 追加 />, document.getElementById('root'));
serviceWorker.unregister();

Appコンポーネントに対して、drizzleのstoreを渡しています。
このdrizzleをview側で読み込んでいきます。

Drizzleでコントラクトの値を読むComponentを作成する

Drizzleのstoreの値を参照するコンポーネントを作成します。

ReadString.jsをsrc/配下に作成します。

import React from "react";

class ReadString extends React.Component {
  state = { dataKey: null };

  componentDidMount() {
    const { drizzle } = this.props;
    const contract = drizzle.contracts.MyStringStore;

    // let drizzle know we want to watch the `myString` method
    const dataKey = contract.methods["myString"].cacheCall();

    // save the `dataKey` to local component state for later reference
    this.setState({ dataKey });
  }

  render() {
    // get the contract state from drizzleState
    const { MyStringStore } = this.props.drizzleState.contracts;

    // using the saved `dataKey`, get the variable we're interested in
    const myString = MyStringStore.myString[this.state.dataKey];

    // if it exists, then we display its value
    return <p>My stored string: {myString && myString.value}</p>;
  }
}

export default ReadString;

Drizzleでコントラクトに値を書き込むComponentを作成する

SetString.jsをsrc/配下に作成します。

import React from "react";

class SetString extends React.Component {
  state = { stackId: null };

  handleKeyDown = e => {
    // if the enter key is pressed, set the value with the string
    if (e.keyCode === 13) {
      this.setValue(e.target.value);
    }
  };

  setValue = value => {
    const { drizzle, drizzleState } = this.props;
    const contract = drizzle.contracts.MyStringStore;

    // let drizzle know we want to call the `set` method with `value`
    const stackId = contract.methods["set"].cacheSend(value, {
      from: drizzleState.accounts[0]
    });

    // save the `stackId` for later reference
    this.setState({ stackId });
  };

  getTxStatus = () => {
    // get the transaction states from the drizzle state
    const { transactions, transactionStack } = this.props.drizzleState;

    // get the transaction hash using our saved `stackId`
    const txHash = transactionStack[this.state.stackId];

    // if transaction hash does not exist, don't display anything
    if (!txHash) return null;

    // otherwise, return the transaction status
    return `Transaction status: ${transactions[txHash].status}`;
  };

  render() {
    return (
      <div>
        <input type="text" onKeyDown={this.handleKeyDown} />
        <div>{this.getTxStatus()}</div>
      </div>
    );
  }
}

export default SetString;

二つのコンポーネントをApp.jsで呼び出す

最後に作成した二つのコンポーネントをApp.jsに追加します。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import ReadString from "./ReadString";
import SetString from "./SetString";

class App extends Component {
  state = { loading: true, drizzleState: null };

  componentDidMount() {
    const { drizzle } = this.props;

    // subscribe to changes in the store
    this.unsubscribe = drizzle.store.subscribe(() => {

      // every time the store updates, grab the state from drizzle
      const drizzleState = drizzle.store.getState();

      // check to see if it's ready, if so, update local component state
      if (drizzleState.drizzleStatus.initialized) {
        this.setState({ loading: false, drizzleState });
      }
    });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    if (this.state.loading) return "Loading Drizzle...";
    return (
      <div className="App">
        <ReadString
          drizzle={this.props.drizzle}
          drizzleState={this.state.drizzleState}
        />
        <SetString
          drizzle={this.props.drizzle}
          drizzleState={this.state.drizzleState}
        />
      </div>
    );
  }
}

export default App;

実行

$ npm start

MetamaskのChrome Extensionが開き、許可をすると、

このような画面が表示されます。

文字列を入力して、Enterを押すと、Ethereumの送金確認がされ、Confirmすると、コントラクトに保存された文字列が変更されます。

以上、Truffle、 Drizzle、Reactのチュートリアルをやってみました。

Drizzleはコントラクトの状態を簡単にReduxに入れることができ、書き込みも手軽にできて、非常に便利でした。

自分は普段Vueを使うことが多いため、DrizzleをVuexでも試してみたいとおもいます。

以上、最後まで読んでいただきありがとうございました。

Fusicエンジニア。Webアプリケーションやブロックチェーンプログラミングをしています。PHP, Vue.js, Solidity etc..

コメントする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です