Hardhat plugin providing comprehensive deployment system for Ethereum smart contracts with replicable deployments and enhanced testing capabilities.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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;
};