Diamond Factory Packages (DFPkg)
A DFPkg is a contract that implements IDiamondFactoryPackage. It packages a set of facet references and the logic required to initialize a new Diamond proxy.
Value
- Facets are deployed separately and referenced by address.
- A package declares exactly which facets and which functions are installed.
- Initialization (
initAccount) and post-deployment hooks are executed inside the deployment transaction via delegatecall. - The same package + arguments always produce a proxy at the same address.
Because facets are immutable references inside the package, every proxy created from the package shares the identical logic implementations.
The DiamondPackageCallBackFactory that executes DFPkg deployments (via pkg.deploy(diamondFactory, args)) is deployed once and reused across chains and projects. You obtain it from your Create3Factory; you do not deploy a new callback factory for each chain or DFPkg use.
Interface
interface IDiamondFactoryPackage {
struct DiamondConfig {
IDiamond.FacetCut[] facetCuts;
bytes4[] interfaces;
}
function packageName() external view returns (string memory);
function facetCuts() external view returns (IDiamond.FacetCut[] memory);
function diamondConfig() external view returns (DiamondConfig memory);
function calcSalt(bytes memory pkgArgs) external view returns (bytes32);
function initAccount(bytes memory initArgs) external;
function postDeploy(address account) external returns (bool);
// ...
}
Typical Package Structure
Important: PkgInit and PkgArgs must be defined inside the I*DFPkg interface (not the contract implementation). This allows type-safe references like IMyDFPkg.PkgInit from FactoryServices and callers.
See the crane-architecture skill references/dfpkg-pattern.md for the full rule and the frequent error of defining them on the contract.
interface IERC20DFPkg {
struct PkgInit {
IFacet erc20Facet; // constructor argument
}
struct PkgArgs {
string name;
string symbol;
uint8 decimals;
uint256 totalSupply;
address recipient;
}
}
contract ERC20DFPkg is IERC20DFPkg, IDiamondFactoryPackage {
IFacet immutable ERC20_FACET;
constructor(PkgInit memory pkgInit) {
ERC20_FACET = pkgInit.erc20Facet;
}
function facetCuts() public view returns (IDiamond.FacetCut[] memory cuts) {
cuts = new IDiamond.FacetCut[](1);
cuts[0] = IDiamond.FacetCut({
facetAddress: address(ERC20_FACET),
action: IDiamond.FacetCutAction.Add,
functionSelectors: ERC20_FACET.facetFuncs()
});
}
function initAccount(bytes memory initArgs) external {
// decode pkgArgs and call ERC20Repo._initialize etc.
}
function calcSalt(bytes memory pkgArgs) public pure returns (bytes32) {
// usually keccak256(abi.encode(pkgArgs)) or similar
}
}
Deployment Flow (via DiamondPackageCallBackFactory)
sequenceDiagram
participant User
participant Factory as DiamondPackageCallBackFactory
participant Pkg as IDiamondFactoryPackage
participant Proxy as MinimalDiamondCallBackProxy
User->>Factory: deploy(pkg, pkgArgs)
Factory->>Pkg: calcSalt(pkgArgs) [delegatecall]
Pkg-->>Factory: salt
Factory->>Factory: compute CREATE2 address
alt proxy already exists
Factory-->>User: return existing proxy
else proxy does not exist
Factory->>Pkg: updatePkg / store context
Factory->>Proxy: CREATE2 deploy (MinimalDiamondCallBackProxy)
Proxy->>Factory: initAccount callback (delegatecall)
Factory->>Pkg: diamondConfig() + initAccount(args) [delegatecall on proxy]
Pkg->>Proxy: perform storage initialization
Factory->>Pkg: postDeploy(proxy)
Pkg-->>Factory: success
Factory->>Proxy: remove temporary post-deploy hook (if any)
Factory-->>User: proxy address
end
The detailed original sequence is also present as NatSpec in contracts/factories/diamondPkg/IDiamondFactoryPackage.sol.
Numbered Steps
- Caller invokes
factory.deploy(pkg, pkgArgs). - Factory calls
pkg.calcSalt(pkgArgs)to obtain the CREATE2 salt for the proxy. - If a proxy already exists at the computed address, it is returned immediately.
- Otherwise the factory deploys a
MinimalDiamondCallBackProxyvia CREATE2. - The proxy calls back into the factory.
- The factory stores the package and arguments, then delegatecalls
pkg.initAccount(processedArgs)on the proxy. - The package performs all storage initialization (setting owners, writing token metadata, etc.).
- The factory calls
pkg.postDeploy(proxy). - Optional post-deploy hook facet is removed.
- The proxy address is returned.
The package never holds proxy state. It only supplies cuts and performs initialization.
Reuse Characteristics
- One
ERC20Facetdeployment serves every ERC20 proxy created fromERC20DFPkgon that chain. - The facet address is identical on every chain when deployed with the same salt.
- Adding a new feature requires only a new facet deployment and a new or updated package. Existing proxies are unaffected unless upgraded.
- Cross-chain deployment scripts can compute addresses in advance and verify that the expected facets and packages already exist at those addresses.
Helper Methods on Packages
Many packages expose convenience deploy(...) functions that encode arguments and forward to the factory:
IERC20 token = erc20Pkg.deploy(
diamondFactory,
"Example",
"EX",
18,
1_000_000e18,
recipient,
bytes32(0)
);
Post-Deploy Hooks
Packages may install a temporary PostDeployAccountHookFacet during initialization. After postDeploy returns, the factory removes the hook facet. This provides a safe window for privileged one-time setup actions.
Application / Consumer Layers
Crane's DFPkg + factory primitives are general. Some projects add registry or manager facades on top for registration, discovery, and access control of certain package types. Those additional rules and entry points are the responsibility of the consuming application — see the consumer's documentation.