Top View


Author yoshino

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

2018/12/25

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でも試してみたいとおもいます。

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

yoshino

yoshino

Twitter X

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