Comprehensive guide for migrating projects from hardhat-deploy v1 to v2, including dependency updates, configuration restructuring, deploy script conversion, test updates, and troubleshooting
This guide provides comprehensive instructions for migrating projects from hardhat-deploy v1 to v2. It's designed for AI assistants to understand and execute the migration process systematically.
Note: For complete working examples, see the template-ethereum-contracts repository which demonstrates a full hardhat-deploy v2 setup.
hardhat-deploy v2 is a complete rewrite that requires Hardhat 3.x and introduces significant architectural changes:
import/export) instead of CommonJS| Aspect | v1 Pattern | v2 Pattern |
|---|---|---|
| Hardhat version | 2.x | 3.x (specifically ^3.1.5) |
| Module system | CommonJS (require/module.exports) | ESM (import/export) |
| Named accounts | namedAccounts in hardhat.config.ts | rocketh/config.ts |
| Deploy function | deployments.deploy(name, {...}) | deploy(name, {account: ..., artifact: ...}) |
| Deployer param | from: address | account: address |
| Solidity config | solidity: "0.8.x" | solidity: {profiles: {default: {version: "..."}}} |
| Test fixtures | deployments.createFixture() | Custom fixture with loadAndExecuteDeploymentsFromFiles() |
| Contract interaction | ethers.getContract() | env.get() + env.execute() |
If you have a production project using hardhat-deploy v1 with Hardhat 2.x, it's often better to stay on v1:
npm uninstall hardhat-deploy
npm install hardhat-deploy@1
v1 continues to receive security fixes but won't get new features.
Before starting the migration, verify your environment meets these requirements:
# Check Node.js version (requires 22+ for v2)
node --version
# Check Hardhat version (requires 3.x+ for v2)
npx hardhat --version
# Check if project is using CommonJS or ESM
grep -q '"type": "module"' package.json && echo "ESM" || echo "CommonJS"
^3.1.5 recommended)Check for these v1 patterns in your project:
// In hardhat.config.ts
- namedAccounts configuration
- require() statements
- module.exports
- solidity: "0.8.x" format
// In deploy scripts
- async function (hre) { ... }
- hre.deployments.deploy()
- hre.getNamedAccounts()
- from: parameter
- log: true parameter
// In tests
- deployments.createFixture()
- ethers.getContract()
- getUnnamedAccounts()
graph TD
A[hardhat.config.ts] -->|contains| B[namedAccounts]
A -->|contains| C[solidity config]
A -->|requires| D[hardhat-deploy]
E[deploy/*.ts] -->|imports| F[HardhatRuntimeEnvironment]
F -->|provides| G[getNamedAccounts]
F -->|provides| H[deployments]
I[test/*.ts] -->|uses| J[deployments.createFixture]
J -->|calls| K[deployments.fixture]
K -->|gets| L[ethers.getContract]
Key Files in v1:
hardhat.config.ts - Single configuration filedeploy/*.ts - Deploy scripts with HRE patterntest/*.ts - Tests using deployments fixtureutils/network.ts - Network configuration helpergraph TD
A[hardhat.config.ts] -->|imports plugins| B[HardhatDeploy]
A -->|uses helpers| C[addNetworksFromEnv]
A -->|contains| D[solidity profiles]
E[rocketh/config.ts] -->|contains| F[accounts config]
E -->|contains| G[extensions]
H[rocketh/deploy.ts] -->|exports| I[deployScript]
H -->|exports| J[artifacts]
K[rocketh/environment.ts] -->|exports| L[loadEnvironmentFromHardhat]
K -->|exports| M[loadAndExecuteDeploymentsFromFiles]
N[deploy/*.ts] -->|imports| I
N -->|uses| J
O[test/*.ts] -->|imports| M
O -->|uses| env.get and env.execute
Key Files in v2:
hardhat.config.ts - Hardhat configuration (no named accounts)rocketh/config.ts - Named accounts and extensionsrocketh/deploy.ts - Deploy script setuprocketh/environment.ts - Environment setup for tests/scriptsdeploy/*.ts - Deploy scripts with new patterntest/*.ts - Tests using new fixture patternUpdate your package.json to use Hardhat 3.x and hardhat-deploy v2:
v1 package.json example:
{
"devDependencies": {
"hardhat": "^2.22.18",
"hardhat-deploy": "^0.14.0",
"hardhat-deploy-ethers": "^0.4.2",
"hardhat-deploy-tenderly": "^1.0.0",
"ethers": "^6.13.5",
"hardhat-deploy": "^0.14.0"
}
}
v2 package.json example: (see template-ethereum-contracts/package.json)
{
"type": "module",
"devDependencies": {
"hardhat": "^3.1.4",
"hardhat-deploy": "^2.0.0",
"rocketh": "^0.17.15",
"@rocketh/deploy": "^0.17.9",
"@rocketh/read-execute": "^0.17.9",
"@rocketh/node": "^0.17.18",
"@rocketh/proxy": "^0.17.13",
"@rocketh/signer": "^0.17.9",
"viem": "^2.45.0",
"earl": "^2.0.0",
"@nomicfoundation/hardhat-viem": "^3.0.1",
"@nomicfoundation/hardhat-node-test-runner": "^3.0.8",
"@nomicfoundation/hardhat-network-helpers": "^3.0.3",
"@nomicfoundation/hardhat-keystore": "^3.0.3"
}
}
Transformation Rules:
"type": "module" at the top levelhardhat to ^3.1.4 or higherhardhat-deploy to ^2.0.0 or higherhardhat-deploy-ethers and hardhat-deploy-tenderlyrocketh, @rocketh/deploy, @rocketh/read-execute, @rocketh/node, @rocketh/signer@rocketh/proxy, @rocketh/export, @rocketh/verifier, @rocketh/docviem for contract interactionsearl for assertions (for node:test)Install dependencies:
pnpm install
v1 hardhat.config.ts example:
import "dotenv/config";
import { HardhatUserConfig } from "hardhat/types";
import "@nomicfoundation/hardhat-chai-matchers";
import "@nomicfoundation/hardhat-ethers";
import "@typechain/hardhat";
import "hardhat-deploy";
import "hardhat-deploy-ethers";
import "hardhat-deploy-tenderly";
import { node_url, accounts, addForkConfiguration } from "./utils/network";
const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.8.17",
settings: {
optimizer: {
enabled: true,
runs: 2000,
},
},
},
],
},
namedAccounts: {
deployer: 0,
simpleERC20Beneficiary: 1,
},
networks: addForkConfiguration({
hardhat: {
initialBaseFeePerGas: 0,
},
localhost: {
url: node_url("localhost"),
accounts: accounts(),
},
mainnet: {
url: node_url("mainnet"),
accounts: accounts("mainnet"),
},
sepolia: {
url: node_url("sepolia"),
accounts: accounts("sepolia"),
},
}),
paths: {
sources: "src",
},
mocha: {
timeout: 0,
},
external: process.env.HARDHAT_FORK
? {
deployments: {
hardhat: ["deployments/" + process.env.HARDHAT_FORK],
localhost: ["deployments/" + process.env.HARDHAT_FORK],
},
}
: undefined,
};
export default config;
v2 hardhat.config.ts example: (see template-ethereum-contracts/hardhat.config.ts)
import type { HardhatUserConfig } from "hardhat/config";
import HardhatNodeTestRunner from "@nomicfoundation/hardhat-node-test-runner";
import HardhatViem from "@nomicfoundation/hardhat-viem";
import HardhatNetworkHelpers from "@nomicfoundation/hardhat-network-helpers";
import HardhatKeystore from "@nomicfoundation/hardhat-keystore";
import HardhatDeploy from "hardhat-deploy";
import {
addForkConfiguration,
addNetworksFromEnv,
addNetworksFromKnownList,
} from "hardhat-deploy/helpers";
const config: HardhatUserConfig = {
plugins: [
HardhatNodeTestRunner,
HardhatViem,
HardhatNetworkHelpers,
HardhatKeystore,
HardhatDeploy,
],
solidity: {
profiles: {
default: {
version: "0.8.17",
},
production: {
version: "0.8.17",
settings: {
optimizer: {
enabled: true,
runs: 999999,
},
},
},
},
},
networks:
// This add the fork configuration for chosen network
addForkConfiguration(
// this add a network config for all known chain using kebab-cases names
// Note that MNEMONIC_<network> (or MNEMONIC if the other is not set) will
// be used for account
// Similarly ETH_NODE_URI_<network> will be used for rpcUrl
// Note that if you set these env variable to have the value: "SECRET" it will be like using:
// configVariable('SECRET_ETH_NODE_URI_<network>')
// configVariable('SECRET_MNEMONIC_<network>')
addNetworksFromKnownList(
// this add network for each respective env var found (ETH_NODE_URI_<network>)
// it will also read MNEMONIC_<network> to populate the accounts
// And like above it will use configVariable if set to SECRET
addNetworksFromEnv(
// and you can add in your specific network here
{
default: {
type: "edr-simulated",
chainType: "l1",
accounts: {
mnemonic: process.env.MNEMONIC || undefined,
},
},
},
),
),
),
paths: {
sources: ["src"],
},
generateTypedArtifacts: {
destinations: [
{
folder: "./generated",
mode: "typescript",
},
],
},
};
export default config;
Transformation Rules:
import 'hardhat-deploy' to import HardhatDeploy from 'hardhat-deploy'namedAccounts section entirelysolidity.compilers to solidity.profilesplugins array with imported pluginshardhat-deploy/helpers for network configurationgenerateTypedArtifacts configurationmocha timeout configuration (not needed in v2)external.deployments configuration (handled differently)utils/network.ts file (no longer needed)v1 tsconfig.json example:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"outDir": "dist"
},
"include": [
"hardhat.config.ts",
"./scripts",
"./deploy",
"./test",
"typechain/**/*"
]
}
v2 tsconfig.json example:
{
"compilerOptions": {
"lib": ["es2023"],
"module": "node16",
"target": "es2022",
"moduleResolution": "node16",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"rootDir": "."
},
"include": ["deploy", "generated"]
}
Create scripts/tsconfig.json:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"rootDir": ".."
},
"include": ["**/*", "../generated/**/*", "../rocketh/**/*"],
"exclude": []
}
Create test/tsconfig.json:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"rootDir": ".."
},
"include": [
"**/*",
"../generated/**/*",
"../rocketh/**/*",
"../hardhat.config.ts"
],
"exclude": []
}
Transformation Rules for tsconfig.json:
module from commonjs to node16target from es5 to es2022moduleResolution from node to node16lib: ["es2023"]skipLibCheck: truesourceMap: true, declaration: true, declarationMap: trueinclude to only ["deploy", "generated"]scripts/tsconfig.json extending the main configtest/tsconfig.json extending the main configNew file: rocketh/config.ts example: (see template-ethereum-contracts/rocketh/config.ts)
// ----------------------------------------------------------------------------
// Typed Config
// ----------------------------------------------------------------------------
import type {
EnhancedEnvironment,
UnknownDeployments,
UserConfig,
} from "rocketh/types";
// this one provide a protocol supporting private key as account
import { privateKey } from "@rocketh/signer";
// we define our config and export it as "config"
export const config = {
accounts: {
deployer: {
default: 0,
},
simpleERC20Beneficiary: {
default: 1,
},
},
data: {},
signerProtocols: {
privateKey,
},
} as const satisfies UserConfig;
// then we import each extensions we are interested in using in our deploy script or elsewhere
// this one provide a deploy function
import * as deployExtension from "@rocketh/deploy";
// this one provide read,execute functions
import * as readExecuteExtension from "@rocketh/read-execute";
// this one provide a deployViaProxy function that let you declaratively
// deploy proxy based contracts
import * as deployProxyExtension from "@rocketh/proxy";
// this one provide a viem handle to clients and contracts
import * as viemExtension from "@rocketh/viem";
// and export them as a unified object
const extensions = {
...deployExtension,
...readExecuteExtension,
...deployProxyExtension,
...viemExtension,
};
export { extensions };
// then we also export the types that our config ehibit so other can use it
type Extensions = typeof extensions;
type Accounts = typeof config.accounts;
type Data = typeof config.data;
type Environment = EnhancedEnvironment<
Accounts,
Data,
UnknownDeployments,
Extensions
>;
export type { Extensions, Accounts, Data, Environment };
Transformation Rules:
rocketh directory: mkdir rockethnamedAccounts from hardhat.config.ts to rocketh/config.ts under accountsNew file: rocketh/deploy.ts example: (see template-ethereum-contracts/rocketh/deploy.ts)
import {
type Accounts,
type Data,
type Extensions,
extensions,
} from "./config.js";
// ----------------------------------------------------------------------------
// we re-export the artifacts, so they are easily available from the alias
import * as artifacts from "../generated/artifacts/index.js";
export { artifacts };
// ----------------------------------------------------------------------------
// we create the rocketh functions we need by passing the extensions to the
// setup function
import { setupDeployScripts } from "rocketh";
const { deployScript } = setupDeployScripts<Extensions, Accounts, Data>(
extensions,
);
export { deployScript };
Transformation Rules:
./config.js../generated/artifacts/index.jssetupDeployScripts from rockethdeployScript and artifactsNew file: rocketh/environment.ts example: (see template-ethereum-contracts/rocketh/environment.ts)
import {
type Accounts,
type Data,
type Extensions,
extensions,
} from "./config.js";
import { setupEnvironmentFromFiles } from "@rocketh/node";
import { setupHardhatDeploy } from "hardhat-deploy/helpers";
// useful for test and scripts, uses file-system
const { loadAndExecuteDeploymentsFromFiles } = setupEnvironmentFromFiles<
Extensions,
Accounts,
Data
>(extensions);
const { loadEnvironmentFromHardhat } = setupHardhatDeploy<
Extensions,
Accounts,
Data
>(extensions);
export { loadEnvironmentFromHardhat, loadAndExecuteDeploymentsFromFiles };
Transformation Rules:
./config.js@rocketh/node and hardhat-deploy/helpersloadEnvironmentFromHardhat for scriptsloadAndExecuteDeploymentsFromFiles for testsv1 deploy script example:
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { parseEther } from "ethers";
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deploy } = deployments;
const { deployer, simpleERC20Beneficiary } = await getNamedAccounts();
await deploy("SimpleERC20", {
from: deployer,
args: [simpleERC20Beneficiary, parseEther("1000000000")],
log: true,
autoMine: true, // speed up deployment on local network (ganache, hardhat), no effect on live networks
});
};
export default func;
func.tags = ["SimpleERC20"];
v2 deploy script example: (see template-ethereum-contracts/deploy/001_deploy_greetings_registry.ts)
import { deployScript, artifacts } from "../rocketh/deploy.js";
import { parseEther } from "viem";
export default deployScript(
async (env) => {
const { deployer, simpleERC20Beneficiary } = env.namedAccounts;
await env.deploy("SimpleERC20", {
artifact: artifacts.SimpleERC20,
account: deployer,
args: [simpleERC20Beneficiary, parseEther("1000000000")],
});
},
{
tags: ["SimpleERC20"],
},
);
Transformation Rules:
HardhatRuntimeEnvironment and DeployFunction importsdeployScript and artifacts from ../rocketh/deploy.jsparseEther from ethers to viemdeployScript() call(hre) to (env)hre.getNamedAccounts() with direct env.namedAccounts accesshre.deployments.deploy() with env.deploy()from: to account:artifact: parameterlog: and autoMine: parameters (not needed in v2)func.tagsv1 proxy deploy script example:
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;
const useProxy = !hre.network.live;
// proxy only in non-live network (localhost and hardhat network) enabling HCR (Hot Contract Replacement)
// in live network, proxy is disabled and constructor is invoked
await deploy("GreetingsRegistry", {
from: deployer,
proxy: useProxy && "postUpgrade",
args: [2],
log: true,
autoMine: true, // speed up deployment on local network (ganache, hardhat), no effect on live networks
});
return !useProxy; // when live network, record the script as executed to prevent rexecution
};
export default func;
func.id = "deploy_greetings_registry"; // id required to prevent reexecution
func.tags = ["GreetingsRegistry"];
v2 proxy deploy script example: (see template-ethereum-contracts/deploy/002_deploy_greetings_registry.ts)
import { deployScript, artifacts } from "../rocketh/deploy.js";
import { parseEther } from "viem";
export default deployScript(
async (env) => {
const { deployer } = env.namedAccounts;
const useProxy = !env.tags.live;
// proxy only in non-live network (localhost and hardhat network) enabling HCR (Hot Contract Replacement)
// in live network, proxy is disabled and constructor is invoked
await env.deployViaProxy(
"GreetingsRegistry",
{
account: deployer,
artifact: artifacts.GreetingsRegistry,
args: ["2"],
},
{
proxyDisabled: !useProxy,
execute: "postUpgrade",
},
);
return !useProxy; // when live network, record the script as executed to prevent rexecution
},
{
tags: ["GreetingsRegistry"],
id: "deploy_greetings_registry", // id required to prevent reexecution
},
);
Transformation Rules for Proxy Deployment:
proxy: useProxy && 'postUpgrade' to env.deployViaProxy() callproxyDisabled: !useProxy instead of conditional proxyexecute: 'postUpgrade' instead of proxy typehre.network.live with env.tags.livev1 test example:
import { expect } from "chai";
import {
ethers,
deployments,
getUnnamedAccounts,
getNamedAccounts,
} from "hardhat";
import { IERC20 } from "../typechain-types";
import { setupUser, setupUsers } from "./utils";
const setup = deployments.createFixture(async () => {
await deployments.fixture("SimpleERC20");
const { simpleERC20Beneficiary } = await getNamedAccounts();
const contracts = {
SimpleERC20: await ethers.getContract<IERC20>("SimpleERC20"),
};
const users = await setupUsers(await getUnnamedAccounts(), contracts);
return {
...contracts,
users,
simpleERC20Beneficiary: await setupUser(simpleERC20Beneficiary, contracts),
};
});
describe("SimpleERC20", function () {
it("transfer fails", async function () {
const { users } = await setup();
await expect(
users[0].SimpleERC20.transfer(users[1].address, 1),
).to.be.revertedWith("NOT_ENOUGH_TOKENS");
});
it("transfer succeed", async function () {
const { users, simpleERC20Beneficiary, SimpleERC20 } = await setup();
await simpleERC20Beneficiary.SimpleERC20.transfer(users[1].address, 1);
await expect(
simpleERC20Beneficiary.SimpleERC20.transfer(users[1].address, 1),
)
.to.emit(SimpleERC20, "Transfer")
.withArgs(simpleERC20Beneficiary.address, users[1].address, 1);
});
});
v2 test example: (see template-ethereum-contracts/test/GreetingsRegistry.test.ts)
import { expect } from "earl";
import { describe, it } from "node:test";
import { network } from "hardhat";
import { EthereumProvider } from "hardhat/types/providers";
import { loadAndExecuteDeploymentsFromFiles } from "../rocketh/environment.js";
import { Abi_SimpleERC20 } from "../generated/abis/SimpleERC20.js";
function setupFixtures(provider: EthereumProvider) {
return {
async deployAll() {
const env = await loadAndExecuteDeploymentsFromFiles({
provider: provider,
});
// Deployment are inherently untyped since they can vary from
// network or even be different from current artifacts so here
// we type them manually assuming the artifact is still matching
const SimpleERC20 = env.get<Abi_SimpleERC20>("SimpleERC20");
return {
env,
SimpleERC20,
namedAccounts: env.namedAccounts,
unnamedAccounts: env.unnamedAccounts,
};
},
};
}
const { provider, networkHelpers } = await network.connect();
const { deployAll } = setupFixtures(provider);
describe("SimpleERC20", function () {
it("transfer fails", async function () {
const { env, SimpleERC20, unnamedAccounts } =
await networkHelpers.loadFixture(deployAll);
await expect(
env.execute(SimpleERC20, {
account: unnamedAccounts[0],
functionName: "transfer",
args: [unnamedAccounts[1], 1n],
}),
).toBeRejectedWith("NOT_ENOUGH_TOKENS");
});
it("transfer succeed", async function () {
const { env, SimpleERC20, unnamedAccounts, namedAccounts } =
await networkHelpers.loadFixture(deployAll);
await env.execute(SimpleERC20, {
account: namedAccounts.simpleERC20Beneficiary,
functionName: "transfer",
args: [unnamedAccounts[1], 1n],
});
env.execute(SimpleERC20, {
account: namedAccounts.simpleERC20Beneficiary,
functionName: "transfer",
args: [unnamedAccounts[1], 1n],
});
// TODO
// expect(...).toEmit(SimpleERC20, 'Transfer')
// .withArgs(simpleERC20Beneficiary.address, users[1].address, 1));
});
});
Transformation Rules for Tests:
mocha to node:testchai to earl (or keep chai if preferred)network from 'hardhat'loadAndExecuteDeploymentsFromFiles()deployments.createFixture() with custom fixturedeployments.fixture() with loadAndExecuteDeploymentsFromFiles()ethers.getContract() with env.get<Abi_Type>()import {Abi_SimpleERC20} from '../generated/abis/SimpleERC20.js'getUnnamedAccounts() with env.unnamedAccountsenv.execute():users[0].SimpleERC20.transfer(users[1].address, 1)env.execute(SimpleERC20, {account: unnamedAccounts[0], functionName: 'transfer', args: [unnamedAccounts[1], 1n]})BigInt literals (1n) instead of Numbers (1) for amountsnetworkHelpers.loadFixture() instead of direct fixture callv1 test utils example:
import { BaseContract } from "ethers";
import hre from "hardhat";
const { ethers } = hre;
export async function setupUsers<
T extends { [contractName: string]: BaseContract },
>(addresses: string[], contracts: T): Promise<({ address: string } & T)[]> {
const users: ({ address: string } & T)[] = [];
for (const address of addresses) {
users.push(await setupUser(address, contracts));
}
return users;
}
export async function setupUser<
T extends { [contractName: string]: BaseContract },
>(address: string, contracts: T): Promise<{ address: string } & T> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = { address };
for (const key of Object.keys(contracts)) {
user[key] = contracts[key].connect(await ethers.getSigner(address));
}
return user as { address: string } & T;
}
v2 test utils example: (see template-ethereum-contracts/test/utils/index.ts)
import { Abi_GreetingsRegistry } from "../../generated/abis/GreetingsRegistry.js";
import { loadAndExecuteDeploymentsFromFiles } from "../../rocketh/environment.js";
import { EthereumProvider } from "hardhat/types/providers";
export function setupFixtures(provider: EthereumProvider) {
return {
async deployAll() {
const env = await loadAndExecuteDeploymentsFromFiles({
provider: provider,
});
// Deployment are inherently untyped since they can vary from
// network or even be different from current artifacts so here
// we type them manually assuming the artifact is still matching
const GreetingsRegistry =
env.get<Abi_GreetingsRegistry>("GreetingsRegistry");
return {
env,
GreetingsRegistry,
namedAccounts: env.namedAccounts,
unnamedAccounts: env.unnamedAccounts,
};
},
};
}
Transformation Rules for Test Utils:
setupUsers and setupUser functions (not needed in v2)setupFixtures function that returns deployment setuploadAndExecuteDeploymentsFromFiles() for deploymentv1 script pattern:
import hre from "hardhat";
async function main() {
const { deployments, getNamedAccounts } = hre;
const { deployer } = await getNamedAccounts();
const MyContract = await deployments.get("MyContract");
const contract = await ethers.getContractAt("MyContract", MyContract.address);
await contract.someFunction();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
v2 script pattern:
import hre from "hardhat";
import { loadEnvironmentFromHardhat } from "./rocketh/environment.js";
import { Abi_MyContract } from "./generated/abis/MyContract.js";
async function main() {
const env = await loadEnvironmentFromHardhat({ hre });
const MyContract = env.get<Abi_MyContract>("MyContract");
await env.execute(MyContract, {
account: env.namedAccounts.deployer,
functionName: "someFunction",
args: [],
});
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Transformation Rules for Scripts:
loadEnvironmentFromHardhat from ./rocketh/environment.jsloadEnvironmentFromHardhat({hre}) instead of direct HRE accessethers.getContract() with env.get<Abi_Type>()env.execute()v1 package.json scripts example:
{
"scripts": {
"prepare": "hardhat typechain",
"compile": "hardhat compile",
"void:deploy": "hardhat deploy --report-gas",
"test": "cross-env HARDHAT_DEPLOY_FIXTURE=true HARDHAT_COMPILE=true mocha --bail --recursive test",
"gas": "cross-env REPORT_GAS=true hardhat test",
"coverage": "cross-env HARDHAT_DEPLOY_FIXTURE=true hardhat coverage",
"dev:node": "cross-env MINING_INTERVAL=\"3000,5000\" hardhat node --hostname 0.0.0.0",
"dev": "cross-env MINING_INTERVAL=\"3000,5000\" hardhat node --hostname 0.0.0.0 --watch",
"local:dev": "hardhat --network localhost deploy --watch",
"execute": "node ./_scripts.js run",
"deploy": "node ./_scripts.js deploy",
"verify": "node ./_scripts.js verify",
"export": "node ./_scripts.js export"
}
}
v2 package.json scripts example: (see template-ethereum-contracts/package.json)
{
"scripts": {
"prepare": "set-defaults .vscode && pnpm compile",
"local_node": "ldenv -d localhost hardhat node",
"compile": "hardhat compile",
"compile:watch": "as-soon -w src pnpm compile",
"fork:execute": "ldenv tsx @=HARDHAT_FORK=@@MODE @@",
"fork:deploy": "pnpm compile --build-profile production && ldenv hardhat @=HARDHAT_FORK=@@MODE deploy @@",
"deploy:dev": "ldenv -d localhost pnpm :deploy+export @@",
"deploy:watch": "wait-on ./generated && ldenv -m localhost pnpm as-soon -w generated -w deploy pnpm run deploy:dev @@MODE @@",
"test": "hardhat test",
"test:watch": "wait-on ./generated && as-soon -w generated -w test hardhat test --no-compile",
"typescript:watch": "as-soon -w js pnpm typescript",
"format:check": "prettier --check .",
"format": "prettier --write .",
"lint": "slippy src/**/*.sol",
"docgen": "ldenv -m default pnpm run deploy @@MODE --save-deployments true --skip-prompts ~~ pnpm rocketh-doc -e @@MODE --except-suffix _Implementation,_Proxy,_Router,_Route ~~ @@",
"execute": "ldenv -n HARDHAT_NETWORK tsx @@",
"deploy": "pnpm compile --build-profile production && ldenv hardhat --network @@MODE deploy @@",
"verify": "ldenv rocketh-verify -e @@MODE @@",
"export": "ldenv rocketh-export -e @@MODE @@",
"typescript": "tsc"
}
}
Transformation Rules for Scripts:
hardhat typechain (no longer needed, artifacts generated automatically)hardhat test (no need for mocha directly)_scripts.js patternsldenv for environment-aware commandscompile:watch using as-soondeploy:watch using as-soon and wait-onrocketh-verify, rocketh-export, rocketh-doctypescript script for TypeScript compilation@@MODE placeholder for network/environmentv1:
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
await deploy("MyContract", {
from: deployer,
args: ["Hello"],
log: true,
});
};
module.exports.tags = ["MyContract"];
v2:
import { deployScript, artifacts } from "../rocketh/deploy.js";
export default deployScript(
async ({ deploy, namedAccounts }) => {
const { deployer } = namedAccounts;
await deploy("MyContract", {
account: deployer,
artifact: artifacts.MyContract,
args: ["Hello"],
});
},
{ tags: ["MyContract"] },
);
v1:
const { deploy } = deployments;
const { deployer, tokenOwner } = await getNamedAccounts();
await deploy("Token", {
from: deployer,
args: [tokenOwner, ethers.utils.parseEther("1000000"), "My Token", "MTK"],
log: true,
});
v2:
import { deployScript, artifacts } from "../rocketh/deploy.js";
import { parseEther } from "viem";
export default deployScript(
async ({ deploy, namedAccounts }) => {
const { deployer, tokenOwner } = namedAccounts;
await deploy("Token", {
account: deployer,
artifact: artifacts.Token,
args: [tokenOwner, parseEther("1000000"), "My Token", "MTK"],
});
},
{ tags: ["Token"] },
);
v1:
await deploy("MyContract", {
from: deployer,
proxy: {
proxyContract: "OpenZeppelinTransparentProxy",
viaAdminContract: "DefaultProxyAdmin",
},
args: [initArg],
log: true,
});
v2:
import * as proxyExtension from "@rocketh/proxy";
// Add to extensions in rocketh/config.ts
const extensions = {
...deployExtension,
...proxyExtension,
};
// Then in deploy script:
await env.deployViaProxy(
"MyContract",
{
account: deployer,
artifact: artifacts.MyContract,
args: [initArg],
},
{
proxyKind: "Transparent",
},
);
v1:
const { deployer } = await getNamedAccounts();
const existing = await deployments.get("MyContract");
console.log("Contract address:", existing.address);
v2:
const MyContract = env.get<Abi_MyContract>("MyContract");
console.log("Contract address:", MyContract.address);
v1:
const MyContract = await ethers.getContract("MyContract");
await MyContract.setValue(42);
const value = await MyContract.getValue();
expect(value).to.equal(42);
v2:
import { Abi_MyContract } from "../generated/abis/MyContract.js";
const MyContract = env.get<Abi_MyContract>("MyContract");
await env.execute(MyContract, {
account: env.namedAccounts.deployer,
functionName: "setValue",
args: [42n],
});
const value = await env.read(MyContract, {
functionName: "getValue",
args: [],
});
expect(value).toEqual(42n);
v1:
const deploymentsList = await deployments.getAll();
const myDeployments = Object.values(deploymentsList);
v2:
const env = await loadAndExecuteDeploymentsFromFiles({
provider: provider,
tags: ["MyTag"],
});
v1:
const useProxy = !hre.network.live;
await deploy("MyContract", {
from: deployer,
proxy: useProxy && "postUpgrade",
args: [initArg],
});
v2:
const useProxy = !env.tags.live;
await env.deployViaProxy(
"MyContract",
{
account: deployer,
artifact: artifacts.MyContract,
args: [initArg],
},
{
proxyDisabled: !useProxy,
execute: "postUpgrade",
},
);
v1:
const networkName = hre.network.name;
if (networkName === "mainnet") {
// mainnet-specific logic
} else {
// testnet logic
}
v2:
const networkName = hre.network.name;
if (env.tags.live) {
// live network logic
} else {
// local/dev network logic
}
Cause: You still have namedAccounts in your hardhat.config.ts file.
Solution: Remove the namedAccounts section from hardhat.config.ts and move it to rocketh/config.ts:
// hardhat.config.ts - REMOVE THIS
namedAccounts: {
deployer: 0,
admin: 1,
},
// rocketh/config.ts - ADD THIS
export const config = {
accounts: {
deployer: {
default: 0,
},
admin: {
default: 1,
},
},
} as const satisfies UserConfig;
Cause: In v2, deploy is available directly in the environment, not through deployments.
Solution: Change your deploy script to use the new pattern:
Before (v1):
const {deploy} = hre.deployments;
await deploy("Contract", {...});
After (v2):
import { deployScript, artifacts } from "../rocketh/deploy.js";
export default deployScript(async ({ deploy }) => {
await deploy("Contract", {
artifact: artifacts.Contract,
account: deployer,
args: [],
});
}, {});
Cause: v2 uses account: instead of from:.
Solution: Change all from: parameters to account::
Before:
await deploy("Contract", {
from: deployer,
args: [],
});
After:
await deploy("Contract", {
account: deployer,
args: [],
});
Cause: ESM modules require explicit file extensions for local imports.
Solution: Add .js extension to all local imports:
Before:
import { deployScript, artifacts } from "../rocketh/deploy";
import { loadEnvironmentFromHardhat } from "./rocketh/environment";
After:
import { deployScript, artifacts } from "../rocketh/deploy.js";
import { loadEnvironmentFromHardhat } from "./rocketh/environment.js";
Cause: v2 uses explicit ABI types from generated artifacts.
Solution: Import ABI types and use them with env.get():
Before:
const MyContract = await ethers.getContract("MyContract");
After:
import { Abi_MyContract } from "../generated/abis/MyContract.js";
const MyContract = env.get<Abi_MyContract>("MyContract");
Cause: Incorrect import of HardhatDeploy plugin.
Solution: Use default import:
Before:
import { HardhatDeploy } from "hardhat-deploy";
After:
import HardhatDeploy from "hardhat-deploy";
Cause: v2 uses a different fixture pattern.
Solution: Create custom fixture using loadAndExecuteDeploymentsFromFiles():
import {loadAndExecuteDeploymentsFromFiles} from '../rocketh/environment.js';
import {network} from 'hardhat';
const {provider, networkHelpers} = await network.connect();
function setupFixtures(provider) {
return {
async deployAll() {
const env = await loadAndExecuteDeploymentsFromFiles({
provider: provider,
});
const MyContract = env.get<Abi_MyContract>("MyContract");
return {env, MyContract, ...};
},
};
}
const {deployAll} = setupFixtures(provider);
// In test
const {env, MyContract} = await networkHelpers.loadFixture(deployAll);
Cause: You're trying to use v2 pattern but haven't imported the correct extensions.
Solution: Ensure you have imported and set up the rocketh extensions:
// rocketh/config.ts
import * as readExecuteExtension from "@rocketh/read-execute";
const extensions = {
...deployExtension,
...readExecuteExtension, // This provides execute function
};
export { extensions };
Cause: v2 uses helper functions for network configuration.
Solution: Use the helper functions from hardhat-deploy/helpers:
import {
addForkConfiguration,
addNetworksFromEnv,
addNetworksFromKnownList,
} from "hardhat-deploy/helpers";
const config: HardhatUserConfig = {
networks: addForkConfiguration(
addNetworksFromKnownList(
addNetworksFromEnv({
hardhat: {
type: "edr-simulated",
chainType: "l1",
},
}),
),
),
};
Cause: v2 uses solidity profiles instead of compilers array.
Solution: Convert to profiles format:
Before (v1):