Building Your First Diamond
Create a working diamond project using Compose then explore every file so you know exactly what you have.
This tutorial uses the Counter example: a minimal diamond with increment and read functions. It's the simplest way to see how diamonds works end-to-end.
Prerequisites
1. Scaffold the project
Run the Compose CLI with npx
You'll be prompt with lot of different options from framework, ERC standards templates, and others.
For this tutorial, we will choose the Counter example with the following options:
Take the time to review the full library available to you. We support most major token standards like ERC20, ERC721 with different type of access control.
You can always skip the interactive prompts entirely:
When the CLI finishes, follow the next steps instruction to move into your project and test it:
2. Explore the project structure
The scaffold generates a diamond with two local facets and one from the Compose library.
Each facet owns a slice of the applicstion logic. CounterDataFacet exposes the count, CounterIncrementFacet mutates it. The Diamond.sol proxy routes calls to the right logic block via function selectors.
Diamond.sol: The proxy
The diamond is the application entrypoint. Users call it, but it holds no logic itself, only the storage state. Every call is delegated to a stateless facet via the fallback function:
When a function is called on the diamond, DiamondMod.diamondFallback() looks up which contract owns that selector and forwards the call.
There nothing more to make a diamond proxy work. Now, let's transition where our application logic lives
CounterDataFacet.sol
Facet are where you implement your application logic. Think of it as stateless logic block that borrow the diamond storage to handle any data.
Any facet that need Counter data uses the same STORAGE_POSITION = keccak256("counter") and CounterStorage struct. They never import each other. They just agree on the same storage contract.
3. Build and test
Now let's compile and run the tests:
You should see compilation succeed and one passing test in /test/Diamond.t.sol
The test deploys a diamond with all three facets the Counter needs, then calls facetAddresses() on DiamondInspectFacet to verify the right number of facets are registered.
4. Write your own test
Let's interact with the Counter through a test. Open test/Diamond.t.sol and take a look at what's already there:
The test file already has a setUp() function that deploys a fresh Diamond instance before each test. There's also one test that verifies all three facets are properly registered on the proxy.
Take a look at the order here. The facets are deployed first as separate contracts, then the proxy is created with references to those pre-deployed facet addresses.
This is the core of composition over inheritance. Instead of a monolithic contract that is everything through inheritance chains, the Diamond has the facets it needs.
They're composed at runtime via the constructor or the upgrade functionality.
Now let's add a test that actually uses our Counter. Add this function inside the DiamondTest contract:
Run the test suite:
All tests pass, demonstrating that data written by one facet is immediately visible to another through the diamond's shared storage.
5. Deploy locally
Start a local Ethereum node in one terminal:
In a second terminal, plan the deployment and generate the managed script:
npx compose deploy plans the deployment from compose.json and writes script/ComposeDeploy.s.sol.
Always review the script, then broadcast it with Anvil's test key:
The deployment is verified and recorded in compose.lock.
You'll see the diamond address logged as the script runs:
Congratulations!
You just deployed your first diamond application.
We've scaffolded together a Counter project using the CLI, written tests that verify shared storage across facets, and deployed a composed system to a local network.
We recommend you to explore all the template available to scaffold. Use the npx compose catalog command to review what's available to you.