@diamondslab/diamonds is a TypeScript framework for deploying, upgrading, and
managing ERC-2535 Diamond Proxy
contracts. It provides a pluggable deployment-strategy system, automatic function
selector management, configuration-driven facet versioning, and ABI tooling.
Package manager: this project uses Yarn 4 (
packageManager: yarn@4.x). The examples below useyarn. npm/pnpm work for consuming the published package but the repo scripts assume Yarn.
- Full ERC-2535 Diamond Proxy Standard support
- Modular facet architecture with automated function selector management
- Selector collision detection and resolution
- State management and validation
- Strategy Pattern: pluggable deployment strategies (Local, RPC, Base, Defender)
- Version Control: configuration-driven versioning for facets and protocols
- Upgrade Automation:
DiamondDeployerdetects an existing deployment and performs an upgrade instead of a fresh deploy - Post-deployment Callbacks: run custom initialization after a cut
- Repository Pattern: pluggable persistence (file-based out of the box)
- Configuration Management: JSON configuration with Zod validation
- ABI Tooling: combined Diamond ABI generation, preview, compare, and validate
- Comprehensive Testing: unit and integration test suites
- Node.js β₯ 18
- Yarn β₯ 4 (for developing this repo; npm/pnpm work for consuming the published package)
yarn add @diamondslab/diamonds
# or
npm install @diamondslab/diamonds
# peer deps (if not already present)
yarn add --dev hardhat @nomicfoundation/hardhat-ethers ethersThe package exposes the following entry points (exports in package.json):
| Entry point | Purpose |
|---|---|
@diamondslab/diamonds |
Main entry β everything documented below is exported here |
@diamondslab/diamonds/core |
Core classes only (Diamond, DiamondDeployer, DeploymentManager, CallbackManager) |
@diamondslab/diamonds/dist/* |
Back-compat deep imports, e.g. @diamondslab/diamonds/dist/repositories/FileDeploymentRepository |
@diamondslab/diamonds/package.json |
Package metadata (for tooling) |
Prefer the root entry point for new code; the dist/* subpaths exist so older
deep imports keep working. Source maps and declaration maps are not shipped.
Everything is exported from the package root (@diamondslab/diamonds):
import {
Diamond,
DiamondDeployer,
FileDeploymentRepository,
LocalDeploymentStrategy,
} from "@diamondslab/diamonds";
import { ethers } from "hardhat";
// Diamond configuration
const config = {
diamondName: "MyDiamond",
networkName: "localhost",
chainId: 31337,
deploymentsPath: "./diamonds",
contractsPath: "./contracts",
};
// Set up diamond + deployment components
const repository = new FileDeploymentRepository(config);
const diamond = new Diamond(config, repository);
// Provide a provider and signer
diamond.setProvider(ethers.provider);
const [signer] = await ethers.getSigners();
diamond.setSigner(signer);
// Deploy using the local strategy (verbose logging on)
const strategy = new LocalDeploymentStrategy(true);
const deployer = new DiamondDeployer(diamond, strategy);
// Deploys if new, upgrades if already deployed
await deployer.deployDiamond();src/
βββ core/ # Core classes
β βββ Diamond.ts # Diamond state + selector registry
β βββ DiamondDeployer.ts # Deploy/upgrade orchestration
β βββ DeploymentManager.ts # Deployment lifecycle
β βββ CallbackManager.ts # Post-deployment callbacks
βββ strategies/ # Deployment strategies
β βββ DeploymentStrategy.ts # Strategy interface
β βββ BaseDeploymentStrategy.ts
β βββ LocalDeploymentStrategy.ts
β βββ RPCDeploymentStrategy.ts
β βββ OZDefenderDeploymentStrategy.ts # legacy (see note below)
βββ repositories/ # Data persistence
β βββ DeploymentRepository.ts
β βββ FileDeploymentRepository.ts
β βββ jsonFileHandler.ts
βββ schemas/ # Zod validation schemas
βββ types/ # TypeScript definitions
βββ utils/ # Utilities (ABI generation, loupe, selectors, β¦)
βββ cli/ # `diamond-abi` CLI
Create a diamond configuration file (e.g. myDiamond.config.json):
{
"protocolVersion": 1.0,
"protocolInitFacet": "MyProtocolFacet",
"facets": {
"DiamondCutFacet": {
"priority": 10,
"versions": { "1.0": {} }
},
"DiamondLoupeFacet": {
"priority": 20,
"versions": { "1.0": {} }
},
"MyCustomFacet": {
"priority": 30,
"versions": {
"1.0": {
"deployInit": "initialize()",
"upgradeInit": "upgradeToV1()",
"callbacks": ["postDeployCallback"],
"deployInclude": ["0x12345678"],
"deployExclude": ["0x87654321"]
}
}
}
}
}# Network Configuration
NETWORK_NAME=sepolia
CHAIN_ID=11155111
RPC_URL=https://sepolia.infura.io/v3/YOUR_KEY
# OpenZeppelin Defender (only for the legacy Defender strategy)
DEFENDER_API_KEY=your_api_key
DEFENDER_API_SECRET=your_api_secret
DEFENDER_RELAYER_ADDRESS=0x...
DEFENDER_SAFE_ADDRESS=0x...
# Deployment Options
VERBOSE_DEPLOYMENT=trueAll strategies extend BaseDeploymentStrategy and accept an optional verbose
flag. Pass a strategy instance to DiamondDeployer.
For development and testing against a local/forked node:
import { LocalDeploymentStrategy } from "@diamondslab/diamonds";
const strategy = new LocalDeploymentStrategy(true); // verboseFor deploying through a configured RPC endpoint / signer:
import { RPCDeploymentStrategy } from "@diamondslab/diamonds";
const strategy =
new RPCDeploymentStrategy(/* see RPCDeploymentStrategy options */);Implement your own by extending BaseDeploymentStrategy and overriding the
protected task hooks:
import { BaseDeploymentStrategy, Diamond } from "@diamondslab/diamonds";
export class CustomDeploymentStrategy extends BaseDeploymentStrategy {
protected async deployDiamondTasks(diamond: Diamond): Promise<void> {
// Custom deployment logic
}
protected async performDiamondCutTasks(diamond: Diamond): Promise<void> {
// Custom diamond cut logic
}
}
β οΈ Deprecated / legacy.OZDefenderDeploymentStrategyis being phased out and is not recommended for new work. It remains exported for backward compatibility. PreferLocalDeploymentStrategy/RPCDeploymentStrategyor a custom strategy.
import { OZDefenderDeploymentStrategy } from "@diamondslab/diamonds";
const strategy = new OZDefenderDeploymentStrategy(
process.env.DEFENDER_API_KEY!,
process.env.DEFENDER_API_SECRET!,
process.env.DEFENDER_RELAYER_ADDRESS!,
false, // auto-approve
process.env.DEFENDER_SAFE_ADDRESS!,
"Safe",
);Facets are versioned in the configuration; upgrades declare which prior versions they apply from:
{
"facets": {
"MyFacet": {
"priority": 100,
"versions": {
"1.0": { "deployInit": "initialize()", "upgradeInit": "upgradeToV1()" },
"2.0": {
"deployInit": "initialize()",
"upgradeInit": "upgradeToV2()",
"fromVersions": [1.0]
}
}
}
}
}Automatic selector management with collision detection:
- Priority-based resolution: higher-priority facets take precedence
- Include/Exclude lists: fine-grained selector control (
deployInclude/deployExclude) - Collision detection: automatic detection and resolution of selector conflicts
- Orphaned selector prevention: validation guards against bad cuts
// In your callbacks directory
export async function postDeployCallback(args: CallbackArgs) {
const { diamond } = args;
console.log(`Post-deployment callback for ${diamond.diamondName}`);
// Custom post-deployment logic
}This package ships a diamond-abi CLI for working with the combined Diamond ABI.
# Generate the combined ABI for a deployed diamond (requires a Hardhat project)
npx diamond-abi generate --diamond MyDiamond --network localhost
# Preview the ABI changes implied by planned cuts (requires a Hardhat project)
npx diamond-abi preview --diamond MyDiamond --verbose
# Compare two ABI files (no chain / Hardhat needed)
npx diamond-abi compare old-abi.json new-abi.json
# Validate an ABI artifact file (no chain / Hardhat needed)
npx diamond-abi validate diamond-abi.json
generateandpreviewconnect to a chain and load your project's Hardhat config (run them inside a Hardhat project).compareandvalidateoperate on ABI JSON files and run standalone. Runnpx diamond-abi <command> --helpfor all options.
The same combined ABI can also be produced programmatically via
generateDiamondAbi / previewDiamondAbi from @diamondslab/diamonds.
import {
Diamond,
DiamondDeployer,
FileDeploymentRepository,
LocalDeploymentStrategy,
} from "@diamondslab/diamonds";
import { ethers } from "hardhat";
async function deployBasicDiamond() {
const config = {
diamondName: "BasicDiamond",
networkName: "localhost",
chainId: 31337,
deploymentsPath: "./diamonds",
contractsPath: "./contracts",
};
const repository = new FileDeploymentRepository(config);
const diamond = new Diamond(config, repository);
diamond.setProvider(ethers.provider);
const [signer] = await ethers.getSigners();
diamond.setSigner(signer);
const strategy = new LocalDeploymentStrategy();
const deployer = new DiamondDeployer(diamond, strategy);
await deployer.deployDiamond();
console.log("Diamond deployed successfully!");
}async function performUpgrade(
diamond: Diamond,
repository: FileDeploymentRepository,
) {
// Update configuration for the new version
const config = repository.loadDeployConfig();
config.protocolVersion = 2.0;
// Add a new facet
config.facets.NewFeatureFacet = {
priority: 150,
versions: {
"2.0": { deployInit: "initialize()", callbacks: ["setupNewFeature"] },
},
};
// Upgrade an existing facet
config.facets.ExistingFacet.versions["2.0"] = {
upgradeInit: "upgradeToV2()",
fromVersions: [1.0],
};
repository.saveDeployConfig(config);
// DiamondDeployer detects the existing deployment and performs the upgrade
const deployer = new DiamondDeployer(diamond, new LocalDeploymentStrategy());
await deployer.deployDiamond();
}import { getSighash } from "@diamondslab/diamonds";
const selector = getSighash("transfer(address,uint256)");
console.log("Selector:", selector);
console.log("Registered:", diamond.isFunctionSelectorRegistered(selector));import { diffDeployedFacets } from "@diamondslab/diamonds";
import { ethers } from "hardhat";
const deployedData = diamond.getDeployedDiamondData();
console.log("Diamond address:", deployedData.DiamondAddress);
console.log("Deployed facets:", Object.keys(deployedData.DeployedFacets ?? {}));
// Returns true when on-chain facets match the local deployment record
const isConsistent = await diffDeployedFacets(
deployedData,
ethers.provider,
true,
);Run from the package directory:
yarn install # install dependencies (Yarn 4)
yarn build # compile TypeScript to dist/
yarn test # run the Hardhat test suite
yarn test:unit # unit tests only
yarn test:integration # integration tests only
yarn test:coverage # tests with coverage
yarn lint # lint- Use multi-signature wallets for mainnet deployments.
- Test upgrades on a testnet/fork first.
- Verify contracts on the relevant block explorer.
- Apply role-based access control to privileged diamond functions.
- Never commit secrets β deployment records may contain addresses/keys and are gitignored.
Contributions are welcome. See CONTRIBUTING.md for the full workflow, and open an issue or pull request on GitHub.
git clone https://github.com/DiamondsLab/diamonds.git
cd diamonds
yarn install
yarn test
yarn build
yarn lint- Follow the existing TypeScript style
- Use Conventional Commits
- Add tests for new functionality
- Update documentation for new features
MIT β see the LICENSE file.
- ERC-2535 Diamond Standard authors
- Hardhat development framework
- OpenZeppelin for security tooling
- The Ethereum community
- Documentation: see the
docs/directory - Issues: github.com/DiamondsLab/diamonds/issues
Built with β€οΈ for the Ethereum ecosystem by DiamondsLab