[ERC20トークン]web3の使い方とサンプルコード

## はじめに  
ethereumやそのトークンにアクセスするためのWebAPIライブラリといえばweb3ですよね。
最近だいぶこいつと仲良くなったので、備忘録として使い方をメモしておきます。

イーサリアムネットワークにアクセスする部分はasync/awaitが必要なので基本的にasync関数で書いてます
これらの関数を呼ぶ時にもawaitを付けて呼ぶのを忘れずに。

トークンはERC20を想定してます。
他のバージョンだと修正が必要かもです

ちなみに現時点でweb3は以下の２バージョンあり、この記事では1.~(ベータ版)　の方であることに注意して下さい。  

* ver0.~  
* ver1.~ (ベータ版)　

## 環境  

web3: "1.0.0-beta.36"

## ライブラリインストール

```bash
npm install web3
npm install keythereum
npm install ethereumjs-tx
```

## 共通コード&設定  

ライブラリ読み込みなどの共通コードです。
以下の３箇所を自分の環境に合わせて書き換えてください
1. 'your provider'
2. 'your contract_address'
3. './your_abi.json'

```javascript
var keythereum = require('keythereum')
const Tx = require('ethereumjs-tx')

const Web3 = require('web3')
const web3 = new Web3()

// 設定
// web3プロバイダーを設定してください
web3.setProvider(new web3.providers.HttpProvider('your provider'))
// トークンのコントラクトアドレスを設定してください
const contractAddress = 'your contract_address'
// トークンのabiファイルを設定してください
const abi = require('./your_abi.json')

const myContract = new web3.eth.Contract(abi, contractAddress)

// abiファイルをデコード
const abiDecoder = require('abi-decoder')
abiDecoder.addABI(abi)
```

## アドレス生成  

イーサリアムのアドレスを生成します。
ハードコーディングしてある数字の部分の細かい意味はまだよく分かっていません（どーん）
ライブラリのマニュアルをそのままコピペしちゃってます。
まあとりあえず動くのでおkなのです！（どーん）
詳しく知りたい人は以下のライブラリのマニュアルを参照ください。
https://github.com/ethereumjs/keythereum

keyObjectは秘密鍵を無くした時にパスワードとkeyObjectがあればアドレス復元を行える偉いヤツです。

```javascript

// アドレス生成
async function generateAddress(password) {
  const params = { keyBytes: 32, ivBytes: 16 }
  const dk = await keythereum.create(params)
  _create_keyObject(password, dk)
  const privateKey = dk.privateKey.toString('hex')
  const address = web3.eth.accounts.privateKeyToAccount(privateKey).address
  const account = {
    address: address,
    secret: privateKey
  }
  console.log('account: ' + JSON.stringify(account))
  return account
}

// keyObject生成
async function _create_keyObject(password, dk) {
  var options = {
    kdf: 'pbkdf2',
    cipher: 'aes-128-ctr',
    kdfparams: {
      c: 262144,
      dklen: 32,
      prf: 'hmac-sha256'
    }
  }
  const keyObject = await keythereum.dump(
    password,
    dk.privateKey,
    dk.salt,
    dk.iv,
    options,
    function(keyObject) {
      console.log('keyObject: ' + JSON.stringify(keyObject))
    }
  )
}

```

## アドレス復元  

秘密鍵からアドレスを復元します
keyObjectから復元する方法もあリますがまだやったことないので割愛します
秘密鍵は先頭に0xが必要であることに注意です
これはイーサリアムネットワークにアクセスしないのでasync/awaitは不要ですね
セキュリティ的にも安心です

```javascript
// アドレス復元
function restore_address(privateKey) {
  const account = web3.eth.accounts.privateKeyToAccount(privateKey)
  console.log('account.address: ' + account.address)
  return account.address
}
```
## 残高取得

最初に設定したコントラクトアドレスのトークンの残高を取得します
ERC20トークンの想定で関数を書いているのでそれ以外の場合は修正が必要かもです

```javascript
// 残高取得
async function getBalance(address) {

  // 残高取得
  var balance = await myContract.methods.balanceOf(address).call()
  balance = parseFloat(balance)

  // トークンの少数桁数を取得
  var decimals = await myContract.methods.decimals().call()
  decimals = parseFloat(decimals)

  balance = balance / 10 ** decimals

  return balance
}
```

## 送金

トークンの送金です
これもERC20トークンの想定で関数を書いているのでそれ以外の場合は修正が必要かもです

送金はちょっと複雑で、まず以下の３つの機能から成り立ってます
1. パラメータ生成
2. 署名
3. 送金

パラメータ生成は誰が誰にいくら送る〜とかの設定ですね
署名は送信する人がちゃんと自分の秘密鍵を持っているか？のチェックですね
秘密鍵だいじ！
送金はそのままですね。

