A Hardhat Extension for Diamonds node module, Tools for deploying and interfacing with ERC-2535 Diamond Proxies in Hardhat projects.
@diamondslab/hardhat-diamonds is a Hardhat plugin that provides scaffolding for working with the ERC-2535 Diamond Proxy Standard. This plugin extends Hardhat with configuration management and utilities specifically designed for Diamond smart contract development, deployment, and upgrades.
This plugin works in conjunction with the @diamondslab/diamonds module to provide a complete Diamond development toolkit.
- Diamond Configuration Management: Centralized configuration for multiple Diamond contracts
- Diamond ABI Generation: Generate combined ABIs from Diamond proxy configurations
- TypeScript Type Generation: Automatic TypeChain integration for type-safe contract interactions
- Type-Safe Integration: Full TypeScript support with proper type definitions
- Hardhat Tasks: Built-in tasks for Diamond operations and development workflow
- Hardhat Integration: Seamless integration with existing Hardhat workflows
- ERC-2535 Compliance: Built specifically for the Diamond Proxy Standard
Install the plugin and its peer dependency:
npm install --save-dev @diamondslab/hardhat-diamonds @diamondslab/diamondsOr with yarn:
yarn add --dev @diamondslab/hardhat-diamonds @diamondslab/diamondsAdd the plugin to your hardhat.config.ts or hardhat.config.js:
import "@diamondslab/hardhat-diamonds";
// Rest of your Hardhat configurationThe package exposes these entry points (via the package.json exports map):
| Import | Purpose |
|---|---|
@diamondslab/hardhat-diamonds |
Main plugin — registers hre.diamonds config and the Diamond tasks. Import once in hardhat.config.ts. |
@diamondslab/hardhat-diamonds/lib |
Programmatic library (no task registration): LocalDiamondDeployer, generateDiamondAbi, generateTypeChainTypes, loadDiamondContract, … |
@diamondslab/hardhat-diamonds/utils |
Circular-dep-safe deployer utils: LocalDiamondDeployer, LocalDiamondDeployerConfig, loadDiamondContract. |
The
/utilsand/libentry points are also available at the legacy/dist/utilsand/dist/libpaths for backward compatibility. ImportLocalDiamondDeployerfrom/utils(not the package root) to avoid the HH9 "Error while loading Hardhat's configuration" error.
Configure your Diamond contracts in your Hardhat config:
import { HardhatUserConfig } from "hardhat/config";
import "@diamondslab/hardhat-diamonds";
const config: HardhatUserConfig = {
// ... other Hardhat configuration
diamonds: {
paths: {
MyDiamond: {
// Diamond-specific configuration
// Refer to diamonds documentation for available options
},
AnotherDiamond: {
// Configuration for another Diamond contract
},
},
},
};
export default config;Once configured, the plugin extends the Hardhat Runtime Environment with a diamonds object and provides built-in tasks for Diamond operations.
The plugin provides several built-in tasks for Diamond contract operations:
Generate a combined ABI for a Diamond proxy contract from its facet configurations.
# Basic usage
npx hardhat diamond:generate-abi --diamond-name ExampleDiamond
# With custom output directory
npx hardhat diamond:generate-abi --diamond-name MyDiamond --output-dir ./custom-abi
# With verbose logging
npx hardhat diamond:generate-abi --diamond-name MyDiamond --verbose
# For specific network
npx hardhat diamond:generate-abi --diamond-name MyDiamond --network sepoliaParameters:
--diamond-name(required): Name of the diamond to generate ABI for--output-dir(optional): Output directory for generated ABI files (default:./diamond-abi)--verbose(flag): Enable verbose logging--validate-selectors(flag): Validate function selector uniqueness (default: enabled)--include-source-info(flag): Include compilation metadata in ABI (default: enabled)--network(optional): Target network (uses current network if not specified)
Generate both a Diamond ABI and TypeScript types using TypeChain.
# Basic usage
npx hardhat diamond:generate-abi-typechain --diamond-name ExampleDiamond
# With custom TypeChain target
npx hardhat diamond:generate-abi-typechain --diamond-name MyDiamond --typechain-target ethers-v6
# With custom output directories
npx hardhat diamond:generate-abi-typechain \
--diamond-name MyDiamond \
--output-dir ./custom-abi \
--typechain-out-dir ./custom-types
# Full verbose output
npx hardhat diamond:generate-abi-typechain --diamond-name MyDiamond --verboseParameters:
--diamond-name(required): Name of the diamond to generate ABI and types for--output-dir(optional): Output directory for generated ABI files (default:./diamond-abi)--typechain-target(optional): TypeChain target (default:ethers-v6)- Supported targets:
ethers-v6,ethers-v5,web3-v1,truffle-v5
- Supported targets:
--typechain-out-dir(optional): TypeChain output directory (default:./diamond-typechain-types)--verbose(flag): Enable verbose logging--validate-selectors(flag): Validate function selector uniqueness--include-source-info(flag): Include compilation metadata in ABI--network(optional): Target network
You can also use the Diamond functionality programmatically in your scripts and tasks:
import { task } from "hardhat/config";
import { generateDiamondAbi } from "@diamondslab/hardhat-diamonds";
task("diamond-info", "Get diamond configuration")
.addParam("name", "Diamond name")
.setAction(async (taskArgs, hre) => {
// Get diamond configuration
const config = hre.diamonds.getDiamondConfig(taskArgs.name);
console.log("Diamond configuration:", config);
// Generate ABI programmatically
const abiResult = await generateDiamondAbi(hre, {
diamondName: taskArgs.name,
verbose: true,
});
console.log("Generated ABI with", abiResult.stats.totalFunctions, "functions");
});The LocalDiamondDeployer class provides a singleton deployer for Diamond contracts on local Hardhat networks and forks. This utility is exported separately from @diamondslab/hardhat-diamonds/dist/utils to avoid circular dependency issues during Hardhat configuration loading.
The LocalDiamondDeployer is included with the @diamondslab/hardhat-diamonds package:
import { LocalDiamondDeployer, LocalDiamondDeployerConfig } from '@diamondslab/hardhat-diamonds/dist/utils';
import hre from 'hardhat';Important: Import from
/dist/utilsrather than the main package to avoid the HH9 "Error while loading Hardhat's configuration" error.
The deployer requires a configuration object with the following properties:
interface LocalDiamondDeployerConfig {
diamondName: string; // Name of the diamond contract
networkName: string; // Network name (e.g., 'hardhat', 'localhost')
provider: JsonRpcProvider; // Ethers provider instance
chainId: bigint; // Chain ID for the network
writeDeployedDiamondData?: boolean; // Whether to persist deployment data (default: false)
configFilePath: string; // Path to diamond config file
signer?: Signer; // Optional ethers Signer (defaults to the first Hardhat signer)
}Note:
signeris typed as an ethersSigner(as of1.2.0), so an impersonated signer — e.g. fromimpersonateAndFundSignerduring fork upgrades — is accepted. When omitted, the deployer uses the first Hardhat signer.
import { Diamond } from '@diamondslab/diamonds';
import { LocalDiamondDeployer, LocalDiamondDeployerConfig } from '@diamondslab/hardhat-diamonds/dist/utils';
import hre from 'hardhat';
// Create configuration
const config: LocalDiamondDeployerConfig = {
diamondName: 'ExampleDiamond',
networkName: 'hardhat',
provider: hre.ethers.provider,
chainId: (await hre.ethers.provider.getNetwork()).chainId,
writeDeployedDiamondData: false,
configFilePath: 'diamonds/ExampleDiamond/examplediamond.config.json',
};
// Get singleton instance (pass hre as first parameter)
const deployer = await LocalDiamondDeployer.getInstance(hre, config);
// Enable verbose logging (optional)
await deployer.setVerbose(true);
// Deploy or retrieve existing diamond
const diamond: Diamond = await deployer.getDiamondDeployed();
// Get deployment data
const deployedData = diamond.getDeployedDiamondData();
console.log('Diamond deployed at:', deployedData.DiamondAddress);getInstance(hre: HardhatRuntimeEnvironment, config: LocalDiamondDeployerConfig): Promise<LocalDiamondDeployer>
Retrieve or create a singleton instance of the deployer.
Parameters:
hre: Hardhat Runtime Environment instanceconfig: Configuration object for the deployer
Returns: Promise resolving to the LocalDiamondDeployer instance
Example:
const deployer = await LocalDiamondDeployer.getInstance(hre, config);Enable or disable verbose logging during deployment.
Parameters:
verbose: Boolean flag to enable/disable verbose output
Example:
await deployer.setVerbose(true);Deploy (or retrieve if already deployed) the Diamond contract.
Returns: Promise resolving to a Diamond instance
Example:
const diamond = await deployer.getDiamondDeployed();import { Diamond } from '@diamondslab/diamonds';
import { LocalDiamondDeployer, LocalDiamondDeployerConfig } from '@diamondslab/hardhat-diamonds/dist/utils';
import { expect } from 'chai';
import hre from 'hardhat';
describe('Diamond Tests', function() {
let diamond: Diamond;
let deployer: LocalDiamondDeployer;
beforeEach(async function() {
const config: LocalDiamondDeployerConfig = {
diamondName: 'ExampleDiamond',
networkName: 'hardhat',
provider: hre.ethers.provider,
chainId: (await hre.ethers.provider.getNetwork()).chainId,
writeDeployedDiamondData: false,
configFilePath: 'diamonds/ExampleDiamond/examplediamond.config.json',
};
deployer = await LocalDiamondDeployer.getInstance(hre, config);
diamond = await deployer.getDiamondDeployed();
});
it('should deploy diamond successfully', async function() {
const deployedData = diamond.getDeployedDiamondData();
expect(deployedData.DiamondAddress).to.be.properAddress;
});
});The LocalDiamondDeployer uses a singleton pattern to ensure only one instance exists per configuration. Subsequent calls with the same configuration will return the existing instance:
const deployer1 = await LocalDiamondDeployer.getInstance(hre, config);
const deployer2 = await LocalDiamondDeployer.getInstance(hre, config);
// deployer1 === deployer2 (same instance)Why /dist/utils import?
The LocalDiamondDeployer is exported from a separate utils module to prevent Hardhat's HH9 error. When hardhat.config.ts imports the main @diamondslab/hardhat-diamonds package, it should not trigger loading of classes that depend on the Hardhat Runtime Environment. By separating the deployer into /dist/utils, it can be imported only when needed in scripts and tests.
Why pass hre as parameter?
Following the DiamondAbiGenerator pattern, LocalDiamondDeployer receives the Hardhat Runtime Environment as a constructor parameter instead of importing it at the module level. This prevents circular dependencies and allows the class to work correctly when the Hardhat configuration is being loaded.
After generating types with TypeChain, you can use them in your frontend applications:
// Import generated types
import { ExampleDiamond__factory } from "./diamond-typechain-types";
import { ethers } from "ethers";
// Connect to your Diamond contract with full type safety
const provider = new ethers.JsonRpcProvider("https://your-rpc-url");
const signer = provider.getSigner();
// Use the factory to connect to deployed Diamond
const diamond = ExampleDiamond__factory.connect("0x...", signer);
// All function calls are now type-safe
const result = await diamond.someFunction();If you're currently using the standalone scripts, here's how to migrate to the new Hardhat tasks:
Before:
npx ts-node scripts/diamond-abi-generator.ts --diamond-name ExampleDiamondAfter:
npx hardhat diamond:generate-abi --diamond-name ExampleDiamondBefore:
npx ts-node scripts/generate-diamond-abi-with-typechain.ts --diamond-name ExampleDiamondAfter:
npx hardhat diamond:generate-abi-typechain --diamond-name ExampleDiamond- Better Integration: Tasks are fully integrated with Hardhat's runtime environment
- Improved Error Handling: Professional error messages and validation
- Network Support: Automatic network configuration from Hardhat config
- Progress Feedback: Real-time progress indicators for long operations
- Consistent CLI: Follows Hardhat's parameter and flag conventions
- Type Safety: Full TypeScript support with proper type checking
- Parameter names follow Hardhat conventions (kebab-case instead of camelCase)
- Default output directories have changed:
- ABI output:
./diamond-abi(was./artifacts/diamond-abi) - TypeChain output:
./diamond-typechain-types(was./typechain-types)
- ABI output:
- Requires plugin installation and configuration in
hardhat.config.ts
Update your hardhat.config.ts to include the plugin:
import { HardhatUserConfig } from "hardhat/config";
import "@diamondslab/hardhat-diamonds";
const config: HardhatUserConfig = {
// ... your existing config
};
export default config;Make sure your diamond configuration file exists in the expected location:
diamonds/
YourDiamondName/
yourdiamondname.config.json
The diamond name is case-sensitive and should match the directory name.
Install TypeChain and the required target package:
# For ethers-v6 (default)
yarn add --dev typechain @typechain/ethers-v6
# For ethers-v5
yarn add --dev typechain @typechain/ethers-v5
# For web3-v1
yarn add --dev typechain @typechain/web3-v1Ensure your diamond configuration includes valid facet contracts:
{
"facets": [
{
"name": "ExampleOwnershipFacet",
"contract": "ExampleOwnershipFacet"
}
]
}This indicates duplicate function selectors across facets. Use --verbose to see details:
npx hardhat diamond:generate-abi --diamond-name MyDiamond --verboseReview your facet contracts to ensure unique function signatures.
Ensure you have write permissions to the output directory, or specify a different one:
npx hardhat diamond:generate-abi --diamond-name MyDiamond --output-dir ./custom-path- Use
--no-validate-selectorsto skip validation for faster generation during development - Specify
--typechain-targetexplicitly to avoid auto-detection overhead - Use absolute paths for output directories to avoid path resolution issues
- Use
--verboseflag for detailed logging - Check the generated files in
diamond-abi/for ABI structure - Review TypeChain documentation for target-specific usage patterns
The main interface for accessing Diamond functionality.
Retrieves the configuration for a specific Diamond contract.
Parameters:
diamondName: The name of the Diamond as defined in the configuration
Returns:
DiamondPathsConfig: The configuration object for the specified Diamond
Throws:
Error: If the Diamond configuration is not found
Example:
const diamondConfig = hre.diamonds.getDiamondConfig("MyDiamond");@diamondslab/hardhat-diamonds/
├── src/
│ ├── index.ts # Main plugin entry point (registers config + tasks)
│ ├── DiamondsConfig.ts # hre.diamonds configuration management class
│ ├── type-extensions.ts # Hardhat type extensions
│ ├── utils.ts # Circular-dep-safe entry (LocalDiamondDeployer, loadDiamondContract)
│ ├── interfaces/ # Shared interfaces
│ ├── lib/ # Programmatic library (no task registration)
│ │ ├── LocalDiamondDeployer.ts
│ │ ├── DiamondAbiGenerator.ts
│ │ ├── TypeChainIntegration.ts
│ │ ├── LoadDiamondArtifact.ts
│ │ └── index.ts
│ └── tasks/ # Hardhat task definitions
│ ├── diamond-abi.ts
│ ├── diamond-abi-typechain.ts
│ ├── shared/ # Task helpers / validation / options
│ └── index.ts
├── test/ # Test files
├── docs/ # VERSIONING.md, TESTING.md, …
├── dist/ # Compiled output (published)
├── CHANGELOG.md
├── LICENSE
└── package.json
- Node.js >= 18
- TypeScript 5.x
- Hardhat ^2.26
- Yarn >= 4 (the package uses
yarn@4.10.3)
yarn buildyarn testyarn lint
yarn lint:fixhardhat: ^2.0.0ethers: ^6.0.0@diamondslab/diamonds: ^1.0.0
- TypeScript 5.x
- Mocha + Chai for testing
- ESLint (flat config) and Prettier for code quality
- CHANGELOG.md — release history (Keep a Changelog)
- docs/VERSIONING.md — SemVer + Conventional Commits policy
- docs/TESTING.md — testing guide
@diamondslab/diamonds: Core Diamond utilities and types- ERC-2535 Diamond Standard: The official Diamond standard specification
Contributions are welcome! Please read CONTRIBUTING.md for the development workflow, commit conventions (Conventional Commits, see docs/VERSIONING.md), and the PR checklist. By participating you agree to the Code of Conduct.
- Fork the repository and create a feature branch (
git checkout -b feature/your-change) - Keep
yarn build,yarn test, andyarn lintgreen; updateCHANGELOG.mdunder[Unreleased] - Commit using Conventional Commits and open a Pull Request against
main
To report a security vulnerability, please follow SECURITY.md — do not open a public issue for security problems.
This project is licensed under the MIT License - see the LICENSE file for details.
For questions and support, please refer to:
- Am0rfu5
Keywords: ethereum, smart-contracts, hardhat, hardhat-plugin, diamond, diamond-proxy, diamond-upgradeable, blockchain, ERC-2535