Skip to content

Commit 963f91f

Browse files
authored
Merge pull request #5507 from rimrakhimov/rimrakhimov/blockscout-plugin
Feature: blockscout plugin support
2 parents ec5d587 + 510c80a commit 963f91f

14 files changed

+491
-7
lines changed

.changeset/khaki-drinks-work.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/hardhat-verify": patch
3+
---
4+
5+
Added Blockscout as a verification provider
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Blockscout } from "./internal/blockscout";

packages/hardhat-verify/src/index.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import {
1717
TASK_VERIFY_SOURCIFY,
1818
TASK_VERIFY_SOURCIFY_DISABLED_WARNING,
1919
TASK_VERIFY_GET_CONTRACT_INFORMATION,
20+
TASK_VERIFY_BLOCKSCOUT,
2021
} from "./internal/task-names";
2122
import {
2223
etherscanConfigExtender,
2324
sourcifyConfigExtender,
25+
blockscoutConfigExtender,
2426
} from "./internal/config";
2527
import {
2628
InvalidConstructorArgumentsError,
@@ -44,6 +46,7 @@ import {
4446
import "./internal/type-extensions";
4547
import "./internal/tasks/etherscan";
4648
import "./internal/tasks/sourcify";
49+
import "./internal/tasks/blockscout";
4750

4851
// Main task args
4952
export interface VerifyTaskArgs {
@@ -84,6 +87,7 @@ export interface VerificationSubtask {
8487

8588
extendConfig(etherscanConfigExtender);
8689
extendConfig(sourcifyConfigExtender);
90+
extendConfig(blockscoutConfigExtender);
8791

8892
/**
8993
* Main verification task.
@@ -180,7 +184,18 @@ subtask(
180184
});
181185
}
182186

183-
if (!config.etherscan.enabled && !config.sourcify.enabled) {
187+
if (config.blockscout.enabled) {
188+
verificationSubtasks.push({
189+
label: "Blockscout",
190+
subtaskName: TASK_VERIFY_BLOCKSCOUT,
191+
});
192+
}
193+
194+
if (
195+
!config.etherscan.enabled &&
196+
!config.sourcify.enabled &&
197+
!config.blockscout.enabled
198+
) {
184199
console.warn(
185200
chalk.yellow(
186201
`[WARNING] No verification services are enabled. Please enable at least one verification service in your configuration.`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ChainConfig } from "../types";
2+
3+
// See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#list-of-chain-ids
4+
export const builtinChains: ChainConfig[] = [
5+
{
6+
network: "mainnet",
7+
chainId: 1,
8+
urls: {
9+
apiURL: "https://eth.blockscout.com/api",
10+
browserURL: "https://eth.blockscout.com/",
11+
},
12+
},
13+
{
14+
network: "sepolia",
15+
chainId: 11155111,
16+
urls: {
17+
apiURL: "https://eth-sepolia.blockscout.com/api",
18+
browserURL: "https://eth-sepolia.blockscout.com/",
19+
},
20+
},
21+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import type { EthereumProvider } from "hardhat/types";
2+
import type { ChainConfig } from "../types";
3+
4+
import { HARDHAT_NETWORK_NAME } from "hardhat/plugins";
5+
6+
import {
7+
ChainConfigNotFoundError,
8+
HardhatNetworkNotSupportedError,
9+
} from "./errors";
10+
import { ValidationResponse } from "./utilities";
11+
import { builtinChains } from "./blockscout.chain-config";
12+
13+
import { Etherscan } from "./etherscan";
14+
15+
/**
16+
* Blockscout verification provider for verifying smart contracts.
17+
*/
18+
export class Blockscout {
19+
private _etherscan: Etherscan;
20+
21+
/**
22+
* Create a new instance of the Blockscout verification provider.
23+
* @param apiUrl - The Blockscout API URL, e.g. https://eth.blockscout.com/api.
24+
* @param browserUrl - The Blockscout browser URL, e.g. https://eth.blockscout.com.
25+
*/
26+
constructor(public apiUrl: string, public browserUrl: string) {
27+
this._etherscan = new Etherscan("api_key", apiUrl, browserUrl);
28+
}
29+
30+
public static async getCurrentChainConfig(
31+
networkName: string,
32+
ethereumProvider: EthereumProvider,
33+
customChains: ChainConfig[]
34+
): Promise<ChainConfig> {
35+
const currentChainId = parseInt(
36+
await ethereumProvider.send("eth_chainId"),
37+
16
38+
);
39+
40+
const currentChainConfig = [
41+
// custom chains has higher precedence than builtin chains
42+
...[...customChains].reverse(), // the last entry has higher precedence
43+
...builtinChains,
44+
].find(({ chainId }) => chainId === currentChainId);
45+
46+
if (currentChainConfig === undefined) {
47+
if (networkName === HARDHAT_NETWORK_NAME) {
48+
throw new HardhatNetworkNotSupportedError();
49+
}
50+
51+
throw new ChainConfigNotFoundError(currentChainId);
52+
}
53+
54+
return currentChainConfig;
55+
}
56+
57+
public static fromChainConfig(chainConfig: ChainConfig): Blockscout {
58+
const apiUrl = chainConfig.urls.apiURL;
59+
const browserUrl = chainConfig.urls.browserURL.trim().replace(/\/$/, "");
60+
61+
return new Blockscout(apiUrl, browserUrl);
62+
}
63+
64+
/**
65+
* Check if a smart contract is verified on Blockscout.
66+
* @link https://docs.blockscout.com/for-users/api/rpc-endpoints/contract#get-contract-source-code-for-a-verified-contract
67+
* @param address - The address of the smart contract.
68+
* @returns True if the contract is verified, false otherwise.
69+
* @throws {NetworkRequestError} if there is an error on the request.
70+
* @throws {ContractVerificationInvalidStatusCodeError} if the API returns an invalid status code.
71+
*/
72+
public async isVerified(address: string): Promise<boolean> {
73+
return this._etherscan.isVerified(address);
74+
}
75+
76+
/**
77+
* Verify a smart contract on Blockscout.
78+
* @link https://docs.blockscout.com/for-users/api/rpc-endpoints/contract#verify-a-contract-with-standard-input-json-file
79+
* @param contractAddress - The address of the smart contract to verify.
80+
* @param sourceCode - The source code of the smart contract.
81+
* @param contractName - The name of the smart contract, e.g. "contracts/Sample.sol:MyContract"
82+
* @param compilerVersion - The version of the Solidity compiler used, e.g. `v0.8.19+commit.7dd6d404`
83+
* @returns A promise that resolves to an `BlockscoutResponse` object.
84+
* @throws {NetworkRequestError} if there is an error on the request.
85+
* @throws {ContractVerificationInvalidStatusCodeError} if the API returns an invalid status code.
86+
* @throws {ContractVerificationMissingBytecodeError} if the bytecode is not found on the block explorer.
87+
* @throws {ContractAlreadyVerifiedError} if the contract is already verified.
88+
* @throws {HardhatVerifyError} if the response status is not OK.
89+
*/
90+
public async verify(
91+
contractAddress: string,
92+
sourceCode: string,
93+
contractName: string,
94+
compilerVersion: string
95+
): Promise<BlockscoutResponse> {
96+
const etherscanResponse = await this._etherscan.verify(
97+
contractAddress,
98+
sourceCode,
99+
contractName,
100+
compilerVersion,
101+
""
102+
);
103+
104+
return new BlockscoutResponse(
105+
etherscanResponse.status,
106+
etherscanResponse.message
107+
);
108+
}
109+
110+
/**
111+
* Get the verification status of a smart contract from Blockscout.
112+
* This method performs polling of the verification status if it's pending.
113+
* @link https://docs.blockscout.com/for-users/api/rpc-endpoints/contract#return-status-of-a-verification-attempt
114+
* @param guid - The verification GUID to check.
115+
* @returns A promise that resolves to an `BlockscoutResponse` object.
116+
* @throws {NetworkRequestError} if there is an error on the request.
117+
* @throws {ContractStatusPollingInvalidStatusCodeError} if the API returns an invalid status code.
118+
* @throws {ContractStatusPollingResponseNotOkError} if the response status is not OK.
119+
*/
120+
public async getVerificationStatus(
121+
guid: string
122+
): Promise<BlockscoutResponse> {
123+
const etherscanResponse = await this._etherscan.getVerificationStatus(guid);
124+
125+
return new BlockscoutResponse(
126+
etherscanResponse.status,
127+
etherscanResponse.message
128+
);
129+
}
130+
131+
/**
132+
* Get the Blockscout URL for viewing a contract's details.
133+
* @param address - The address of the smart contract.
134+
* @returns The URL to view the contract on Blockscout's website.
135+
*/
136+
public getContractUrl(address: string): string {
137+
return `${this.browserUrl}/address/${address}#code`;
138+
}
139+
}
140+
141+
class BlockscoutResponse implements ValidationResponse {
142+
public readonly status: number;
143+
public readonly message: string;
144+
145+
constructor(status: number, message: string) {
146+
this.status = status;
147+
this.message = message;
148+
}
149+
150+
public isPending() {
151+
return this.message === "Pending in queue";
152+
}
153+
154+
public isFailure() {
155+
return this.message === "Fail - Unable to verify";
156+
}
157+
158+
public isSuccess() {
159+
return this.message === "Pass - Verified";
160+
}
161+
162+
public isAlreadyVerified() {
163+
return (
164+
// returned by blockscout
165+
this.message.startsWith("Smart-contract already verified") ||
166+
// returned by etherscan
167+
this.message.startsWith("Contract source code already verified") ||
168+
this.message.startsWith("Already Verified")
169+
);
170+
}
171+
172+
public isOk() {
173+
return this.status === 1;
174+
}
175+
}

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type LodashCloneDeepT from "lodash.clonedeep";
22
import type { HardhatConfig, HardhatUserConfig } from "hardhat/types";
3-
import type { EtherscanConfig, SourcifyConfig } from "../types";
3+
import type {
4+
EtherscanConfig,
5+
SourcifyConfig,
6+
BlockscoutConfig,
7+
} from "../types";
48

59
import chalk from "chalk";
610

@@ -44,3 +48,16 @@ export function sourcifyConfigExtender(
4448
const userSourcifyConfig = cloneDeep(userConfig.sourcify);
4549
config.sourcify = { ...defaultSourcifyConfig, ...userSourcifyConfig };
4650
}
51+
52+
export function blockscoutConfigExtender(
53+
config: HardhatConfig,
54+
userConfig: Readonly<HardhatUserConfig>
55+
): void {
56+
const defaultBlockscoutConfig: BlockscoutConfig = {
57+
enabled: false,
58+
customChains: [],
59+
};
60+
const cloneDeep = require("lodash.clonedeep") as typeof LodashCloneDeepT;
61+
const userBlockscoutConfig = cloneDeep(userConfig.blockscout);
62+
config.blockscout = { ...defaultBlockscoutConfig, ...userBlockscoutConfig };
63+
}

packages/hardhat-verify/src/internal/task-names.ts

+7
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ export const TASK_VERIFY_SOURCIFY_ATTEMPT_VERIFICATION =
2424
"verify:sourcify-attempt-verification";
2525
export const TASK_VERIFY_SOURCIFY_DISABLED_WARNING =
2626
"verify:sourcify-disabled-warning";
27+
28+
// Blockscout
29+
export const TASK_VERIFY_BLOCKSCOUT = "verify:blockscout";
30+
export const TASK_VERIFY_BLOCKSCOUT_RESOLVE_ARGUMENTS =
31+
"verify:blockscout-resolve-arguments";
32+
export const TASK_VERIFY_BLOCKSCOUT_ATTEMPT_VERIFICATION =
33+
"verify:blockscout-attempt-verification";

0 commit comments

Comments
 (0)