トランザクションハッシュが戻り値として取得できるので送金の結果は以下から確認できます
https://etherscan.io/tx/{hash}
※hashの部分は取得したhashに置き換えてね！

```javascript

// 送金メイン
async function send(from, to, amount, privateKey) {

  // パラメータ生成
  var parameter = {}
  parameter = await _create_txjson(parameter, from, to, amount)

  // 署名
  const transaction = new Tx(parameter)
  // ここでは0xがあるとエラーになるのでsubstrで外してる
  transaction.sign(Buffer.from(privateKey.substr(2), 'hex'))

  // 送金
  _send(transaction)
}

// パラメータ生成
async function _create_txjson(parameter, from, to, amount) {
  try {

    parameter.from = from
    parameter.to = contractAddress

    var decimals = await myContract.methods.decimals().call()
    decimals = parseFloat(decimals)

    parameter.data = await myContract.methods
      .transfer(to, amount * 10 ** decimals)
      .encodeABI()

    var gasLimit = await web3.eth.estimateGas(parameter)
    // estimateGasを実行した瞬間とトランザクションを送信する瞬間で変化が起きる可能性があるので若干の余裕を持たせると安心です。
    gasLimit = gasLimit + 10000

    parameter.gasLimit = web3.utils.toHex(gasLimit)

    var gasPrice = await web3.eth.getGasPrice()
    gasPrice = parseInt(gasPrice) + 3000000000

    parameter.gasPrice = web3.utils.toHex(gasPrice)
    const count = await web3.eth.getTransactionCount(from)
    // この場合、nonceはgetTransactionCountの値をそのまま入れれば良いですが、実際はトランザクション毎にインクリメントする必要がある値ですから、トランザクションを複数生成する際はその点注意が必要です。
    // 具体的には、複数のトランザクションを作成して、その後にsendするようなケースの場合に、nonceのインクリメントを忘れていて同一の値になっていると1つ目以降のトランザクションはエラーになってしまいます。
    parameter.nonce = count

    return parameter

  } catch (error) {
    console.log('error.name: ' + error.name)
    console.log('error.message: ' + error.message)
  }
}

// 送金
function _send(transaction) {
  var response = null
  // ここでは0xが必要
  web3.eth
    .sendSignedTransaction('0x' + transaction.serialize().toString('hex'))
    .once('transactionHash', hash => {
      console.info('transactionHash', 'https://etherscan.io/tx/' + hash)
      response = hash
    })
    .once('receipt', receipt => {
      console.info('receipt', receipt)
    })
    .on('confirmation', (confirmationNumber, receipt) => {
      console.info('confirmation', confirmationNumber, receipt)
    })
    .on('error', error => {
      console.log('error' + error)
    })
}

```

## 取引詳細  

ERC20トークンの取引の詳細を取得します
これもERC20トークンの想定で関数を書いているのでそれ以外の場合は修正が必要かもです

トランザクションデータにはタイムスタンプは入ってないので、タイムスタンプを取得するためにトランザクションが入っているブロックのタイムスタンプを別途取得しています

```javascript
// 取引詳細
async function get_transaction(transactionHash) {
  var transaction = await web3.eth.getTransaction(transactionHash)

  var decimals = await myContract.methods.decimals().call()
  decimals = parseFloat(decimals)

  // そのままだと読めないのでデコードして読めるようにする
  const params = abiDecoder.decodeMethod(transaction.input)

  var transaction_erc20 = transaction
  transaction_erc20.contractaddress = transaction.to
  transaction_erc20.to = params.params[0].value
  transaction_erc20.amount = params.params[1].value / 10 ** decimals

  // タイムスタンプはtransactionに入ってないので別途取得する
  var block = await web3.eth.getBlock(transaction_erc20.blockHash)
  transaction_erc20.timestamp = _timeConverter(block.timestamp)

  console.log('transaction_erc20: ' + JSON.stringify(transaction_erc20))
  return transaction_erc20
}

// タイムスタンプを変換
function _timeConverter(UNIX_timestamp) {
  var a = new Date(UNIX_timestamp * 1000)
  var year = a.getFullYear()
  var month = a.getMonth()
  var date = a.getDate()
  var hour = a.getHours()
  var min = a.getMinutes()
  var sec = a.getSeconds()
  // 2019-01-18 01:51:52
  var time =
    year + '-' + month + '-' + date + ' ' + hour + ':' + min + ':' + sec
  return time
}
```

## おわりに  

web3は他にもまだまだ機能はありますがとりあえずこれだけあれば基本的なウォレットは作れるかなあと思います
絶賛進化中なこともあり、web3の情報は古いのも多くハマりやすいですよね（特に送金、、）
この記事もすぐ古くなっちゃうと思うのでぜひ編集リクエストお待ちしてます

参考：公式ドキュメント
https://web3js.readthedocs.io

以上です。
ではよきイーサリアムライフを〜