name: hardhat-deploy-migration description: Comprehensive guide for migrating projects from hardhat-deploy v1 to v2, including dependency updates, configuration restructuring, deploy script conversion, test updates, and troubleshooting
SKILL: Migrate hardhat-deploy v1 to v2
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.
Table of Contents
- Introduction
- Prerequisites Check
- Architecture Comparison
- Step-by-Step Migration
- Common Patterns & Examples
- Troubleshooting Guide
- Migration Checklist
- Advanced Topics
Introduction
Overview
hardhat-deploy v2 is a complete rewrite that requires Hardhat 3.x and introduces significant architectural changes:
- ESM Modules: v2 uses native ES modules (
import/export) instead of CommonJS - Rocketh Integration: v2 integrates with the rocketh ecosystem for deployment management
- Configuration Changes: Named accounts moved from hardhat.config.ts to rocketh/config.ts
- Plugin System: Enhanced extensibility through rocketh extensions
Key Differences Summary
| 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() |
When to Stay on v1
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.
Prerequisites Check
Before starting the migration, verify your environment meets these requirements:
Checklist
# 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"
Required Versions
- Node.js: 22 or higher
- Hardhat: 3.x or higher (specifically
^3.1.5recommended) - TypeScript: 5.x (for TypeScript projects)
Pre-Migration Assessment
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()
Architecture Comparison
v1 Architecture
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 helper
v2 Architecture
graph 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 pattern
Step-by-Step Migration
Step 1: Update Dependencies
Update 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:
- Add
"type": "module"at the top level - Update
hardhatto^3.1.4or higher - Update
hardhat-deployto^2.0.0or higher - Remove
hardhat-deploy-ethersandhardhat-deploy-tenderly - Add rocketh packages:
rocketh,@rocketh/deploy,@rocketh/read-execute,@rocketh/node,@rocketh/signer - Add optional packages:
@rocketh/proxy,@rocketh/export,@rocketh/verifier,@rocketh/doc - Add
viemfor contract interactions - Add
earlfor assertions (for node:test) - Add Hardhat 3.x plugins
Install dependencies:
pnpm install
Step 2: Restructure Configuration
2.1 Convert hardhat.config.ts
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:
- Change from
import 'hardhat-deploy'toimport HardhatDeploy from 'hardhat-deploy' - Remove
namedAccountssection entirely - Convert
solidity.compilerstosolidity.profiles - Add
pluginsarray with imported plugins - Use helper functions from
hardhat-deploy/helpersfor network configuration - Add
generateTypedArtifactsconfiguration - Remove
mochatimeout configuration (not needed in v2) - Remove
external.deploymentsconfiguration (handled differently) - Delete
utils/network.tsfile (no longer needed)
2.5 Update tsconfig.json for ESM
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:
- Update
modulefromcommonjstonode16 - Update
targetfromes5toes2022 - Update
moduleResolutionfromnodetonode16 - Add
lib: ["es2023"] - Add
skipLibCheck: true - Add
sourceMap: true,declaration: true,declarationMap: true - Change
includeto only["deploy", "generated"] - Create
scripts/tsconfig.jsonextending the main config - Create
test/tsconfig.jsonextending the main config
2.2 Create rocketh/config.ts
New 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:
- Create
rockethdirectory:mkdir rocketh - Move
namedAccountsfrom hardhat.config.ts torocketh/config.tsunderaccounts - Import required rocketh extensions
- Export extensions as unified object
- Export TypeScript types for type safety
2.3 Create rocketh/deploy.ts
New 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:
- Import config and extensions from
./config.js - Import generated artifacts from
../generated/artifacts/index.js - Setup deploy scripts using
setupDeployScriptsfrom rocketh - Export
deployScriptandartifacts
2.4 Create rocketh/environment.ts
New 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:
- Import config and extensions from
./config.js - Setup environment functions from
@rocketh/nodeandhardhat-deploy/helpers - Export
loadEnvironmentFromHardhatfor scripts - Export
loadAndExecuteDeploymentsFromFilesfor tests
Step 3: Convert Deploy Scripts
3.1 Simple Deployment
v1 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:
- Remove
HardhatRuntimeEnvironmentandDeployFunctionimports - Import
deployScriptandartifactsfrom../rocketh/deploy.js - Change
parseEtherfrometherstoviem - Wrap function in
deployScript()call - Change parameter from
(hre)to(env) - Replace
hre.getNamedAccounts()with directenv.namedAccountsaccess - Replace
hre.deployments.deploy()withenv.deploy() - Change
from:toaccount: - Add explicit
artifact:parameter - Remove
log:andautoMine:parameters (not needed in v2) - Move tags to second argument object instead of
func.tags
3.2 Proxy Deployment
v1 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:
- Change
proxy: useProxy && 'postUpgrade'toenv.deployViaProxy()call - Move proxy configuration to second argument object
- Use
proxyDisabled: !useProxyinstead of conditional proxy - Use
execute: 'postUpgrade'instead of proxy type - Replace
hre.network.livewithenv.tags.live
Step 4: Convert Tests
4.1 Test with Deployments
v1 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:
- Change test runner from
mochatonode:test - Change assertion library from
chaitoearl(or keep chai if preferred) - Import
networkfrom 'hardhat' - Create custom fixture function using
loadAndExecuteDeploymentsFromFiles() - Replace
deployments.createFixture()with custom fixture - Replace
deployments.fixture()withloadAndExecuteDeploymentsFromFiles() - Replace
ethers.getContract()withenv.get<Abi_Type>() - Import ABI types from generated artifacts:
import {Abi_SimpleERC20} from '../generated/abis/SimpleERC20.js' - Replace
getUnnamedAccounts()withenv.unnamedAccounts - Replace contract method calls with
env.execute():
- Old:
users[0].SimpleERC20.transfer(users[1].address, 1) - New:
env.execute(SimpleERC20, {account: unnamedAccounts[0], functionName: 'transfer', args: [unnamedAccounts[1], 1n]})
- Use
BigIntliterals (1n) instead of Numbers (1) for amounts - Use
networkHelpers.loadFixture()instead of direct fixture call
4.2 Test Utilities Update
v1 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:
- Remove
setupUsersandsetupUserfunctions (not needed in v2) - Create
setupFixturesfunction that returns deployment setup - Use
loadAndExecuteDeploymentsFromFiles()for deployment - Import ABI types from generated artifacts
Step 5: Update Scripts
v1 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:
- Import
loadEnvironmentFromHardhatfrom./rocketh/environment.js - Import ABI types from generated artifacts
- Use
loadEnvironmentFromHardhat({hre})instead of direct HRE access - Replace
ethers.getContract()withenv.get<Abi_Type>() - Replace contract method calls with
env.execute()
Step 6: Update package.json Scripts
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:
- Remove
hardhat typechain(no longer needed, artifacts generated automatically) - Update test command to use
hardhat test(no need for mocha directly) - Remove
_scripts.jspatterns - Use
ldenvfor environment-aware commands - Add
compile:watchusingas-soon - Add
deploy:watchusingas-soonandwait-on - Use rocketh commands:
rocketh-verify,rocketh-export,rocketh-doc - Add
typescriptscript for TypeScript compilation - Use
@@MODEplaceholder for network/environment
Common Patterns & Examples
Pattern 1: Simple Contract Deployment
v1:
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"] },
);
Pattern 2: Contract Deployment with Constructor Arguments
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"] },
);
Pattern 3: Proxy Deployment
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",
},
);
Pattern 4: Reading Existing Deployments
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);
Pattern 5: Contract Interaction in Tests
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);
Pattern 6: Getting Deployments by Tag
v1:
const deploymentsList = await deployments.getAll();
const myDeployments = Object.values(deploymentsList);
v2:
const env = await loadAndExecuteDeploymentsFromFiles({
provider: provider,
tags: ["MyTag"],
});
Pattern 7: Conditional Deployment
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",
},
);
Pattern 8: Network-Specific Configuration
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
}
Troubleshooting Guide
Error: "namedAccounts is not supported"
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;
Error: "deployments.deploy is not a function"
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: [],
});
}, {});
Error: "from is not a valid parameter"
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: [],
});
Error: Import errors with .js extensions
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";
Error: Type errors with artifacts
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");
Error: "HardhatDeploy is not a constructor"
Cause: Incorrect import of HardhatDeploy plugin.
Solution: Use default import:
Before:
import { HardhatDeploy } from "hardhat-deploy";
After:
import HardhatDeploy from "hardhat-deploy";
Error: Test fixtures not working
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);
Error: "env.execute is not a function"
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 };
Error: Network configuration not working
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",
},
}),
),
),
};
Error: solidity config not working
Cause: v2 uses solidity profiles instead of compilers array.
Solution: Convert to profiles format:
Before (v1):
solidity: {
compilers: [
{
version: '0.8.17',
settings: {
optimizer: {
enabled: true,
runs: 2000,
},
},
},
],
}
After (v2):
solidity: {
profiles: {
default: {
version: '0.8.17',
},
production: {
version: '0.8.17',
settings: {
optimizer: {
enabled: true,
runs: 999999,
},
},
},
},
}
Error: Module not found for utils/network.ts
Cause: This file is no longer needed in v2.
Solution: Delete the utils/network.ts file and use the helper functions from hardhat-deploy/helpers.
Error: Hardhat tasks not working
Cause: v2 may have different task names or requirements.
Solution: Check the task documentation and ensure you're using the correct syntax:
# Compile with production profile
npx hardhat compile --build-profile production
# Deploy to specific network
npx hardhat --network sepolia deploy
# Run tests
npx hardhat test
Error: Proxied.sol import not found
Cause: In hardhat-deploy v2, the Proxied.sol helper contract has moved from hardhat-deploy/solc_0.8/proxy/Proxied.sol to the @rocketh/proxy package.
Solution: Update the Solidity import path in your contracts:
Before (v1):
import "hardhat-deploy/solc_0.8/proxy/Proxied.sol";
After (v2):
import "@rocketh/proxy/solc_0_8/ERC1967/Proxied.sol";
Note: The path uses solc_0_8 with an underscore, not a dot.
Migration Checklist
Use this checklist to verify your migration is complete and working correctly.
Phase 1: Dependencies
- Updated package.json with
"type": "module" - Updated
hardhatto version 3.x or higher - Updated
hardhat-deployto version 2.x or higher - Removed
hardhat-deploy-ethersandhardhat-deploy-tenderly - Added
rockethpackage - Added
@rocketh/deploy,@rocketh/read-execute,@rocketh/node - Added
@rocketh/proxy(if using proxies) - Added optional packages:
@rocketh/export,@rocketh/verifier,@rocketh/doc - Added
viempackage - Added Hardhat 3.x plugins
- Updated
tsconfig.jsonfor ESM (module: "node16", moduleResolution: "node16") - Created
scripts/tsconfig.jsonwith extended configuration - Created
test/tsconfig.jsonwith extended configuration - Ran
pnpm installsuccessfully
Phase 2: Configuration
- Converted hardhat.config.ts to use
export default - Removed
namedAccountsfrom hardhat.config.ts - Imported
HardhatDeployfrom 'hardhat-deploy' - Added plugins array with all required plugins
- Converted solidity config to use profiles
- Used helper functions for network configuration
- Added
generateTypedArtifactsconfiguration - Deleted
utils/network.tsfile - Created
rocketh/config.tswith named accounts - Added extensions to rocketh/config.ts
- Created
rocketh/deploy.tswith deployScript setup - Created
rocketh/environment.tswith environment setup
Phase 3: Deploy Scripts
- Converted all deploy scripts to use
deployScriptwrapper - Changed from
(hre)to(env)parameter - Replaced
hre.getNamedAccounts()withenv.namedAccounts - Changed
from:toaccount:in all deploy calls - Added explicit
artifact:parameter to all deploy calls - Removed
log:andautoMine:parameters - Moved tags to second argument object
- Converted proxy deployments to use
env.deployViaProxy() - Updated
hre.network.livetoenv.tags.live - Imported artifacts from
../rocketh/deploy.js
Phase 4: Tests
- Changed test runner to
node:test(or kept mocha if preferred) - Updated assertion library imports
- Created custom fixture functions
- Replaced
deployments.createFixture()with custom fixtures - Imported ABI types from generated artifacts
- Replaced
ethers.getContract()withenv.get<Abi_Type>() - Replaced
getUnnamedAccounts()withenv.unnamedAccounts - Converted contract method calls to
env.execute() - Updated test utilities
- Used
networkHelpers.loadFixture()for fixtures
Phase 5: Scripts
- Imported
loadEnvironmentFromHardhatfrom rocketh/environment - Imported ABI types from generated artifacts
- Replaced direct HRE access with
loadEnvironmentFromHardhat() - Converted contract method calls to
env.execute()
Phase 6: Package.json Scripts
- Removed
hardhat typechaincommand - Updated test command to use
hardhat test - Removed
_scripts.jspatterns - Added
ldenvcommands for environment-aware operations - Added watch commands using
as-soon - Added rocketh commands:
rocketh-verify,rocketh-export,rocketh-doc - Added TypeScript compilation script
Phase 7: Verification
- Ran
npx hardhat compilesuccessfully - Ran
npx hardhat deployon local network successfully - Ran
npx hardhat testsuccessfully - Verified deployments in
deployments/directory - Verified generated artifacts in
generated/directory - Tested on test network (if applicable)
- Verified contract interactions work correctly
- Checked type errors with
tsc --noEmit
Phase 8: Documentation
- Updated project README with v2-specific instructions
- Documented any custom rocketh extensions used
- Updated CI/CD configuration if needed
- Created migration notes for team members
Advanced Topics
Fork Testing
v2 provides enhanced fork testing through the Hardhat 3.x integration.
Setup:
import { addForkConfiguration } from "hardhat-deploy/helpers";
const config: HardhatUserConfig = {
networks: addForkConfiguration({
hardhat: {
type: "edr-simulated",
chainType: "l1",
},
}),
};
Usage:
# Fork from mainnet
HARDHAT_FORK=mainnet npx hardhat test
# Fork from specific block
HARDHAT_FORK=mainnet HARDHAT_FORK_NUMBER=12345678 npx hardhat test
Environment Variable Configuration
v2 makes it easy to configure networks using environment variables:
# Set RPC URL for a network
ETH_NODE_URI_MAINNET=https://mainnet.infura.io/v3/YOUR_KEY
ETH_NODE_URI_SEPOLIA=https://sepolia.infura.io/v3/YOUR_KEY
# Set mnemonic for accounts
MNEMONIC="your twelve word mnemonic here"
# Or use network-specific mnemonics
MNEMONIC_MAINNET="production mnemonic"
MNEMONIC_SEPOLIA="testnet mnemonic"
The addNetworksFromEnv() helper will automatically create network configurations for these variables.
Customizing Rocketh Extensions
You can add custom extensions to the rocketh configuration:
// rocketh/config.ts
import * as customExtension from "./my-custom-extension";
const extensions = {
...deployExtension,
...readExecuteExtension,
...customExtension, // Add your custom extension
};
// my-custom-extension.ts
export function myCustomFunction(env, options) {
// Your custom logic here
return result;
}
Integration with CI/CD
GitHub Actions Example:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: "22"
cache: "pnpm"
- run: pnpm install
- run: pnpm compile --build-profile production
- name: Deploy to Sepolia
env:
ETH_NODE_URI_SEPOLIA: ${{ secrets.SEPOLIA_RPC_URL }}
MNEMONIC_SEPOLIA: ${{ secrets.SEPOLIA_MNEMONIC }}
run: pnpm deploy sepolia
- name: Verify Contracts
env:
ETH_NODE_URI_SEPOLIA: ${{ secrets.SEPOLIA_RPC_URL }}
MNEMONIC_SEPOLIA: ${{ secrets.SEPOLIA_MNEMONIC }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
run: pnpm verify sepolia
Multi-Contract Deployments
For complex deployments with multiple interdependent contracts:
export default deployScript(
async ({ deploy, namedAccounts }) => {
const { deployer } = namedAccounts;
// Deploy first contract
const ContractA = await deploy("ContractA", {
account: deployer,
artifact: artifacts.ContractA,
args: [],
});
// Deploy second contract with address of first
const ContractB = await deploy("ContractB", {
account: deployer,
artifact: artifacts.ContractB,
args: [ContractA.address],
});
},
{ tags: ["multi"] },
);
Deployment Verification
Use the verification extension to verify contracts on block explorers:
# Verify all deployments
pnpm verify sepolia
# Verify specific contract
pnpm rocketh-verify -e sepolia --contract MyContract
Deployment Export
Export deployments for use in other projects:
# Export deployments to JSON
pnpm export sepolia
# This creates a deployments.json file with all contract addresses and ABIs
Hot Contract Replacement (HCR)
v2 maintains the HCR feature from v1, allowing rapid development cycles:
export default deployScript(async ({ deploy, namedAccounts }) => {
const { deployer } = namedAccounts;
const useProxy = !env.tags.live;
await env.deployViaProxy(
"MyContract",
{
account: deployer,
artifact: artifacts.MyContract,
args: [],
},
{
proxyDisabled: !useProxy, // Only use proxy in dev
execute: "postUpgrade",
},
);
}, {});
With watch mode, any contract changes will automatically redeploy the proxy:
pnpm deploy:watch sepolia
TypeScript Type Safety
v2 provides enhanced TypeScript support through generated types:
import { Abi_MyContract } from "../generated/abis/MyContract.js";
// Fully typed contract access
const MyContract = env.get<Abi_MyContract>("MyContract");
// Type-safe function calls with IntelliSense
await env.execute(MyContract, {
account: deployer,
functionName: "setValue", // TypeScript will suggest available functions
args: [42n], // TypeScript will validate argument types
});
// Type-safe reads
const value = await env.read(MyContract, {
functionName: "getValue",
args: [], // TypeScript will validate return type
});
// value is typed as bigint
Handling Existing Deployments
If you have existing deployments from v1, v2 can read them directly:
- Ensure your
deployments/directory structure is maintained - The
.chainfile format is compatible - Deployment JSON files work with both versions
To migrate deployment metadata to v2 format:
# Compile contracts
pnpm compile
# Deploy to refresh metadata
pnpm hardhat deploy --network <network> --reset
Additional Resources
Official Documentation
- hardhat-deploy v2 Documentation
- Setup First Project Guide
- Migration from v1 Guide
- Hardhat 3.x Documentation
- Rocketh Documentation
Example Projects
- template-ethereum-contracts - Complete working example using v2
- Basic Demo
- Diamond Demo
- Proxies Demo
Community
Summary
Migrating from hardhat-deploy v1 to v2 involves:
- Updating dependencies to Hardhat 3.x and v2 packages
- Restructuring configuration into multiple files (hardhat.config.ts, rocketh/*.ts)
- Converting deploy scripts to use the new pattern with
deployScript - Updating tests to use the new fixture pattern
- Modernizing scripts to use
loadEnvironmentFromHardhat - Updating package.json scripts for the new workflow
The migration requires significant changes, but results in:
- Better TypeScript support
- More modular architecture
- Enhanced extensibility through rocketh
- Modern ESM modules
- Improved developer experience
Take the migration step by step, test each phase thoroughly, and refer to this guide and the template-ethereum-contracts repository for concrete examples of the transformations needed.