UNPKG

@nomicfoundation/hardhat-verify

Version:
247 lines 10.5 kB
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