Truffle+Drizzle+Reactのチュートリアルをやってみる
2018/12/25
Table of Contents
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でも試してみたいとおもいます。
以上、最後まで読んでいただきありがとうございました。