For the complete documentation index, see llms.txt
OpenZeppelin contracts for Compact
OpenZeppelin Contracts for Compact is a library of reusable smart contract building blocks written in Compact for Midnight. It mirrors the structure of OpenZeppelin's Solidity contracts library, providing token standards, access-control patterns, and security primitives that compose into custom contracts.
The library is modular: instead of inheriting a base contract, you import individual modules into your own .compact source with a prefix and call their circuits as building blocks.
The library is currently at version 0.2.0. The project's own README calls the code highly experimental, and the maintainers explicitly state: DO NOT USE IT IN PRODUCTION. The repository's audits folder holds OpenZeppelin's audit report for version 0.1.0. Version 0.2.0 includes changes made after that audit.
Use these contracts for prototyping, learning, and development on Preview and Preprod only.
Prerequisites
Before using the library, ensure that you have:
- The Compact developer tools installed.
- Node.js 22 or later and Yarn available.
- A working understanding of Compact module imports and the
prefixsyntax.
Compatibility
The table below lists the Compact compiler and language versions the library targets. Verify these against your local toolchain before integrating.
| Component | Version | Notes |
|---|---|---|
| Library version | 0.2.0 | The release that yarn add @openzeppelin/compact-contracts installs. Rapid iteration expected. |
| Compact compiler | 0.29.0 | Pinned by the library's CI badge. |
| Compact language | >= 0.21.0 | Per pragma in every source file. |
| Token amounts | Uint<128> (not Uint<256>) | Midnight encoding limit; Uint<256> is not supported by the Compact compiler. |
| Midnight Network | Preprod and Preview only | Not for Mainnet. |
Version 0.2.0 needs compiler 0.29.0 or later. OpenZeppelin builds and tests it with 0.29.0, while the official Midnight Toolchain has shipped newer releases. The pragma >= 0.21.0 keeps source-level compatibility: the example on this page also compiles with compilers 0.31.1, 0.34.0, and 0.35.0.
The compiler also decides which ledger version the compiled contract targets. Compiler 0.29.0 targets ledger version 7, and 0.31.1 targets ledger version 8. Compilers 0.34.0 and 0.35.0 target ledger version 9. To deploy, compile with the toolchain that the compatibility matrix lists for your target network. Verify your local compact compile --version before reporting issues.
The npm beta tag and the repository's main branch carry prereleases that target compiler 0.34.0 and pragma language_version >= 0.26.0. This page does not cover them.
Modules
The library organizes its modules into four families. Most modules import Utils (zero-address and Either helpers) for composition.
Access control · access/
These modules control who can call specific circuits in your contract. They are listed from simplest to most complex: single-owner first, then role-based, then their privacy-preserving variants.
| Module | Purpose | OpenZeppelin docs |
|---|---|---|
Ownable | Single-owner authorization. Stores the owner as Either<Bytes<32>, ContractAddress>. Supports ownership transfer, renunciation, and safe/unsafe variants for contract-address owners. | Guide · API |
AccessControl | Role-based authorization with admin hierarchy. Roles are Bytes<32> identifiers. Each role has a configurable admin role that controls grants and revocations. | Guide · API |
ShieldedAccessControl | Privacy-preserving role-based access control. Stores role commitments in a MerkleTree<20> and revokes via nullifiers, so role membership can be proven in zero knowledge. | Guide · API |
ZOwnablePK | Privacy-preserving single-owner authorization. Stores the owner as a commitment hash instead of a public key, so on-chain data does not reveal who the owner is. | Documented alongside Ownable |
Security · security/
These modules enforce lifecycle and operational guards on your contract.
| Module | Purpose | OpenZeppelin docs |
|---|---|---|
Initializable | One-time initialization guard. Stops a contract from initializing more than once. No other module in the library imports it: modules that need initialization track their own flag. OpenZeppelin advises using it only in your top-level contract. | Guide · API |
Pausable | Emergency pause switch. Gates circuit execution behind assertNotPaused(). Wrap _pause() and _unpause() in your own authorization logic. | Guide · API |
Tokens · token/
These modules implement standard token interfaces adapted for Compact.
| Module | Purpose | OpenZeppelin docs |
|---|---|---|
FungibleToken | ERC-20-style fungible token. Supports transfer, approve, transferFrom, _mint, and _burn. Balances use Uint<128> (not Uint<256> - see Compatibility). Includes safe and unsafe transfer variants. | Guide · API |
NonFungibleToken | ERC-721-style non-fungible token. Tracks ownership, approvals, operator approvals, and token URIs. Token IDs use Uint<128>. | Guide · API |
MultiToken | ERC-1155-style multi-token. Manages multiple token types in a single contract. Does not support batched operations (Compact lacks dynamic arrays). | Guide · API |
Utilities · utils/
Helper functions for type safety and input validation.
| Module | Purpose | OpenZeppelin docs |
|---|---|---|
Utils | Pure helpers for working with Either<ZswapCoinPublicKey, ContractAddress> values. Includes zero-checks, equality checks, and canonicalize which prevents crafted-input attacks. | Guide · API |
Installation
The library ships as the npm package @openzeppelin/compact-contracts. The latest tag installs version 0.2.0, the release this page describes.
To add the library to a new project, create the project and install the package:
mkdir my-project && cd my-project
yarn init -y
yarn add @openzeppelin/compact-contracts
The package contains the Compact source of each module in the access/, security/, token/, and utils/ folders.
The imports in your contract point to files in node_modules. Yarn 2 and later use Plug'n'Play by default, which does not create that folder. If your project has no node_modules folder after the install, run yarn config set nodeLinker node-modules, then run yarn install. Alternatively, install the package with npm install @openzeppelin/compact-contracts.
Compose a contract
Import individual modules into your .compact source with a prefix, then call their circuits as building blocks. Note the prefix Pausable_; convention: the prefix itself ends in _. Calling the internally underscored _pause circuit becomes Pausable__pause() - the double underscore is intentional.
pragma language_version >= 0.21.0;
import CompactStandardLibrary;
import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
prefix Pausable_;
import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;
constructor(
_name: Opaque<"string">,
_symbol: Opaque<"string">,
_decimals: Uint<8>,
_recipient: Either<Bytes<32>, ContractAddress>,
_amount: Uint<128>,
_initOwner: Either<Bytes<32>, ContractAddress>,
) {
Ownable_initialize(_initOwner);
FungibleToken_initialize(_name, _symbol, _decimals);
FungibleToken__mint(_recipient, _amount);
}
export circuit transfer(
to: Either<Bytes<32>, ContractAddress>,
value: Uint<128>,
): Boolean {
Pausable_assertNotPaused();
return FungibleToken_transfer(to, value);
}
export circuit pause(): [] {
Ownable_assertOnlyOwner();
Pausable__pause();
}
export circuit unpause(): [] {
Ownable_assertOnlyOwner();
Pausable__unpause();
}
export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
return Ownable_computeAccountId(secretKey);
}
The Bytes<32> side of each address holds an account identifier, not a wallet public key. Ownable and FungibleToken derive it as a persistentHash of the caller's secret key, and each exports a pure computeAccountId circuit that computes it. The example wraps the one from Ownable in its own computeAccountId circuit. Your DApp can call pureCircuits.computeAccountId(secretKey) from the compiled output to get the identifiers that the constructor and transfer take. The compiled contract declares the witnesses wit_OwnableSK and wit_FungibleTokenSK, which your DApp implements to return that 32-byte secret key. The package does not include witness implementations. For details, see OpenZeppelin's Ownable guide.
Compile
Run the Compact compiler to generate TypeScript bindings and ZK circuit artifacts from your contract:
compact compile MyContract.compact artifacts/MyContract
A successful compile lists each circuit with its proving-system size. This output comes from compiler 0.31.1, and row counts differ between compiler versions:
Compiling 3 circuits:
circuit "pause" (k=13, rows=2362)
circuit "transfer" (k=13, rows=3855)
circuit "unpause" (k=13, rows=2359)
Overall progress [====================] 3/3
The pure computeAccountId circuit needs no proof, so the output does not list it.
k is the domain size (the circuit is laid out on 2^k rows), and rows is how many of those rows the circuit uses. Smaller circuits prove and verify faster - see OpenZeppelin's ZK Circuits 101 for a longer explanation.
Patterns to know
These patterns apply across the library and affect how you structure your contracts.
-
Modular composition by delegation: OpenZeppelin's Module/Contract pattern is the formal framing the library expects you to follow. Each module exports two kinds of circuits: external ones with no leading underscore (like
transfer,approve) that are safe to expose as-is, and public ones with a leading underscore (like_mint,_burn) that serve as composable building blocks you wrap in your own contract logic. Internal helpers remain unexported. Your contract imports modules with a prefix and wires their circuits together; it should not callinitialize()outside its constructor or re-export circuits raw. -
Composition through prefixes: The
import ... prefix X_;syntax namespaces each module so multiple modules can coexist in a single contract without identifier collisions. Circuits whose names start with_show up asX__nameat the call site. -
Initialization is explicit:
Ownable,ZOwnablePK,ShieldedAccessControl, and the three token modules each track their own initialization flag. Each requires an explicitinitializecall from your constructor. Until that call runs, their guarded circuits fail at runtime with acontract not initializederror. -
Safe circuits reject contract-address recipients: Version
0.2.0targets compiler0.29.0, which predates cross-contract calls (they arrived with ledger version 9). Every module's safe path (e.g.,Ownable.transferOwnership,AccessControl.grantRole,FungibleToken.transfer) therefore rejectsContractAddressrecipients. Each module pairs the safe variant with_unsafe*circuits explicitly named to make the risk visible. The maintainers plan to deprecate the unsafe variants once the library supports cross-contract calls. -
Caller authorization comes in two flavors:
Ownable,AccessControl, and the token modules identify the caller by an account identifier. It is apersistentHashof a secret key that each module reads from its own witness, such aswit_OwnableSK. They do not rely onownPublicKey(), in line with the derived-identity pattern. Unlike the example in that guide, the hash has no domain separator. Neither uses a per-contract salt, so the identifier is the same in every contract that uses the same key. Use a different key for each contract if the identities must not be linkable.ZOwnablePKandShieldedAccessControluse a witness-derived secret combined with hashing and instance salts to verify the caller via commitments - never exposing the public key on-chain.ZOwnablePKstill hashesownPublicKey()into its owner identifier, so the secret nonce from itswit_secretNoncewitness is what authenticates the caller. The privacy-preserving variants are the recommended choice when the contract should not reveal who its owners or role-holders are. -
Either<Bytes<32>, ContractAddress>is the canonical address type: TheUtils.canonicalizehelper zeroes out the unused side of anEitherto prevent crafted-input attacks where both sides carry data.
Resources
For more information, see the following links.
- Project repository - README, source, and contribution guide.
- OpenZeppelin documentation site - The project's own reference, organized as Overview, Learn, Modules, and API Reference.
- Module/Contract pattern - Formal terminology for
internal,public, andexternalcircuits and the rules for safe composition. - ZK Circuits 101 - Short primer on what the
k=N, rows=Nnumbers in the compile output mean and why circuit size matters. - Building a fungible-token contract with OpenZeppelin and Compact - Dev Diaries walkthrough of
FungibleToken, written for an earlier release of the library.
Reporting issues
For issues with the library code, file on OpenZeppelin's tracker. For security disclosures, email security@openzeppelin.com.
For issues with this documentation page, file on the Midnight docs repository.