Getting started with ZKApps on Mina: everything you need to know to write, test and deploy your Zero-Knowledge App
Table of contents
- Zero Knowledge what Proof? 🤯
- What’s the Mina Protocol?
- ZKApps
- zkApp development Frameworks
- 🛠️Preparing our o1js environment
- Setting up our ZK Project
- Deployment process 🚀
- Interacting with our contract
- Understanding the GraphQL Interface 🔍
- Key pairs 🗝️🔑
- Creating the Interaction Script 🛠️
- Deploying on local
- Test your smart contract
- Deploying our contract for Testing
- Writing Integration Tests
- Best Practices and Tips 🌟
- Common Questions and Tips
- Tips to keep in mind 🧠
- Sources: 🥸
Today, we're diving deep into the complex world of Zero-Knowledge Proofs (ZKPs) and discovering how to build state-of-the-art applications harnessing this technology on the blockchain. Whether you're a seasoned developer or an inquisitive newbie, this guide will arm you with the understanding and tools needed to grasp the concepts, set up your development environment, write and test your smart contract, and finally deploy and interact with it on the Mina Network.
We'll explore all the main functionalities and I'll share some handy tricks to streamline your development process.
🖐️ But first, let’s start with a quick introduction to some foundational concepts.
If you're already familiar with the basics of Zero-Knowledge Proofs and the Mina Protocol, you can skip this section and jump straight to the tutorial by clicking here.
Zero Knowledge what Proof? 🤯
Zero-Knowledge Proofs (ZKPs) are cryptographic techniques that enable one party to prove to another that they know a specific piece of information without revealing the information itself. This ensures privacy and security during the verification process.
To make this concept clearer, let's use a real-world analogy:
🌎 Picture yourself in a cutting-edge, ultra-secure data center where you hold the access card to a restricted server room. You need to prove to a security guard that you have the credentials to enter this room, but you don't want to hand over your precious access card details.
Here's how you can achieve this in a zero-knowledge way:
Proof of Knowledge: Instead of giving the guard your access card, you simply walk up to the door and swipe your card. The door unlocks, proving you indeed have the right credentials.
Zero Knowledge Transfer: The guard sees the door unlock but doesn't gain access to your card's details or any sensitive information. They only witness the action and its result.
Verification: The guard is fully convinced that you possess the correct credentials without needing to know the specifics of your access card.
Now, in the digital world: In computing, ZKPs allow you to demonstrate that you performed a specific computation correctly without disclosing your private data or inputs. It's like unlocking the door (proving the computation) without showing the security guard (verifier) your access card (private data). This technique ensures both privacy and security.
Real-World Applications of ZKPs
ZKPs aren't just sci-fi jargon—they're revolutionizing various fields right now! Here are some examples of where these cryptographic wizards are flexing their muscles:
Privacy-preserving identity verification: Proving who you are without spilling all your personal beans.
Confidential financial transactions: Ensuring your money moves are as secretive as a ninja.
Secure voting systems: Making sure your vote counts without revealing your choice.
Private credential verification: Verifying you have the right stuff without showing your credentials.
Supply chain privacy: Keeping track of products without snooping on the details.
Gaming: Proving you made a killer move without giving away your strategy.
Remember, this isn't just cool tech—it's the cutting-edge stuff shaping the future of privacy and scalability in blockchain applications. And guess what? We're only scratching the surface! There are endless possibilities still to be discovered. That's what makes it so exciting; it's like being part of the frontier that’s shaping the digital future.
👀 But fair warning: ZKPs are a complex concept. They might twist your brain into a knot initially, and that's totally normal. It’s like solving a puzzle—sometimes you need a few tries to get it right.
What’s the Mina Protocol?
Mina is a groundbreaking blockchain protocol that's changing the game with its innovative approach to Zero-Knowledge (ZK) applications. Here's what makes Mina stand out:
Recursive Zero-Knowledge Proofs: Mina leverages infinitely recursive ZK proofs to verify its blockchain. This means it can prove the entire state of the blockchain with a single, constant-sized proof, making it incredibly efficient.
Platform for Decentralized ZK Apps: Mina provides a robust platform for developers to build decentralized ZK applications (zkApps). These zkApps are Turing complete, meaning they can perform any computation, and they offer enhanced privacy, off-chain computation, and composability.
Tiny but Mighty: Despite its powerful capabilities, Mina maintains a constant size of just 22KB. This makes it incredibly lightweight and accessible, even for users with limited hardware resources. This is achieved through zk-SNARKs, which compress the entire blockchain state into a small, easily verifiable proof.
Privacy and Security: With zkApps, Mina ensures that data never leaves your local device. Only proofs are submitted to the chain, not personal data, ensuring privacy and security.
How zk-SNARKs Enable Constant Size:
Instead of storing the entire transaction history, Mina nodes only need to store this compact proof. When a new block is produced, a new proof is generated that verifies the validity of all previous transactions and the new block. Any node can quickly verify this proof, regardless of its computational resources.
If you want to know more about mina you can check the docs 👉https://docs.minaprotocol.com/
ZKApps
ZKApps are programs whose execution can be proven without revealing the underlying data used in the computation. This is achieved through zk-proofs, as I explained earlier.
The key benefit of zkApps on Mina is that they perform off-chain execution — all computational tasks are carried out off-chain, resulting in a flat gas fee during verification. This offers a significant advantage over blockchains that rely on on-chain computations, which often lead to high and unpredictable gas fees.
zkApp development Frameworks
When developing a zkApp within the Mina ecosystem, you have two robust frameworks at your disposal, each designed to streamline and optimize different solution types. Read the next section to discover the ideal match for your project’s unique needs.
Choosing Your Weapon: o1js vs Protokit for Mina zkApps 🚀
Mina Protocol offers two powerful frameworks for zkApp development:
o1js - For L1 development and infrastructure
Protokit - For building zkApp-chains (Mina L2) and complex applications
Let's break down each one and see how they can help you build zkApps!
o1js: the native Mina L1 framework 🛠️
o1js is your go-to framework when you want to build directly on Mina's L1. Think of it as the "Solidity" of Mina, but with the power of zero-knowledge proofs baked in!
It provides high-level abstractions for defining circuits in TypeScript, making zk-proof development accessible to developers without deep cryptographic expertise. It lets you write arbitrary zk programs leveraging a rich set of built-in provable operations, like basic arithmetic, hashing, signatures, boolean operations, comparisons, and more.
Here's a simple example of what a basic zkApp contract might look like using o1js:
import { SmartContract, State, method, Field, UInt64, Poseidon } from 'o1js';
class Counter extends SmartContract {
@state(Field) public commitment = State();
@method()
public increment(a: UInt64, b: UInt64) {
const hash = Poseidon.hash(a.toFields());
const commitment = this.commitment.get();
this.commitment.assertEquals(commitment); // Precondition: Current commitment matches
commitment.assertEquals(hash, "Nope, wrong number!"); // Input validation
const incremented = a.add(b);
const incrementedCommitment = Poseidon.hash(incremented.toFields());
this.commitment.set(incrementedCommitment); // Update on-chain state
}
}
This example demonstrates a zkApp that increments a counter. The commitment
stores the hash of the current counter value. The increment
method verifies that the provided input matches the current commitment, increments the counter, and updates the commitment with the new hash. The crucial aspect is that this entire computation can be proven without revealing the actual counter value.
Now, you might be wondering: "This looks similar to other smart contract languages, but what makes it special?" 🤔
The magic lies in how o1js handles state and zero-knowledge proofs. Every method in your contract automatically generates ZK proofs when executed, providing privacy and verifiability by default!
Key Considerations for o1js Development 📝
State Management
Limited to 8 on-chain field elements
Need more storage? Use the Offchain Storage API, or Store Merkle three roots on the contract.
Dealing with concurrent users requires careful planning
Protocol Limitations
Subject to L1 throughput constraints
Need to handle state updates carefully
What can I use O1js for?
Writing general-purpose zk circuits
Writing zk smart contracts on Mina
You should consider using o1js when:
Your project should be able to work within the protocol’s throughput limitations.
Your project should not require more than eight on-chain field elements to manage state.
- Note: If your project requires more than eight on-chain field elements to manage state, consider using the experimental Offchain Storage API.
Your project should be able to manage state efficiently, as o1js contracts do not provide any easy-to-use equivalent for access to shared states.
👀 SnarkyJs aclaration
o1js has taken over as the go-to library for creating and interacting with smart contracts in the Mina ecosystem. But you might come across several tutorials where the library is referred to as 'SnarkyJS.'
The transition from SnarkyJS to o1js is designed to be seamless. Developers can easily update their zkApp-cli with a simple command:
npm i -g zkapp-cli@latest
For those still using SnarkyJS, removing it and installing o1js is straightforward, ensuring that existing zkApps can continue functioning without the need for redeployments:
npm remove snarkyjs && npm install o1js
👀 For existing zkApps, make sure to update your imports from snarkyjs to o1js
This makes the upgrade process smooth and hassle-free, so you can focus on developing innovative applications without any interruptions.
If you want to delve into the key concepts of o1js, you can explore the Mina Protocol page . In this article, however, we're not going to focus on the basics of o1js. Instead, we'll equip you with the knowledge and tools to write, test, and deploy your smart contract seamlessly.
Protokit: The L2 Powerhouse 🏗️
When your application needs more horsepower or complex state management, Protokit comes to the rescue! It's designed for building zkApp-chains (L2s) and offers a more familiar development experience for those coming from EVM chains.
Let's look at a simple Protokit example:
Why Choose Protokit? 🎯
Unlimited Storage: No more 8-field limitation!
Concurrent Users: Built-in support for multiple users accessing shared state
Familiar Experience: If you know Solidity, you'll feel right at home
High Throughput: Perfect for applications that need to scale
You should consider using Protokit when:
Your project requires high throughput and multiple concurrent users.
Your project requires shared or global state access.
You are already familiar with building on Ethereum.
The Million Dollar Question 💭
"But which one should I choose?" 🤔
Pick o1js if you're all about that L1 life and want to build core infrastructure
Go with Protokit if you need high throughput or are building the next big multi-user dApp
You can see a complete comparation of both framework in the mina documentation 👉 https://docs.minaprotocol.com/zkapps/zkapp-development-frameworks#framework-comparison
Take into account Protokit vs. zkApps
Understanding the differences between Protokit and zkApps is essential. While zkApps are individual smart contracts designed for specific functionalities, Protokit allows developers to create entire blockchains with custom rules. This framework manages the complexities of block production, consensus, and state management within zk-rollups.
O1js provides tools to create zkApps with verifiable computations, while Protokit combines these zkApps into scalable, privacy-preserving blockchains. O1js serves as the building block by defining the circuits that power Protokit's zkVM and application logic, leveraging familiar TypeScript syntax and high-level abstractions. Meanwhile, Protokit acts as the orchestrator, managing the intricacies of running a zk-rollup, including block production, consensus, and communication with the Mina main chain. This allows developers to focus on building their application logic without worrying about the underlying infrastructure.
🛠️Preparing our o1js environment
These are all the tools that we need to start developing zero knowledge proof apps in mina blockchain using o1js.
Let's get our hands dirty! Here's what you'll need:
1. Install Node.js
First, head over to nodejs.org and download the latest LTS version. After installation, verify it works by doing the following command:
$ node --version
$ npm --version
2. Install Visual Studio Code
Download and install VS Code from code.visualstudio.com. It's an excellent editor for TypeScript development.
3. Install ZK App CLI
The ZK App CLI is your Swiss Army knife for ZK app development, it provides CLI interface for scaffolding zk-apps and outlines the process of creating, developing and managing zk-apps.
$ npm install -g zkapp-cli
Verify the installation:
$ zk --version
You should get something as:
For a more detailed system check, you can do the following command:
$ zk system
With this command, you'll receive detailed information about your system. This can be incredibly useful if you encounter any issues and need support. By sharing this information with other developers, you can quickly convey your current system specifications, making it easier for them to understand your setup and provide relevant assistance.
Now that we have all the tools we need, we can start a new project with o1js
Now, we are going to generate a ZKApp and trying to deploy it to the Mina blockchain
💡Tip: To update to the latest version of the zkApp CLI you can use:
$ npm update -g zkapp-cli
Why Use TypeScript for zkApps?
zkApps are crafted using TypeScript for some solid reasons. Here's why the O(1) Labs team made this tech-savvy choice:
Familiar Territory: TypeScript is built on JavaScript, which most developers know and love. It’s like upgrading from a bicycle to a motorbike—familiar yet more powerful.
Type Safety: With TypeScript, you get static typing, meaning fewer bugs and cleaner code. It's like having a spell-checker for your coding syntax.
Even if you're new to TypeScript, fear not! You don’t need to be a TypeScript wizard to follow along.
If you're craving a deeper dive into why TypeScript was chosen and the essentials of O(1) Labs’ zk framework, I highly recommend watching this video where Gregor Mitscha-Baude, one of the masterminds behind O1js MinaProtocol, explains the design and technical decisions. 👉 https://www.youtube.com/watch?v=D5ASDWd7Xto.
Setting up our ZK Project
To create a new project, use the following command:
$ zk project <project_name>
This command would start the process of create us a new developer environment with all the essentials to start writing our ZKApps, but before, the CLI would ask us some followup questions as seen in the image below:
For this tutorial, we are going to use the “none” option
When finish all the installation process you should have something like this
Project file structure breakdown 📁
Let's examine the key files in our newly created project:
.
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions CI configuration file
│
├── build/
│ ├── src/ # Compiled source files
│ ├── _hash # Build hash file
│ ├── cache.json # Cache configuration
│ └── node_modules/ # Build-specific dependencies
│
├── src/
│ ├── Add.test.ts # Test file for Add contract
│ ├── Add.ts # Main smart contract implementation
│ ├── index.ts # Entry point file
│ └── interact.ts # Contract interaction utilities
│
├── .eslintrc.cjs # ESLint configuration
├── .gitattributes # Git attributes configuration
├── .gitignore # Git ignore rules
├── .npmignore # npm package ignore rules
├── .prettierignore # Prettier ignore rules
├── .prettierrc # Prettier configuration
├── babel.config.js # Babel transpiler configuration
├── config.json # Project configuration
├── jest-resolver.js # Jest test resolver
├── jest.config.js # Jest test configuration
├── LICENSE # Project license file
├── package-lock.json # Lock file for npm dependencies
├── package.json # Project metadata and dependencies
├── README.md # Project documentation
└── tsconfig.json # TypeScript configuration
1. The smart contract (add.ts file)
This is the main smart contract file containing the ZK app logic
import { Field, SmartContract, state, State, method } from 'o1js';
export class Add extends SmartContract {
@state(Field) num = State<Field>();
init() {
super.init();
this.num.set(Field(1));
}
@method async update() {
const currentState = this.num.getAndRequireEquals();
const newState = currentState.add(2);
this.num.set(newState);
}
}
Let's break down this smart contract:
It defines a simple contract called
Add
Initializes a state variable num with value 1
Has an
update
method that adds 2 to the current valueUses decorators (
@state
,@method
) to define state and methods
2. Interaction script (interact.ts file)
This file demonstrates how to interact with your smart contract and generate zero-knowledge proofs. We'll discuss it in more detail later in the article.
Deployment process 🚀
For make this possible we first need to create our network configuration:
This would allow us to set up a network in where we can send our transaction to
For that, we run the following command
$ zk config
You'll need to provide:
Network name, could be anything (e.g., "testnet")
API URL
- We are going to use the Berkeley network so we need to put the following 👉
https://api.minascan.io/node/devnet/v1/graphql
- We are going to use the Berkeley network so we need to put the following 👉
Take into account:
The name of the test network in the url to use could change, as the network continues to develop but rn you could use the Berkeley network
- Fee amount (typically 0.1 MINA)
Fees are used to pay for transaction processing and smart contract
When finishing this it would update the config.json
file with all the specification we give during the setting
And you would get an output like the following
Some quick notes 📝
On testnets
If you're not familiar with test environments, it's a controlled setting where you can deploy and test your projects safely. This environment mimics real-world conditions but without any real-world consequences, allowing you to catch errors, fine-tune performance, and ensure everything works as expected before going live.
It provides a sandbox for experimentation and debugging, ensuring your smart contract or application is rock-solid before it's deployed in a real-world scenario.
On transaction cost
When building zero-knowledge proofs in the context of a blockchain, we need to make a small payment for every transaction sent to the blockchain. In Ethereum, this payment is made using ETH, while in the Mina blockchain, we use MINA.
In the case of Mina, transaction costs are lower because we are not performing computations directly on the blockchain. Instead, the computations are done off-chain, and only the resulting proofs are verified on-chain. This significantly reduces the computational load and, consequently, the transaction fees.
Getting test MINA Tokens
Now, you may be asking: "But how am I going to pay to interact with the Mina network? Do I have to spend real money for this?" 😨
The answer is no, as I said earlier, we are going to deploy our smart contract to a testnet network, and yes we have to pay for every transaction we make on the network, but we could use fake MINA to make those transactions.
And one way to obtaining fake mina is by going to a faucet
Faucets
A faucet is a development tool that gives you free test tokens to experiment with blockchain applications without using real money. These tokens only work on test networks (testnets), not on main networks (mainnet).
You can get free test MINA tokens from the faucet. Here's how:
Visit the Mina faucet website
- You can see the faucet link when completing the zk config command 👉 https://faucet.minaprotocol.com/
Or use https://faucet.minaprotocol.com/?address=<FEE-PAYER-ACCOUNT>
Input your generated address
- ZK config generate us a key pair, that's what it gives of an address, to publicly identify our deployed verifier (Our smart contract)
You can see it from the output of zk-config
Click "Request"
Wait approximately 3 minutes for confirmation.
👀 This can vary depending on the activity of the testnet
💡 Tip: Keep track of your address! It usually ends with a unique identifier (like "th6x").
Now, we go back to our project
Deploying our Smart Contract
Once we have our test MINA tokens, we can go back and deploy our contract.
We can run the following command
$ zk deploy <your_network_name>
If you follow along with me, your network name would be ‘testnet’ so the command would go like
$ deploy testnet
And this would take us through the deployment process
Understanding the deployment 🧐
When you deploy your smart contract, several important things happen:
A verification key is generated from your smart contract code
This key becomes part of your smart contract's identity on the network
- The verification key is how you identify an account to be a zkapp
The contract becomes a verifier that can validate zero-knowledge proofs
The Prover-Verifier relationship
Your ZK application consists of two main parts:
Verifier (on-chain): Your deployed smart contract
Prover (local): Your interaction script running on your machine
interact.ts
file
Interacting with our contract
Once we have our contract deployed, we can start interacting with it using a script, typically named interact.ts
First, let's start by creating our interaction script. We'll need some essential imports from the Mina Protocol libraries. Head over to the interact.ts file. You can delete the existing content, and we'll start fresh. I'll explain each part of the code so you can have a comprehensive understanding of what's happening.
Here's a step-by-step guide to creating your interaction script:
import {
Mina,
PrivateKey,
PublicKey,
fetchAccount
} from 'o1js';
And of course we import our contract, in our case
import { Add } from './add.js'; // Our smart contract
👀 Note: We use .js
and not .ts
because we are importing the files that are already compiled from the build folder.
Let's break down what each import does:
Mina: The main interface to interact with the Mina network
PrivateKey & PublicKey: Classes for handling cryptographic keys
fetchAccount: Function to retrieve account data from the blockchain
Add: Our smart contract class that we created previously
This would be the necessary packages we are going to use to interact with our contract
Connecting to the Mina Network 🌐
To interact with the Mina network, we need to set up our network configuration:
For that we write in our file the following code
const Network = Mina.Network('https://api.minascan.io/node/devnet/v1/graphql')
Mina.setActiveInstance(Network);
We pas as an argument the network that we previously us to deploy our smart contracts
💡 Tip: You can find your network URL in the
config.json
file that was created during project setup.
Creating a reference to our Smart Contract 📝
To interact with our deployed smart contracts, we need to create an instance of our deployed smart contract using its public key:
We need to convert the public key from base58 to a public key object
const zkAppKey = PublicKey.fromBase58('B62qph2VodgSo4LQ...'); // Your contract's public key
We need to pass a value, and that value is the public key of your smart contract
const zkApp = new Add(zkAppKey) our smart contract reference
You may be asking: "Where do I get this public key from?" You can find it in two places:
The Mina Explorer after deployment
In your project's keys directory under the deploy alias
Reading the state from the Blockchain 📊
To read the current state from our smart contract we can make the following:
// First fetch the account
await fetchAccount({ publicKey: appKey });
// Then read the state
const currentNum = zkApp.num.get().toString();
And then we display into the console,
console.log('Current state value:', currentNum);
This code:
Fetches the latest account state from the blockchain
Reads the num state variable from our contract
Converts it to a string (required because Mina uses special field types for ZK proofs)
💡 Tip: Always fetch the account before trying to read state to ensure you have the latest data!
Basically what we are doing here is going to our smart contract and get the current value of the ‘num’ state
And then with that we can run
$ npm run build && node build/src/interact.js
And you can see we get the value of 1
(which is the default value that was set on the default smart contract code)
With that, we make our first interaction with the mina chain 😄
Understanding the GraphQL Interface 🔍
The Mina Protocol offers a robust GraphQL
interface for network interactions. You can connect your GraphQL client to http://localhost:3085/graphql
or open this URL in your browser to use the GraphQL IDE.
Please note that Mina APIs are still under construction, so these endpoints may change. Keep this in mind while developing your applications
You can read more about the mina protocol graphql api by going to the documentation 👉 https://docs.minaprotocol.com/node-developers/graphql-api
Key pairs 🗝️🔑
Before continuing, let's understand what we're trying to achieve. In a ZK app, we have two types of key pairs:
The ZK app's own private and public keys
User key pairs for interacting with the ZK app
Generating a Key Pair 🔑
First, let's create a key pair that we'll use to interact with our deployed ZK app. We'll create a new file called generate.ts:
import { PrivateKey } from 'o1js';
// Generate a random private key
const privateKey = PrivateKey.random();
// Derive the public key from the private key
const publicKey = privateKey.toPublicKey();
// Create an object with base58 encoded keys
const keyPair = {
privateKey: privateKey.toBase58(),
publicKey: publicKey.toBase58()
};
console.log(keyPair);
💡 Tip: Always store your private keys securely and never share them. The public key can always be derived from the private key, but not vice versa!
Creating the Interaction Script 🛠️
Let's create our interaction script that will communicate with the ZK app, we can still use our interact.ts file
import { PrivateKey, PublicKey, Mina } from 'o1js';
// …rest of the code
const accountPrivateKey = PrivateKey.fromBase58('your-private-key-here');
const accountPublicKey = accountPrivateKey.toPublicKey();
Await Add.compile() // this is require to create ZKProof
// Create a transaction
const tx = await Mina.transaction({
sender: accountPublicKey, //who would pay the fee
fee: 0.1e9 // 0.1 MINA
}, async () => {
zkApp.update()
});
// Create the proof (this may take some time)
console.log('Proving...');
await tx.prove();
// Sign and send the transaction
const sentTx = await tx.sign([accountPrivateKey]).send();
// Get the transaction URL for the explorer
const txUrl = https://minascan.io/mainnet/berkeley/transaction/${sentTx.hash()};
console.log(`View transaction at: ${txUrl}`);
Understanding what happend 🧮
One of the fascinating aspects of ZK apps is how they handle code execution. When you update the state of a ZK app:
The code executes locally on your machine, not on the blockchain
A zero-knowledge proof is generated to prove the computation was correct
The proof is sent to the network for verification
If valid, the state updates; if invalid, the transaction fails
The network can verify your computation without redoing it
The actual computation remains private
The system is highly secure against tampering
💡 Tip: Any modification to the contract code after deployment will result in invalid proofs. If you need to change the logic, you'll need to deploy a new contract!
Deploying on local
Now, we are going to learn how to work with a local blockchain. If you've been struggling with long wait times during development or worried about spending real tokens while testing, this guide is exactly what you need!
Why use local Blockchain? 🤔
Before we jump into the code, let's understand why local blockchain development is so important:
Lightning-fast development cycles
No waiting for network confirmations
Complete control over test accounts
Generate test MINA tokens at will
No real costs involved
Setting up your local environment 🛠️
First, let's import all the necessary components from the Mina Protocol libraries:
import {
Mina,
PrivateKey,
AccountUpdate,
PublicKey
} from 'o1js';
// Import your smart contract
import { Add } from './Add';
Initializing the local Blockchain
Here's where the magic happens:
// Initialize local blockchain
const Local = Mina.LocalBlockchain({
proofsEnabled: true
});
Mina.setActiveInstance(Local);
This code creates a local instance of the Mina blockchain. The proofsEnabled flag determines whether zero-knowledge proofs will be generated - set it to false during early development to speed things up!
Setting up test accounts 👥
Now, here's something interesting - we need three different accounts:
// Deployer account
const {
privateKey: deployerKey,
publicKey: deployerAccount
} = Local.testAccounts[0];
// Sender account
const {
privateKey: senderKey,
publicKey: senderAccount
} = Local.testAccounts[1];
// ZkApp account
const zkAppPrivateKey = PrivateKey.random();
const zkAppAccount = zkAppPrivateKey.toPublicKey();
You might be wondering, "Why do we need three accounts?" Let me break it down:
Deployer Account: Responsible for deploying the smart contract
Sender Account: Will interact with the smart contract
ZkApp Account: The actual address of your smart contract
💡 Tip Always use a separate account for deployment rather than using the ZkApp account itself. This follows the principle of separation of concerns!
Deploying our Smart Contract 📝
Let's deploy our contract:
// Create instance of our contract
const zkApp = new Add(zkAppAccount);
console.log('Compiling contract...');
await zkApp.compile();
// Create and send deploy transaction
const deployTxn = await Mina.transaction(deployerAccount, () => {
AccountUpdate.fundNewAccount(deployerAccount);
zkApp.deploy();
});
await deployTxn.sign([deployerKey, zkAppPrivateKey]).send();
Here's what's happening step by step:
We create a new instance of our contract
Compile it to generate verification keys
Create a deployment transaction
Fund the new account and deploy
Sign with both the deployer and ZkApp keys
Interacting with our contract 🔄
Now that our contract is deployed, we can interact with it:
// Read the current state
let num = await zkApp.num.get();
console.log('Initial state:', num.toString());
// Create an update transaction
const txn = await Mina.transaction(senderAccount, () => {
zkApp.update();
});
console.log('Proving transaction...');
await txn.prove();
await txn.sign([senderKey]).send();
// Read updated state
num = await zkApp.num.get();
console.log('Updated state:', num.toString());
💡 Tip: Local blockchain transactions are instant - no need to wait for block confirmations!
Advanced tips and tricks 🎯
- Disable Proofs During Development:
const Local = Mina.LocalBlockchain({
proofsEnabled: false // Speed up development!
});
- Generate Multiple Test Accounts:
const testAccount = Local.testAccounts[2]; // Get more pre-funded accounts
- Quick State Reset:
Mina.setActiveInstance(Local); // Reset to clean state
Common Pitfalls to Avoid ⚠️
Don't forget to compile before deployment
Always sign transactions with the correct keys
Remember to fund new accounts before using them
Keep proof generation enabled for final testing
💡 Tip: Keep your local development environment separate from your testnet and mainnet configurations to avoid any confusion or accidental deployments!
Test your smart contract
Testing is a crucial part of developing zkApps on the Mina Protocol. In this section, we'll walk through everything you need to know about testing your zkApps locally, from setting up your testing environment to writing integration tests. Whether you're new to zkApp development or looking to improve your testing practices, this tutorial has got you covered!
Getting Started with Test Files
The zkApp CLI makes it super easy to create test files for your smart contracts. Let's start with the basics, see this example
$ zk file foo
This command generates two files:
foo.ts
: Your main contract filefoo.test.ts
: The corresponding test file
Tip 💡 Always organize your tests in a way that mirrors your smart contract's structure. This makes it easier to maintain and understand your test suite as your project grows.
Structuring your tests
All zkApp projects created using the zkApp CLI come with the Jest JavaScript testing framework. While Jest is a robust and popular choice, you're free to use any testing framework that suits your preferences and project needs. This flexibility allows you to tailor your testing environment to best fit your development workflow and ensure your zkApp performs flawlessly.
Let's look at how to organize your tests properly. The Jest testing framework uses describe blocks to group related tests together. Here's a basic structure:
describe('MyContract', () => {
describe('initialization', () => {
it('should deploy with correct initial state', () => {
// Test deployment and initial state
});
});
describe('state modifications', () => {
it('should update state correctly', () => {
// Test state updates
});
});
});
Best Practices 🎯
Group related tests together in describe blocks
Use clear, descriptive test names
Test one thing per test case
Follow the Arrange-Act-Assert pattern
Setting Up the Local Blockchain
One of the most powerful features for testing zkApps is the ability to simulate a local blockchain. Let's see how to set this up:
async function setupLocalBlockchain() {
// Create a local blockchain instance
const Local = await Mina.LocalBlockchain();
Mina.setActiveInstance(Local);
// Access test accounts
const feePayer = Local.testAccounts[0].key;
// Create zkApp account
const zkAppPrivateKey = PrivateKey.random();
const zkAppAddress = zkAppPrivateKey.toPublicKey();
return { Local, feePayer, zkAppPrivateKey, zkAppAddress };
}
Deploying our contract for Testing
Here's a complete example of deploying a contract in your test environment:
async function deployContract() {
const { Local, feePayer, zkAppPrivateKey, zkAppAddress } = await setupLocalBlockchain();
// Create contract instance
const zkAppInstance = new MyContract(zkAppAddress);
// Deploy transaction
const txn = await Mina.transaction(feePayer, async () => {
AccountUpdate.fundNewAccount(feePayer);
await zkAppInstance.deploy({ zkappKey: zkAppPrivateKey });
});
await txn.prove();
await txn.sign([feePayer, zkAppPrivateKey]).send();
return { zkAppInstance, feePayer };
}
Writing Integration Tests
Here's a comprehensive example of an integration test that verifies contract behavior:
describe('MyContract Integration Tests', () => {
let zkAppInstance: MyContract;
let feePayer: PrivateKey;
beforeAll(async () => {
({ zkAppInstance, feePayer } = await deployContract());
});
it('should initialize with correct state', async () => {
const state = zkAppInstance.num.get();
expect(state).toEqual(Field(1));
});
it('should update state correctly', async () => {
// Prepare transaction
const txn = await Mina.transaction(feePayer, async () => {
await zkAppInstance.update();
});
// Prove and send
await txn.prove();
await txn.sign([feePayer]).send();
// Verify new state
const newState = zkAppInstance.num.get();
expect(newState).toEqual(Field(3));
});
});
Running our tests 🏃♂️
Execute your tests using these npm commands:
# Run all tests
$ npm run test
# Run tests in watch mode
$ npm run testw
# Generate test coverage report
$ npm run coverage
Tip 💡Use watch mode during development for instant feedback as you make changes to your code!
Best Practices and Tips 🌟
Test Coverage: Aim for high test coverage but focus on testing critical paths and edge cases.
Error Cases: Don't forget to test failure scenarios and error conditions.
Clean Setup: Use beforeEach or beforeAll to ensure a clean state for each test.
Meaningful Assertions: Write assertions that verify the actual business logic, not just state changes.
Documentation: Use test descriptions as documentation for your contract's behavior.
Common Questions and Tips
Q: "Do I need special hardware to develop ZK apps?" A: No! You can develop and test ZK apps on any standard development machine.
Q: "How do I interact with the Mina network? Won't that cost money?" A: For development and testing, you can use Mina's Berkeley testnet, which uses test tokens that are free. You only need real MINA tokens when deploying to mainnet.
Q: "What if I run into version compatibility issues?" A: Always use the zk system command to check your versions. If you encounter problems, try updating your tools:
Q: Is the smart contract code stored on-chain? A: No! This is one of the unique aspects of Mina Protocol. Only the verification key and state are stored on-chain. The actual contract logic remains local, and zero knowledge proofs are used to verify correct execution.
Tips to keep in mind 🧠
Keep your development tools updated regularly
Join the Mina Discord community for support
Start with simple proofs before tackling complex applications
Use TypeScript's type system to catch errors early
Always test your proofs thoroughly before deployment
Keep your private keys secure and never commit them to version control
Monitor your transaction fees and manage them appropriately
That's all folks.
I hope this article was helpful for you.
If you have any comment or suggestions, please leave it in the comments section.
You can follow me on twitter @0bkevin and DM me, I’m always happy to talk and get to know more people in this amazing community.
Sources: 🥸
Create Your Own ZK Smart Contract series by Mina protocol 👉https://www.youtube.com/playlist?list=PLItixFkgfjYEvV0SwNpguAu-sNQ4cjyZ0
ZK10: o1js: The dawn of zk smart contracts - Gregor Mitscha-Baude 👉https://www.youtube.com/watch?v=D5ASDWd7Xto.
Berkeley Testnet Faucet 👉https://faucet.minaprotocol.com/
GraphQL api reference 👉 https://docs.minaprotocol.com/graphql-api
Mina Protocol Documentation👉 docs.minaprotocol.com