Slack の Reaction で ERC20 Token を付与するシステムを自分でも作ってみた

# はじめに 
[tiperc20: Slack上でERC20トークンを送りあってコミュニケーション
](https://tech.pepabo.com/2017/12/09/tiperc20/) の話を聞いて、これは面白そうだと思ったので、勉強がてら自分で実装してみました。

基本的には、[この](https://github.com/kentaro/tiperc20) repository のコンセプトを nodejs で実装し直した形です。

また、誰が誰に何のリアクションでトークンを送ったかを簡単に確認出来るように、ちょっとした web 面も作りました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/28899/6be8df0d-d29a-4564-191f-30bac9a4b5b5.png)


ユーザーは slack で自分の発言に reaction がつくと、前もって登録しておいた自分の Ethereum アカウントに ERC20 準拠の Token をもらえます。チームの Ethereum に対するリテラシー向上や Slack チーム内のコミュニケーションを促す目的での導入を想定しています。

# ユーザー体験

## 1. Slack 上から自分の Ethereum Address を登録する

slash command を先に登録しておいて、address を登録します
<img width="1062" alt="スクリーンショット 2018-03-31 15.57.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/0ecd6331-2a97-d8b4-edc8-a027f9846922.png">

App が、アドレスを登録したよ！と教えてくれます
<img width="611" alt="スクリーンショット 2018-03-31 15.57.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/e0580d1d-8047-db54-edae-7ef80e2f1c27.png">

これで準備は完了です

## 2. イケてる発言を行い、誰かが Reaction をつけてくれるのを待つ

<img width="692" alt="スクリーンショット 2018-03-31 16.00.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/de8075b7-1936-4f7d-ee9e-d674c8c52e8c.png">

## 3. Reaction がついたら、Web サイトや Metamask などを見て Token が付与されたのを確認

<img width="652" alt="スクリーンショット 2018-03-31 16.03.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/42f671e5-ff65-be0f-552d-a4a41185d759.png">

# トークンの付与とガスの支払いアカウントに関して

ERC20 トークンを発行したマスターアカウントの秘密鍵を nodejs のサーバーに環境変数として保管し、Token 付与はそのアカウントから行います。

このサーバーに保存したアカウント (便宜上マスターアカウントと呼ぶことにします) が Token 付与のガス代を支払うため、マスターアカウントには Ethereum をディポジットしておく必要があります。

# 全体の構成

全体の構成は以下のような感じです。

<img width="1041" alt="slack-token-image4.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/91596053-ca9b-c0ba-4bba-fa9f1582831d.png">

Ethereum へは Infura API を通してアクセスします。

## Reaction 発生時の処理

Reaction は Slack の Event API 経由で受け取ります。

Reaction が発生したら

1. ユーザーの slack userid から上記で登録した ethereum address を取得します。
2. reaction の種類が予め登録されたものかどうかをチェックし、token を送信するための job を作って queue に入れます。
3. Queue に入った job は 1 つずつ順番に取り出され、token の付与を行う Tx を作って ethereum に投げます。

![slack-token-image5.png](https://qiita-image-store.s3.amazonaws.com/0/28899/cf05510f-ff03-df4a-3d35-34520ddb5050.png)


## Job Queue の必要性

Ethereum には Account nonce と呼ばれる仕組みがあり、各アカウントのトランザクションは順番にしか実行できません。

>Account nonce: a transaction counter in each account. This prevents replay attacks where a transaction sending eg. 20 coins from A to B can be replayed by B over and over to continually drain A's balance.
>
>https://github.com/ethereum/wiki/wiki/Glossary

リアクションが一斉についたりした際に、1 つ前のトランザクションが終わる前に次のトランザクションを発行しようとすると nonce が合わなくなる可能性があります。

web3 (1.0系) では transaction を sign する際に nonce を指定しなければ自動で account の transaction カウントを取得して利用します。(http://web3js.readthedocs.io/en/1.0/web3-eth-accounts.html?highlight=nonce#signtransaction) 

この挙動によって、トランザクションの結果が確定する前に次のトランザクションを署名してしまうと、同じ nonce を持ったトランザクションが作られてしまうためどちらかが必ず失敗してしまいます。これを避けるために job queue を利用して順番にトランザクションを処理していく方法を採用しました。

# 使い方

ここからは、実際のシステムの使い方を説明していきます。作成した repository は [こちら](https://github.com/tsuzukit/slack-token)です。

## ライブラリのインストールとイメージの起動

システムは docker-compose を利用しているため、[Docker for Mac](https://docs.docker.com/docker-for-mac/) などを予めインストールしておく必要があります。

repository を git clone したら、まずは依存関係のあるライブラリをインストールします。

```
$ sh script/prepare.sh
```

インストールが終わったら docker-compose を起動します。

```
$ sh script/dev/start.sh
```

## 環境ファイル

環境ファイルは `./app` 以下に `.env` という名前で作成します。
例となるファイルをコピーして作っても問題ありません。

```
$ cp ./app/.env.example ./app/.env
```

内容は以下を参考に自分の環境に合わせて埋めてください

```
MONGO_DATABASE=slack-token

SALT=

ETHERESCAN_URL=https://<main 以外の場合はネットワーク名>.etherscan.io/
INFURA_ENDPOINT=https://rinkeby.infura.io/<Access Token>
SERVER_ACCOUNT_ADDRESS=<Ether account address>
SERVER_ACCOUNT_PRIVATE_KEY=<Ether account private key>
CONTRACT_ADDRESS=<Deploy 済みの ERC20 token address>

SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
SLACK_VERIFICATION_TOKEN=
SLACK_TOKEN=<起動時に emoji.list を叩いてカスタム絵文字を取得するために使う>

# カンマ区切りで記載。指定しなければ全てに反応する
EMOJI=slightly_smiling_face,bug

# カンマ区切りで Slack のチャンネルIDを記載。指定しなければ全てに対応する
CHANNEL=
```

ここで指定した `SERVER_ACCOUNT_ADDRESS` アカウントがマスターアカウントになります。`CONTRACT_ADDRESS` は Token を作成して Deploy した後で記載してください。また、設定した `INFURA_ENDPOINT` の向先によって、どのネットワークを利用するかが決まります。


## Token の作成と Deploy

まずは、Token を作成します。

`app/contract/contracts/CustomToken.sol` の変数を修正してください

```
string public name = "Token の名前";
string public symbol = "TICKER";
uint8 public decimals = 18; // Token の最小単位 
uint256 initialSupply = 10000e18; // 初期発行量 (1 = 最小単位なのでこの場合 10000 token の発行になる) 
```

### コントラクトのテスト

`ganache-cli` を利用して、実際に ethereum の network にはコントラクトを deploy せずにテストを行い、正しく solidity が記述できているかを確認します。

```bash
$ sh script/contract/test.sh
```

### Deploy

```bash
$ sh script/contract/deploy.sh
```

Deploy されたら、環境ファイルにコントラクトアドレスを記載してください。

# サーバーを立ち上げる

Slack App を配布するためには https 化する必要がある (恐らく配布時のみ？) [参考](https://api.slack.com/slash-commands#ssl) 

[Let's encrypt](https://letsencrypt.org/) などで取得した `fullchain.pem` と `privkey.pem` を この repository の root に置いてください (Symbolic link でも問題ありません)

本番(ステージング)環境でシステムを起動する際には `docker-compose-ssl.yml` を使います。ssl 用の証明書は実行時に volume mount されます。

```bash
# 下記コマンドで ssl 設定をしてある nginx image を build します
$ sudo sh script/stg/build.sh

# 下記コマンドで証明書を上記コンテナにマウントしつつ、docker-compose でサービスを起動します
$ sudo sh script/stg/start.sh
```

## Slack との結合

## Scope

Slack token を利用するためには以下のスコープの設定が必要です。

- Slash command Scope
- Reactions Scope
- Emoji Scope

<img width="589" alt="slack-token-image7.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/136a9b53-6312-518b-2cfe-3dacccc9debd.png">

### Slash command

Slack の slash command は `/api/register/` に来るように設定してください

<img width="279" alt="slack-token-image1.png" src="https://qiita-image-store.s3.amazonaws.com/0/28899/adfc9e23-03c2-c517-dd7d-f34dfaffb7b2.png">

### Reaction Event

Event は [reaction_added](https://api.slack.com/events/reaction_added) を購読してください。Slack の管理画面メニューの `Event Sbuscriptions` を選択して、Event 送信先の Request URL を指定します。`Add Workspace Event` ボタンから `reaction_added` を追加してください。

### Emoji

カスタム絵文字対応を行うために [emoji.list](https://api.slack.com/methods/emoji.list) API を起動時に呼びます。API を呼ぶための Token を管理画面から取得して、`.env` の `SLACK_TOKEN` に指定してください。

# 動作確認

設定が完了したらサーバーを起動してください。サーバーに ssh した状態で docker の状態を確認すると下記のようにプロセスが立っているはずです。

```
$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                      NAMES
hogehogehoge        slack-token-nginx   "nginx -g 'daemon of…"   2 days ago          Up 2 days           0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   slack-token-nginx
hogehogehoge        slack-token-queue   "/bin/sh -c 'node qu…"   2 days ago          Up 2 days                                                      slack-token-queue
hogehogehoge        slack-token-app     "/bin/sh -c 'node ap…"   2 days ago          Up 2 days                                                      slack-token-app
hogehogehoge        slack-token-mongo   "docker-entrypoint.s…"   2 days ago          Up 2 days           27017/tcp                                  slack-token-mongo
hogehogehoge        slack-token-redis   "docker-entrypoint.s…"   2 days ago          Up 2 days           6379/tcp                                   slack-token-redis
```

ブラウザからアクセスしてみてください。Web 面が表示されるはずです。

この状態で、slack から　reaction を付け、Web 面をリロードすると処理中のトランザクションが表示されます。Etherscan へのリンクを表示するようにしているのでクリックすれば Etherscan からトランザクションを確認する事が出来ます。

# 終わりに

自分で作ってみると勉強になる事が多くて、今後も出来るだけ手を動かして行きたいと思いました。
account nonce 周りの対応などはこれがベストプラクティスかはまだ解っていないので、もっと良いやり方をご存知の方がいらっしゃれば是非教えて下さい。