Skip to content

Commit 5af85da

Browse files
committed
Support Etherscan API v2
1 parent 46da544 commit 5af85da

File tree

7 files changed

+271
-9
lines changed

7 files changed

+271
-9
lines changed

.changeset/nasty-readers-explode.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/hardhat-verify": patch
3+
---
4+
5+
Support Etherscan API v2

packages/hardhat-ignition/src/utils/getApiKeyAndUrls.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,31 @@ import { NomicLabsHardhatPluginError } from "hardhat/plugins";
55
export function getApiKeyAndUrls(
66
etherscanApiKey: string | Record<string, string>,
77
chainConfig: ChainConfig
8-
): [apiKey: string, apiUrl: string, webUrl: string] {
8+
): [
9+
apiKey: string,
10+
apiUrl: string,
11+
webUrl: string,
12+
chainId: number | undefined
13+
] {
914
const apiKey: string =
1015
typeof etherscanApiKey === "string"
1116
? etherscanApiKey
1217
: etherscanApiKey[chainConfig.network];
1318

19+
const chainId =
20+
typeof etherscanApiKey === "string" ? chainConfig.chainId : undefined;
21+
1422
if (apiKey === undefined) {
1523
throw new NomicLabsHardhatPluginError(
1624
"@nomicfoundation/hardhat-ignition",
1725
`No etherscan API key configured for network ${chainConfig.network}`
1826
);
1927
}
2028

21-
return [apiKey, chainConfig.urls.apiURL, chainConfig.urls.browserURL];
29+
return [
30+
apiKey,
31+
chainConfig.urls.apiURL,
32+
chainConfig.urls.browserURL,
33+
chainId,
34+
];
2235
}

packages/hardhat-ignition/test/verify/getApiKeyAndUrls.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { assert } from "chai";
33
import { getApiKeyAndUrls } from "../../src/utils/getApiKeyAndUrls";
44

