Hardhatを使ってデプロイしたコードを一括で全てEtherscanに登録する

Etherscanではコードを登録すると、対象のコントラクトのページにて登録したコードが確認できるようになります。
例えばWBTCだと、下記のページでコードを確認できます。
https://etherscan.io/token/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599#code
![全画面_2023_12_24_12_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92492/0d5abeea-8095-2c70-445d-3bb7bd41e52b.png)

コードの登録はWebページから1つずつ行うこともできますが、今回はhardhatを利用してデプロイしたコードを一括で全て登録する方法を紹介します。

# NPMモジュールをインストールする
hardhatがインストールされているリポジトリで、`@nomicfoundation/hardhat-verify`をインストールします。

```terminal
npm install --save-dev @nomicfoundation/hardhat-verify
```

古いバージョンの`@nomiclabs/hardhat-etherscan`というモジュールも存在していますが、`deprecated`となっているので要注意です。

#  Hardhatのコンフィグを更新する
hardhat.config.tsで`@nomicfoundation/hardhat-verify`を読み込みます。

```ts: hardhat.config.ts
import "@nomicfoundation/hardhat-verify";
```

また、コンフィグには以下のようにEtherscanのAPIキーの設定も追加する必要があります。

```js
module.exports = {
  networks: {
    mainnet: { ... }
  },
  etherscan: {
    // https://etherscan.io/ で取得したEtherscanのAPIキー
    apiKey: "YOUR_ETHERSCAN_API_KEY"
  }
};
```

# 基本的な使い方

ターミナルでコマンドを実行してコードを登録する場合は、第一引数にコントラクトアドレス、第２引数には対象のコントラクトの引数を指定して`npx hardhat verify`を実行する必要があります。

例えば、コントラクトが

```
contract Foo {
  constructor (uint x) { ... }
}
```

の場合、コマンドは

```terminal
npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS 1
```
となります。

# タスクを作成して一括登録する

### 1. タスクを作る
Hardhatのタスク内で`verify`タスクを実行するには、下記のようにrunファンクションで`verify:verify`というサブタスクを呼び出す必要があります。

```ts: tasks/verify-all-contracts.ts
import { task } from 'hardhat/config';

task(
  'verify-all-contracts',
  'Verify and register the all contracts on Etherscan',
).setAction(async (_, { run }) => {
  const address = "対象のコントラクトアドレス";
  const constructorArguments = "対象のコントラクトのcounstructorに渡す引数";

  // Etherscanにコードを登録する
  await run('verify:verify', {
    address,
    constructorArguments,
  });
});
```

### 2. デプロイした全てのコントラクトの一覧を取得する

デプロイした全てのコントラクトの一覧を取得するために、deployments配下のターゲットとなるネットーワークのjsonファイルを全て読み込み、それらに対してそれぞれ`verify:verify`サブタスクを実行します。
また、前ステップでハードコードされていた`address`も`deployments.get`で取得した値に置き換えます。


```ts: tasks/verify-all-contracts.ts
import fs from 'fs';
import { task } from 'hardhat/config';

task(
  'verify-all-contracts',
  'Verify and register the all contracts on Etherscan',
).setAction(async (_, { deployments, run, network }) => {
  const constructorArguments = "対象のコントラクトのcounstructorに渡す引数";

  // デプロイした全てのコントラクトの一覧を取得する
  const fileNames = fs
    .readdirSync(`deployments/${network.name}`, {
      withFileTypes: true,
    })
    .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.json'))
    .map(({ name }) => name.replace('.json', ''));

  // Etherscanにコードを登録する
  for (const fileName of fileNames) {
    const { address } = await deployments.get(fileName);

    await run('verify:verify', {
      address,
      constructorArguments,
    });
  }
```

### 3. constructorの引数のマッピングを作成する。
constructorの引数をファイル名からマッピングできるように、下記のようなオブジェクトを作成し、`constructorArguments`と置き換えます。

```ts
const constructorArgumentsMap = {
  Foo: [1],
  Bar: [50, 'string'],
};
```

最終的なコードはこちら。

```ts: tasks/verify-all-contracts.ts
import fs from 'fs';
import { task } from 'hardhat/config';

// constructorの引数のマッピングを定義する
const constructorArgumentsMap = {
  Foo: [1],
  Bar: [50, 'string'],
};

task(
  'verify-all-contracts',
  'Verify and register the all contracts on Etherscan',
).setAction(async (_, { deployments, run, network }) => {
  // デプロイした全てのコントラクトの一覧を取得する
  const fileNames = fs
    .readdirSync(`deployments/${network.name}`, {
      withFileTypes: true,
    })
    .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.json'))
    .map(({ name }) => name.replace('.json', ''));

  // Etherscanにコードを登録する
  for (const fileName of fileNames) {
    const { address } = await deployments.get(fileName);

    const constructorArguments = constructorArgumentsMap[fileName];
    await run('verify:verify', {
      address,
      constructorArguments,
    });
  }
```

### 4. タスクをhardhat.config.tsで読み込む
作成したタスクをhardhat.config.tsで以下のように読み込みます。

```ts: hardhat.config.ts
import './tasks/verify-all-contracts';
```

これで作成したタスクがhardhatコマンドで実行できるようになります。

### 5. コマンドを実行する

下記コマンドで実行し、全てのコントラクトをEtherscanに登録します。

```terminal
npx hardhat verify-all-contracts --network <任意のネットワーク>
```

mainnetに登録する場合は、`npx hardhat verify-all-contracts --network mainnet`となります。

# 終わりに

これでいつでもEtherscanに気軽にコードが登録できるようになりました。
コントラクトがProxyパターンだったり、複数のチェーンをサポートしていて何度もコントラクトをデプロイしなければならないケースなどは特にこの一括で登録する方法が役に立つと思うので、ぜひ試してみてください。