Create your first Midnight contract
In this tutorial, you'll create a simple "Hello World" smart contract using Compact, Midnight's smart contract language. You'll learn how to write privacy-preserving logic, compile it into zero-knowledge circuits, and prepare it for deployment on the Preprod network.
A Midnight smart contract uses zero-knowledge proofs (ZKPs) to maintain data confidentiality while proving computation correctness. The core value proposition is selective disclosure, where users can prove specific information while keeping sensitive data private.
Overview
By the end of this tutorial, you'll:
- Create a Compact smart contract with state storage
- Compile the contract into zero-knowledge circuits
- Understand the generated artifacts and their purposes
- Prepare your contract for deployment on Preprod
Prerequisites
Before you begin, ensure you have the following:
- Compact toolchain: Follow the install the toolchain guide to install the necessary tools.
- Command-line knowledge: Basic familiarity with terminal operations.
- Code editor: An IDE such as Visual Studio Code.
Set up your project
Create a project folder and navigate to it:
mkdir my-midnight-contract
cd my-midnight-contract
Create the required directories:
mkdir contracts
Your project structure should now look like this:
my-midnight-contract/
└── contracts/
The contracts folder contains your Compact smart contract source files.
Create the contract file
Create a new file named hello-world.compact in the contracts directory:
touch contracts/hello-world.compact
Open this file in your code editor.
Add the language version directive
The pragma language_version directive specifies which version of Compact your contract uses. This protects your contract from breaking changes in future language versions.
Add this line at the top of contracts/hello-world.compact:
pragma language_version 0.20;
This directive tells the compiler that your contract requires Compact version 0.20.
Define the ledger state
The on-chain state of your contract is declared with ledger declarations. On-chain state is public and persistent on the blockchain.
Add the ledger declaration:
pragma language_version 0.20;
export ledger message: Opaque<"string">;
This line creates a state variable named message that stores a string value.
The export keyword makes this variable accessible from the compiler's generated JavaScript implementation of the contract.
The Opaque<"string"> type represents foreign data (JavaScript strings in this case) that Compact can store and pass around without needing to interpret its internal structure.
Create the storeMessage circuit
Circuits are the functions of a Compact smart contract. They define the logic that modifies state or performs computations, and they're compiled into zero-knowledge circuits.
Add the circuit definition:
pragma language_version 0.20;
export ledger message: Opaque<"string">;
export circuit storeMessage(newMessage: Opaque<"string">): [] {
message = disclose(newMessage);
}
Breaking it down:
export circuitdoes two things: it makes the circuit visible in the compiler-generated TypeScript/JavaScript code for your DApp to call, and it includes the circuit in the contract that gets deployed on-chain.storeMessageis the circuit name.newMessage: Opaque<"string">is the input parameter. Circuit parameters are always private by default. TheOpaque<"string">type indicates it's a string value from JavaScript.: []is an empty tuple type that indicates the circuit returns no value.message = disclose(newMessage)stores the message on the public ledger. Thedisclose()function marks the private value as safe to store publicly. Without it, trying to assignnewMessagedirectly to the ledger returns a compiler error.
Your complete contract should look like this:
pragma language_version 0.20;
export ledger message: Opaque<"string">;
export circuit storeMessage(newMessage: Opaque<"string">): [] {
message = disclose(newMessage);
}
Compile the contract
Compiling transforms your Compact code into zero-knowledge circuits, generates cryptographic keys, and creates TypeScript APIs and a JavaScript implementation for the contract to be used by DApps.
Run the compiler from your project root:
compact compile contracts/hello-world.compact contracts/managed/hello-world
You should see the following output:
Compiling 1 circuits:
circuit "storeMessage" (k=6, rows=26)
The compilation process will:
- Parse and validate your Compact code.
- Generate zero-knowledge circuits from your logic.
- Create proving and verifying keys for the circuits.
- Generate the TypeScript API and JavaScript implementation for the contract.
When compilation completes, you'll see a new directory structure:
contracts/
├ ── managed/
| └── hello-world/
| ├── compiler/
| ├── contract/
| ├── keys/
| └── zkir/
└── hello-world.compact
Here's what each directory contains:
- contract/: The compiled contract artifacts, which includes the JavaScript implementation and type definitions.
- keys/: Cryptographic proving and verifying keys that enable zero-knowledge proofs.
- zkir/: Zero-Knowledge Intermediate Representation—the bridge between Compact and the ZK backend.
- compiler/: Compiler-generated JSON output that other tools can use to understand the contract structure.
Troubleshoot
This section covers common issues that you might encounter during compilation and their solutions.
Compiler not found
If the compact compile command isn't recognized, verify your installation:
- Check if the Compact devtools are installed:
which compactorcompact --version. - If step 1 succeeds, verify the compiler toolchain is installed:
compact compile --version. - For details on installing the Compact toolchain, refer to the install the toolchain guide.
Compilation errors
If you see syntax or type errors:
- Verify your
pragma language_versionmatches your compiler version. - Check that all required imports are present.
- Ensure the
discloseoperator is used where required. - Review the error message for specific line numbers and issues.
Next steps
Now that you've compiled your first Midnight contract, you're ready to deploy it to the Preprod network. For more information, refer to the deploy hello world contract guide.