55
describe("getApiKeyAndUrls", function () {
6-
it("should return the correct API URLs when given a string", function () {
6+
it("should return the correct API URLs and chain id when given a string", function () {
77
const apiKeyList = getApiKeyAndUrls("testApiKey", {
88
network: "mainnet",
99
chainId: 1,
@@ -17,10 +17,11 @@ describe("getApiKeyAndUrls", function () {
1717
"testApiKey",
1818
"https://api.etherscan.io/api",
1919
"https://etherscan.io",
20+
1,
2021
]);
2122
});
2223

23-
it("should return the correct API URLs when given an apiKey object", function () {
24+
it("should return the correct API URLs without chain id when given an apiKey object", function () {
2425
const apiKeyList = getApiKeyAndUrls(
2526
{
2627
goerli: "goerliApiKey",
@@ -40,10 +41,11 @@ describe("getApiKeyAndUrls", function () {
4041
"goerliApiKey",
4142
"https://api-goerli.etherscan.io/api",
4243
"https://goerli.etherscan.io",
44+
undefined,
4345
]);
4446
});
4547

46-
it("should return the correct API URLs when given a string and the network is not mainnet", function () {
48+
it("should return the correct API URLs and chain id when given a string and the network is not mainnet", function () {
4749
const apiKeyList = getApiKeyAndUrls("goerliApiKey", {
4850
network: "goerli",
4951
chainId: 5,
@@ -57,6 +59,7 @@ describe("getApiKeyAndUrls", function () {
5759
"goerliApiKey",
5860
"https://api-goerli.etherscan.io/api",
5961
"https://goerli.etherscan.io",
62+
5,
6063
]);
6164
});
6265

packages/hardhat-verify/src/internal/blockscout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class Blockscout {
2424
* @param browserUrl - The Blockscout browser URL, e.g. https://eth.blockscout.com.
2525
*/
2626
constructor(public apiUrl: string, public browserUrl: string) {
27-
this._etherscan = new Etherscan("api_key", apiUrl, browserUrl);
27+
this._etherscan = new Etherscan("api_key", apiUrl, browserUrl, undefined);
2828
}
2929

3030
public static async getCurrentChainConfig(

packages/hardhat-verify/src/internal/etherscan.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88

99
import { HARDHAT_NETWORK_NAME } from "hardhat/plugins";
1010

11+
import picocolors from "picocolors";
1112
import {
1213
ContractStatusPollingInvalidStatusCodeError,
1314
ContractVerificationMissingBytecodeError,
@@ -27,6 +28,8 @@ import { builtinChains } from "./chain-config";
2728
// Used for polling the result of the contract verification.
2829
const VERIFICATION_STATUS_POLLING_TIME = 3000;
2930

31+
export const ETHERSCAN_V2_API_URL = "https://api.etherscan.io/v2/api";
32+
3033
/**
3134
* Etherscan verification provider for verifying smart contracts.
3235
* It should work with other verification providers as long as the interface
@@ -38,12 +41,16 @@ export class Etherscan {
3841
* @param apiKey - The Etherscan API key.
3942
* @param apiUrl - The Etherscan API URL, e.g. https://api.etherscan.io/api.
4043
* @param browserUrl - The Etherscan browser URL, e.g. https://etherscan.io.
44+
* @param chainId - Chain id when willing to use the v2 api, undefined otherwise
4145
*/
4246
constructor(
4347
public apiKey: string,
4448
public apiUrl: string,
45-
public browserUrl: string
46-
) {}
49+
public browserUrl: string,
50+
public chainId: number | undefined
51+
) {
52+
this.apiUrl = chainId === undefined ? apiUrl : ETHERSCAN_V2_API_URL;
53+
}
4754

4855
public static async getCurrentChainConfig(
4956
networkName: string,
@@ -80,7 +87,25 @@ export class Etherscan {
8087
const apiUrl = chainConfig.urls.apiURL;
8188
const browserUrl = chainConfig.urls.browserURL.trim().replace(/\/$/, "");
8289

83-
return new Etherscan(resolvedApiKey, apiUrl, browserUrl);
90+
// If a user sets a single api key, it means it's etherscan.io key, and we can use the api v2 with multiple chain support
91+
// If multiple keys are set, it means that l2/sidechain explorers are being used, and those keys don't work with etherscan.io api v2.
92+
// So we keep using the v1 api of their respective explorers
93+
const isV2 = typeof apiKey === "string";
94+
95+
if (!isV2) {
96+
console.warn(
97+
picocolors.yellow(
98+
"[WARNING] Network and explorer-specific api keys are deprecated in favour of the new Etherscan v2 api. Support for v1 is expected to end by May 31st, 2025. To migrate, please specify a single Etherscan.io api key the apiKey config value."
99+
)
100+
);
101+
}
102+
103+
return new Etherscan(
104+
resolvedApiKey,
105+
apiUrl,
106+
browserUrl,
107+
isV2 ? chainConfig.chainId : undefined
108+
);
84109
}
85110

86111
/**
@@ -99,6 +124,10 @@ export class Etherscan {
99124
address,
100125
});
101126

127+
if (this.chainId !== undefined) {
128+
parameters.set("chainid", String(this.chainId));
129+
}
130+
102131
const url = new URL(this.apiUrl);
103132
url.search = parameters.toString();
104133

@@ -162,6 +191,11 @@ export class Etherscan {
162191
});
163192

164193
const url = new URL(this.apiUrl);
194+
195+
if (this.chainId !== undefined) {
196+
url.searchParams.append("chainid", String(this.chainId));
197+
}
198+
165199
let response: Dispatcher.ResponseData | undefined;
166200
let json: EtherscanVerifyResponse | undefined;
167201
try {
@@ -218,6 +252,11 @@ export class Etherscan {
218252
action: "checkverifystatus",
219253
guid,
220254
});
255+
256+
if (this.chainId !== undefined) {
257+
parameters.set("chainid", String(this.chainId));
258+
}
259+
221260
const url = new URL(this.apiUrl);
222261
url.search = parameters.toString();
223262

packages/hardhat-verify/test/integration/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ describe("verify task integration tests", () => {
3131

3232
// suppress sourcify info message
3333
let consoleInfoStub: SinonStub;
34+
let consoleWarnStub: SinonStub;
3435
before(() => {
3536
consoleInfoStub = sinon.stub(console, "info");
37+
consoleWarnStub = sinon.stub(console, "warn");
3638
});
3739

3840
// suppress warnings
3941
after(() => {
4042
consoleInfoStub.restore();
43+
consoleWarnStub.restore();
4144
});
4245

4346
it("should return after printing the supported networks", async function () {

0 commit comments

Comments
 (0)