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 chain.

Relates to #1448.
  • Loading branch information
kanej committed Dec 7, 2021
1 parent ed4e146 commit e04944c
Show file tree
Hide file tree
Showing 14 changed files with 540 additions and 151 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-terms-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomiclabs/hardhat-etherscan": major
---

Support multiple api keys in `hardhat-etherscan` to allow for verification against multiple networks (issue #1448)
36 changes: 36 additions & 0 deletions packages/hardhat-etherscan/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Contributing to Hardhat Etherscan

The `hardhat-etherscan` plugin works with any explorer that is powered by Etherscan or that has a compatible verification API. This guide explains how to send a Pull Request that adds support for a new chain.

1. Update [types.ts](./src/types.ts) to include the new chain:
- Add the chain as an entry in the `Chains` enum, ensuring the name and string value match.
2. Update [ChainConfig](./src/ChainConfig.ts) with the parameters for the chain you want to add:

- Add an entry to the `chainConfig` object under the name you added to the `Chains` enum
- Under that `chainConfig` entry add the chain id. For example, if your network is called "foo_chain" and its chain id is 1234, you need to add:

```jsx
foo_chain: {
chainId: 1234,
...
},
```

- Add the proper URLs under the `chainConfig` chain entry. For example:

```jsx
foo_chain: {
chainId: 1234,
urls: {
apiURL: "https://api.foochainscan.io/api",
browserURL: "https://foochainscan.io",
},
},
```

Here `apiURL` is the endpoint that corresponds to the API of the explorer, which will be used to send the verification request. `browserURL` is the URL of the explorer, that will be used to show a link after a successful verification. For this example, you would see a message with something like: `https://foochainscan.io/address/0xabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde#code`

3. Send a pull request with these changes.
4. As part of the PR, and for each chain you are adding:
- Indicate a public JSON-RPC endpoint that can be used for that chain.
- Send some funds to this address: `0x4444c3F7D7d3153Dc0773C31ae10cf9B5495d4Bb`. These will be used by us to check that a contract can be deployed and verified with these changes. Don't send too much value, just enough to deploy a simple contract.
56 changes: 56 additions & 0 deletions packages/hardhat-etherscan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ module.exports = {
};
```

Alternatively you can specify more than one block explorer API key, by passing an object under the `apiKey` property, see [`Multiple API keys and alternative block explorers`](#multiple-api-keys-and-alternative-block-explorers).

Lastly, run the `verify` task, passing the address of the contract, the network where it's deployed, and the constructor arguments that were used to deploy it (if any):

```bash
Expand Down Expand Up @@ -120,6 +122,56 @@ module.exports = {
};
```

### Multiple API keys and alternative block explorers

If your project targets multiple evm sidechains, you many need to verify using the block explorers particular to those chains, and so need to set multiple API keys.

A block explorer API key can be set per chain, hardhat will use the `chainId` of the specified network to determine which block explorer and API key to use.

To configure the API keys for the chains you are using, provide an object with under `etherscan/apiKey` with properties matching the chain:

```js
module.exports = {
networks: {
mainnet: { ... },
testnet: { ... }
},
etherscan: {
apiKey: {
mainnet: "YOUR_ETHERSCAN_API_KEY",
ropsten: "YOUR_ETHERSCAN_API_KEY",
rinkeby: "YOUR_ETHERSCAN_API_KEY",
goerli: "YOUR_ETHERSCAN_API_KEY",
kovan: "YOUR_ETHERSCAN_API_KEY",
// binance smart chain
bsc = "YOUR_BSCSCAN_API_KEY",
bsc_testnet = "YOUR_BSCSCAN_API_KEY",
// huobi eco chain
heco = "YOUR_HECOINFO_API_KEY",
heco_testnet = "YOUR_HECOINFO_API_KEY",
// fantom mainnet
opera = "YOUR_FTMSCAN_API_KEY",
ftm_testnet = "YOUR_FTMSCAN_API_KEY",
// optimistim
optimistic_ethereum = "YOUR_OPTIMISTIC_ETHERSCAN_API_KEY",
optimistic_kovan = "YOUR_OPTIMISTIC_ETHERSCAN_API_KEY",
// polygon
polygon = "YOUR_POLYGONSCAN_API_KEY",
polygon_mumbai = "YOUR_POLYGONSCAN_API_KEY",
// arbitrum
arbitrum_one = "YOUR_ARBISCAN_API_KEY",
arbitrum_testnet = "YOUR_ARBISCAN_API_KEY",
// avalanche
avalanche = "YOUR_SNOWTRACE_API_KEY",
avalanche_fuji_testnet = "YOUR_SNOWTRACE_API_KEY",
// moonriver
moonriver = "YOUR_MOONRIVER_MOONSCAN_API_KEY",
moonbase_alpha = "YOUR_MOONRIVER_MOONSCAN_API_KEY",
}
}
};
```

### Using programmatically

To call the verification task from within a Hardhat task or script, use the `"verify:verify"` subtask. Assuming the same contract as [above](#complex-arguments), you can run the subtask like this:
Expand Down Expand Up @@ -161,3 +213,7 @@ The plugin works by fetching the bytecode in the given address and using it to c
## Known limitations
- Adding, removing, moving or renaming new contracts to the hardhat project or reorganizing the directory structure of contracts after deployment may alter the resulting bytecode in some solc versions. See this [Solidity issue](https://github.com/ethereum/solidity/issues/9573) for further information.
## Contributing
See the [Contribution Guide](./CONTRIBUTING.md) for details.
152 changes: 152 additions & 0 deletions packages/hardhat-etherscan/src/ChainConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { ChainConfig } from "./types";

// See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids
export const chainConfig: ChainConfig = {
mainnet: {
chainId: 1,
urls: {
apiURL: "https://api.etherscan.io/api",
browserURL: "https://etherscan.io",
},
},
ropsten: {
chainId: 3,
urls: {
apiURL: "https://api-ropsten.etherscan.io/api",
browserURL: "https://ropsten.etherscan.io",
},
},
rinkeby: {
chainId: 4,
urls: {
apiURL: "https://api-rinkeby.etherscan.io/api",
browserURL: "https://rinkeby.etherscan.io",
},
},
goerli: {
chainId: 5,
urls: {
apiURL: "https://api-goerli.etherscan.io/api",
browserURL: "https://goerli.etherscan.io",
},
},
kovan: {
chainId: 42,
urls: {
apiURL: "https://api-kovan.etherscan.io/api",
browserURL: "https://kovan.etherscan.io",
},
},
bsc: {
chainId: 56,
urls: {
apiURL: "https://api.bscscan.com/api",
browserURL: "https://bscscan.com",
},
},
bsc_testnet: {
chainId: 97,
urls: {
apiURL: "https://api-testnet.bscscan.com/api",
browserURL: "https://testnet.bscscan.com",
},
},
heco: {
chainId: 128,
urls: {
apiURL: "https://api.hecoinfo.com/api",
browserURL: "https://hecoinfo.com",
},
},
heco_testnet: {
chainId: 256,
urls: {
apiURL: "https://api-testnet.hecoinfo.com/api",
browserURL: "https://testnet.hecoinfo.com",
},
},
opera: {
chainId: 250,
urls: {
apiURL: "https://api.ftmscan.com/api",
browserURL: "https://ftmscan.com",
},
},
ftm_testnet: {
chainId: 4002,
urls: {
apiURL: "https://api-testnet.ftmscan.com/api",
browserURL: "https://testnet.ftmscan.com",
},
},
optimistic_ethereum: {
chainId: 10,
urls: {
apiURL: "https://api-optimistic.etherscan.io/api",
browserURL: "https://optimistic.etherscan.io/",
},
},
optimistic_kovan: {
chainId: 69,
urls: {
apiURL: "https://api-kovan-optimistic.etherscan.io/api",
browserURL: "https://kovan-optimistic.etherscan.io/",
},
},
polygon: {
chainId: 137,
urls: {
apiURL: "https://api.polygonscan.com/api",
browserURL: "https://polygonscan.com",
},
},
polygon_mumbai: {
chainId: 80001,
urls: {
apiURL: "https://api-testnet.polygonscan.com/api",
browserURL: "https://mumbai.polygonscan.com/",
},
},
arbitrum_one: {
chainId: 42161,
urls: {
apiURL: "https://api.arbiscan.io/api",
browserURL: "https://arbiscan.io/",
},
},
arbitrum_testnet: {
chainId: 421611,
urls: {
apiURL: "https://api-testnet.arbiscan.io/api",
browserURL: "https://testnet.arbiscan.io/",
},
},
avalanche: {
chainId: 43114,
urls: {
apiURL: "https://api.snowtrace.io/api",
browserURL: "https://snowtrace.io/",
},
},
avalanche_fuji_testnet: {
chainId: 43113,
urls: {
apiURL: "https://api-testnet.snowtrace.io/api",
browserURL: "https://testnet.snowtrace.io/",
},
},
moonriver: {
chainId: 1285,
urls: {
apiURL: "https://api-moonriver.moonscan.io/api",
browserURL: "https://moonscan.io",
},
},
moonbase_alpha: {
chainId: 1287,
urls: {
apiURL: "https://api-moonbase.moonscan.io/api",
browserURL: "https://moonbase.moonscan.io/",
},
},
};
33 changes: 17 additions & 16 deletions packages/hardhat-etherscan/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ import {
toCheckStatusRequest,
toVerifyRequest,
} from "./etherscan/EtherscanVerifyContractRequest";
import { chainConfig } from "./ChainConfig";
import {
EtherscanURLs,
getEtherscanEndpoints,
retrieveContractBytecode,
} from "./network/prober";
import resolveEtherscanApiKey from "./resolveEtherscanApiKey";
import {
Bytecode,
ContractInformation,
Expand All @@ -63,6 +64,7 @@ import {
} from "./solc/metadata";
import { getLongVersion } from "./solc/version";
import "./type-extensions";
import { EtherscanNetworkEntry, EtherscanURLs } from "./types";

interface VerificationArgs {
address: string;
Expand Down Expand Up @@ -156,15 +158,6 @@ 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 { isAddress } = await import("@ethersproject/address");
if (!isAddress(address)) {
throw new NomicLabsHardhatPluginError(
Expand All @@ -191,8 +184,14 @@ If your constructor has no arguments pass an empty array. E.g:
TASK_VERIFY_GET_COMPILER_VERSIONS
);

const etherscanAPIEndpoints: EtherscanURLs = await run(
TASK_VERIFY_GET_ETHERSCAN_ENDPOINT
const {
network: verificationNetwork,
urls: etherscanAPIEndpoints,
}: EtherscanNetworkEntry = await run(TASK_VERIFY_GET_ETHERSCAN_ENDPOINT);

const etherscanAPIKey = resolveEtherscanApiKey(
etherscan,
verificationNetwork
);

const deployedBytecodeHex = await retrieveContractBytecode(
Expand Down Expand Up @@ -283,10 +282,11 @@ Possible causes are:
contractInformation,
etherscanAPIEndpoints,
address,
etherscanAPIKey: etherscan.apiKey,
etherscanAPIKey,
solcFullVersion,
deployArgumentsEncoded,
});

if (success) {
return;
}
Expand All @@ -296,7 +296,7 @@ Possible causes are:
etherscanAPIEndpoints,
contractInformation,
address,
etherscan.apiKey,
etherscanAPIKey,
contractInformation.compilerInput,
solcFullVersion,
deployArgumentsEncoded
Expand Down Expand Up @@ -443,7 +443,7 @@ async function attemptVerification(
console.log(
`Successfully submitted source code for contract
${contractInformation.sourceName}:${contractInformation.contractName} at ${contractAddress}
for verification on Etherscan. Waiting for verification result...
for verification on the block explorer. Waiting for verification result...
`
);

Expand Down Expand Up @@ -592,7 +592,7 @@ See https://etherscan.io/solcversions for more information.`
);

subtask(TASK_VERIFY_GET_ETHERSCAN_ENDPOINT).setAction(async (_, { network }) =>
getEtherscanEndpoints(network.provider, network.name)
getEtherscanEndpoints(network.provider, network.name, chainConfig)
);

subtask(TASK_VERIFY_GET_CONTRACT_INFORMATION)
Expand Down Expand Up @@ -729,6 +729,7 @@ subtask(TASK_VERIFY_VERIFY_MINIMUM_BUILD)
minimumBuild.output.contracts[contractInformation.sourceName][
contractInformation.contractName
].evm.deployedBytecode.object;

const matchedBytecode =
contractInformation.compilerOutput.contracts[
contractInformation.sourceName
Expand Down
Loading

0 comments on commit e04944c

Please sign in to comment.