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
Address management system providing cleaner deployment scripts through named address mappings with network-specific configuration support.
Access configured named accounts for deployment scripts and tests.
/**
* Get named accounts as configured in hardhat.config
* @returns Promise resolving to mapping of account names to addresses
*/
getNamedAccounts(): Promise<{ [name: string]: Address }>;
/**
* Get unnamed accounts (remaining accounts not assigned names)
* @returns Promise resolving to array of unused account addresses
*/
getUnnamedAccounts(): Promise<string[]>;
/**
* Get current network chain ID
* @returns Promise resolving to chain ID as string
*/
getChainId(): Promise<string>;Usage Examples:
import { getNamedAccounts } from "hardhat";
// In deploy scripts
const func: DeployFunction = async function (hre) {
const { deployments } = hre;
const { deploy } = deployments;
const { deployer, tokenOwner, multisig } = await getNamedAccounts();
await deploy("MyContract", {
from: deployer,
args: [tokenOwner, multisig],
log: true,
});
};
// In tests
describe("Contract Tests", function () {
it("should work with named accounts", async function () {
const { deployer, user, admin } = await getNamedAccounts();
// Deploy or interact using named accounts
await deployments.deploy("TestContract", {
from: deployer,
args: [admin],
});
// Execute transactions
await deployments.execute(
"TestContract",
{ from: user },
"userFunction"
);
});
});
// Access unnamed accounts for additional signers
const func: DeployFunction = async function (hre) {
const { deployer, admin } = await getNamedAccounts();
const unnamedAccounts = await hre.getUnnamedAccounts();
// Use first unnamed account as temporary signer
const tempSigner = unnamedAccounts[0];
await deployments.execute(
"MyContract",
{ from: tempSigner },
"temporaryOperation"
);
};Configure named accounts in hardhat.config with network-specific overrides.
Usage Examples:
// hardhat.config.ts
export default {
namedAccounts: {
// Simple configuration
deployer: {
default: 0, // Use account index 0 by default
1: "0x1234567890123456789012345678901234567890", // Override for mainnet (chainId 1)
4: "0x9876543210987654321098765432109876543210", // Override for rinkeby (chainId 4)
},
// Multi-network configuration
tokenOwner: {
default: 1, // Use account index 1 by default
localhost: 0, // Use account 0 on localhost
hardhat: 0, // Use account 0 on hardhat network
mainnet: "0xMainnetTokenOwner...",
polygon: "0xPolygonTokenOwner...",
},
// Fixed addresses across networks
treasury: {
default: "0xTreasuryAddress...", // Same address everywhere
},
// Conditional configuration
multisig: {
default: 2, // Use account index 2 by default
1: "0xMainnetMultisig...", // Mainnet multisig
137: "0xPolygonMultisig...", // Polygon multisig
localhost: null, // No multisig on localhost
hardhat: null, // No multisig on hardhat
},
// User accounts for testing
user1: { default: 3 },
user2: { default: 4 },
user3: { default: 5 },
// Protocol-specific accounts
governance: {
default: "0xGovernanceContract...",
testnet: 6, // Use account index on testnets
},
},
networks: {
mainnet: {
// Network configuration
},
polygon: {
// Network configuration
},
localhost: {
// Local development
},
},
};
// Using in deploy scripts with network awareness
const func: DeployFunction = async function (hre) {
const { deployments, network } = hre;
const { deploy } = deployments;
const { deployer, tokenOwner, multisig, governance } = await getNamedAccounts();
await deploy("GovernanceToken", {
from: deployer,
args: [tokenOwner, governance],
log: true,
});
// Only set multisig on networks where it's configured
if (multisig) {
await deployments.execute(
"GovernanceToken",
{ from: tokenOwner },
"transferOwnership",
multisig
);
}
};Named accounts support various resolution strategies based on configuration.
Usage Examples:
// Configuration showing different resolution types
export default {
namedAccounts: {
// Account index resolution
deployer: {
default: 0, // Use accounts[0]
mainnet: 1, // Use accounts[1] on mainnet
},
// Direct address resolution
treasury: {
default: "0x1234567890123456789012345678901234567890",
polygon: "0x9876543210987654321098765432109876543210",
},
// Null resolution (account not available)
multisig: {
default: null, // No multisig by default
mainnet: "0xMainnetMultisig...", // Only on mainnet
},
// Network name based resolution
admin: {
default: 2,
localhost: 0, // Different account for local development
hardhat: 0, // Different account for hardhat network
rinkeby: "0xRinkebyAdmin...", // Specific address for testnet
},
// Chain ID based resolution
validator: {
default: 3,
1: "0xMainnetValidator...", // Ethereum mainnet
137: "0xPolygonValidator...", // Polygon
80001: "0xMumbaiValidator...", // Polygon Mumbai testnet
},
},
};
// Usage with conditional logic
const func: DeployFunction = async function (hre) {
const { deployments, network } = hre;
const accounts = await getNamedAccounts();
const { deployer, admin, multisig } = accounts;
console.log(`Deploying on ${network.name} with deployer: ${deployer}`);
if (admin) {
console.log(`Admin account: ${admin}`);
}
if (multisig) {
console.log(`Multisig available: ${multisig}`);
} else {
console.log("No multisig configured for this network");
}
await deploy("MyContract", {
from: deployer,
args: [admin || deployer], // Fallback to deployer if no admin
log: true,
});
};Named accounts integrate seamlessly with Hardhat's account management and network switching.
Usage Examples:
// Using with hardhat-ethers
import { ethers, getNamedAccounts } from "hardhat";
describe("Contract Tests", function () {
it("should work with ethers signers", async function () {
const { deployer, user } = await getNamedAccounts();
// Get signers for named accounts
const deployerSigner = await ethers.getSigner(deployer);
const userSigner = await ethers.getSigner(user);
// Use with contract instances
const contract = await ethers.getContract("MyContract", deployerSigner);
await contract.connect(userSigner).userMethod();
});
});
// Using with account impersonation
describe("Impersonation Tests", function () {
it("should impersonate named accounts", async function () {
const { multisig } = await getNamedAccounts();
if (multisig) {
// hardhat-deploy automatically enables impersonation for named accounts
await deployments.execute(
"MyContract",
{ from: multisig }, // This will be impersonated on localhost/hardhat
"adminFunction"
);
}
});
});
// Using with different networks
task("deploy-cross-network", "Deploy across networks")
.setAction(async (taskArgs, hre) => {
const { deployer, admin } = await getNamedAccounts();
console.log(`Network: ${hre.network.name}`);
console.log(`Deployer: ${deployer}`);
console.log(`Admin: ${admin}`);
// Deploy logic here
});
// Run with different networks:
// npx hardhat deploy-cross-network --network localhost
// npx hardhat deploy-cross-network --network mainnet
// npx hardhat deploy-cross-network --network polygonConfigure companion networks to access deployments from other networks during deployment scripts.
/**
* Companion network configuration in hardhat.config
*/
interface CompanionNetworksConfig {
[companionName: string]: string; // Network name to reference
}
/**
* Companion network access in deployment scripts
*/
interface CompanionNetwork {
deployments: {
get(name: string): Promise<Deployment>;
getOrNull(name: string): Promise<Deployment | null>;
all(): Promise<{ [name: string]: Deployment }>;
};
getNamedAccounts(): Promise<{ [name: string]: Address }>;
getChainId(): Promise<string>;
}Configuration Examples:
// hardhat.config.ts
export default {
networks: {
// Layer 1 network
mainnet: {
url: "https://mainnet.infura.io/v3/...",
accounts: ["0x..."],
},
// Layer 2 network with L1 companion
optimism: {
url: "https://mainnet.optimism.io",
accounts: ["0x..."],
companionNetworks: {
l1: "mainnet", // Reference mainnet as L1
ethereum: "mainnet", // Alternative reference name
},
},
// Polygon with Ethereum companion
polygon: {
url: "https://polygon-rpc.com",
accounts: ["0x..."],
companionNetworks: {
ethereum: "mainnet",
l1: "mainnet",
},
},
// Testnet with companion testnet
mumbai: {
url: "https://rpc-mumbai.maticvigil.com",
accounts: ["0x..."],
companionNetworks: {
ethereum: "goerli",
l1: "goerli",
},
},
// Local development with multiple companions
localhost: {
url: "http://127.0.0.1:8545",
companionNetworks: {
l1: "localhost", // Self reference for testing
mainnet: "localhost", // Use localhost as mainnet for testing
},
},
},
namedAccounts: {
deployer: {
default: 0,
},
// Cross-chain accounts can be different
bridgeOperator: {
default: 1,
mainnet: "0xMainnetBridgeOperator...",
optimism: "0xOptimismBridgeOperator...",
polygon: "0xPolygonBridgeOperator...",
},
},
};Usage Examples:
// Accessing companion network deployments
const func: DeployFunction = async function (hre) {
const { deployments, companionNetworks } = hre;
const { deploy } = deployments;
const { deployer, bridgeOperator } = await getNamedAccounts();
// Access L1 contracts from L2 deployment
if (companionNetworks.l1) {
const l1TokenBridge = await companionNetworks.l1.deployments.get("TokenBridge");
const l1ChainId = await companionNetworks.l1.getChainId();
console.log(`L1 TokenBridge at: ${l1TokenBridge.address} (Chain: ${l1ChainId})`);
// Deploy L2 contract with L1 bridge reference
await deploy("L2TokenBridge", {
from: deployer,
args: [
l1TokenBridge.address, // L1 bridge address
l1ChainId, // L1 chain ID
bridgeOperator, // L2 bridge operator
],
log: true,
});
}
// Access multiple companion networks
if (companionNetworks.ethereum) {
const ethereumDeployments = await companionNetworks.ethereum.deployments.all();
console.log(`Found ${Object.keys(ethereumDeployments).length} Ethereum contracts`);
// Use Ethereum contract addresses in L2 deployment
const ethereumToken = await companionNetworks.ethereum.deployments.get("WETH");
await deploy("L2WrappedToken", {
from: deployer,
args: [
ethereumToken.address, // Reference to L1 token
"Wrapped ETH (L2)",
"WETH-L2",
],
log: true,
});
}
};
// Cross-chain validation
const func: DeployFunction = async function (hre) {
const { companionNetworks, network } = hre;
console.log(`Deploying on ${network.name}`);
// Validate L1 deployments exist before L2 deployment
if (companionNetworks.l1) {
try {
const l1Contracts = await companionNetworks.l1.deployments.all();
const requiredContracts = ["TokenBridge", "MessagePasser", "StateCommitmentChain"];
for (const contractName of requiredContracts) {
if (!l1Contracts[contractName]) {
throw new Error(`Required L1 contract ${contractName} not found`);
}
console.log(`✓ Found L1 ${contractName} at ${l1Contracts[contractName].address}`);
}
} catch (error) {
console.error("L1 validation failed:", error);
throw error;
}
}
// Proceed with L2 deployment
await deployments.deploy("L2Contract", {
from: deployer,
args: [],
log: true,
});
};
// Multi-network synchronization
const func: DeployFunction = async function (hre) {
const { deployments, companionNetworks } = hre;
// Get accounts from different networks
const localAccounts = await getNamedAccounts();
const l1Accounts = companionNetworks.l1
? await companionNetworks.l1.getNamedAccounts()
: {};
console.log("Local deployer:", localAccounts.deployer);
console.log("L1 deployer:", l1Accounts.deployer);
// Use different signers for different networks
await deploy("CrossChainContract", {
from: localAccounts.deployer, // Local signer
args: [
l1Accounts.bridgeOperator || localAccounts.deployer, // L1 reference
],
log: true,
});
};/**
* Named accounts configuration in hardhat.config
*/
interface NamedAccountsConfig {
[name: string]:
| string // Direct address
| number // Account index
| {
[network: string]: null | number | string;
default?: number | string;
};
}
/**
* Resolved named accounts mapping
*/
type NamedAccounts = { [name: string]: Address };
type Address = string;