@nomicfoundation/hardhat-verify
Version:
Hardhat plugin for verifying contracts
247 lines • 10.5 kB
JavaScript
import { HardhatError } from "@nomicfoundation/hardhat-errors";
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
import { isObject, sleep } from "@nomicfoundation/hardhat-utils/lang";
import { getProxyUrl, getRequest, postJsonRequest, ResponseStatusCodeError, shouldUseProxy, } from "@nomicfoundation/hardhat-utils/request";
export const SOURCIFY_PROVIDER_NAME = "sourcify";
const VERIFICATION_STATUS_POLLING_SECONDS = 3;
export const SOURCIFY_API_URL = "https://sourcify.dev/server";
export class Sourcify {
chainId;
name;
url;
apiUrl;
dispatcherOrDispatcherOptions;
pollingIntervalMs;
static async resolveConfig({ chainId, verificationProvidersConfig, dispatcher, }) {
return {
verificationProviderConfig: verificationProvidersConfig.sourcify,
chainId,
dispatcher,
};
}
static async create({ verificationProviderConfig, chainId, dispatcher, }) {
return new Sourcify({
chainId,
apiUrl: verificationProviderConfig.apiUrl,
dispatcher,
});
}
// Not used by sourcify, but required by the VerificationProvider interface
static async getSupportedChains() {
return new Map();
}
constructor(sourcifyConfig) {
this.chainId = String(sourcifyConfig.chainId);
this.name = sourcifyConfig.name ?? "Sourcify";
this.apiUrl = sourcifyConfig.apiUrl ?? SOURCIFY_API_URL;
this.url = `${this.apiUrl}/repo-ui`;
const proxyUrl = shouldUseProxy(this.apiUrl)
? getProxyUrl(this.apiUrl)
: undefined;
this.dispatcherOrDispatcherOptions =
sourcifyConfig.dispatcher ??
(proxyUrl !== undefined ? { proxy: proxyUrl } : {});
this.pollingIntervalMs =
sourcifyConfig.dispatcher !== undefined
? 0
: VERIFICATION_STATUS_POLLING_SECONDS;
}
getContractUrl(address) {
return `${this.url}/${this.chainId}/${address}`;
}
getVerificationJobUrl(guid) {
return `${this.apiUrl}/verify-ui/jobs/${guid}`;
}
async isVerified(address) {
let response;
let responseBody;
try {
response = await getRequest(`${this.apiUrl}/v2/contract/${this.chainId}/${address}`, undefined, this.dispatcherOrDispatcherOptions);
responseBody = await response.body.json();
}
catch (error) {
ensureError(error);
if (error instanceof ResponseStatusCodeError &&
isSourcifyLookupResponse(error.body)) {
// Unverified contracts are returned with status 404
return error.body.match !== null;
}
if (error instanceof ResponseStatusCodeError &&
isSourcifyErrorResponse(error.body)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.EXPLORER_REQUEST_STATUS_CODE_ERROR, {
name: this.name,
url: this.apiUrl,
statusCode: error.statusCode,
errorMessage: error.body.message,
});
}
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.EXPLORER_REQUEST_FAILED, {
name: this.name,
url: this.apiUrl,
errorMessage: error.cause instanceof Error ? error.cause.message : error.message,
});
}
if (!isSourcifyLookupResponse(responseBody)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_UNEXPECTED_RESPONSE, { message: JSON.stringify(responseBody) });
}
return responseBody.match !== null;
}
async verify({ contractAddress, compilerInput, contractName, compilerVersion, creationTxHash, }) {
const body = {
stdJsonInput: compilerInput,
contractIdentifier: contractName,
compilerVersion,
};
if (creationTxHash !== undefined) {
body.creationTransactionHash = creationTxHash;
}
let response;
let responseBody;
try {
response = await postJsonRequest(`${this.apiUrl}/v2/verify/${this.chainId}/${contractAddress}`, body, undefined, this.dispatcherOrDispatcherOptions);
responseBody = await response.body.json();
}
catch (error) {
ensureError(error);
if (error instanceof ResponseStatusCodeError &&
isSourcifyErrorResponse(error.body)) {
if (error.body.customCode === "already_verified") {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_ALREADY_VERIFIED, {
contract: contractName,
address: contractAddress,
});
}
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_REQUEST_FAILED, { message: error.body.message });
}
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.EXPLORER_REQUEST_FAILED, {
name: this.name,
url: this.apiUrl,
errorMessage: error.cause instanceof Error ? error.cause.message : error.message,
});
}
if (!isSourcifyVerificationResponse(responseBody)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_UNEXPECTED_RESPONSE, { message: JSON.stringify(responseBody) });
}
return responseBody.verificationId;
}
async pollVerificationStatus(guid, contractAddress, contractName) {
let response;
let responseBody;
try {
response = await getRequest(`${this.apiUrl}/v2/verify/${guid}`, undefined, this.dispatcherOrDispatcherOptions);
responseBody = await response.body.json();
}
catch (error) {
ensureError(error);
if (error instanceof ResponseStatusCodeError &&
isSourcifyErrorResponse(error.body)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_STATUS_POLLING_FAILED, { message: error.body.message });
}
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.EXPLORER_REQUEST_FAILED, {
name: this.name,
url: this.apiUrl,
errorMessage: error.cause instanceof Error ? error.cause.message : error.message,
});
}
if (!isSourcifyVerificationStatusResponse(responseBody)) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_UNEXPECTED_RESPONSE, { message: JSON.stringify(responseBody) });
}
const verificationStatus = new SourcifyVerificationStatus(responseBody);
if (verificationStatus.isPending()) {
await sleep(this.pollingIntervalMs);
return await this.pollVerificationStatus(guid, contractAddress, contractName);
}
if (verificationStatus.isAlreadyVerified()) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_ALREADY_VERIFIED, {
contract: contractName,
address: contractAddress,
});
}
if (verificationStatus.isBytecodeMissingInNetworkError()) {
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_MISSING_BYTECODE, {
url: this.apiUrl,
address: contractAddress,
});
}
if (!(verificationStatus.isFailure() || verificationStatus.isSuccess())) {
// Reaching this point shouldn't be possible unless the API is behaving in a new way.
throw new HardhatError(HardhatError.ERRORS.HARDHAT_VERIFY.GENERAL.CONTRACT_VERIFICATION_UNEXPECTED_RESPONSE, { message: verificationStatus.message });
}
const success = verificationStatus.isSuccess();
let message = verificationStatus.message;
if (!success) {
message = `${message}
More info at: ${this.getVerificationJobUrl(guid)}`;
}
return {
success,
message,
};
}
}
function isSourcifyErrorResponse(response) {
return (isObject(response) &&
"customCode" in response &&
"message" in response &&
"errorId" in response);
}
function isSourcifyLookupResponse(response) {
return (isObject(response) &&
"match" in response &&
"creationMatch" in response &&
"runtimeMatch" in response &&
"chainId" in response &&
"address" in response);
}
function isSourcifyVerificationResponse(response) {
return isObject(response) && "verificationId" in response;
}
function isSourcifyVerificationStatusResponse(response) {
return (isObject(response) &&
"isJobCompleted" in response &&
"verificationId" in response &&
"jobStartTime" in response &&
"contract" in response);
}
class SourcifyVerificationStatus {
response;
constructor(response) {
this.response = response;
}
get message() {
if (!this.response.isJobCompleted) {
return "Pending in queue";
}
if (this.response.error !== undefined) {
return this.response.error.message;
}
return `Contract verified with status "${this.response.contract.match}"`;
}
isPending() {
return !this.response.isJobCompleted;
}
isFailure() {
return this.response.isJobCompleted && this.response.error !== undefined;
}
isSuccess() {
return (this.response.isJobCompleted &&
this.response.error === undefined &&
this.response.contract.match !== null);
}
isBytecodeMissingInNetworkError() {
return (this.response.isJobCompleted &&
this.response.error?.customCode === "contract_not_deployed");
}
isAlreadyVerified() {
return (this.response.isJobCompleted &&
this.response.error?.customCode === "already_verified");
}
/**
* SourcifyVerificationStatusResponse represents a successful verification,
* so this always returns true.
*/
isOk() {
return true;
}
}
//# sourceMappingURL=sourcify.js.map