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;