Web3.js  0.X.X系統はもう古い!  1.0.0-betaを使ってみた

## 緒言
### はじめに
[Web3.js](https://github.com/ethereum/web3.js/)は、etereumと連携して動作するDapp (Distributed Application) を作成するためのJavaScript APIです。記事執筆現在では、0.20がリリースされていますが、1.0.0系統もbeta版として開発されています。**1.0.0系統は[Promise](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises)を用いた実装になっており、コールバックヘルが起きにくく、[await/async](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await)も使える**ので嬉しい限りです。しかも、型定義ファイルも含まれているようなので、TypeScriptやVS Codeでの開発もしやすいです。今から始めるなら、新しい方がいい!! しかし、ほとんどのブログ記事では0.X.X系統で書かれているので、少々情報が不足しています (未完成ながら[公式ドキュメント](https://web3js.readthedocs.io/en/1.0/getting-started.html)はあります)。せっかくなので、**Web3.js 1.0.0**に触れた記録を残しておきたいと思います。

### 環境
今回は[truffle](http://truffleframework.com/)のローカルネットワーク上で[metacoin](http://truffleframework.com/boxes/metacoin)ボックスに含まれる`Metacoin`コントラクトを操作します。`Metacoin.sol`は`truffle compile`で`Metacoin.json`にコンパイル済みであることを前提に進めます。

なお、Solidityやtruffle等々の話は割愛します。また、コード例はJavaScriptではなく、TypeScriptを用いています (TS最高!!)。完全に同じものではないですが、動くサンプルを見たい方は、[こちら](https://github.com/YuichiNukiyama/truffle-ts-percel-box)を使ってください。

## Web3のインスタンス生成
事前にDappからアクセスできるように、ethereumのローカルネットワークを起動しておいてください (`truffle develop`コマンドをコンソールで実行)。そのうえで、web3に接続先のURIを渡してインスタンス生成します (truffleのローカルネットの既定のポートは`9545`です)。なお、1.0.0系統からは`ws` (websocket)プロトコルでアクセス可能です。

```ts
import Web3 from "web3";
const web3 = new Web3("ws://localhost:9545");
```

## コントラクトのデプロイ
### truffleでデプロイ
Solidityで作成したコントラクトは、ネットワーク上にデプロイする必要があります (余談ですが、本番環境にデプロイしたコントラクトは永久に削除できないので注意!!)。ローカルネットワークへのデプロイはtruffleで行った方が楽です。metacoinボックスでは設定がされているので簡単なコマンドで実行できます。

```powershell
truffle develop  # 起動済みの場合は不要
compile          # コンパイル
migrate          # ローカルネットワークへデプロイ
```

ちなみに、デプロイされたコントラクトのアドレスは`MetaCoin.json`の`address`プロパティに格納されます。コントラクトを生成する際に必要になるので、確認してください。また、truffleを止めるとデプロイしたコントラクトは失われるため、次回以降も再度デプロイする必要があるので注意してください。

```json:MetaCoin.json
{
"networks": {
    "4447": {
      "events": {},
      "links": {
        "ConvertLib": "0x345ca3e014aaf5dca488057592ee47305d9b3e10"
      },
      "address": "0xf25186b5081ff5ce73482ad761db0eb0d25abfbf",
      "transactionHash": "0xc48822d7e8b51eb73e1676184f9de5fd966b11025f002fc613192f1c1759caa8"
    }
  }
}
```

### Web3.jsでデプロイ
Web3.jsでデプロイする場合は、`deploy`メソッドを使用します。`deploy`メソッドには引数として、コントラクトのバイトコードを渡します。少々面倒なのは、**コントラクトに依存関係がある場合 (コントラクト内で`import`や`using`を使っている場合) は、デプロイ順序がある**ことです。たとえば、`Metacoin`コントラクトは`ConvertLib`ライブラリに依存しているので、`ConvertLib`ライブラリを先にデプロイする必要があります。また、コンパイル済みの`Metacoin.json`の`bytecode`を見てみると以下のようになっています。

```
"bytecode": "0x6060604052341561000f57600080fd5b6127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506103c5806100636000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637bd703e81461005c57806390b98a11146100a9578063f8b2cb4f14610103575b600080fd5b341561006757600080fd5b610093600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610150565b6040518082815260200191505060405180910390f35b34156100b457600080fd5b6100e9600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101f8565b604051808215151515815260200191505060405180910390f35b341561010e57600080fd5b61013a600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610351565b6040518082815260200191505060405180910390f35b600073__ConvertLib____________________________6396e4ee3d61017584610351565b60026000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808381526020018281526020019250505060206040518083038186803b15156101d657600080fd5b6102c65a03f415156101e757600080fd5b505050604051805190509050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610249576000905061034b565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509190505600a165627a7a72305820ec40ff9d451a379618c92e1c10efe2fac972c74825e3def2225c6ffd724d26bc0029"
```

横に長いですが、よく見てみるとバイトコード内に`__ConvertLib____________________________`という部分が含まれています。**この部分を`ConvertLib`ライブラリのバイトコードで置き換えなくてはなりません**。正確には、`ConvertLib`ライブラリのバイトコードのHexプレフィックス (0x) を削除した値で置き換えます ([こちらのページ](https://ethereum.stackexchange.com/questions/6927/what-are-the-steps-to-compile-and-deploy-a-library-in-solidity)が参考になります)。

それでは実際にデプロイしてみます。ソースコードの細かい解説はコメントとして埋め込んでおくので、そちらを参照してください。

```typescript
// コンパイル済みのSolidityをインポート
import CompiledMetaCoin from "../build/contracts/MetaCoin.json";
import CompiledConverLib from "../build/contracts/ConvertLib.json";
import Web3 from "web3";

const web3 = new Web3("ws://localhost:9545");
var lib: Contract;
var metacoinContract: Contract;
var accounts: string[];

function async deploy() {
// web3.eth.getAccountsメソッドでアカウントのアドレスをstring配列として取得できる
// truffleでは既定で10アカウント作成されている
accounts = await web3.eth.getAccounts();

// ConvertLibライブラリを先にデプロイ
// Contractをインスタンス生成。1つ目の引数にはabiを渡す。
// デプロイ済みの場合は、2つ目の引数としてコントラクトのアドレスを渡せばOK
lib = await new web3.eth.Contract(CompiledConverLib.abi)
        // デプロイの設定。dataプロパティにはコンパイル済みのバイトコードを指定
        // コントラクトのコンストラクターが引数を取る場合はargumentsプロパティに配列として指定する
        .deploy({ data: CompiledConverLib.bytecode, arguments: [] })
        // コントラクトをブロックチェーンに組み込む (デプロイ)
        .send({
            from: accounts[0],
            gas: 100000,
            gasPrice: 100000
        });

    metacoinContract = await new web3.eth.Contract(CompiledMetaCoin.abi);
    // __ConvertLib____________________________をConvertLibのバイトコードに置換
    // bytecodeの0x部分は削除しておく必要がある。
    const metaCoinBytecode = CompiledMetaCoin.bytecode.replace(/_+ConvertLib_+/g, CompiledConverLib.bytecode.replace("0x", ""));

    metacoinContract = await metacoin.deploy({ data: metaCoinBytecode, arguments: [] })
        .send({
            from: accounts[0],
            gas: 2000000,
            gasPrice: 2000000
        });

}
```

#### Promise
繰り返しになりますがWeb3.js 1.0.0からは`Promise`ベースの実装になっています。`await/async`を使用しない場合は、`then`メソッドで処理をつなげていくことになります。たとえば、アドレスの取得であれば以下のようになります。

```typescript
web3.eth.getAccounts()
        .then((accounts) => {
             console.log(accounts);
        });
```

#### sendメソッドとcallメソッドの違い
コントラクトのデプロイやメソッドの実行するには、`call`か`send`メソッドを使用します。両者の違いはコントラクトの状態を変化させるかどうかです。`metacoin`コントラクトであれば、デプロイや`balance`の状態を変化させる`sendCoin`メソッドは、`send`メソッドで実行します。一方、`getBalance`メソッドは`balance`の値を返すだけで、コントラクトの状態を変化させません。つまり、`pure`なメソッドです。そのため、`call`メソッドで呼び出せます。ちなみに、状態を変化させる`send`メソッドの方がgasコストは高くなるので注意してください。参考に、solidityのコントラクトを掲載しておきます。

```javascript:Metacoin.sol
// メソッド部分以外は省略しています
contract MetaCoin {
	function sendCoin(address receiver, uint amount) public returns(bool sufficient) {
		if (balances[msg.sender] < amount) return false;
		balances[msg.sender] -= amount;
		balances[receiver] += amount;
		Transfer(msg.sender, receiver, amount);
		return true;
	}

	function getBalance(address addr) public view returns(uint) {
		return balances[addr];
	}
}
```

なお、`send`メソッドなどの引数として渡しているオブジェクトの`from`プロパティは実行するアカウントのアドレスを、`gas`プロパティはガスを、`gasPrice`プロパティはガスの価格を表します。本番環境で実行する場合、コストに直結する部分のため、公式ドキュメントを見ておくことをお勧めします。

## コントラクトのメソッド呼び出し
本節では、truffleでコントラクトをデプロイしている前提で話を進めていきます。コントラクトのメソッドは、`インスタンス.methods.メソッド名`で呼び出すことになります。先述したように、コントラクトの状態変化を伴うメソッド (ここでは`sendCoin`) は`send`で、状態変化しないメソッド (getBalance) は`call`で呼び出します。

```typescript:CoinOperator.ts
import Web3 from "web3";
import metaCoin from "../build/contracts/MetaCoin.json";
import { Contract } from "web3/types";


export default class CoinOperator {
    private readonly web3: Web3;
    private readonly metaCoinContract: Contract;

    public constructor() {
        this.web3 = new Web3("ws://localhost:9545");
        /* Notice: `metaCoin.networks["4447"].address` return the address when truffle deployed the contract.
        *  4447 is default network id of truffle develop.
        *  for more detail, see `../buid/contract/MetaCoin.json`.
        * */
        this.metaCoinContract = new this.web3.eth.Contract(metaCoin.abi, metaCoin.networks["4447"].address);
    }

    /**
     * return all accounts. truffle has 10 accounts by default.
     */
    public getAllAccounts() {
        return this.web3.eth.getAccounts();
    }

    /**
     * get the balance of methacoin.
     * @param address address to get the balance of methacoin.
     */
    public getBalance(address: string): Promise<number> {
        return this.metaCoinContract.methods.getBalance(address)
                .call({ from: address });
    }

    /**
     * get the balance of methacoin in eth.
     * @param address address to get the balance of methacoin.
     */
    public getBalanceInEth(address: string): Promise<number> {
        return this.metaCoinContract.methods.getBalanceInEth(address)
                .call({ from: address });
    }

    public sendCoin(fromAddress: string, toAddress: string, amount: number): Promise<boolean> {
        return this.metaCoinContract.methods.sendCoin(toAddress, amount)
            .send({ from: fromAddress });
    }
}
```

#### コントラクトの生成
前節でも少々触れましたが、デプロイ済みのコントラクトの場合、`this.metaCoinContract = new this.web3.eth.Contract(metaCoin.abi, "0xf25186b5081ff5ce73482ad761db0eb0d25abfbf")`のように第2引数にコントラクトのアドレスを指定して使用します(サンプルコードでは、truffleでコンパイルしたアドレスをJSONファイルから取得しています)。

#### CoinOperatorクラスの使用
メソッドをTypeScriptのクラスでカプセル化してしまっているので、利用するコードも載せておきます。

```typescript:index.ts
import ConinOperator from "./ConinOperator";
const coinOperator = new ConinOperator();

window.onload = async () => {
    const accounts = await web3.eth.getAccounts();
    // balanceの取得
    const balance = await coinOperator.getBalance(accounts[0]);
    console.log("balance:" + balance.toString());
}
```

## 最後に
大したことはやっていないコードですが、Web3.js 1.0.0はまだ情報が少ないのでお役に立てれば幸いです。本筋と関係ありませんが、やっぱり`await/async`は便利。