Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

158 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Diamonds Module

npm version License: MIT TypeScript Hardhat

@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 use yarn. npm/pnpm work for consuming the published package but the repo scripts assume Yarn.

✨ Key Features

πŸ—οΈ Complete Diamond Implementation

  • Full ERC-2535 Diamond Proxy Standard support
  • Modular facet architecture with automated function selector management
  • Selector collision detection and resolution
  • State management and validation

πŸ”„ Deployment Management

  • Strategy Pattern: pluggable deployment strategies (Local, RPC, Base, Defender)
  • Version Control: configuration-driven versioning for facets and protocols
  • Upgrade Automation: DiamondDeployer detects an existing deployment and performs an upgrade instead of a fresh deploy
  • Post-deployment Callbacks: run custom initialization after a cut

🏭 Production Ready

  • 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

πŸš€ Quick Start

Prerequisites

  • Node.js β‰₯ 18
  • Yarn β‰₯ 4 (for developing this repo; npm/pnpm work for consuming the published package)

Installation

yarn add @diamondslab/diamonds
# or
npm install @diamondslab/diamonds

# peer deps (if not already present)
yarn add --dev hardhat @nomicfoundation/hardhat-ethers ethers

Entry points

The 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.

Basic Usage

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();

πŸ“‹ Project Structure

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

πŸ”§ Configuration

Diamond Configuration

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"]
        }
      }
    }
  }
}

Environment Configuration

# 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=true

πŸ”„ Deployment Strategies

All strategies extend BaseDeploymentStrategy and accept an optional verbose flag. Pass a strategy instance to DiamondDeployer.

Local Strategy

For development and testing against a local/forked node:

import { LocalDeploymentStrategy } from "@diamondslab/diamonds";

const strategy = new LocalDeploymentStrategy(true); // verbose

RPC Strategy

For deploying through a configured RPC endpoint / signer:

import { RPCDeploymentStrategy } from "@diamondslab/diamonds";

const strategy =
  new RPCDeploymentStrategy(/* see RPCDeploymentStrategy options */);

Custom Strategy

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
  }
}

OpenZeppelin Defender Strategy (legacy)

⚠️ Deprecated / legacy. OZDefenderDeploymentStrategy is being phased out and is not recommended for new work. It remains exported for backward compatibility. Prefer LocalDeploymentStrategy / RPCDeploymentStrategy or 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",
);

πŸ“Š Advanced Features

Version Management

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]
        }
      }
    }
  }
}

Function Selector Management

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

Post-Deployment Callbacks

// 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
}

πŸ’Ž Diamond ABI Tooling

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

generate and preview connect to a chain and load your project's Hardhat config (run them inside a Hardhat project). compare and validate operate on ABI JSON files and run standalone. Run npx diamond-abi <command> --help for all options.

The same combined ABI can also be produced programmatically via generateDiamondAbi / previewDiamondAbi from @diamondslab/diamonds.

πŸ“– Examples

Basic Diamond Deployment

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!");
}

Multi-Facet Upgrade

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();
}

πŸ” Monitoring and Debugging

Inspect Function Selectors

import { getSighash } from "@diamondslab/diamonds";

const selector = getSighash("transfer(address,uint256)");
console.log("Selector:", selector);

console.log("Registered:", diamond.isFunctionSelectorRegistered(selector));

Compare On-Chain vs. Local State

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,
);

πŸ§ͺ Testing & Development

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

πŸ” Security Considerations

  1. Use multi-signature wallets for mainnet deployments.
  2. Test upgrades on a testnet/fork first.
  3. Verify contracts on the relevant block explorer.
  4. Apply role-based access control to privileged diamond functions.
  5. Never commit secrets β€” deployment records may contain addresses/keys and are gitignored.

🀝 Contributing

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

Coding Standards

  • Follow the existing TypeScript style
  • Use Conventional Commits
  • Add tests for new functionality
  • Update documentation for new features

πŸ“„ License

MIT β€” see the LICENSE file.

πŸ™ Acknowledgments

πŸ“ž Support


Built with ❀️ for the Ethereum ecosystem by DiamondsLab

About

Core of the ERC-2535 Diamond Proxy Standard Mangement System

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages