【Node.js】ethers.jsでトークンを送信する


最近話題のweb3を使ったアプリケーション作りたいと思い、とりあえずプログラムでトークンを送信することに挑戦しました。

公式のコードを参考にやってみました。
https://ethereum.org/ja/developers/tutorials/send-token-etherjs/

ほぼこの通りなのですが、このままではエラーが出るので修正しました。（後述）


## 動作確認環境
- macOS Ventura
- Node.js v20.0.0


## 準備

### Remix（トークン作成）

作り方はこちら
https://qiita.com/tatsuya1970/items/b68da529bcf1a92c5d10

トークンを作成したら、トークンのコントラクトアドレスを控える。
<img src="https://i.gyazo.com/7f7202155ec022c064b3a2cacb090314.png" width=400>

そして、トークンのABIをコピーし、
<img src="https://i.gyazo.com/960f9a478cce71119a948f4a27488eed.png" width=400>

あとで作成する index.js と同じディレクトリに、 abi.json として保存します。

<br>

### ウォレットの秘密鍵

ここではMetamaskを例にします。
右上の3本点をクリック
<img src="https://i.gyazo.com/d06a7bc12f07097408b628628828fb9c.png" width=400>

アカウントの詳細　＞　Show Privatekey

この秘密鍵を控えておく。


<br>

### Infura
Infuraは、Ethereumノードへのアクセスを提供するクラウドサービスです。
https://www.infura.io/

あとで必要なので、Project ID を控えておきます。
[![Image from Gyazo](https://i.gyazo.com/2c686c0bc4266994d47fbf11299d3af2.png)](https://gyazo.com/2c686c0bc4266994d47fbf11299d3af2)

なお、その他は触らないこと。
よかれと思い、ここのチェックをオンにしたため、後々凄くハマってしまいました。
[![Image from Gyazo](https://i.gyazo.com/e1dfdf1473021231e2ae58438b69a2f1.png)](https://gyazo.com/e1dfdf1473021231e2ae58438b69a2f1)

<br><br>

## 開発

### ether.js

Ethereumブロックチェーンのライブラリ ethers.js と
環境変数設定のライブラリ dotenv をインストールします。

```
$ npm install --save ethers
$ npm install --save dotenv
```

<br>

### コード

ほぼ[公式のコード](https://ethereum.org/ja/developers/tutorials/send-token-etherjs/)と同じです。

#### 修正・追加箇所

##### 修正箇所

```
（修正前）　window.ethersProvider
（修正後） provider
```

##### 追加箇所
同じディレクトリに保存したABIのJSONファイルを読み出す箇所を追加
```
const fs = require('fs');
const send_abi = JSON.parse(fs.readFileSync('./abi.json', 'utf8'));
```


#### コード

##### 環境変数
さきほど控えた環境変数を設定

```.env
INFURA_PROJECT_ID=INFURAのプロジェクトID
PRIVATE_KEY=送信元のウォレットのプライベートキー
```

##### メイン
```index.js
'use strict';
require("dotenv").config();
const { ethers } = require("ethers");
const infuraProjectId = process.env.INFURA_PROJECT_ID;

const provider = new ethers.providers.InfuraProvider('goerli', infuraProjectId);

let private_key = process.env.PRIVATE_KEY;
let send_token_amount = "1";
let to_address = "送信先のアドレス";
let send_address = "送信元のアドレス";
let gas_limit = "0x100000";
let wallet = new ethers.Wallet(private_key);

let walletSigner = wallet.connect(provider);
let contract_address = "先ほど控えたトークンのコントラクトアドレス";

send_token(
  contract_address,
  send_token_amount,
  to_address,
  send_address,
  private_key
);

function send_token(
    contract_address,
    send_token_amount,
    to_address,
    send_account,
    private_key
  ) {

    let wallet = new ethers.Wallet(private_key);
    let walletSigner = wallet.connect(provider);

    provider.getGasPrice().then((currentGasPrice) => {  
     let gas_price = ethers.utils.hexlify(parseInt(currentGasPrice));
     console.log(`gas_price: ${gas_price}`);

      if (contract_address) {
        const fs = require('fs');
        const send_abi = JSON.parse(fs.readFileSync('./abi.json', 'utf8'));
        // general token send
        let contract = new ethers.Contract(
          contract_address,
          send_abi,
          walletSigner
        ) ;
        // How many tokens?
        let numberOfTokens = ethers.utils.parseUnits(send_token_amount, 18);
        console.log(`numberOfTokens: ${numberOfTokens}`);
  
        // Send tokens
        contract.transfer(to_address, numberOfTokens).then((transferResult) => {
          console.dir(transferResult);
          console.log("sent token");
        })
      } // ether send
      else {
        const tx = {
          from: send_account,
          to: to_address,
          value: ethers.utils.parseEther(send_token_amount),
          //nonce: window.ethersProvider.getTransactionCount(
          nonce: provider.getTransactionCount(
            send_account,
            "latest"
          ),
          gasLimit: ethers.utils.hexlify(gas_limit), // 100000
          gasPrice: gas_price,
        }

        console.dir(tx);
        try {
          walletSigner.sendTransaction(tx).then((transaction) => {
            console.dir(transaction)
            console.log("Send finished!");
          })
        } catch (error) {
            console.log("failed to send!!");
        }
      }
    });
  }
```

<br><br>

これで実行すると、
```
TypeError: Cannot read properties of undefined (reading 'InfuraProvider')
```

困った。
詰んだと思ったら、このブログにヒントがありました。
https://qiita.com/Voldemort/items/b53d9bfe50db5920c425

バージョン5.xにダウングレードするとうまくいきました。

```
$ npm uninstall ethers
$ npm i -S ethers@5.7.2
```


## 結果

送金できました。
https://goerli.etherscan.io/tx/0x8238bc31332f2694767afd6adbceb1eaf39f24b8980428a267bd5965d7be6fd9

[![Image from Gyazo](https://i.gyazo.com/81f4375adbbf3c087af8c7ffa1c0efde.png)](https://gyazo.com/81f4375adbbf3c087af8c7ffa1c0efde)