Etherscanではコードを登録すると、対象のコントラクトのページにて登録したコードが確認できるようになります。
例えばWBTCだと、下記のページでコードを確認できます。
https://etherscan.io/token/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599#code
コードの登録はWebページから1つずつ行うこともできますが、今回はhardhatを利用してデプロイしたコードを一括で全て登録する方法を紹介します。
NPMモジュールをインストールする
hardhatがインストールされているリポジトリで、@nomicfoundation/hardhat-verify
をインストールします。
npm install --save-dev @nomicfoundation/hardhat-verify
古いバージョンの@nomiclabs/hardhat-etherscan
というモジュールも存在していますが、deprecated
となっているので要注意です。
Hardhatのコンフィグを更新する
hardhat.config.tsで@nomicfoundation/hardhat-verify
を読み込みます。
import "@nomicfoundation/hardhat-verify";
また、コンフィグには以下のようにEtherscanのAPIキーの設定も追加する必要があります。
module.exports = {
networks: {
mainnet: { ... }
},
etherscan: {
// https://etherscan.io/ で取得したEtherscanのAPIキー
apiKey: "YOUR_ETHERSCAN_API_KEY"
}
};
基本的な使い方
ターミナルでコマンドを実行してコードを登録する場合は、第一引数にコントラクトアドレス、第2引数には対象のコントラクトの引数を指定してnpx hardhat verify
を実行する必要があります。
例えば、コントラクトが
contract Foo {
constructor (uint x) { ... }
}
の場合、コマンドは
npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS 1
となります。
タスクを作成して一括登録する
1. タスクを作る
Hardhatのタスク内でverify
タスクを実行するには、下記のようにrunファンクションでverify:verify
というサブタスクを呼び出す必要があります。
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
で取得した値に置き換えます。
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
と置き換えます。
const constructorArgumentsMap = {
Foo: [1],
Bar: [50, 'string'],
};
最終的なコードはこちら。
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で以下のように読み込みます。
import './tasks/verify-all-contracts';
これで作成したタスクがhardhatコマンドで実行できるようになります。
5. コマンドを実行する
下記コマンドで実行し、全てのコントラクトをEtherscanに登録します。
npx hardhat verify-all-contracts --network <任意のネットワーク>
mainnetに登録する場合は、npx hardhat verify-all-contracts --network mainnet
となります。
終わりに
これでいつでもEtherscanに気軽にコードが登録できるようになりました。
コントラクトがProxyパターンだったり、複数のチェーンをサポートしていて何度もコントラクトをデプロイしなければならないケースなどは特にこの一括で登録する方法が役に立つと思うので、ぜひ試してみてください。