Skip to content

Commit

Permalink
Support multiple api keys in hardhat-etherscan
Browse files Browse the repository at this point in the history
The config has been extended to allow both a string as api key or an object that can contain multiple api keys, keyed by network.

Relates to #1448.
  • Loading branch information
kanej committed Dec 3, 2021
1 parent 4f108b5 commit d14b7eb
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 11 deletions.
16 changes: 6 additions & 10 deletions packages/hardhat-etherscan/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from "hardhat/utils/contract-names";
import path from "path";
import semver from "semver";
import { isString } from "util";

import { encodeArguments } from "./ABIEncoder";
import { etherscanConfigExtender } from "./config";
Expand Down Expand Up @@ -50,6 +51,7 @@ import {
getEtherscanEndpoints,
retrieveContractBytecode,
} from "./network/prober";
import resolveEtherscanApiKey from "./resolveEtherscanApiKey";
import {
Bytecode,
ContractInformation,
Expand Down Expand Up @@ -128,7 +130,7 @@ const verify: ActionType<VerificationArgs> = async (
contract,
libraries: librariesModule,
},
{ run }
{ run, hardhatArguments: { network } }
) => {
const constructorArguments: any[] = await run(
TASK_VERIFY_GET_CONSTRUCTOR_ARGUMENTS,
Expand All @@ -147,6 +149,7 @@ const verify: ActionType<VerificationArgs> = async (
constructorArguments,
contract,
libraries,
network,
});
};

Expand All @@ -156,14 +159,7 @@ const verifySubtask: ActionType<VerificationSubtaskArgs> = async (
) => {
const { etherscan } = config;

if (etherscan.apiKey === undefined || etherscan.apiKey.trim() === "") {
throw new NomicLabsHardhatPluginError(
pluginName,
`Please provide an Etherscan API token via hardhat config.
E.g.: { [...], etherscan: { apiKey: 'an API key' }, [...] }
See https://etherscan.io/apis`
);
}
const etherscanApiKey = resolveEtherscanApiKey(etherscan, network.name);

const { isAddress } = await import("@ethersproject/address");
if (!isAddress(address)) {
Expand Down Expand Up @@ -296,7 +292,7 @@ Possible causes are:
etherscanAPIEndpoints,
contractInformation,
address,
etherscan.apiKey,
etherscanApiKey,
contractInformation.compilerInput,
solcFullVersion,
deployArgumentsEncoded
Expand Down
44 changes: 44 additions & 0 deletions packages/hardhat-etherscan/src/resolveEtherscanApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { NomicLabsHardhatPluginError } from "hardhat/src/plugins";
import { pluginName } from "./constants";
import { Networks, EtherscanConfig } from "./types";

const isNetworkKey = (network: string): network is Networks => {
return network in Networks;
};

const checkKey = (key: string | undefined): string => {
if (key === undefined || key === "") {
throw new NomicLabsHardhatPluginError(
pluginName,
`Please provide an Etherscan API token via hardhat config.
E.g.: { [...], etherscan: { apiKey: 'an API key' }, [...] }
See https://etherscan.io/apis`
);
}

return key;
};

const resolveEtherscanApiKey = (
etherscan: EtherscanConfig,
network: string
): string => {
if (etherscan.apiKey === undefined || typeof etherscan.apiKey === "string") {
return checkKey(etherscan.apiKey);
}

const apiKeys = etherscan.apiKey;

if (!isNetworkKey(network)) {
throw new NomicLabsHardhatPluginError(
pluginName,
`Unrecognized network: ${network}`
);
}

const key = apiKeys[network];

return checkKey(key);
};

export default resolveEtherscanApiKey;
15 changes: 14 additions & 1 deletion packages/hardhat-etherscan/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
export enum Networks {
mainnet = "mainnet",
rinkeby = "rinkeby",
ropsten = "ropsten",
polygon = "polygon",
optimism = "optimism",
optimismTestnet = "optimismTestnet",
}

export type EtherscanApiKeys = {
[Network in Networks]?: string;
};

export interface EtherscanConfig {
apiKey?: string;
apiKey?: string | EtherscanApiKeys;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/cache
/artifacts-dir
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pragma solidity 0.5.15;

contract TestContract {

uint amount;

string message = "placeholder";

constructor(uint _amount) public {
amount = _amount + 20;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require("../../../src/index");

module.exports = {
etherscan: {
apiKey: {
mainnet: "mainnet-testtoken",
ropsten: "ropsten-testtoken",
},
},
solidity: {
version: "0.5.15",
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,23 @@ describe("hardhat-etherscan configuration defaults in an empty project", functio
it("the etherscan field should be present", function () {
assert.isDefined(this.env.config.etherscan);
});

it("the apiKey subfield should be the empty string", function () {
assert.equal(this.env.config.etherscan.apiKey, "");
});
});

describe("hardhat-etherscan configuration with multiple api keys", function () {
useEnvironment("hardhat-project-multiple-apikeys-config", "hardhat");

it("the etherscan field should be present", function () {
assert.isDefined(this.env.config.etherscan);
});

it("the apiKey subfield should be the apiKeys object", function () {
assert.deepEqual(this.env.config.etherscan.apiKey, {
mainnet: "mainnet-testtoken",
ropsten: "ropsten-testtoken",
});
});
});
73 changes: 73 additions & 0 deletions packages/hardhat-etherscan/test/resolveEtherscanApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { assert } from "chai";
import resolveEtherscanApiKey from "../src/resolveEtherscanApiKey";
import { EtherscanConfig } from "../src/types";

describe("Etherscan API Key resolution", () => {
describe("provide one api key", () => {
it("returns the api key no matter the network", () => {
assert.equal(
resolveEtherscanApiKey({ apiKey: "testtoken" }, "mainnet"),
"testtoken"
);

assert.equal(
resolveEtherscanApiKey({ apiKey: "testtoken" }, "rinkeby"),
"testtoken"
);
});
});

describe("provide multiple api keys", () => {
it("can retrieve different keys depending on --network", () => {
const etherscanConfig: EtherscanConfig = {
apiKey: {
mainnet: "mainnet-testtoken",
rinkeby: "rinkeby-testtoken",
},
};

assert.equal(
resolveEtherscanApiKey(etherscanConfig, "mainnet"),
"mainnet-testtoken"
);
assert.equal(
resolveEtherscanApiKey(etherscanConfig, "rinkeby"),
"rinkeby-testtoken"
);
});

it("should throw if api key is for unrecognized network", () => {
assert.throws(() =>
resolveEtherscanApiKey(
// @ts-expect-error
{ apiKey: { newthing: "testtoken" } },
"newthing"
)
);
});
});

describe("provide no api key", () => {
it("should throw if api key root is undefined", () => {
assert.throws(() =>
resolveEtherscanApiKey({ apiKey: undefined }, "rinkeby")
);
});

it("should throw if api key root is empty string", () => {
assert.throws(() => resolveEtherscanApiKey({ apiKey: "" }, "rinkeby"));
});

it("should throw if network subkey is undefined", () => {
assert.throws(() =>
resolveEtherscanApiKey({ apiKey: { rinkeby: undefined } }, "rinkeby")
);
});

it("should throw if network subkey is empty string", () => {
assert.throws(() =>
resolveEtherscanApiKey({ apiKey: { rinkeby: "" } }, "rinkeby")
);
});
});
});

0 comments on commit d14b7eb

Please sign in to comment.