This document provides guidance for AI agents working with the hardhat-deploy codebase.
hardhat-deploy is a Hardhat plugin for replicable smart contract deployments and easy testing across multiple EVM chains. Version 2 is a complete rewrite that integrates with rocketh, a framework-agnostic deployment system.
- Named Accounts: Associate names with addresses for clearer scripts
- Proxy Deployments: Declarative proxy patterns including OpenZeppelin transparent proxies
- Diamond Support: Deploy EIP-2535 Diamonds declaratively
- Hot Contract Replacement (HCR): Live contract updates during development
- Browser-Compatible: Deployment scripts can run in browsers
- Type Safety: Full TypeScript support with generated artifacts
hardhat-deploy/
├── packages/
│ └── hardhat-deploy/ # Main package source
│ ├── src/
│ │ ├── index.ts # Plugin entry point
│ │ ├── cli.ts # CLI for project initialization
│ │ ├── helpers.ts # Network/config helper functions
│ │ ├── types.ts # TypeScript type definitions
│ │ ├── config/ # Configuration handling
│ │ ├── hook-handlers/ # Hardhat hook handlers
│ │ └── tasks/ # Hardhat tasks (deploy)
│ └── templates/ # Project templates for `init`
├── demoes/
│ ├── basic/ # Basic deployment example
│ ├── diamond/ # EIP-2535 Diamond pattern example
│ └── proxies/ # Proxy deployment patterns example
├── documentation/ # VitePress documentation
├── skills/
│ └── hardhat-deploy-migration/ # v1 to v2 migration guide
└── .vitepress/ # Documentation site config
1. Plugin Entry (packages/hardhat-deploy/src/index.ts)
The main Hardhat plugin that registers:
- Hook handlers for config and solidity compilation
- The
deploytask - Integration with rocketh environment
const hardhatPlugin: HardhatPlugin = {
id: 'hardhat-deploy',
hookHandlers: {
config: () => import('./hook-handlers/config.js'),
solidity: () => import('./hook-handlers/solidity.js'),
},
tasks: [
task('deploy', 'Deploy contracts')
.addFlag({name: 'skipPrompts', description: 'if set, skip any prompts'})
.addOption({name: 'saveDeployments', ...})
.addOption({name: 'tags', ...})
.setAction(() => import('./tasks/deploy.js'))
.build(),
],
};2. Helper Functions (packages/hardhat-deploy/src/helpers.ts)
Provides utilities for:
loadEnvironmentFromHardhat()- Load rocketh environment from HardhatsetupHardhatDeploy()- Setup with custom extensionsaddNetworksFromEnv()- Auto-configure networks from env varsaddNetworksFromKnownList()- Add known chainsaddForkConfiguration()- Configure fork testinggetRPC()/getMnemonic()- Get network credentials
3. Deploy Task (packages/hardhat-deploy/src/tasks/deploy.ts)
Executes deployment scripts using loadAndExecuteDeploymentsFromFiles() from @rocketh/node.
4. CLI (packages/hardhat-deploy/src/cli.ts)
Provides the hardhat-deploy init command to scaffold new projects from templates.
hardhat-deploy v2 uses rocketh's extension system. Extensions are imported and combined in the project's rocketh/config.ts:
| Extension | Purpose |
|---|---|
@rocketh/deploy |
Basic deploy() function |
@rocketh/proxy |
deployViaProxy() for upgradeable contracts |
@rocketh/diamond |
diamond() for EIP-2535 Diamond deployments |
@rocketh/read-execute |
read(), execute(), readByName(), executeByName(), and tx() for contract interactions |
@rocketh/viem |
Viem client integration via viem() returning getContract(), getWritableContract(), walletClient, publicClient |
@rocketh/signer |
Signer protocols (e.g., privateKey) |
@rocketh/export |
Export deployments to JS/TS/JSON formats |
@rocketh/verifier |
Verify contracts on Etherscan, Sourcify, or Blockscout |
Every hardhat-deploy v2 project requires three configuration files in the rocketh/ directory:
import type {UserConfig} from 'rocketh/types';
export const config = {
accounts: {
deployer: { default: 0 },
admin: { default: 1 },
},
data: {},
} as const satisfies UserConfig;
// Import and combine extensions
import * as deployExtension from '@rocketh/deploy';
import * as readExecuteExtension from '@rocketh/read-execute';
const extensions = {
...deployExtension,
...readExecuteExtension,
};
export {extensions};
// Export types
export type {Extensions, Accounts, Data};import {type Extensions, type Accounts, type Data, extensions} from './config.js';
import * as artifacts from '../generated/artifacts/index.js';
import {setupDeployScripts} from 'rocketh';
const {deployScript} = setupDeployScripts<Extensions, Accounts, Data>(extensions);
export {deployScript, artifacts};import {type Extensions, type Accounts, type Data, extensions} from './config.js';
import {setupEnvironmentFromFiles} from '@rocketh/node';
import {setupHardhatDeploy} from 'hardhat-deploy/helpers';
const {loadAndExecuteDeploymentsFromFiles} = setupEnvironmentFromFiles<Extensions, Accounts, Data>(extensions);
const {loadEnvironmentFromHardhat} = setupHardhatDeploy<Extensions, Accounts, Data>(extensions);
export {loadEnvironmentFromHardhat, loadAndExecuteDeploymentsFromFiles};Deploy scripts use the deployScript() wrapper:
import {deployScript, artifacts} from '../rocketh/deploy.js';
export default deployScript(
async ({deploy, namedAccounts}) => {
const {deployer} = namedAccounts;
await deploy('MyContract', {
account: deployer, // Who deploys (was 'from:' in v1)
artifact: artifacts.MyContract, // Contract artifact
args: ['arg1', 'arg2'],
});
},
{tags: ['MyContract'], id: 'deploy_my_contract'},
);Tests use node:test with the fixture pattern:
import {expect} from 'earl';
import {describe, it} from 'node:test';
import {network} from 'hardhat';
import {loadAndExecuteDeploymentsFromFiles} from '../rocketh/environment.js';
function setupFixtures(provider) {
return {
async deployAll() {
const env = await loadAndExecuteDeploymentsFromFiles({provider});
const MyContract = env.get<Abi_MyContract>('MyContract');
return {env, MyContract, namedAccounts: env.namedAccounts};
},
};
}
const {provider, networkHelpers} = await network.connect();
const {deployAll} = setupFixtures(provider);
describe('MyContract', () => {
it('should work', async () => {
const {env, MyContract} = await networkHelpers.loadFixture(deployAll);
// Read from contract
const value = await env.read(MyContract, {functionName: 'getValue', args: []});
// Execute transaction
await env.execute(MyContract, {
account: env.namedAccounts.deployer,
functionName: 'setValue',
args: [42n],
});
});
});| Aspect | v1 | v2 |
|---|---|---|
| Module System | CommonJS | ESM ("type": "module") |
| Hardhat Version | 2.x | 3.x |
| Named Accounts | hardhat.config.ts |
rocketh/config.ts |
| Deploy Parameter | from: address |
account: address |
| Artifact | Implicit | Explicit artifact: artifacts.X |
| Test Fixtures | deployments.createFixture() |
Custom with loadAndExecuteDeploymentsFromFiles() |
| Contract Access | ethers.getContract() |
env.get<Abi_Type>() |
| Execution | contract.method() |
env.execute(contract, {...}) |
- Create file in
deploy/directory (e.g.,deploy/002_my_contract.ts) - Import
deployScriptandartifactsfrom../rocketh/deploy.js - Use the
deployScript()wrapper with tags
- Install the extension package (e.g.,
@rocketh/proxy) - Import in
rocketh/config.ts - Add to the
extensionsobject - Update the
Extensionstype export
# Local deployment
npx hardhat deploy
# Specific network
npx hardhat --network sepolia deploy
# With specific tags
npx hardhat deploy --tags MyContract
# Production build + deploy
npx hardhat compile --build-profile production && npx hardhat --network mainnet deploy# Set fork network via environment
HARDHAT_FORK=mainnet npx hardhat test
# With specific block
HARDHAT_FORK=mainnet HARDHAT_FORK_NUMBER=12345678 npx hardhat testThe helper functions read these environment variables:
| Variable | Purpose |
|---|---|
ETH_NODE_URI_<network> |
RPC URL for network (e.g., ETH_NODE_URI_MAINNET) |
MNEMONIC |
Default mnemonic for all networks |
MNEMONIC_<network> |
Network-specific mnemonic |
HARDHAT_FORK |
Network to fork from |
HARDHAT_FORK_NUMBER |
Block number to fork from |
Set to SECRET to use configVariable() for sensitive data.
- Deploy scripts:
deploy/NNN_description.ts(numbered for order) - Solidity sources:
src/ContractName/ContractName.sol - Generated artifacts:
generated/artifacts/andgenerated/abis/ - Deployment records:
deployments/<network>/ContractName.json
The project uses VitePress for documentation at documentation/. Key files:
introduction.md- Project overviewinstallation.md- Setup guideconfiguration.md- Config optionshow-to/- How-to guides
For v1 to v2 migration guidance, see skills/hardhat-deploy-migration/SKILL.md. This comprehensive guide covers:
- Dependency updates
- Configuration restructuring
- Deploy script conversion
- Test updates
- Common troubleshooting
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm test
# Start documentation dev server
pnpm docs:dev
# Watch mode for package development
pnpm dev- TypeScript with strict mode
- ESM modules (
.jsextensions required in imports) - Prettier for formatting
- Use
as const satisfiesfor typed configs - Explicit artifact imports in deploy scripts
- Always use
.jsextensions for local imports in ESM - Named accounts are configured in
rocketh/config.ts, nothardhat.config.ts - Use
account:notfrom:for deployer specification - Artifacts must be explicitly imported and passed to deploy functions
- Tests use
node:testnot mocha/vitest - Use BigInt literals (
1n) for numeric values in contract calls - Check the demos for working examples of patterns
- The
generated/directory is created by compilation - don't edit manually