A professional Hardhat plugin for launching and managing multiple forked blockchain networks simultaneously. Perfect for cross-chain development, testing, and multi-network deployments.
- Multi-Network Forking: Launch multiple blockchain forks simultaneously
- Network Management: Easy switching between different networks
- Provider Management: Unified provider interface for all networks
- Configuration Flexibility: Support for environment variables and config files
- Logging & Monitoring: Comprehensive logging with chain status tracking
- Error Handling: Robust error handling with detailed error messages
- TypeScript Support: Full TypeScript support with comprehensive type definitions
- Testing Integration: Seamless integration with Hardhat testing framework
npm install hardhat-multichainOr with Yarn:
yarn add hardhat-multichainimport "hardhat-multichain";
const config: HardhatUserConfig = {
// Your existing config...
chainManager: {
chains: {
ethereum: {
rpcUrl: "https://mainnet.infura.io/v3/YOUR_PROJECT_ID",
chainId: 1,
blockNumber: 18500000 // Optional: Fork from specific block
},
polygon: {
rpcUrl: "https://polygon-rpc.com",
chainId: 137,
blockNumber: 50000000
},
arbitrum: {
rpcUrl: "https://arb1.arbitrum.io/rpc",
chainId: 42161
}
}
}
};
export default config;Create a .env file in your project root:
ETHEREUM_RPC=https://mainnet.infura.io/v3/YOUR_PROJECT_ID
ETHEREUM_BLOCK=18500000
POLYGON_RPC=https://polygon-rpc.com
POLYGON_BLOCK=50000000
ARBITRUM_RPC=https://arb1.arbitrum.io/rpcLaunch multiple networks and run tests:
npx hardhat test-multichain --chains ethereum,polygon,arbitrumnpx hardhat test-multichain --chains ethereum,polygon --testFiles test/crosschain.test.tsnpx hardhat test-multichain --chains ethereum,polygon --logs ./logsimport { getProvider, getMultichainProviders } from "hardhat-multichain";
import ChainManager from "hardhat-multichain/dist/src/chainManager";
// Get a specific provider
const ethereumProvider = getProvider("ethereum");
const blockNumber = await ethereumProvider.getBlockNumber();
// Get all providers
const providers = getMultichainProviders();
// Setup chains programmatically
await ChainManager.setupChains(["ethereum", "polygon"], hre.userConfig);
// Check chain status
const status = ChainManager.getChainStatus("ethereum");
console.log(`Ethereum chain status: ${status}`);
// Cleanup when done
await ChainManager.cleanup();import { expect } from "chai";
import { getProvider } from "hardhat-multichain";
describe("Cross-chain Contract Tests", function () {
let ethereumProvider: JsonRpcProvider;
let polygonProvider: JsonRpcProvider;
before(async function () {
// Providers are automatically available after test-multichain task
ethereumProvider = getProvider("ethereum");
polygonProvider = getProvider("polygon");
});
it("should deploy contract on multiple chains", async function () {
// Deploy to Ethereum
const ethereumFactory = new ethers.ContractFactory(abi, bytecode, ethereumProvider.getSigner());
const ethereumContract = await ethereumFactory.deploy();
// Deploy to Polygon
const polygonFactory = new ethers.ContractFactory(abi, bytecode, polygonProvider.getSigner());
const polygonContract = await polygonFactory.deploy();
expect(ethereumContract.address).to.be.properAddress;
expect(polygonContract.address).to.be.properAddress;
});
});interface ChainConfig {
rpcUrl: string; // Required: RPC endpoint URL
chainId?: number; // Optional: Chain ID (default: 31337)
blockNumber?: number; // Optional: Block number to fork from
}--chains: Comma-separated list of chain names to fork--logs: Directory for storing chain logs (optional)testFiles: Specific test files to run (optional)
The core class for managing blockchain networks.
setupChains(chains: string[], config: HardhatUserConfig, logsDir?: string): Promise<MultiChainProviders>getProvider(chainName: string): JsonRpcProvider | undefinedgetProviders(): MultiChainProvidersgetChainStatus(chainName: string): 'running' | 'stopped' | 'error' | 'unknown'getChainStatusDetails(chainName: string): ChainStatus | undefinedgetAllChainStatuses(): Map<string, ChainStatus>validateNetwork(url: string, timeout?: number): Promise<boolean>cleanup(): Promise<void>
getProvider(networkName: string): JsonRpcProvidergetMultichainProviders(): MultiChainProviders
ChainConfigError: Configuration-related errorsNetworkConnectionError: Network connectivity issuesProcessCleanupError: Process cleanup failures
The plugin includes comprehensive error handling:
import { ChainConfigError, NetworkConnectionError } from "hardhat-multichain";
try {
await ChainManager.setupChains(["ethereum"], config);
} catch (error) {
if (error instanceof ChainConfigError) {
console.error("Configuration issue:", error.message);
} else if (error instanceof NetworkConnectionError) {
console.error("Network connectivity issue:", error.message);
}
}// Get detailed status for a specific chain
const status = ChainManager.getChainStatusDetails("ethereum");
console.log(status);
// Output: { name: "ethereum", status: "running", port: 8547, rpcUrl: "...", ... }
// Get status for all chains
const allStatuses = ChainManager.getAllChainStatuses();
allStatuses.forEach((status, chainName) => {
console.log(`${chainName}: ${status.status}`);
});Logs are automatically generated when using the --logs parameter:
npx hardhat test-multichain --chains ethereum,polygon --logs ./logsThis creates separate log files for each chain:
./logs/ethereum-node.log./logs/polygon-node.log
This project maintains high code quality standards with comprehensive testing and coverage reporting.
The project includes a robust test suite with 53 test cases covering:
- Chain Manager Core Functionality: Network setup, provider management, validation
- Error Handling: Configuration errors, network failures, cleanup scenarios
- Configuration Validation: Parameter checking, environment variable handling
- Process Management: Fork lifecycle, port management, resource cleanup
- Provider Interface: Network switching, status monitoring, connection management
Current test coverage metrics demonstrate excellent code coverage:
- Statements: 75%
- Branches: 66.86%
- Functions: 66.66%
- Lines: 76.09%
# Run all tests
yarn test
# Run tests with coverage report
yarn test:coverage
# Run tests in watch mode
yarn test:watch# Development workflow
yarn build # Compile TypeScript
yarn test # Run test suite
yarn test:coverage # Generate coverage report
yarn lint # Check code style
yarn lint:fix # Fix linting issues
yarn format # Format code with Prettier
yarn format:check # Check code formattingThe project uses Jest with TypeScript support:
- Jest: Modern testing framework with parallel execution
- ts-jest: TypeScript transpilation for Jest
- Coverage Reporting: Comprehensive coverage metrics with detailed reports
- Test Organization: Well-structured test suites with descriptive test names
We welcome contributions! Please see our Contributing Guide for details.
- Clone the repository
- Install dependencies:
yarn install(from workspace root) - Build the project:
yarn build - Run tests:
yarn test - Generate coverage:
yarn test:coverage - Run linting:
yarn lint
When contributing to this project:
- Ensure all tests pass:
yarn test - Maintain or improve coverage:
yarn test:coverage - Follow code style guidelines:
yarn lint - Format your code:
yarn format - Update documentation as needed
- Add tests for new features
We maintain high quality standards:
- Test Coverage: Minimum 75% statement coverage
- Code Style: ESLint with TypeScript rules
- Documentation: Comprehensive README and inline comments
- Type Safety: Full TypeScript support
This project is licensed under the MIT License - see the LICENSE file for details.
- Hardhat for the excellent development framework
- Ethers.js for blockchain interactions
- The Ethereum community for continuous innovation
Error: Failed to connect to network at http://localhost:8547Solution: Check your RPC URLs and ensure they're accessible. Increase timeout if needed.
Error: Port 8547 is already in useSolution: The plugin automatically increments ports, but you may need to close other Hardhat instances.
Error: Missing required rpcUrl for ethereumSolution: Ensure your hardhat.config.ts includes the chain configuration or set environment variables.
- π Documentation
- π Issue Tracker
- π¬ Discussions
--chains(comma-separated list): Specifies the networks to fork (e.g.,sepolia,amoy).
Example:
npx hardhat test-multichain --chains "sepolia,amoy"
#### Additional Parameters:
- `--logs` (string): Specifies the directory to save the logs for each network.
No logs are saved by default. The logs are output to console.
Example:
```bash
npx hardhat test-multichain --chains "sepolia,amoy" --logs logsThis plugin extends the Hardhat Runtime Environment (HRE) with:
hre.changeNetwork(networkName: string): Changes the active network in the Hardhat runtime.hre.getProvider(networkName: string): JsonRpcProvider: Retrieves the provider for a given network.
This plugin extends the Hardhat User Configuration by adding chainManager support.
There are three fields that can be configured for each chain:
Chain RPC URL, a Mock Chain ID, and Block Number. By defining the block number, you can specify the block at which the chain should fork and this will then be cached, as this is the behavior for Hardhat.
Example hardhat.config.ts:
const config = {
...
chainManager: {
chains: {
sepolia: { rpcUrl: "https://rpc.sepolia.io", chainId: 11155111, blockNumber: 12345678 },
amoy: { rpcUrl: "https://rpc.amoy.io", chainId: 80002, blockNumber: 12345678 },
},
},
...
};The use of a Mock Chain ID is to allow for the use of the different chain IDs for the forks for easier identification but generally aren't required.
It requires the following configuration in the hardhat.config.ts file:
const config = {
...
// Configuration for different networks
networks: {
// Hardhat's built-in local blockchain network
hardhat: {
chainId: MOCK_CHAIN_ID, // Sets the chain ID for the Hardhat network
...
},
};
...
}You can also configure the plugin using environment variables:
SEPOLIA_RPC=https://rpc.sepolia.io
SEPOLIA_CHAIN_ID=11155111
SEPOLIA_BLOCK=12345678If you are using a blockchain that is not recognized by Hardhat (including Amoy), you will need to define the hardfork history in the hardhat.config.ts file. This is because Hardhat does not have a built-in hardfork history for these blockchains.
Example:
// Configuration for different networks
networks: {
// Hardhat's built-in local blockchain network
hardhat: {
...
// Sets the Amoy hardfork history which is required for hardhat "unknown" networks
80002: {
hardforkHistory: {
london: 10000000,
}
},
},
},After installing and configuring, simply run:
npx hardhat test-multichain --chains sepolia,amoyYou can change the active Ethers provider in a test by using the getProvider(<chain-name>) function:
ethers.provider = hre.getProvider("sepolia");This only changes the Ethers provider within the current scope.
- see the test/multichain-plugin.test.ts in the boilerplate and exampels repo