drizzleでブロックチェーンTodoアプリを作る

[Ethereum Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ethereum) の7日目です！
よろしくお願いします！

# はじめに
drizzleはethereum上でDAppsを作るためのフロントエンドライブラリです。
Redux上に構築されており、Reduxを通してコントラクトやトランザクションのステータスにアクセスできます。
今回は簡単なTodoアプリを作ってみます！
チュートリアルなのでテストとか権限・セキュリティ関係は割愛です。
公式サイトにも[チュートリアル](https://truffleframework.com/tutorials/getting-started-with-drizzle-and-react)があり、もっとシンプルな内容を扱っています。
公式の後でこちらをやってみてもらえると、より深く理解できるかと思います。
完成イメージはこちら。
![完成イメージ.gif](https://qiita-image-store.s3.amazonaws.com/0/133447/49734229-2c8f-28f0-e13f-f72afc27fcca.gif)


# ethereum側の構築
## 準備
まず[truffle](https://truffleframework.com/docs/truffle/overview)や[ganache](https://truffleframework.com/docs/ganache/overview)をインストールしていない方はインストールしましょう。

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

次にプロジェクトを新たに作成します。

```
$ mkdir todo-chain
$ cd todo-chain
$ truffle init
```

## コントラクトの作成
次にコントラクトを作成していきましょう。

```solidity:contracts/TodoStore.sol

pragma solidity ^0.4.23;

contract TodoStore {
  struct Todo {
    string title;
    bool isDone;
  }

  Todo[] todoList;

  function addTodo(string x) public {
    todoList.push(Todo(x, false));
  }

  function showTodo(uint _index) public view returns(uint, string, bool) {
    if (todoList.length > _index) {
      string memory title = todoList[_index].title;
      return (_index, title, todoList[_index].isDone);
    }
    return (0, "Wrong index", false);
  }

  function doneTodo(uint _index, bool result) public {
    if (todoList.length > _index) {
      todoList[_index].isDone = result;
    }
  }

  function getTodoLength() public view returns(uint) {
    return todoList.length;
  }
}
```
シンプルなので、読めば何をやっているかわかってもらえるかと思います。
クライアントからtodoを取得するメソッドがありますが、１件ずつ取得のものしか準備されておらず、イケてないですね…
本当は全件or複数件取得したいのですが、stringの配列を返せないというSolidityの素敵仕様のため、こうしています。
誰かいいHackをご存知でしたら教えてください（泣）

migrationファイルはこんな感じに。超シンプル。

```Javascript:migrations/2_deploy_contracts.js

const TodoStore = artifacts.require("TodoStore");

module.exports = function(deployer) {
  deployer.deploy(TodoStore);
};
```

また、ganache-cli上に作るのでネットワーク設定もしていきます。

```Javascript:truffle.js

module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*",
    }
  }
};
```

## マイグレーション
ここまできたら、migrateしてみましょう！

```
$ ganache-cli -b 3
$ truffle compile
$ truffle migrate
```
これでethereum側はOKです！

# フロントエンド側の構築
次にフロントエンド側の実装です。

## 準備
前述の通りdrizzleはReduxをベースに構築されているので、様々なJSフレームワークを乗せることができます。
公式はReact推しみたいで、[drizzle-react](https://github.com/trufflesuite/drizzle-react)というライブラリも準備されているので、Reactを使っていきましょう！

```
$ npx create-react-app client
```

いろいろできましたね。npxもcreate-react-appも最高！

次に必要なパッケージを追加でインストールしていきます。
チュートリアルなのでなるべく余計なものは入れたくないんですが、ちょっとデザインかっこつけたいのでmaterial-uiだけは入れます。

```
$ cd client
$ npm install -S @material-ui/core drizzle drizzle-react
```
（チュートリアルだしnpmにしたけど、やっぱyarnの方が早いな。。）
clientの内部にコントラクトへのシンボリックリンクを貼っておきます。

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

## drizzleインスタンスの実装
まずはおおもとである`index.js`から。
こんな感じにしましょう！じゃーん！

```Javascript:client/src/index.js

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

import { Drizzle, generateStore } from 'drizzle';
import { DrizzleContext } from 'drizzle-react';
import TodoStore from './contracts/TodoStore.json';
import TodoKeyContext from './contexts/todoKey';

const options = { contracts: [TodoStore] };
const drizzleStore = generateStore(options);
const drizzle = new Drizzle(options, drizzleStore);

ReactDOM.render(
  <DrizzleContext.Provider drizzle={drizzle}>
    <TodoKeyContext.Provider>
      <App />
    </TodoKeyContext.Provider>
  </DrizzleContext.Provider>,
  document.getElementById('root'));
serviceWorker.unregister();
```

ポイントはこの辺ですね。

```Javascript
import TodoStore from './contracts/TodoStore.json';
import { Drizzle, generateStore } from 'drizzle';
...

const options = { contracts: [TodoStore] };
const drizzleStore = generateStore(options);
const drizzle = new Drizzle(options, drizzleStore);
```
drizzleがTodoStore.jsonを参照してstoreに格納します。
それによって、drizzleインスタンスからコントラクトのメソッドを呼び出せるようになります。

```Javascript:
import { DrizzleContext } from 'drizzle-react';
...

ReactDOM.render(
  <DrizzleContext.Provider drizzle={drizzle}>
	...
  </DrizzleContext.Provider>,
```
Context APIを通してdrizzleインスタンスを渡し、子コンポーネントからもdrizzleインスタンスを操作できるようにします。

## Contextの作成
ではどうやってTodoを呼び出すのでしょう？
今回は独自の`DrizzleContext`の他に、独自のContextを通して呼び出すことにしました。
それが`TodoKeyContext`です。

```Javascript:client/src/contexts/todoKey.js

import React from 'react';

const Context = React.createContext();

class Provider extends React.Component {
  constructor(props) {
    super(props);

    this.fetchTodoKeys = async (contract) => {
      const todoLength = await contract.methods.getTodoLength().call();
      const dataKeys = [];
      for (let i = 0; i < todoLength; i++) {
        await dataKeys.push(contract.methods.showTodo.cacheCall(i));
      }
      this.setState({
        ...this.state,
        dataKeys,
      });
    };

    this.turnFetchStatus = (newState) => {
      this.setState({
        ...this.state,
        isFetchingTodo: newState,
      });
    }

    this.state = {
      dataKeys: [],
      fetchTodoKeys: this.fetchTodoKeys,
      isFetchingTodo: false,
      turnFetchStatus: this.turnFetchStatus,
    };
  }

  render() {
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    );
  }
}

export default {
  Consumer: Context.Consumer,
  Provider
};

```

drizzleでは各メソッドをcacheCallで呼び出し、その戻り値をトラッキングします。
たとえば`showTodo`メソッドでi番目のtodoを呼び出しておくと、ethereum上でi番目のtodoが変更されたことを検知して、stateが更新されるんですね。
ここでいう「i番目のtodo」はstore上にkey値で管理されているので、そのkeyを取得するのがこの`TodoKeyContext`コンテキストのおもな役割です。
また、keyの取得中に無駄なレンダリングが走らないよう、状態管理もしています。

## Containerの作成
いよいよComponentを作成していきます！
まずは`App.jsx`から。
なんとなくComponentは拡張子を`.jsx`にしたいのでそうします。既存の`App.js`は消してください。

```Javascript:client/src/App.jsx

import React from 'react';
import { DrizzleContext } from 'drizzle-react';
import logo from './logo.svg';
import './App.css';

const App = initialized => {
  if(!initialized) return 'Loading...';
  return (
    <div className='App'>
      <header className='App-header'>
        <img src={logo} className='App-logo' alt='logo' />
        <p>dummy</p>
      </header>
    </div>
  );
}

const withContext = () => (
  <DrizzleContext.Consumer>
    {({ initialized }) => App(initialized)}
  </DrizzleContext.Consumer>
);
export default withContext;
```

logoとかstyleとかは`create-react-app`のものを流用。だってかっこいいし。
中身は特に難しいことをしていないですね。
気になるのはちょくちょく出てくる`initialized`でしょうか。`DrizzleContext`を通して持ってきてますね。
`DrizzleContext`では`drizzle`, `drizzleState`, `initialized`を返します。
`drizzleState`は`drizzle.store.getState()`で取得されるstoreに保持されたstateで、 `initialized`は`drizzleState`内のプロパティです。
`drizzle` > `drizzleState` > `initialized`というイメージですね。
`drizzle`インスタンスさえあれば全部引っ張り出せますが、よく使うのでContextに乗っけてるんでしょう。
詳細を知りたい方は[drizzle-react](https://github.com/trufflesuite/drizzle-react)のソースを読んでみるといいかと！中身はとてもシンプルです。

ここまできたら一旦アプリを立ち上げてみましょう！`client/`ディレクトリで

```
$ npm start
```
「Loading...」という画面が表示されてから、[MetaMask](https://metamask.io/)との接続確認があり、承認後に以下のような画面が出れば、とりあえずOKです！
![dummy.png](https://qiita-image-store.s3.amazonaws.com/0/133447/a279137a-b40c-1aa7-0648-70bf4ec23a9a.png)


## Todoの追加
Todoの追加機能を作っていきます。まずは`App.jsx`の修正。

```diff:client/App.jsx

 import logo from './logo.svg';
 import './App.css';
+import AddTodo from './components/AddTodo';

...

         <img src={logo} className='App-logo' alt='logo' />
-        <p>dummy</p>
+        <AddTodo />
       </header>
```

次に`AddTodo`コンポーネントを作ります。どん！

```Javascript:client/components/AddTodo.jsx

import React, { Component } from 'react';
import TextField from '@material-ui/core/TextField';
import { DrizzleContext } from 'drizzle-react';
import TodoKeyContext from '../contexts/todoKey';

class AddTodo extends Component {
  state = { stackId: null };

  handleKeyDown = e => {
    if (e.keyCode === 13) {
      this.addTodo(e.target.value);
    }
  };

  addTodo = value => {
    const { drizzle, drizzleState } = this.props;
    const contract = drizzle.contracts.TodoStore;

    const stackId = contract.methods.addTodo.cacheSend(value, {
      from: drizzleState.accounts[0]
    });
    this.setState({ stackId });
    this.props.turnFetchStatus(true);
  };

  getTxStatus = () => {
    const { transactions, transactionStack } = this.props.drizzleState;
    const txHash = transactionStack[this.state.stackId];

    if (!txHash) return null;
    if (
      transactions[txHash].status === 'success' &&
      this.props.isFetchingTodo
    ) {
      this.props.turnFetchStatus(false);
      this.props.fetchTodoKeys(this.props.drizzle.contracts.TodoStore);
    }
    return transactions[txHash].status === 'success'
      ? 'Todo追加に成功しました！'
      : 'Todo追加中…';
  };

  render() {
    return (
      <div style={{ textAlign: 'center', margin: '50px auto' }}>
        <TextField
          type='text'
          placeholder='Todoを入力してください'
          onKeyDown={this.handleKeyDown}
          style={{ width: '500px', backgroundColor: 'white' }}
        />
        <div>{this.getTxStatus()}</div>
      </div>
    );
  }
}

const withContext = () => (
  <DrizzleContext.Consumer>
    {({ drizzle, drizzleState }) => (
      <TodoKeyContext.Consumer>
        {({ fetchTodoKeys, isFetchingTodo, turnFetchStatus }) => (
          <AddTodo
            drizzle={drizzle}
            drizzleState={drizzleState}
            fetchTodoKeys={fetchTodoKeys}
            isFetchingTodo={isFetchingTodo}
            turnFetchStatus={turnFetchStatus}
          />
        )}
      </TodoKeyContext.Consumer>
    )}
  </DrizzleContext.Consumer>
);
export default withContext;
```

結構ずっしりですね…２箇所解説します。

```Javascript:
  addTodo = value => {
    const { drizzle, drizzleState } = this.props;
    const contract = drizzle.contracts.TodoStore;

    const stackId = contract.methods.addTodo.cacheSend(value, {
      from: drizzleState.accounts[0]
    });
    this.setState({ stackId });
    this.props.turnFetchStatus(true);
  };
```

`drizzle`インスタンスからコントラクトを呼び出し、`cacheSend`メソッドを使ってコントラクト内のメソッド`addTodo`に送信しています。引数は`addTodo`メソッドの引数と送信者のアカウントですね。後者は`drizzleState`から取得。
`cacheSend`を実行すると、送信したトランザクションスタックのIDが返ってくるので、そちらを`AddTodo`のstateに保持しておきます。

次にトランザクション管理。

```Javascript:

  getTxStatus = () => {
    const { transactions, transactionStack } = this.props.drizzleState;
    const txHash = transactionStack[this.state.stackId];

    if (!txHash) return null;
    if (
      transactions[txHash].status === 'success' &&
      this.props.isFetchingTodo
    ) {
      this.props.turnFetchStatus(false);
      this.props.fetchTodoKeys(this.props.drizzle.contracts.TodoStore);
    }
    return transactions[txHash].status === 'success'
      ? 'Todo追加に成功しました！'
      : 'Todo追加中…';
  };
```
こちらは`drizzleState`からトランザクション情報を取得して、対応する文字列を返すようにしています。
トランザクション情報の識別には先ほどstateに保持したトランザクションスタックのIDが必要です。
また、Todo追加のトランザクションが完了したタイミングで、Todoリスト（TodoKeys）を取得し直す役割もあります。

実行するとこんな感じになるはずです！
![AddTodo.png](https://qiita-image-store.s3.amazonaws.com/0/133447/6c025124-067d-bac5-fc2e-61483e148ddc.png)


## Todoを見る
次は追加したTodoを確認していきましょう〜

```diff:client/App.jsx

 import AddTodo from './components/AddTodo';
+import ShowTodo from './components/ShowTodo';

...

         <img src={logo} className='App-logo' alt='logo' />
         <AddTodo />
+        <ShowTodo />
       </header>
```

```Javascript:client/components/ShowTodo.jsx

import React, { Component } from 'react';
import { DrizzleContext } from 'drizzle-react';
import TodoKeyContext from '../contexts/todoKey';
import TodoTable from './TodoTable';

class ShowTodo extends Component {
  async componentDidMount() {
    await this.props.fetchTodoKeys(this.props.drizzle.contracts.TodoStore);
  }

  render() {
    const { TodoStore } = this.props.drizzleState.contracts;
    const todos = this.props.dataKeys.map(key =>
      TodoStore.showTodo[key] ? TodoStore.showTodo[key].value : []
    );
    return <TodoTable todos={todos} />;
  }
}

const withContext = () => (
  <DrizzleContext.Consumer>
    {({ drizzle, drizzleState }) => (
      <TodoKeyContext.Consumer>
        {({ dataKeys, fetchTodoKeys }) => (
          <ShowTodo
            drizzle={drizzle}
            drizzleState={drizzleState}
            dataKeys={dataKeys}
            fetchTodoKeys={fetchTodoKeys}
          />
        )}
      </TodoKeyContext.Consumer>
    )}
  </DrizzleContext.Consumer>
);
export default withContext;
```

このコンポーネントはシンプルですね。
`drizzleState`内のコントラクトプロパティに、`showTodo`メソッドの値が入っているので、`TodoKeyContext`で持っておいたキーをもとに取得します。
そして、それを次の`TodoTable`に渡すだけです。

Todoテーブルはこちら。

```Javascript:client/components/TodoTable.jsx

import React, { Component } from 'react';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Checkbox from '@material-ui/core/Checkbox';
import Paper from '@material-ui/core/Paper';
import { withStyles } from '@material-ui/core/styles';
import { DrizzleContext } from 'drizzle-react';

class TodoTable extends Component {

  render() {
    const CustomTableCell = withStyles(theme => ({
      head: {
        backgroundColor: theme.palette.common.black,
        color: theme.palette.common.white
      },
      body: {
        fontSize: 14
      }
    }))(TableCell);

    const rows = this.props.todos.map(todo => (
      <TableRow key={todo[0]}>
        <CustomTableCell>{todo[0]}</CustomTableCell>
        <CustomTableCell>{todo[1]}</CustomTableCell>
        <CustomTableCell>
          <Checkbox checked={todo[2]} />
        </CustomTableCell>
      </TableRow>
    ));

    return (
      <Paper style={{ width: '70%', margin: '0 auto 100px' }}>
        <Table>
          <TableHead>
            <TableRow>
              <CustomTableCell>No</CustomTableCell>
              <CustomTableCell>title</CustomTableCell>
              <CustomTableCell>status</CustomTableCell>
            </TableRow>
          </TableHead>
          <TableBody>{rows}</TableBody>
        </Table>
      </Paper>
    );
  }
}

const withContext = props => (
  <DrizzleContext.Consumer>
    {({ drizzle, drizzleState }) => (
      <TodoTable
        drizzle={drizzle}
        drizzleState={drizzleState}
        todos={props.todos}
      />
    )}
  </DrizzleContext.Consumer>
);
export default withContext;
```
material-uiでがっつりスタイリングしていますが、中身は表示させてるだけですね。
特に難しくはないかと思います！
こんな感じになります〜。Todo追加して遊んでみてください！
![ShowTodo.png](https://qiita-image-store.s3.amazonaws.com/0/133447/631e94dd-b175-606f-78bb-36f33c2a21c7.png)


## Todoにチェックをつける
このままだといつまでたってもTodoが完了しないので、チェックをつけて完了できるようにしましょう！
まずはチェックボックスとトランザクション状態を表示するようにします。


```diff:client/components/TodoTable.jsx

 import TableRow from '@material-ui/core/TableRow';
+import Checkbox from '@material-ui/core/Checkbox';
 import Paper from '@material-ui/core/Paper';
 
...

       <TableRow key={todo[0]}>
         <CustomTableCell>{todo[0]}</CustomTableCell>
         <CustomTableCell>{todo[1]}</CustomTableCell>
+        <CustomTableCell>
+          <Checkbox checked={todo[2]} onChange={this.onCheck(todo[0])} />
+        </CustomTableCell>
       </TableRow>
     ));

     return (
-      <Paper style={{ width: '70%', margin: '0 auto 100px' }}>
-        <Table>
-          <TableHead>
-            <TableRow>
-              <CustomTableCell>No</CustomTableCell>
-              <CustomTableCell>title</CustomTableCell>
-            </TableRow>
-          </TableHead>
-          <TableBody>{rows}</TableBody>
-        </Table>
-      </Paper>
+      <>
+        <div>{this.getTxStatus()}</div>
+        <Paper style={{ width: '70%', margin: '0 auto 100px' }}>
+          <Table>
+            <TableHead>
+              <TableRow>
+                <CustomTableCell>No</CustomTableCell>
+                <CustomTableCell>title</CustomTableCell>
+                <CustomTableCell>done</CustomTableCell>
+              </TableRow>
+            </TableHead>
+            <TableBody>{rows}</TableBody>
+          </Table>
+        </Paper>
+      </>
     );
```

次にチェックボックスをクリックした際の処理を実装します。

```diff:client/components/TodoTable.jsx

 class TodoTable extends Component {
+  state = { stackId: null };
+
+  onCheck = key => (e, checked) => {
+    const { drizzle, drizzleState } = this.props;
+    const contract = drizzle.contracts.TodoStore;
+    const stackId = contract.methods.doneTodo.cacheSend(key, checked, {
+      from: drizzleState.accounts[0]
+    });
+
+    this.setState({ stackId });
+  };
```
`AddTodo`のときとおなじですね。`cacheSend`でコントラクト上の`doneTodo`メソッドを呼び出しています。
最後にトランザクションの処理です！こちらも`AddTodo`のときと同じ。

```diff:client/components/TodoTable.jsx

    this.setState({ stackId });
  };
+
+  getTxStatus = () => {
+    const { transactions, transactionStack } = this.props.drizzleState;
+    const txHash = transactionStack[this.state.stackId];
+
+    if (!txHash) return null;
+    return transactions[txHash].status === 'success'
+      ? 'Todo状態を変更しました！'
+      : 'Todo状態を変更中…';
+  };

   render() {
     const CustomTableCell = withStyles(theme => ({
```
これで完成です！
こんな感じになっているはず！！
![DoneTodo.png](https://qiita-image-store.s3.amazonaws.com/0/133447/4eed6e4e-4ce9-1602-6655-6380c617075f.png)


# おわりに
drizzle初チャレンジでしたが、ちゃんと準備すればethereum上の値を追っかけてくれるのはいいですね！
Reduxを使用している方は特にやりやすいんじゃないでしょうか！