Automated contract verification with Etherscan and Sourcify, including source submission and verification status management.
Submit contract source code to Etherscan for automatic verification and public access.
/**
* Submit contract sources to Etherscan for verification
* @param deploymentName - Name of deployed contract to verify
* @param options - Verification options
* @returns Promise that resolves when verification is submitted
*/
submitSources(
deploymentName: string,
options?: {
apiKey?: string;
apiUrl?: string;
license?: string;
forceLicense?: boolean;
solcInput?: boolean;
sleep?: number;
}
): Promise<void>;
/**
* Verify all deployed contracts on Etherscan
* @param options - Global verification options
* @returns Promise that resolves when all verifications are submitted
*/
etherscanVerify(options?: {
apiKey?: string;
apiUrl?: string;
license?: string;
forceLicense?: boolean;
solcInput?: boolean;
sleep?: number;
}): Promise<void>;Usage Examples:
// Verify single contract
await deployments.deploy("MyContract", {
from: deployer,
args: ["constructor", "args"],
log: true,
});
// Submit to Etherscan for verification
await submitSources("MyContract", {
apiKey: process.env.ETHERSCAN_API_KEY,
license: "MIT",
sleep: 1000, // Wait 1s between submissions
});
// Verify all contracts
await etherscanVerify({
apiKey: process.env.ETHERSCAN_API_KEY,
license: "MIT",
solcInput: true, // Use full solc input if minimal sources fail
sleep: 2000, // Wait 2s between verifications
});
// Network-specific verification
const networkName = deployments.getNetworkName();
const config = hre.config.networks[networkName];
const verifyConfig = config.verify?.etherscan;
if (verifyConfig) {
await etherscanVerify({
apiKey: verifyConfig.apiKey,
apiUrl: verifyConfig.apiUrl,
license: "SPDX-License-Identifier: MIT",
});
}
// Conditional verification for production networks
const { deploy } = deployments;
const deployment = await deploy("ProductionContract", {
from: deployer,
args: [tokenAddress, admin],
log: true,
});
if (deployment.newlyDeployed && hre.network.tags?.production) {
console.log("Submitting contract for verification...");
await submitSources("ProductionContract", {
apiKey: process.env.ETHERSCAN_API_KEY,
license: "Business Source License 1.1",
forceLicense: true, // Override source license
});
}Submit contract metadata to Sourcify for decentralized verification and IPFS storage.
/**
* Submit contract sources to Sourcify for verification
* @param deploymentName - Name of deployed contract to verify
* @param options - Sourcify submission options
* @returns Promise that resolves when sources are submitted
*/
submitSourcesToSourcify(
deploymentName: string,
options?: {
endpoint?: string;
writeFailingMetadata?: boolean;
}
): Promise<void>;
/**
* Verify all deployed contracts with Sourcify
* @param options - Global Sourcify options
* @returns Promise that resolves when all sources are submitted
*/
sourcifyVerify(options?: {
endpoint?: string;
writeFailingMetadata?: boolean;
}): Promise<void>;Usage Examples:
// Submit single contract to Sourcify
await deployments.deploy("OpenSourceContract", {
from: deployer,
args: ["public", "contract"],
log: true,
});
await submitSourcesToSourcify("OpenSourceContract", {
endpoint: "https://sourcify.dev/server/",
writeFailingMetadata: true, // Write metadata files on failure for debugging
});
// Verify all contracts with Sourcify
await sourcifyVerify({
endpoint: "https://repo.sourcify.dev/",
writeFailingMetadata: false,
});
// Custom Sourcify instance
await sourcifyVerify({
endpoint: "https://custom-sourcify.example.com/",
});
// Error handling with metadata writing
try {
await submitSourcesToSourcify("ComplexContract");
} catch (error) {
console.error("Sourcify verification failed:", error);
// Retry with metadata debugging
await submitSourcesToSourcify("ComplexContract", {
writeFailingMetadata: true, // This will write metadata files for inspection
});
}
// Batch verification with error handling
const contractNames = ["Token", "Sale", "Governance"];
for (const contractName of contractNames) {
try {
await submitSourcesToSourcify(contractName);
console.log(`✓ ${contractName} submitted to Sourcify`);
} catch (error) {
console.error(`✗ Failed to submit ${contractName}:`, error);
}
}Configure verification settings in hardhat.config.ts for different networks and providers.
Configuration Examples:
// hardhat.config.ts
export default {
networks: {
mainnet: {
url: "https://mainnet.infura.io/v3/...",
accounts: ["0x..."],
verify: {
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
apiUrl: "https://api.etherscan.io/api", // Default Ethereum
},
},
},
polygon: {
url: "https://polygon-rpc.com",
accounts: ["0x..."],
verify: {
etherscan: {
apiKey: process.env.POLYGONSCAN_API_KEY,
apiUrl: "https://api.polygonscan.com/api",
},
},
},
bsc: {
url: "https://bsc-dataseed1.binance.org",
accounts: ["0x..."],
verify: {
etherscan: {
apiKey: process.env.BSCSCAN_API_KEY,
apiUrl: "https://api.bscscan.com/api",
},
},
},
optimism: {
url: "https://mainnet.optimism.io",
accounts: ["0x..."],
verify: {
etherscan: {
apiKey: process.env.OPTIMISM_API_KEY,
apiUrl: "https://api-optimistic.etherscan.io/api",
},
},
},
arbitrum: {
url: "https://arb1.arbitrum.io/rpc",
accounts: ["0x..."],
verify: {
etherscan: {
apiKey: process.env.ARBISCAN_API_KEY,
apiUrl: "https://api.arbiscan.io/api",
},
},
},
},
// Global verification settings
verify: {
etherscan: {
apiKey: {
mainnet: process.env.ETHERSCAN_API_KEY,
polygon: process.env.POLYGONSCAN_API_KEY,
bsc: process.env.BSCSCAN_API_KEY,
optimisticEthereum: process.env.OPTIMISM_API_KEY,
arbitrumOne: process.env.ARBISCAN_API_KEY,
},
},
},
sourcify: {
enabled: true,
endpoint: "https://sourcify.dev/server/",
},
};
// Usage in deploy scripts
const func: DeployFunction = async function (hre) {
const { deployments, network } = hre;
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
const deployment = await deploy("MultiNetworkContract", {
from: deployer,
args: ["universal", "contract"],
log: true,
});
// Verify on all networks except localhost/hardhat
if (deployment.newlyDeployed && network.live) {
console.log(`Verifying on ${network.name}...`);
// Try Etherscan first
try {
await submitSources("MultiNetworkContract");
console.log("✓ Etherscan verification submitted");
} catch (error) {
console.error("✗ Etherscan verification failed:", error);
}
// Also submit to Sourcify
try {
await submitSourcesToSourcify("MultiNetworkContract");
console.log("✓ Sourcify verification submitted");
} catch (error) {
console.error("✗ Sourcify verification failed:", error);
}
}
};Handle complex verification scenarios including proxy contracts, libraries, and multi-file contracts.
Usage Examples:
// Verify proxy implementation
const func: DeployFunction = async function (hre) {
const { deployments } = hre;
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
// Deploy with proxy
const proxyDeployment = await deploy("MyContract", {
from: deployer,
args: ["init", "args"],
proxy: {
proxyContract: "OpenZeppelinTransparentProxy",
execute: {
init: {
methodName: "initialize",
args: ["init", "args"],
},
},
},
log: true,
});
if (proxyDeployment.newlyDeployed) {
// Verify both proxy and implementation
await submitSources("MyContract"); // This verifies the implementation
// The proxy contract is automatically handled
console.log("✓ Proxy and implementation verified");
}
};
// Verify contracts with libraries
const func: DeployFunction = async function (hre) {
const { deployments } = hre;
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
// Deploy library first
const mathLib = await deploy("MathLibrary", {
from: deployer,
log: true,
});
// Deploy contract using library
const contractWithLib = await deploy("CalculatorContract", {
from: deployer,
args: [100],
libraries: {
MathLibrary: mathLib.address,
},
log: true,
});
if (contractWithLib.newlyDeployed) {
// Verify library first
await submitSources("MathLibrary");
// Then verify main contract (library linkage is handled automatically)
await submitSources("CalculatorContract");
console.log("✓ Library and main contract verified");
}
};
// Batch verification with retry logic
const verifyWithRetry = async (contractName: string, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
await submitSources(contractName, {
sleep: 1000 * (i + 1), // Increasing delay
});
console.log(`✓ ${contractName} verified on attempt ${i + 1}`);
return;
} catch (error) {
console.log(`Attempt ${i + 1} failed for ${contractName}:`, error);
if (i === maxRetries - 1) {
throw error;
}
}
}
};
// Use in deployment
const func: DeployFunction = async function (hre) {
const contracts = ["TokenA", "TokenB", "DEX", "Governance"];
// Deploy all contracts first
for (const contractName of contracts) {
await deployments.deploy(contractName, {
from: deployer,
args: [], // Contract-specific args
log: true,
});
}
// Then verify all with retry logic
for (const contractName of contracts) {
try {
await verifyWithRetry(contractName);
} catch (error) {
console.error(`Failed to verify ${contractName} after retries:`, error);
}
}
};Check verification status and manage verification workflows.
Usage Examples:
// Check if contract needs verification
const func: DeployFunction = async function (hre) {
const { deployments, network } = hre;
const contracts = await deployments.all();
// Only verify on live networks
if (!network.live) {
console.log("Skipping verification on local network");
return;
}
console.log(`Checking ${Object.keys(contracts).length} contracts for verification...`);
for (const [name, deployment] of Object.entries(contracts)) {
// Check if deployment has enough metadata for verification
if (!deployment.metadata || !deployment.solcInputHash) {
console.log(`⚠️ ${name}: Missing verification metadata`);
continue;
}
// Check if recently deployed (needs verification)
if (deployment.receipt && deployment.receipt.blockNumber) {
const currentBlock = await hre.ethers.provider.getBlockNumber();
const deploymentBlock = deployment.receipt.blockNumber;
if (currentBlock - deploymentBlock < 100) { // Recently deployed
console.log(`🔍 ${name}: Recently deployed, submitting for verification`);
try {
await submitSources(name);
console.log(`✓ ${name}: Verification submitted`);
} catch (error) {
console.error(`✗ ${name}: Verification failed -`, error);
}
} else {
console.log(`ℹ️ ${name}: Already verified or old deployment`);
}
}
}
};
// Environment-specific verification
const func: DeployFunction = async function (hre) {
const { deployments, network } = hre;
// Different verification strategies per environment
const verificationConfig = {
mainnet: {
etherscan: true,
sourcify: true,
sleep: 5000, // Conservative rate limiting
},
testnet: {
etherscan: true,
sourcify: false, // Skip Sourcify for testnets
sleep: 2000,
},
development: {
etherscan: false,
sourcify: false,
},
};
const config = verificationConfig[network.tags?.production ? 'mainnet' :
network.tags?.testnet ? 'testnet' : 'development'];
if (config.etherscan) {
console.log("Starting Etherscan verification...");
await etherscanVerify({
sleep: config.sleep,
});
}
if (config.sourcify) {
console.log("Starting Sourcify verification...");
await sourcifyVerify();
}
if (!config.etherscan && !config.sourcify) {
console.log("Verification skipped for development environment");
}
};interface EtherscanVerificationOptions {
/** Etherscan API key */
apiKey?: string;
/** Etherscan API URL (different per network) */
apiUrl?: string;
/** SPDX license identifier */
license?: string;
/** Force license override */
forceLicense?: boolean;
/** Use full solc input instead of minimal sources */
solcInput?: boolean;
/** Sleep time between API calls (ms) */
sleep?: number;
}
interface SourcifyVerificationOptions {
/** Sourcify server endpoint */
endpoint?: string;
/** Write metadata files on verification failure for debugging */
writeFailingMetadata?: boolean;
}
interface VerificationConfig {
etherscan?: {
apiKey?: string | { [network: string]: string };
apiUrl?: string;
};
}
type VerificationResult = {
success: boolean;
contractName: string;
address: string;
error?: string;
};